Devices & Components
1
Li-Ion Battery 1000mAh
1
Raspberry Pi Pico
1
DoodlePi PCB (Gerber files available below)
6
Generic push button
1
JST Connectors
1
Slide Switch
1
1,3" OLED Display SH1106 128x64 I2C
1
TP4056 with battery protection
Hardware & Tools
1
Soldering iron (generic)
Software & Tools
Arduino IDE
Project description
Code
DoodlePi
cpp
Upload this code using Arduino IDE.
1/* 2* Project Name: DoodlePi 3* Designed For: Raspberry Pi Pico/Pico W 4* 5* 6* License: GPL3+ 7* This project is licensed under the GNU General Public License v3.0 or later. 8* You are free to use, modify, and distribute this software under the terms 9* of the GPL, as long as you preserve the original license and credit the original 10* author. For more details, see <https://www.gnu.org/licenses/gpl-3.0.en.html>. 11* 12* Copyright (C) 2025 Ameya Angadi 13* 14* Code Created And Maintained By: Ameya Angadi 15* Last Modified On: November 11, 2025 16* Version: 2.0.0 17* 18*/ 19 20#include <Wire.h> 21#include <Adafruit_GFX.h> 22#include <Adafruit_SH110X.h> 23 24#define OLED_I2C_ADDRESS 0x3C 25#define DISPLAY_WIDTH 128 26#define DISPLAY_HEIGHT 64 27#define OLED_RESET -1 28 29Adafruit_SH1106G display = Adafruit_SH1106G(DISPLAY_WIDTH, DISPLAY_HEIGHT, &Wire, OLED_RESET); 30 31const unsigned char Splash_Screen[] PROGMEM = { 32// "DoodlePi", 128x64px 330xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 340x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 350x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 360x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 370x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 380x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 390x80, 0x00, 0x00, 0x07, 0xf8, 0x00, 0x00, 0x00, 0xe7, 0x00, 0x07, 0xf8, 0x00, 0x00, 0x00, 0x01, 400x80, 0x00, 0x00, 0x07, 0xfc, 0x00, 0x00, 0x00, 0xe7, 0x00, 0x07, 0xfc, 0x00, 0x00, 0x00, 0x01, 410x80, 0x00, 0x00, 0x07, 0xfc, 0x00, 0x00, 0x00, 0xe7, 0x00, 0x07, 0xfd, 0xc0, 0x00, 0x00, 0x01, 420x80, 0x00, 0x00, 0x07, 0x1c, 0x00, 0x00, 0x00, 0xe7, 0x00, 0x07, 0x1d, 0xc0, 0x00, 0x00, 0x01, 430x80, 0x00, 0x00, 0x07, 0x1c, 0x00, 0x00, 0x00, 0xe7, 0x00, 0x07, 0x1d, 0xc0, 0x00, 0x00, 0x01, 440x80, 0x00, 0x00, 0x07, 0x1c, 0x00, 0x00, 0x00, 0xe7, 0x00, 0x07, 0x1c, 0x00, 0x00, 0x00, 0x01, 450x80, 0x00, 0x00, 0x07, 0x1c, 0xfc, 0x3f, 0x0f, 0xe7, 0x1f, 0x87, 0x1d, 0xc0, 0x00, 0x00, 0x01, 460x80, 0x00, 0x00, 0x07, 0x1d, 0xfe, 0x7f, 0x9f, 0xe7, 0x3f, 0xc7, 0x1d, 0xc0, 0x00, 0x00, 0x01, 470x80, 0x00, 0x00, 0x07, 0x1d, 0xce, 0x73, 0x9c, 0xe7, 0x39, 0xc7, 0xfd, 0xc0, 0x00, 0x00, 0x01, 480x80, 0x00, 0x00, 0x07, 0x1d, 0xce, 0x73, 0x9c, 0xe7, 0x39, 0xc7, 0xfd, 0xc0, 0x00, 0x00, 0x01, 490x80, 0x00, 0x00, 0x07, 0x1d, 0xce, 0x73, 0x9c, 0xe7, 0x39, 0xc7, 0xf9, 0xc0, 0x00, 0x00, 0x01, 500x80, 0x00, 0x00, 0x07, 0x1d, 0xce, 0x73, 0x9c, 0xe7, 0x3f, 0xc7, 0x01, 0xc0, 0x00, 0x00, 0x01, 510x80, 0x00, 0x00, 0x07, 0x1d, 0xce, 0x73, 0x9c, 0xe7, 0x3f, 0xc7, 0x01, 0xc0, 0x00, 0x00, 0x01, 520x80, 0x00, 0x00, 0x07, 0x1d, 0xce, 0x73, 0x9c, 0xe7, 0x38, 0x07, 0x01, 0xc0, 0x00, 0x00, 0x01, 530x80, 0x00, 0x00, 0x07, 0x1d, 0xce, 0x73, 0x9c, 0xe7, 0x39, 0xc7, 0x01, 0xc0, 0x00, 0x00, 0x01, 540x80, 0x00, 0x00, 0x07, 0xfd, 0xce, 0x73, 0x9c, 0xe7, 0x39, 0xc7, 0x01, 0xc0, 0x00, 0x00, 0x01, 550x80, 0x00, 0x00, 0x07, 0xfd, 0xfe, 0x7f, 0x9f, 0xe7, 0x3f, 0xc7, 0x01, 0xc0, 0x00, 0x00, 0x01, 560x80, 0x00, 0x00, 0x07, 0xf8, 0xfc, 0x3f, 0x0e, 0xe7, 0x1f, 0x87, 0x01, 0xc0, 0x00, 0x00, 0x01, 570x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 580x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 590x80, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x01, 600x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 610x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x0e, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 620x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x0a, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 630x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x7a, 0xc2, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 640x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x4b, 0x46, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 650x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x7a, 0x08, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 660x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x42, 0x08, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 670x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x7a, 0x0e, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 680x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 690x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 700x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 710x83, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 720x82, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 730x82, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 740x82, 0x00, 0x00, 0x00, 0x18, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 750x82, 0x00, 0x00, 0x00, 0x24, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 760x82, 0x00, 0x00, 0x00, 0x24, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 770x82, 0x00, 0x80, 0x00, 0x18, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 780x82, 0x01, 0xc0, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 790x82, 0x03, 0x60, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 800x82, 0x06, 0xf0, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 810x82, 0x0d, 0xf8, 0x00, 0xe0, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 820x82, 0x1b, 0xfc, 0x01, 0xb0, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 830x82, 0x37, 0xfe, 0x03, 0x78, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 840x82, 0x6f, 0xff, 0x06, 0xfc, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 850x82, 0xdf, 0xff, 0x8d, 0xfe, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 860x83, 0xbf, 0xff, 0xdb, 0xff, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 870x83, 0xff, 0xff, 0xf7, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 880x83, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 890x83, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 900x83, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0xed, 0x49, 910x83, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a, 0xa9, 0x55, 920x83, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0xad, 0xdd, 930x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a, 0xa8, 0x55, 940x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a, 0xad, 0xd5, 950x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 960xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff 97}; 98 99// Button pins 100const int BtnPenControl = 10; 101const int BtnEraser = 11; 102const int BtnPenDown = 12; 103const int BtnPenLeft = 13; 104const int BtnPenRight = 14; 105const int BtnPenUp = 15; 106 107int x = 2, y = 2; // Start inside the border 108bool penDown = true; 109bool cursorVisible = true; 110unsigned long lastToggleTime = 0; 111const int debounceDelay = 300; 112unsigned long lastCursorBlink = 0; 113const int blinkInterval = 500; 114 115// Canvas state tracker (true = drawn) 116bool canvas[DISPLAY_WIDTH][DISPLAY_HEIGHT] = { false }; 117 118void drawBorder() { 119display.drawRect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT, SH110X_WHITE); 120} 121 122void drawSquare(int x, int y, bool color) { 123for (int dx = 0; dx < 2; dx++) { 124for (int dy = 0; dy < 2; dy++) { 125if (x + dx < DISPLAY_WIDTH && y + dy < DISPLAY_HEIGHT) { 126display.drawPixel(x + dx, y + dy, color ? SH110X_WHITE : SH110X_BLACK); 127} 128} 129} 130} 131 132void updateCanvasState(int x, int y, bool state) { 133for (int dx = 0; dx < 2; dx++) { 134for (int dy = 0; dy < 2; dy++) { 135if (x + dx < DISPLAY_WIDTH && y + dy < DISPLAY_HEIGHT) { 136canvas[x + dx][y + dy] = state; 137} 138} 139} 140} 141 142bool squareIsEmpty(int x, int y) { 143for (int dx = 0; dx < 2; dx++) { 144for (int dy = 0; dy < 2; dy++) { 145if (canvas[x + dx][y + dy]) return false; 146} 147} 148return true; 149} 150 151void setup() { 152 153pinMode(BtnPenControl, INPUT_PULLUP); 154pinMode(BtnEraser, INPUT_PULLUP); 155pinMode(BtnPenUp, INPUT_PULLUP); 156pinMode(BtnPenDown, INPUT_PULLUP); 157pinMode(BtnPenLeft, INPUT_PULLUP); 158pinMode(BtnPenRight, INPUT_PULLUP); 159pinMode(LED_BUILTIN, OUTPUT); 160 161digitalWrite(LED_BUILTIN, HIGH); 162display.begin(OLED_I2C_ADDRESS, true); 163display.clearDisplay(); 164display.setTextColor(SH110X_WHITE); 165display.setCursor(0, 0); 166display.drawBitmap(0, 0, Splash_Screen, 128, 64, SH110X_WHITE); 167display.display(); 168delay(3000); 169digitalWrite(LED_BUILTIN, LOW); 170display.clearDisplay(); 171drawBorder(); 172display.display(); 173 174} 175 176void loop() { 177// Toggle pen status 178if (digitalRead(BtnPenControl) == LOW && digitalRead(BtnEraser) == HIGH) { 179if (millis() - lastToggleTime > debounceDelay) { 180penDown = !penDown; 181lastToggleTime = millis(); 182} 183} 184 185// Clear screen if both buttons are pressed 186if (digitalRead(BtnPenControl) == LOW && digitalRead(BtnEraser) == LOW) { 187x = 2; 188y = 2; 189penDown = true; 190display.clearDisplay(); 191drawBorder(); 192memset(canvas, 0, sizeof(canvas)); 193display.display(); 194delay(200); 195return; 196} 197 198// Blinking cursor if pen is up and square is empty 199if (!penDown && millis() - lastCursorBlink > blinkInterval) { 200cursorVisible = !cursorVisible; 201lastCursorBlink = millis(); 202 203if (squareIsEmpty(x, y)) { 204drawSquare(x, y, cursorVisible); 205display.display(); 206} 207} 208 209// Movement logic 210int newX = x; 211int newY = y; 212 213if (digitalRead(BtnPenUp) == LOW && y > 2) newY--; 214if (digitalRead(BtnPenDown) == LOW && y < DISPLAY_HEIGHT - 4) newY++; 215if (digitalRead(BtnPenLeft) == LOW && x > 2) newX--; 216if (digitalRead(BtnPenRight) == LOW && x < DISPLAY_WIDTH - 4) newX++; 217 218if (newX != x || newY != y) { 219// Erase old cursor if blinking and not over drawn pixels 220if (!penDown && squareIsEmpty(x, y)) { 221drawSquare(x, y, false); 222} 223 224x = newX; 225y = newY; 226 227if (penDown) { 228if (digitalRead(BtnEraser) == LOW) { 229// Erase mode 230drawSquare(x, y, false); 231updateCanvasState(x, y, false); 232} else { 233// Draw mode 234drawSquare(x, y, true); 235updateCanvasState(x, y, true); 236} 237} else { 238// Show cursor 239if (squareIsEmpty(x, y)) { 240drawSquare(x, y, true); 241cursorVisible = true; 242lastCursorBlink = millis(); 243} 244} 245 246display.display(); 247delay(50); 248} 249}
Downloadable files
Schematics
Use this Schematics for reference.
DoodlePi Schematics.png

Circuit Diagram
Use this Circuit diagram for reference.
DoodlePi.png

Flowchart
This flowchart explains the code in a simple way.
DoodlePi Flowchart.png

DoodlePi Gerber Files
Upload these gerber files to your PCB manufacturer
DoodlePi_PCB.zip
Comments
Only logged in users can leave comments