DoodlePi: a Raspberry Pi Pico Pixel Art Drawing Toy (Inspired From Etch a Sketch)

by Ameya Angadi in Circuits > Raspberry Pi

66 Views, 0 Favorites, 0 Comments

DoodlePi: a Raspberry Pi Pico Pixel Art Drawing Toy (Inspired From Etch a Sketch)

2_20250814_204118_0001.png

DoodlePi is a DIY drawing device powered by the Raspberry Pi Pico/Pico W and inspired by the iconic Etch A Sketch.

With simple button controls, you can create line art or pixel art directly on a 1.3-inch OLED display.

It features pen up/down, an eraser, and a quick “clear screen” option for endless creative fun.

Supplies

  1. Raspberry Pi Pico/Pico W
  2. 1.3 inch OLED Display (SH1106 module, 128x64 pixels)
  3. Momentary push buttons × 6
  4. Perf board (optional)
  5. Breadboard
  6. Jumper wires
  7. USB cable
  8. Soldering iron & solder (if using perf board)

Make the Breadboard Connections

DoodlePi.png

Connect your 1.3-inch SH1106 OLED module to the Raspberry Pi Pico using the circuit diagram shared above.


VCC → VSYS (Pico pin 39)

GND → GND (Pico pin 38)

SDA → GP4 (Pico pin 6)

SCL → GP5 (Pico pin 7)

Assemble the Buttons on a Perf Board (Optional)

20250611_210501(1).jpg

Arrange the six buttons in a way that feels natural to hold, similar to a game controller layout.

Four buttons can be used for Up, Down, Left, Right, one for Pen Toggle (Pen up/down), and one for Clear/Eraser mode.

Solder each button to the perf board, then connect them to the Pico with jumper wires according to the previous circuit diagram.

I have particularly soldered the buttons in a such a way that allows me to use it like a keypad module.

Note: This step is completely optional and the button connections can be made on breadboard for simplicity.

Upload the Code

Use Arduino IDE to upload the provided C++ code.

Make sure to install Earle Philhower's Pico Core and required libraries. Follow this tutorial Getting Started with Raspberry Pi Pico in Arduino IDE

Required Libraries:

  1. Adafruit GFX Library
  2. Adafruit SH110X Library
  3. Earle Philhower's Pico Core

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: August 15, 2025
* Version: 1.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, 0x04, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x0c, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x7a, 0xc4, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x4b, 0x44, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x7a, 0x04, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x42, 0x04, 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);

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);
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);
}
}

Downloads

Understanding the Controls

Screenshot (5).png
  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. Pen Toggle Function → Press the Pen Toggle 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 Pen Toggle and Erase button simultaneously to wipe the entire screen clean instantly.
Pro tip: Combine pen toggle and careful movement for clean, precise lines.


Watch the following YouTube video for a better understanding of these controls.

Watch the YouTube Video for Instructions and Output

DoodlePi: A Raspberry Pi Pico Pixel Art Drawing Toy | Build a Raspberry Pi Pico Etch A Sketch

Example Drawings & Pixel Art

20250808_201919.jpg
20250808_204940.jpg

A few creations made by me using DoodlePi are shown above.

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!