Then depending on the specific boards port (my nano has a usb-c port) connect it to a computer with the arduino IDE installed. And upload the attached file, or you can find the code on my GitHub: ISOPOD GitHub.
The ISOPOD graphics are stored as bitmaps within the file. If you want to use other graphics, websites like: https://javl.github.io/image2cpp/ can be used to covert images to bitmaps. (Remember to set the correct width and height within the code and when converting the image)
#include <DFRobotDFPlayerMini.h>
#include <SoftwareSerial.h>
#include <U8g2lib.h>
// OLED Display
U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);
// DFPlayer
SoftwareSerial mySerial(10, 11);
DFRobotDFPlayerMini myDFPlayer;
// Buttons
#define buttonNext 3
#define buttonPause 4
#define buttonPrevious 5
#define ACTIVATED LOW
// State
int currentTrack = 1;
bool isPlaying = true;
int currentVolume = 20;
int currentPage = 0; // 0 = Home, 1 = Game
// Double-click handling
bool pauseClicked = false;
unsigned long lastPauseClickTime = 0;
const unsigned long doubleClickThreshold = 250; // milliseconds
bool waitingForSecondClick = false;
unsigned long lastAnimTime = 0;
uint8_t animFrame = 0;
const unsigned long animInterval = 250; // ms between frames
void setup()
{
u8g2.begin();
pinMode(buttonPause, INPUT_PULLUP);
pinMode(buttonNext, INPUT_PULLUP);
pinMode(buttonPrevious, INPUT_PULLUP);
mySerial.begin(9600);
delay(3000);
if (!myDFPlayer.begin(mySerial))
{
drawError("DFPlayer Error");
while (1)
;
}
myDFPlayer.volume(currentVolume);
myDFPlayer.play(currentTrack);
flashPage();
delay(2000);
updateDisplay();
}
void loop()
{
static bool updateNeeded = false;
// ---- Pause Button (Single click or Double-click) ----
if (digitalRead(buttonPause) == ACTIVATED)
{
unsigned long now = millis();
if (waitingForSecondClick && (now - lastPauseClickTime < doubleClickThreshold))
{
// Double-click: Change page
currentPage = (currentPage == 0) ? 1 : 0;
waitingForSecondClick = false;
pauseClicked = false;
updateNeeded = true;
while (digitalRead(buttonPause) == ACTIVATED)
;
delay(50); // debounce
}
else if (!pauseClicked)
{
pauseClicked = true;
lastPauseClickTime = now;
waitingForSecondClick = true;
while (digitalRead(buttonPause) == ACTIVATED)
;
delay(50); // debounce
}
}
// Handle single click after timeout
if (waitingForSecondClick && (millis() - lastPauseClickTime > doubleClickThreshold))
{
if (pauseClicked)
{
isPlaying = !isPlaying; // Toggle state
if (isPlaying)
{
myDFPlayer.start(); // Play (no need to check success)
}
else
{
myDFPlayer.pause(); // Pause
}
updateNeeded = true;
pauseClicked = false;
waitingForSecondClick = false;
}
}
// ---- Next Button ----
static unsigned long buttonNextPressedTime = 0;
if (digitalRead(buttonNext) == ACTIVATED)
{
if (buttonNextPressedTime == 0)
{
buttonNextPressedTime = millis();
}
else if (millis() - buttonNextPressedTime > 800)
{
if (currentVolume < 30)
currentVolume++;
myDFPlayer.volume(currentVolume); // Send it anyway
updateNeeded = true;
delay(300);
}
}
else if (buttonNextPressedTime != 0)
{
if (millis() - buttonNextPressedTime < 800)
{
currentTrack++;
myDFPlayer.play(currentTrack); // Send it anyway
isPlaying = true;
}
buttonNextPressedTime = 0;
updateNeeded = true;
}
// ---- Previous Button ----
static unsigned long buttonPreviousPressedTime = 0;
if (digitalRead(buttonPrevious) == ACTIVATED)
{
if (buttonPreviousPressedTime == 0)
{
buttonPreviousPressedTime = millis();
}
else if (millis() - buttonPreviousPressedTime > 800)
{
if (currentVolume > 0)
currentVolume--;
myDFPlayer.volume(currentVolume);
updateNeeded = true;
delay(300);
}
}
else if (buttonPreviousPressedTime != 0)
{
if (millis() - buttonPreviousPressedTime < 800)
{
if (currentTrack > 1)
currentTrack--;
myDFPlayer.play(currentTrack);
isPlaying = true;
}
buttonPreviousPressedTime = 0;
updateNeeded = true;
}
// ---- Update OLED Display ----
// ---- Animation timer for Game page ----
if (currentPage == 1 && millis() - lastAnimTime > animInterval)
{
animFrame = 1 - animFrame;
lastAnimTime = millis();
updateNeeded = true;
}
if (updateNeeded)
{
updateDisplay();
updateNeeded = false;
}
}
// ----------------- UI Functions -----------------
const uint8_t bitmap_dance_width = 40, bitmap_dance_height = 30;
const uint8_t bitmap_dance2_width = 42, bitmap_dance2_height = 28;
const uint8_t bitmap_eep_width = 38, bitmap_eep_height = 48;
const uint8_t bitmap_eep2_width = 38, bitmap_eep2_height = 48;
// Bitmap for Isopod
const unsigned char epd_bitmap_isopod[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00,
0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00,
0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
0x00, 0x30, 0x00, 0x00, 0xfc, 0x0f, 0x30, 0x00, 0x00, 0xfc, 0x0f, 0x30, 0x00, 0x00, 0x03, 0x3c,
0x3c, 0x00, 0x00, 0x03, 0x3c, 0x3c, 0x00, 0x00, 0xcf, 0xf3, 0x0c, 0x00, 0x00, 0xcf, 0xf3, 0x0c,
0x00, 0xf0, 0xf3, 0xcf, 0xff, 0x00, 0xf0, 0xf3, 0xcf, 0xff, 0x00, 0xf3, 0xfc, 0xf3, 0x3f, 0x00,
0xf3, 0xfc, 0xf3, 0x3f, 0xf0, 0xfc, 0xfc, 0x0c, 0xcf, 0xf0, 0xfc, 0xfc, 0x0c, 0xcf, 0x3c, 0xff,
0xfc, 0xf0, 0x3c, 0x3c, 0xff, 0xfc, 0xf0, 0x3c, 0x3f, 0x0f, 0x33, 0x0f, 0x03, 0x3f, 0x0f, 0x33,
0x0f, 0x03, 0x00, 0x30, 0xcf, 0x33, 0x0f, 0x00, 0x30, 0xcf, 0x33, 0x0f};
// '1-idel', 40x28px
const unsigned char bitmap_idel[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00,
0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00,
0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
0x00, 0x30, 0x00, 0x00, 0xfc, 0x0f, 0x30, 0x00, 0x00, 0xfc, 0x0f, 0x30, 0x00, 0x00, 0x03, 0x3c,
0x3c, 0x00, 0x00, 0x03, 0x3c, 0x3c, 0x00, 0x00, 0xcc, 0xf3, 0x0c, 0x00, 0x00, 0xcc, 0xf3, 0x0c,
0x00, 0xf0, 0xf3, 0xcf, 0x3f, 0x00, 0xf0, 0xf3, 0xcf, 0x3f, 0x00, 0xf3, 0xfc, 0xf3, 0x3f, 0x00,
0xf3, 0xfc, 0xf3, 0x3f, 0xf0, 0xfc, 0xfc, 0x0c, 0xff, 0xf0, 0xfc, 0xfc, 0x0c, 0xff, 0x3c, 0xff,
0xfc, 0xf0, 0x3c, 0x3c, 0xff, 0xfc, 0xf0, 0x3c, 0x3f, 0x0f, 0x33, 0x0f, 0x03, 0x3f, 0x0f, 0x33,
0x0f, 0x03, 0x00, 0x30, 0xcf, 0x33, 0x0f, 0x00, 0x30, 0xcf, 0x33, 0x0f};
// 'dance', 40x30px
const unsigned char bitmap_dance[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00,
0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00,
0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0xfc, 0x0f,
0x30, 0x00, 0x00, 0xfc, 0x0f, 0x30, 0x00, 0x00, 0x03, 0x3c, 0x0c, 0x00, 0x00, 0x03, 0x3c, 0x0c,
0x00, 0xf0, 0xf0, 0xf3, 0x0c, 0x00, 0xf0, 0xf0, 0xf3, 0x0c, 0x00, 0xfc, 0xfc, 0xcf, 0x3f, 0x00,
0xfc, 0xfc, 0xcf, 0x3f, 0x00, 0xf3, 0xfc, 0xf3, 0xff, 0x00, 0xf3, 0xfc, 0xf3, 0xff, 0xf0, 0xfc,
0xfc, 0x0c, 0xff, 0xf0, 0xfc, 0xfc, 0x0c, 0xff, 0x3f, 0xcf, 0xfc, 0xf0, 0x3c, 0x3f, 0xcf, 0xfc,
0xf0, 0x3c, 0x3f, 0x33, 0x33, 0x0f, 0x03, 0x3f, 0x33, 0x33, 0x0f, 0x03, 0x00, 0xfc, 0x0f, 0xcf,
0x0f, 0x00, 0xfc, 0x0f, 0xcf, 0x0f};
// '5-dance', 42x28px
const unsigned char bitmap_dance2[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00,
0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00,
0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xf0, 0x3f,
0xc0, 0x00, 0x00, 0x00, 0xf0, 0x3f, 0xc0, 0x00, 0x00, 0x00, 0x0c, 0xf0, 0xc0, 0x00, 0x00, 0x00,
0x0c, 0xf0, 0xc0, 0x00, 0x00, 0x00, 0x33, 0xcf, 0x33, 0x00, 0x00, 0x00, 0x33, 0xcf, 0x33, 0x00,
0x00, 0xc0, 0xcf, 0x3f, 0xff, 0x00, 0x00, 0xc0, 0xcf, 0x3f, 0xff, 0x00, 0x00, 0xcc, 0xf3, 0xcf,
0xff, 0x00, 0x00, 0xcc, 0xf3, 0xcf, 0xff, 0x00, 0xff, 0xf3, 0xf3, 0x33, 0xfc, 0x03, 0xff, 0xf3,
0xf3, 0x33, 0xfc, 0x03, 0xfc, 0xfc, 0xf3, 0xc3, 0xf3, 0x00, 0xfc, 0xfc, 0xf3, 0xc3, 0xf3, 0x00,
0xf0, 0x3c, 0xcc, 0x3c, 0x0c, 0x00, 0xf0, 0x3c, 0xcc, 0x3c, 0x0c, 0x00, 0x00, 0xc0, 0x3c, 0xcf,
0xfc, 0x00, 0x00, 0xc0, 0x3c, 0xcf, 0xfc, 0x00};
// '2-eep', 38x48px
const unsigned char bitmap_eep[] PROGMEM = {
0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00,
0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xf0, 0x0f, 0x00, 0x00, 0x00, 0xf0, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00,
0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00,
0x00, 0xf0, 0x0f, 0x00, 0x00, 0x00, 0xf0, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xff, 0x00, 0x00, 0x00, 0xc0, 0xff, 0x00, 0x00, 0x00,
0x00, 0x30, 0x30, 0x00, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00, 0x0c, 0x0c, 0x00, 0x00, 0x00,
0x0c, 0x0c, 0x00, 0x00, 0x00, 0x03, 0x0c, 0x00, 0x00, 0x00, 0x03, 0x0c, 0x00, 0x00, 0xc0, 0xff,
0x0c, 0x00, 0x00, 0xc0, 0xff, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x30,
0x00, 0x00, 0xfc, 0x0f, 0x30, 0x00, 0x00, 0xfc, 0x0f, 0x30, 0x00, 0x00, 0x03, 0x3c, 0x3c, 0x00,
0x00, 0x03, 0x3c, 0x3c, 0x00, 0x00, 0xcc, 0xf3, 0x0c, 0x00, 0x00, 0xcc, 0xf3, 0x0c, 0x00, 0xf0,
0xf3, 0xcf, 0x3f, 0x00, 0xf0, 0xf3, 0xcf, 0x3f, 0x00, 0xf3, 0xfc, 0xf3, 0x3f, 0x00, 0xf3, 0xfc,
0xf3, 0x3f, 0xf0, 0xfc, 0xfc, 0xfc, 0x0f, 0xf0, 0xfc, 0xfc, 0xfc, 0x0f, 0x3c, 0xff, 0xfc, 0x00,
0x3c, 0x3c, 0xff, 0xfc, 0x00, 0x3c, 0x3f, 0x3f, 0xc3, 0x33, 0x03, 0x3f, 0x3f, 0xc3, 0x33, 0x03};
// '3-eep', 38x48px
const unsigned char bitmap_eep2[] PROGMEM = {
0x00, 0xf0, 0x3f, 0x00, 0x00, 0x00, 0xf0, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0xc0,
0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f, 0x00, 0x00, 0x00, 0xf0, 0x3f,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00,
0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00,
0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00,
0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0xc0, 0x0f, 0x00, 0x0c, 0x00, 0xc0, 0x0f,
0x00, 0x0c, 0x00, 0x00, 0x03, 0x00, 0x0c, 0x00, 0x00, 0x03, 0x00, 0x0c, 0x00, 0xc0, 0x0f, 0x00,
0x30, 0x00, 0xc0, 0x0f, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x30,
0x00, 0x00, 0xfc, 0x0f, 0x30, 0x00, 0x00, 0xfc, 0x0f, 0x30, 0x00, 0x00, 0x03, 0x3c, 0x0c, 0x00,
0x00, 0x03, 0x3c, 0x0c, 0x00, 0x00, 0xcc, 0xf3, 0x0c, 0x00, 0x00, 0xcc, 0xf3, 0x0c, 0x00, 0xf0,
0xf3, 0xcf, 0x3f, 0x00, 0xf0, 0xf3, 0xcf, 0x3f, 0x00, 0xf3, 0xfc, 0xf3, 0x3f, 0x00, 0xf3, 0xfc,
0xf3, 0x3f, 0xf0, 0xfc, 0xfc, 0xfc, 0x0f, 0xf0, 0xfc, 0xfc, 0xfc, 0x0f, 0x3c, 0xff, 0xfc, 0x00,
0x3c, 0x3c, 0xff, 0xfc, 0x00, 0x3c, 0x3f, 0x3f, 0xc3, 0x33, 0x03, 0x3f, 0x3f, 0xc3, 0x33, 0x03};
// start page
void flashPage()
{
u8g2.clearBuffer();
u8g2.drawFrame(10, 10, 108, 44);
u8g2.setFont(u8g2_font_pcsenior_8u);
u8g2.drawXBMP(60, 25, 40, 28, epd_bitmap_isopod);
u8g2.setCursor(30, 35);
u8g2.print(F("ISOPOD"));
u8g2.sendBuffer();
}
// header
void drawHeader()
{
u8g2.setDrawColor(1);
u8g2.drawFrame(0, 0, 128, 14);
u8g2.setFont(u8g2_font_open_iconic_embedded_1x_t);
if (currentPage == 0)
{
u8g2.drawGlyph(5, 11, 0x0040); // Home icon
}
else
{
u8g2.setFont(u8g2_font_unifont_t_animals);
u8g2.drawGlyph(5, 11, 0x0230); // Animal icon
}
u8g2.setFont(u8g2_font_5x8_tf);
u8g2.setCursor(90, 11);
u8g2.print("VOL ");
u8g2.print(currentVolume);
u8g2.setDrawColor(1);
}
// draw the page to screen
void updateDisplay()
{
u8g2.clearBuffer();
drawHeader();
if (currentPage == 0)
{
drawHomePage();
}
else
{
drawGamePage();
}
u8g2.sendBuffer();
}
// home page
void drawHomePage()
{
u8g2.drawFrame(0, 15, 128, 33);
u8g2.setFont(u8g2_font_pcsenior_8u);
u8g2.setCursor(10, 35);
u8g2.print("SONG:");
u8g2.setCursor(60, 35);
u8g2.print(currentTrack);
u8g2.drawFrame(0, 50, 128, 14);
u8g2.setFont(u8g2_font_open_iconic_play_1x_t);
u8g2.drawGlyph(25, 61, 0x0047); // Previous
u8g2.drawGlyph(60, 61, isPlaying ? 0x0044 : 0x0045); // Play/Pause
u8g2.drawGlyph(95, 61, 0x0048); // Next
}
// isopod page
void drawGamePage()
{
u8g2.drawFrame(0, 15, 128, 49);
// Bottom-right corner for all images
const int x_br = 75, y_br = 63;
if (isPlaying)
{
if (animFrame == 0)
{
u8g2.drawXBMP(
x_br - bitmap_dance_width,
y_br - bitmap_dance_height,
bitmap_dance_width,
bitmap_dance_height,
bitmap_dance);
}
else
{
u8g2.drawXBMP(
x_br - bitmap_dance2_width,
y_br - bitmap_dance2_height,
bitmap_dance2_width,
bitmap_dance2_height,
bitmap_dance2);
}
}
else
{
if (animFrame == 0)
{
u8g2.drawXBMP(
x_br - bitmap_eep_width,
y_br - bitmap_eep_height,
bitmap_eep_width,
bitmap_eep_height,
bitmap_eep);
}
else
{
u8g2.drawXBMP(
x_br - bitmap_eep2_width,
y_br - bitmap_eep2_height,
bitmap_eep2_width,
bitmap_eep2_height,
bitmap_eep2);
}
}
}
void drawError(const char *msg)
{
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_ncenB14_tr);
u8g2.drawStr(0, 30, msg);
u8g2.sendBuffer();
}