Ameya Angadi
Published © GPL3+

DoodlePi: a Raspberry Pi Pico Pixel Art Drawing Toy

DIY pixel art gadget inspired from Etch A Sketch — draw, erase, and create retro-style art.

BeginnerFull instructions provided2 hours59
DoodlePi: a Raspberry Pi Pico Pixel Art Drawing Toy

Things used in this project

Hardware components

Raspberry Pi Pico W
Raspberry Pi Pico W
×1
1.3 inch OLED Display (SH1106 module, 128x64 pixels)
×1
Pushbutton switch 12mm
SparkFun Pushbutton switch 12mm
×6
SparkFun Snappable Protoboard
SparkFun Snappable Protoboard
Optional
×1
Breadboard (generic)
Breadboard (generic)
×1
Jumper wires (generic)
Jumper wires (generic)
×1
USB-A to Micro-USB Cable
USB-A to Micro-USB Cable
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
(if using perf board)

Story

Read more

Schematics

Circuit Diagram

Use this to make all connections

Code

DoodlePi

Arduino
Upload This 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);
  }
}

Credits

Ameya Angadi
10 projects • 8 followers

Comments