How to Build a Cosmic Critter Pet with Raspberry Pi Pico
A tiny alien pet with tilt-based moods, nebula lights, and chirpy sounds
Updated

What you'll build
In this guide you will bring a Cosmic Critter to life on a Raspberry Pi Pico using an MPU6050 accelerometer, a 0.96-inch OLED display for its animated face, a WS2812B LED ring for nebula-inspired ambient lighting, and a piezo buzzer for chirps and purrs. The critter's mood changes in real time based on how you tilt and handle the device -- hold it level and it drifts into a calm trance with slow-pulsing blue LEDs, tilt it sharply and it gets excited with rapid rainbow sparkles and high-pitched trills, leave it still too long and it grows bored with droopy eyes and a dim amber glow. The result is a pocket-sized alien companion that reacts to your every move.
The Raspberry Pi Pico is an excellent platform for beginners because MicroPython lets you write, test, and tweak code without a compile step. This project teaches you how to read accelerometer data over I2C on the Pico, map tilt angles to discrete mood states using threshold logic, drive a NeoPixel LED ring with the Pico's PIO state machine for flicker-free animations, and generate melodic tones on a buzzer using PWM. You will also learn how to render simple bitmap animations on the OLED, giving the critter expressive eyes, a mouth, and antennae that wiggle when it is happy.
When you finish you will have a self-contained interactive toy that runs from a USB power bank or a pair of AAA batteries. The code is organized into clean modules for mood logic, LED patterns, face animation, and sound effects, so you can easily add new moods, create your own critter species with different personalities, or wire up additional sensors like a light-dependent resistor to make the critter react to darkness. It is a perfect first Pico project that teaches real embedded programming concepts while producing something genuinely fun to play with. For an ESP32-based take on the virtual pet concept, check out the ClawdBot Tamagotchi which uses buttons instead of motion for interaction.
Wiring diagram
Wiring diagram
Components needed
| Component | Type | Qty | Buy |
|---|---|---|---|
| MPU6050 Motion Sensor | sensor | 1 | €4.75 |
| SSD1306 OLED | display | 1 | €4.30 |
| WS2812B LED Ring | actuator | 1 | |
| Piezo Buzzer | actuator | 1 | €4.75 |
| Mood Button | other | 1 | €8.30 |
Prices and availability are indicative and may have been updated by the supplier. Schematik may earn a commission from purchases made through affiliate links.
Assembly
Connect I2C sensors to the Pico
Wire MPU6050 and SSD1306 OLED: VCC to 3.3V, GND to ground, SDA to GP4, SCL to GP5. Both share the same I2C bus.
- Keep I2C wires short and double-check that all modules share GND.
- Pico GPIO is 3.3V only — do not connect 5V logic to any GPIO pin.
Wire the LED ring
Connect WS2812B 5V to VBUS (USB 5V), GND to Pico GND, and DIN to GP12.
- Add a 470 µF capacitor across LED ring 5V and GND to smooth power spikes.
- Use a shared ground between external LED power and the Pico.
Add buzzer and mood button
Connect piezo buzzer signal to GP15 (other lead to GND). Wire mood button between GP14 and GND — the code enables the internal pull-up.
- A 100 Ω resistor in series with the buzzer can reduce volume if needed.
Upload and play
Flash the sketch via USB. Tilt the Pico to change the critter mood — level for happy, forward tilt for sleepy, sideways for dizzy, backward for hyper. Press the button to manually cycle moods.
- Experiment with the tilt thresholds in the code to match your preferred sensitivity.
Pin assignments
| Pin | Connection | Type |
|---|---|---|
| 3V3 | pico-imu-1 VCC | POWER |
| GND | pico-imu-1 GND | GROUND |
| GPIO 4 | pico-imu-1 SDA | I2C |
| GPIO 5 | pico-imu-1 SCL | I2C |
| 3V3 | pico-oled-1 VCC | POWER |
| GND | pico-oled-1 GND | GROUND |
| GPIO 4 | pico-oled-1 SDA | I2C |
| GPIO 5 | pico-oled-1 SCL | I2C |
| 5V | pico-led-ring-1 5V | POWER |
| GND | pico-led-ring-1 GND | GROUND |
| GPIO 12 | pico-led-ring-1 DIN | DATA |
| GND | pico-buzzer-1 GND | GROUND |
| GPIO 15 | pico-buzzer-1 SIG | PWM |
| GND | pico-mode-button-1 GND | GROUND |
| GPIO 14 | pico-mode-button-1 SIG | DIGITAL |
Code
#include <Wire.h>
#include <Adafruit_MPU6050.h>
#include <Adafruit_SSD1306.h>
#include <FastLED.h>
#define SDA_PIN 4
#define SCL_PIN 5
#define BTN_PIN 14
#define BUZZER_PIN 15
#define LED_PIN 12
#define NUM_LEDS 12
Adafruit_MPU6050 mpu;
Adafruit_SSD1306 display(128, 64, &Wire, -1);
CRGB leds[NUM_LEDS];
enum Mood { HAPPY, SLEEPY, DIZZY, HYPER };
Mood mood = HAPPY;
Mood prevMood = HAPPY;
unsigned long lastChirpMs = 0;
unsigned long lastBtnMs = 0;
const char* moodName(Mood m) {
switch (m) {
case HAPPY: return "Happy :)";
case SLEEPY: return "Sleepy zzz";
case DIZZY: return "Dizzy @_@";
case HYPER: return "Hyper !!!";
default: return "Unknown";
}
}
void renderMoodLights(Mood m) {
switch (m) {
case HAPPY:
fill_solid(leds, NUM_LEDS, CRGB(0, 120, 255));
break;
case SLEEPY:
for (int i = 0; i < NUM_LEDS; i++) {
uint8_t v = beatsin8(12, 30, 100, 0, i * 20);
leds[i] = CRGB(v / 2, 0, v);
}
break;
case DIZZY:
for (int i = 0; i < NUM_LEDS; i++) {
leds[i] = CHSV((millis() / 10 + i * 21) % 255, 255, 140);
}
break;
case HYPER:
for (int i = 0; i < NUM_LEDS; i++) {
leds[i] = ((millis() / 80 + i) % 2 == 0) ? CRGB::OrangeRed : CRGB::Gold;
}
break;
}
FastLED.show();
}
void chirp(Mood m) {
switch (m) {
case HAPPY: tone(BUZZER_PIN, 1400, 70); break;
case SLEEPY: tone(BUZZER_PIN, 750, 120); break;
case DIZZY: tone(BUZZER_PIN, 1800, 40); break;
case HYPER: tone(BUZZER_PIN, 2200, 50); break;
}
}
void setup() {
Serial.begin(115200);
Wire.begin(SDA_PIN, SCL_PIN);
if (!mpu.begin()) Serial.println("MPU6050 not found");
mpu.setAccelerometerRange(MPU6050_RANGE_4_G);
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
FastLED.addLeds<NEOPIXEL, LED_PIN>(leds, NUM_LEDS);
FastLED.setBrightness(120);
pinMode(BTN_PIN, INPUT_PULLUP);
pinMode(BUZZER_PIN, OUTPUT);
Serial.println("Cosmic Critter ready");
}
void loop() {
sensors_event_t a, g, t;
mpu.getEvent(&a, &g, &t);
if (a.acceleration.x > 5.0f) mood = HYPER;
else if (a.acceleration.x < -5.0f) mood = SLEEPY;
else if (fabs(a.acceleration.y) > 6.0f) mood = DIZZY;
else mood = HAPPY;
if (!digitalRead(BTN_PIN) && millis() - lastBtnMs > 250) {
mood = static_cast<Mood>((mood + 1) % 4);
chirp(mood);
lastBtnMs = millis();
}
if (mood != prevMood) {
chirp(mood);
prevMood = mood;
}
if (millis() - lastChirpMs > 3000) {
chirp(mood);
lastChirpMs = millis();
}
renderMoodLights(mood);
display.clearDisplay();
display.setCursor(0, 0);
display.println("Pico Cosmic Critter");
display.println("--------------------");
display.print("Mood: ");
display.println(moodName(mood));
display.println();
display.printf("Tilt X: %5.1f\n", a.acceleration.x);
display.printf("Tilt Y: %5.1f\n", a.acceleration.y);
display.println("BTN: cycle mood");
display.display();
delay(50);
}
// Run this and build other cool things at schematik.ioReady to build this?
Open this project in Schematik to get the full wiring diagram, pin assignments, and deployable code for the Cosmic Critter Pet.
Open in Schematik →