/*
** SD-Card file browser (WIP)
**
** A simple SD-Card file browser utilizing an SSD1306 display and
** an SD-Card reader module.
**
** (connect a resistor from pin 0 to 3v on real hardware)
*/
#include <SD.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define CS_PIN 5
#define CD_PIN 4
#define LISTINGCHARLIMIT 19
#define PATHCHARLIMIT 20
#ifdef ESP32 // ESP32 Doesn't have these by default
#define max(a, b) (a>b? a : b)
#define min(a, b) (a<b? a : b)
#endif
File root;
Adafruit_SSD1306 display(128, 64, &Wire, -1, 1000000);
struct dpad {
bool up, upPressed, upReleased;
bool down, downPressed, downReleased;
bool left, leftPressed, leftReleased;
bool right, rightPressed, rightReleased;
bool select, selectPressed, selectReleased;
};
dpad systemDPad = dpad();
static const unsigned char PROGMEM fileIcon[] = {
0b11111000,
0b10001100,
0b10001110,
0b10000010,
0b10000010,
0b10000010,
0b11111110
};
static const unsigned char PROGMEM directoryIcon[] = {
0b11110000,
0b10001110,
0b11110010,
0b10000010,
0b10000010,
0b10000010,
0b11111110
};
uint8_t u, pu; // Up (GPIO 17)
uint8_t d, pd; // Down (GPIO 0)
uint8_t l, pl; // Left (GPIO 16)
uint8_t r, pr; // Right (GPIO 15)
uint8_t s, ps; // Select (GPIO 2)
uint selection = 0;
String dirPath = "/";
uint itemCount = 0;
void setup() {
Serial.begin(115200);
display.begin(SSD1306_SWITCHCAPVCC, 0x3C); // Begin I2C Communication With Display
display.clearDisplay(); // Clear Display Buffer
display.setTextSize(1); // Text size should be 1px
display.setTextColor(SSD1306_INVERSE); // Toggle pixels while drawing instead of setting to one specific color
display.setCursor(0, 0); // Reset Cursor
display.print("Booting...");
display.display(); // Display On Screen
pinMode(CD_PIN, INPUT);
initInput();
if (digitalRead(CD_PIN) != 0) {
display.clearDisplay();
display.setCursor(0, 0);
display.print("Please insert card.");
display.display();
while (digitalRead(CD_PIN) != 0);
}
display.print("\nInitializing card... ");
display.display();
if (!SD.begin(CS_PIN)) {
Serial.println("\nCould not initialize the card.");
while (true);
}
root = SD.open(dirPath);
}
void loop() {
display.clearDisplay();
updateInput();
printDirectory(9, 10, selection, root);
display.fillRect(0, 0, 128, 10, SSD1306_BLACK);
display.drawFastHLine(0, 10, 128, SSD1306_WHITE);
display.setCursor(2, 1);
display.print(dirPath.length() > PATHCHARLIMIT ? dirPath.substring(dirPath.length() - PATHCHARLIMIT, dirPath.length()) : dirPath);
if (systemDPad.selectPressed) notificationBox("This feature is not yet implemented.", 96, 48, 8, 6);
display.display();
}
void printTestPattern(uint8_t spacing, int16_t offset, uint8_t amount) {
int linePosition = offset;
for (uint8_t i = 0; i < amount; i++) {
display.drawFastHLine(1, linePosition, 126, SSD1306_WHITE);
linePosition += spacing + 2;
}
uint8_t crsr = offset + 2;
String curr = "abcdefghijklmnopqrstuvwxyz";
for (uint8_t i = 0; i < amount - 1; i++) {
display.drawBitmap(3, crsr, fileIcon, 8, 7, SSD1306_WHITE);
display.setCursor(12, crsr);
display.print(curr.length() > LISTINGCHARLIMIT ? (curr.substring(0, LISTINGCHARLIMIT - 3) + "...") : curr);
crsr += spacing + 2;
}
uint8_t start = max(offset, 8);
uint8_t len = min(crsr-1, 64) - start;
display.drawFastVLine(0, start, len, SSD1306_WHITE);
display.drawFastVLine(127, start, len, SSD1306_WHITE);
}
void printDirectory(uint8_t spacing, int16_t offset, uint16_t selected, File source) {
File curr = source.openNextFile();
int linePosition = offset;
itemCount = 0;
display.drawFastHLine(1, linePosition, 126, SSD1306_WHITE);
linePosition += spacing + 2;
uint8_t crsr = offset + 2;
while (curr != NULL) {
String name = curr.name();
itemCount++;
display.drawBitmap(3, crsr, curr.isDirectory()? directoryIcon : fileIcon, 8, 7, SSD1306_WHITE);
display.setCursor(12, crsr);
display.print(name.length() > LISTINGCHARLIMIT ? (name.substring(0, LISTINGCHARLIMIT - 3) + "...") : name);
crsr += spacing + 2;
display.drawFastHLine(1, linePosition, 126, SSD1306_WHITE);
if((selection+1) == itemCount) display.fillRect(0, linePosition-spacing-1, 128, spacing+1, SSD1306_INVERSE);
linePosition += spacing + 2;
curr = source.openNextFile();
}
source.rewindDirectory(); /* Change this section so it has the current directory listing in RAM
and updates the RAM listing when switching directories. */
uint8_t start = max(offset, 8);
uint8_t len = min(crsr-1, 64) - start;
display.drawFastVLine(0, start, len, SSD1306_WHITE);
display.drawFastVLine(127, start, len, SSD1306_WHITE);
}
void notificationBox(String message, uint8_t width, uint8_t height, uint8_t radius, uint8_t padding) {
message.replace("\n", "");
display.fillRoundRect(64 - ((width+padding) / 2), 32 - ((height+padding) / 2), width+padding, height+padding, radius*1.4, SSD1306_BLACK);
display.drawRoundRect(64 - (width / 2), 32 - (height / 2), width, height, radius, SSD1306_WHITE);
uint8_t textX = 64 - (width / 2) + (radius / 2);
uint8_t textY = 32 - (height / 2) + (radius / 2);
for (uint8_t i = 0; i <= (message.length() / 14); i++) {
display.setCursor(textX, textY + (i*9));
display.print(message.substring(i*14, min((i+1)*14, message.length())));
}
display.display();
while (systemDPad.select) updateInput();
while (!systemDPad.select) updateInput();
while (systemDPad.select) updateInput();
}
void initInput() {
pinMode(17, INPUT); // Up
pinMode(35, INPUT); // Down
pinMode(16, INPUT); // Left
pinMode(34, INPUT); // Right
pinMode(2, INPUT); // Select
u, pu = digitalRead(17);
d, pd = digitalRead(35);
l, pl = digitalRead(16);
r, pr = digitalRead(34);
s, ps = digitalRead(2);
updateInput();
}
void updateInput() {
pu = u;
pd = d;
pl = l;
pr = r;
ps = s;
u = digitalRead(17);
d = digitalRead(35);
l = digitalRead(16);
r = digitalRead(34);
s = digitalRead(2);
if (d>pd) selection++;
if (u>pu) selection--;
selection = (selection == (itemCount)) ? 0 : selection;
selection = constrain(selection, 0, itemCount-1);
systemDPad.up = u; systemDPad.upPressed = u>pu; systemDPad.upReleased = u<pu;
systemDPad.down = d; systemDPad.downPressed = d>pd; systemDPad.downReleased = d<pd;
systemDPad.left = l; systemDPad.leftPressed = l>pl; systemDPad.leftReleased = l<pl;
systemDPad.right = r; systemDPad.rightPressed = r>pr; systemDPad.rightReleased = r<pr;
systemDPad.select = s; systemDPad.selectPressed = s>ps; systemDPad.selectReleased = s<ps;
}