Introduction: DoodlePi V2: a Raspberry Pi Pico Pixel Art Drawing Toy

About: I’m Ameya Angadi, a robotics and electronics enthusiast creating easy-to-understand open-source tech. From Arduino to Raspberry Pi, IoT, and automation, I aim to spark ideas and solve problems. Whether you're …

Welcome to DoodlePi V2, the upgraded, handheld version of the Raspberry Pi Pico drawing toy! While the breadboard version(DoodlePi V1) is great for prototyping, this custom PCB edition transforms the project into a robust, portable, and professional-looking device.

It features a built-in battery charger, a sleek black aesthetic, and an ergonomic button layout perfect for hours of creative drawing.

Supplies

  1. Raspberry Pi Pico/Pico W
  2. 1.3 inch OLED Display (SH1106 module, 128x64 pixels)
  3. DoodlePi PCB(Gerber files available below)
  4. Momentary Push Buttons (6×6×5mm) × 6 pieces
  5. JST Connector (2mm Pitch)
  6. TP4056 Charging Module (Preferably Type C)
  7. Slide Switch (3 Pin, 2.54mm Pitch)
  8. 1000 mAh Li-ion Battery
  9. Soldering Iron & Solder

Step 1: Design the PCB

The heart of this project is the custom circuit board. The design is Open Source Hardware.

  1. Download the Gerber files from below.
  2. Upload the files to a PCB manufacturer.


Recommendation: Choose a Black Solder Mask with White Silkscreen to match the signature DoodlePi aesthetic.


Download gerber files by clicking here

For the complete open-source hardware files, including the code and PCB design files, please visit the DoodlePi GitHub repository.

Circuit Diagram is given at the last.

Step 2: Solder All the Parts

Solder the parts (prefarably) in this order:

  1. Raspberry Pi Pico
  2. TP4056 Charging Module
  3. Slide Switch
  4. JST Connector
  5. Push Buttons
  6. OLED Display
After soldering the parts, use isopropyl alcohol to clean the PCB and remove the excess flux, making it look better.

Step 3: Upload the Code

  1. Connect the Raspberry Pi Pico to your computer via USB.
  2. Open the Arduino IDE.
  3. Make sure to install Earle Philhower's Pico Core. Follow this tutorial Getting Started with Raspberry Pi Pico in Arduino IDE
  4. Install the required libraries:
  5. Adafruit GFX
  6. Adafruit SH110X
  7. Select the board "Raspberry Pi Pico" and the correct COM port.
  8. Upload the DoodlePi.ino code shared below.
Note: Ensure the I2C Address is correct (Default is 0x3C).


Code:

/*
* Project Name: DoodlePi
* Designed For: Raspberry Pi Pico/Pico W
*
*
* License: GPL3+
* This project is licensed under the GNU General Public License v3.0 or later.
* You are free to use, modify, and distribute this software under the terms
* of the GPL, as long as you preserve the original license and credit the original
* author. For more details, see <https://www.gnu.org/licenses/gpl-3.0.en.html>.
*
* Copyright (C) 2025 Ameya Angadi
*
* Code Created And Maintained By: Ameya Angadi
* Last Modified On: November 11, 2025
* Version: 2.0.0
*
*/

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SH110X.h>

#define OLED_I2C_ADDRESS 0x3C
#define DISPLAY_WIDTH 128
#define DISPLAY_HEIGHT 64
#define OLED_RESET -1

Adafruit_SH1106G display = Adafruit_SH1106G(DISPLAY_WIDTH, DISPLAY_HEIGHT, &Wire, OLED_RESET);

const unsigned char Splash_Screen[] PROGMEM = {
// "DoodlePi", 128x64px
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x80, 0x00, 0x00, 0x07, 0xf8, 0x00, 0x00, 0x00, 0xe7, 0x00, 0x07, 0xf8, 0x00, 0x00, 0x00, 0x01,
0x80, 0x00, 0x00, 0x07, 0xfc, 0x00, 0x00, 0x00, 0xe7, 0x00, 0x07, 0xfc, 0x00, 0x00, 0x00, 0x01,
0x80, 0x00, 0x00, 0x07, 0xfc, 0x00, 0x00, 0x00, 0xe7, 0x00, 0x07, 0xfd, 0xc0, 0x00, 0x00, 0x01,
0x80, 0x00, 0x00, 0x07, 0x1c, 0x00, 0x00, 0x00, 0xe7, 0x00, 0x07, 0x1d, 0xc0, 0x00, 0x00, 0x01,
0x80, 0x00, 0x00, 0x07, 0x1c, 0x00, 0x00, 0x00, 0xe7, 0x00, 0x07, 0x1d, 0xc0, 0x00, 0x00, 0x01,
0x80, 0x00, 0x00, 0x07, 0x1c, 0x00, 0x00, 0x00, 0xe7, 0x00, 0x07, 0x1c, 0x00, 0x00, 0x00, 0x01,
0x80, 0x00, 0x00, 0x07, 0x1c, 0xfc, 0x3f, 0x0f, 0xe7, 0x1f, 0x87, 0x1d, 0xc0, 0x00, 0x00, 0x01,
0x80, 0x00, 0x00, 0x07, 0x1d, 0xfe, 0x7f, 0x9f, 0xe7, 0x3f, 0xc7, 0x1d, 0xc0, 0x00, 0x00, 0x01,
0x80, 0x00, 0x00, 0x07, 0x1d, 0xce, 0x73, 0x9c, 0xe7, 0x39, 0xc7, 0xfd, 0xc0, 0x00, 0x00, 0x01,
0x80, 0x00, 0x00, 0x07, 0x1d, 0xce, 0x73, 0x9c, 0xe7, 0x39, 0xc7, 0xfd, 0xc0, 0x00, 0x00, 0x01,
0x80, 0x00, 0x00, 0x07, 0x1d, 0xce, 0x73, 0x9c, 0xe7, 0x39, 0xc7, 0xf9, 0xc0, 0x00, 0x00, 0x01,
0x80, 0x00, 0x00, 0x07, 0x1d, 0xce, 0x73, 0x9c, 0xe7, 0x3f, 0xc7, 0x01, 0xc0, 0x00, 0x00, 0x01,
0x80, 0x00, 0x00, 0x07, 0x1d, 0xce, 0x73, 0x9c, 0xe7, 0x3f, 0xc7, 0x01, 0xc0, 0x00, 0x00, 0x01,
0x80, 0x00, 0x00, 0x07, 0x1d, 0xce, 0x73, 0x9c, 0xe7, 0x38, 0x07, 0x01, 0xc0, 0x00, 0x00, 0x01,
0x80, 0x00, 0x00, 0x07, 0x1d, 0xce, 0x73, 0x9c, 0xe7, 0x39, 0xc7, 0x01, 0xc0, 0x00, 0x00, 0x01,
0x80, 0x00, 0x00, 0x07, 0xfd, 0xce, 0x73, 0x9c, 0xe7, 0x39, 0xc7, 0x01, 0xc0, 0x00, 0x00, 0x01,
0x80, 0x00, 0x00, 0x07, 0xfd, 0xfe, 0x7f, 0x9f, 0xe7, 0x3f, 0xc7, 0x01, 0xc0, 0x00, 0x00, 0x01,
0x80, 0x00, 0x00, 0x07, 0xf8, 0xfc, 0x3f, 0x0e, 0xe7, 0x1f, 0x87, 0x01, 0xc0, 0x00, 0x00, 0x01,
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x80, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x01,
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x0e, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x0a, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x7a, 0xc2, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x4b, 0x46, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x7a, 0x08, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x42, 0x08, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x7a, 0x0e, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x83, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x82, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x82, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x82, 0x00, 0x00, 0x00, 0x18, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x82, 0x00, 0x00, 0x00, 0x24, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x82, 0x00, 0x00, 0x00, 0x24, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x82, 0x00, 0x80, 0x00, 0x18, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x82, 0x01, 0xc0, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x82, 0x03, 0x60, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x82, 0x06, 0xf0, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x82, 0x0d, 0xf8, 0x00, 0xe0, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x82, 0x1b, 0xfc, 0x01, 0xb0, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x82, 0x37, 0xfe, 0x03, 0x78, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x82, 0x6f, 0xff, 0x06, 0xfc, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x82, 0xdf, 0xff, 0x8d, 0xfe, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x83, 0xbf, 0xff, 0xdb, 0xff, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x83, 0xff, 0xff, 0xf7, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x83, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x83, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x83, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0xed, 0x49,
0x83, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a, 0xa9, 0x55,
0x83, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0xad, 0xdd,
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a, 0xa8, 0x55,
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a, 0xad, 0xd5,
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
};

// Button pins
const int BtnPenControl = 10;
const int BtnEraser = 11;
const int BtnPenDown = 12;
const int BtnPenLeft = 13;
const int BtnPenRight = 14;
const int BtnPenUp = 15;

int x = 2, y = 2; // Start inside the border
bool penDown = true;
bool cursorVisible = true;
unsigned long lastToggleTime = 0;
const int debounceDelay = 300;
unsigned long lastCursorBlink = 0;
const int blinkInterval = 500;

// Canvas state tracker (true = drawn)
bool canvas[DISPLAY_WIDTH][DISPLAY_HEIGHT] = { false };

void drawBorder() {
display.drawRect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT, SH110X_WHITE);
}

void drawSquare(int x, int y, bool color) {
for (int dx = 0; dx < 2; dx++) {
for (int dy = 0; dy < 2; dy++) {
if (x + dx < DISPLAY_WIDTH && y + dy < DISPLAY_HEIGHT) {
display.drawPixel(x + dx, y + dy, color ? SH110X_WHITE : SH110X_BLACK);
}
}
}
}

void updateCanvasState(int x, int y, bool state) {
for (int dx = 0; dx < 2; dx++) {
for (int dy = 0; dy < 2; dy++) {
if (x + dx < DISPLAY_WIDTH && y + dy < DISPLAY_HEIGHT) {
canvas[x + dx][y + dy] = state;
}
}
}
}

bool squareIsEmpty(int x, int y) {
for (int dx = 0; dx < 2; dx++) {
for (int dy = 0; dy < 2; dy++) {
if (canvas[x + dx][y + dy]) return false;
}
}
return true;
}

void setup() {

pinMode(BtnPenControl, INPUT_PULLUP);
pinMode(BtnEraser, INPUT_PULLUP);
pinMode(BtnPenUp, INPUT_PULLUP);
pinMode(BtnPenDown, INPUT_PULLUP);
pinMode(BtnPenLeft, INPUT_PULLUP);
pinMode(BtnPenRight, INPUT_PULLUP);
pinMode(LED_BUILTIN, OUTPUT);

digitalWrite(LED_BUILTIN, HIGH);
display.begin(OLED_I2C_ADDRESS, true);
display.clearDisplay();
display.setTextColor(SH110X_WHITE);
display.setCursor(0, 0);
display.drawBitmap(0, 0, Splash_Screen, 128, 64, SH110X_WHITE);
display.display();
delay(3000);
digitalWrite(LED_BUILTIN, LOW);
display.clearDisplay();
drawBorder();
display.display();

}

void loop() {
// Toggle pen status
if (digitalRead(BtnPenControl) == LOW && digitalRead(BtnEraser) == HIGH) {
if (millis() - lastToggleTime > debounceDelay) {
penDown = !penDown;
lastToggleTime = millis();
}
}

// Clear screen if both buttons are pressed
if (digitalRead(BtnPenControl) == LOW && digitalRead(BtnEraser) == LOW) {
x = 2;
y = 2;
penDown = true;
display.clearDisplay();
drawBorder();
memset(canvas, 0, sizeof(canvas));
display.display();
delay(200);
return;
}

// Blinking cursor if pen is up and square is empty
if (!penDown && millis() - lastCursorBlink > blinkInterval) {
cursorVisible = !cursorVisible;
lastCursorBlink = millis();

if (squareIsEmpty(x, y)) {
drawSquare(x, y, cursorVisible);
display.display();
}
}

// Movement logic
int newX = x;
int newY = y;

if (digitalRead(BtnPenUp) == LOW && y > 2) newY--;
if (digitalRead(BtnPenDown) == LOW && y < DISPLAY_HEIGHT - 4) newY++;
if (digitalRead(BtnPenLeft) == LOW && x > 2) newX--;
if (digitalRead(BtnPenRight) == LOW && x < DISPLAY_WIDTH - 4) newX++;

if (newX != x || newY != y) {
// Erase old cursor if blinking and not over drawn pixels
if (!penDown && squareIsEmpty(x, y)) {
drawSquare(x, y, false);
}

x = newX;
y = newY;

if (penDown) {
if (digitalRead(BtnEraser) == LOW) {
// Erase mode
drawSquare(x, y, false);
updateCanvasState(x, y, false);
} else {
// Draw mode
drawSquare(x, y, true);
updateCanvasState(x, y, true);
}
} else {
// Show cursor
if (squareIsEmpty(x, y)) {
drawSquare(x, y, true);
cursorVisible = true;
lastCursorBlink = millis();
}
}

display.display();
delay(50);
}
}

Step 4: Connect the Battery

Connect the Li-ion battery with the JST Connector. Use double sided tape to stick it to the PCB.


General Note: Most TP4056 Modules will show a RED Light to indicate charging and BLUE/GREEN Light to indicate full charge as shown above.

Step 5: Controls

  1. Movement Function → Press any single arrow button to move the cursor in that direction.
  2. Diagonal Movement Function → Press any two buttons (ex- Up and Right) to move the cursor diagonally.
  3. Switch Mode Function → Press the Switch Mode button to switch between drawing mode and movement mode.
  4. Eraser Function → Press eraser button while using arrow keys to erase particular parts of the drawing.
  5. Clear Screen Function → Press the Switch Mode and Erase button simultaneously to wipe the entire screen clean instantly.
Pro tip: Combine pen toggle and careful movement for clean, precise lines.

Step 6: Watch the YouTube Tutorial

Step 7: Power Up and Play!

Flip the slide switch to ON position. You should see the splash screen followed by the drawing canvas.

The images above are some example drawings made on the DoodlePi V2.

Step 8: Future Improvements

DoodlePi is constantly evolving! The next planned version (V3) will soon feature a 3D-printed enclosure, Wi-Fi integration for saving art to the cloud, and a color OLED display.


The hardware powering DoodlePi is surprisingly capable; I am currently developing firmware to repurpose this console into a retro gaming platform for classics like Tetris, Pong, or Space Invaders. Follow me to be the first to get the codes for these upcoming game updates!

Step 9: Conclusion

DoodlePi is all about creativity, whether you’re recreating pixel game sprites or making abstract art.

If you make your own version or design something cool, share it with the hashtag #DoodlePi or post it in the comments. Let’s see what you can create!

If you want to explore more, check out my profile for related projects, and don’t forget to follow me for updates on new tutorials and advanced projects!

Step 10: Circuit Diagram

Use this Circuit diagram for reference.

Step 11: Flowchart

This flowchart explains the code in a simple way. (It does not represent the exact code, and is meant for just a basic understanding of what is happening)