Dear people on this forum I'm very much struggling with this project of mine.
I'm trying to recreate this project, but rewriting the code to work with a 5.65inch e-paper from waveshare instead. The display does not have any internal memory, it's all comming from the ESP32 Firebeetle 2 i'm using.
I cannot get the E-paper to display the whole thing, only half the screen (running out of memory it seems). Do you have any direction(s) I could take to try and make this work?
#include <SPI.h>
#include <FS.h>
#include <SD.h>
#include "epd5in65f.h"
#include <Preferences.h>
#include <algorithm>
#include <vector>
#include "time_utils.h"
//This is the pin for the transistor that powers the external components
#define TRANSISTOR_PIN 26
Preferences preferences;
Epd epd;
unsigned long delta; // Variable to store the time it took to update the display for deep sleep calculations
unsigned long deltaSinceTimeObtain; // Variable to store the time it took to update the display since the time was obtained for deep sleep calculations
#define SD_CS_PIN 22
uint16_t width() { return EPD_WIDTH; }
uint16_t height() { return EPD_HEIGHT; }
SPIClass vspi(VSPI); // VSPI for SD card
// Color palette for dithering
uint8_t colorPallete[7*3] = {
0, 0, 0, // Black
255, 255, 255, // White
67, 138, 28, // Green
100, 64, 255, // Blue
191, 0, 0, // Red
255, 243, 56, // Yellow
232, 126, 0, // Orange
};
uint16_t read16(fs::File &f) {
uint16_t result;
((uint8_t *)&result)[0] = f.read(); // LSB
((uint8_t *)&result)[1] = f.read(); // MSB
return result;
}
uint32_t read32(fs::File &f) {
uint32_t result;
((uint8_t *)&result)[0] = f.read(); // LSB
((uint8_t *)&result)[1] = f.read();
((uint8_t *)&result)[2] = f.read();
((uint8_t *)&result)[3] = f.read(); // MSB
return result;
}
SET_LOOP_TASK_STACK_SIZE(16384);
void setup() {
Serial.begin(115200);
delta = millis();
Serial.println("Starting setup");
analogReadResolution(12);
preferences.begin("e-paper", false);
esp_sleep_wakeup_cause_t wakeup_reason;
wakeup_reason = esp_sleep_get_wakeup_cause();
if(wakeup_reason == ESP_SLEEP_WAKEUP_TIMER) {
Serial.println("Woke up from deep sleep due to timer.");
} else {
Serial.println("Did not wake up from deep sleep.");
}
pinMode(TRANSISTOR_PIN, OUTPUT);
digitalWrite(TRANSISTOR_PIN, HIGH);
delay(100); // Increased delay to ensure power stabilizes
Serial.println("Initializing SD card...");
while(!SD.begin(SD_CS_PIN, vspi)){
Serial.println("Card Mount Failed");
delay(1000);
hibernate();
}
Serial.println("SD card initialized");
initializeWifi();
initializeTime();
deltaSinceTimeObtain = millis();
Serial.println("Initializing e-Paper display...");
if (epd.Init() != 0) {
Serial.println("e-Paper init failed");
hibernate();
}
Serial.println("e-Paper initialized successfully");
Serial.println("Clearing display...");
epd.Clear(EPD_5IN65F_WHITE);
delay(100);
checkSDFiles();
String file = getNextFile();
drawBmp(file.c_str());
digitalWrite(TRANSISTOR_PIN, LOW);
preferences.end();
}
void loop() {
hibernate();
}
void hibernate() {
Serial.println("Starting deep sleep");
esp_deep_sleep(static_cast<uint64_t>(getSecondsTillNextImage(delta, deltaSinceTimeObtain))* 1e6);
}
bool drawBmp(const char *filename) {
Serial.println("Drawing bitmap file: " + String(filename));
fs::File bmpFS = SD.open(filename);
if (!bmpFS) {
Serial.println("File not found");
return false;
}
// BMP header parsing remains the same...
uint16_t magic = read16(bmpFS);
if (magic != ('B' | ('M' << 8))) {
Serial.println("Not a BMP file");
bmpFS.close();
return false;
}
read32(bmpFS); // filesize
read32(bmpFS); // reserved
uint32_t seekOffset = read32(bmpFS);
uint32_t headerSize = read32(bmpFS);
uint32_t w = read32(bmpFS);
uint32_t h = read32(bmpFS);
read16(bmpFS); // planes
uint16_t bitDepth = read16(bmpFS);
uint32_t compression = read32(bmpFS);
Serial.print("Image dimensions: ");
Serial.print(w); Serial.print("x"); Serial.println(h);
if (compression != 0 || (bitDepth != 24 && bitDepth != 1 && bitDepth != 4 && bitDepth != 8)) {
Serial.println("Unsupported BMP format");
bmpFS.close();
return false;
}
// Calculate scaling factors
float scale_w = 1.0f;
float scale_h = 1.0f;
if (w > EPD_WIDTH || h > EPD_HEIGHT) {
scale_w = (float)EPD_WIDTH / w;
scale_h = (float)EPD_HEIGHT / h;
if (scale_w > scale_h) scale_w = scale_h;
if (scale_h > scale_w) scale_h = scale_w;
}
uint32_t new_w = (uint32_t)(w * scale_w);
uint32_t new_h = (uint32_t)(h * scale_h);
uint16_t x = (EPD_WIDTH - new_w) / 2;
uint16_t y = (EPD_HEIGHT - new_h) / 2;
// Process palette if needed
uint32_t palette[256];
if (bitDepth <= 8) {
read32(bmpFS); read32(bmpFS); read32(bmpFS);
uint32_t paletteSize = read32(bmpFS);
if (paletteSize == 0) paletteSize = 1 << bitDepth;
bmpFS.seek(14 + headerSize);
for (uint16_t i = 0; i < paletteSize; i++) {
palette[i] = read32(bmpFS);
}
}
// Allocate half frame buffer
const uint32_t HALF_HEIGHT = EPD_HEIGHT / 2;
UBYTE* halfBuffer = (UBYTE*)malloc((EPD_WIDTH * HALF_HEIGHT) / 2);
if (!halfBuffer) {
Serial.println("Failed to allocate half buffer");
bmpFS.close();
return false;
}
uint32_t lineSize = ((bitDepth * w + 31) >> 5) << 2;
uint8_t* lineBuffer = (uint8_t*)malloc(lineSize);
if (!lineBuffer) {
free(halfBuffer);
bmpFS.close();
return false;
}
int batteryVolts = analogReadMilliVolts(0) * 1.69657;
// Process image in two halves
for (int half = 0; half < 2; half++) {
Serial.printf("Processing half %d of the image\n", half + 1);
// Clear half buffer
memset(halfBuffer, EPD_5IN65F_WHITE, (EPD_WIDTH * HALF_HEIGHT) / 2);
// Calculate start and end Y positions for this half
int startY = half * HALF_HEIGHT;
int endY = startY + HALF_HEIGHT;
// Process this half of the image
for (int y_pos = startY; y_pos < endY; y_pos++) {
if (y_pos < y || y_pos >= y + new_h) continue;
int src_y = (int)((y_pos - y) / scale_h);
if (src_y >= h) continue;
uint32_t pos = seekOffset + (h - 1 - src_y) * lineSize;
bmpFS.seek(pos);
bmpFS.read(lineBuffer, lineSize);
for (int x_pos = 0; x_pos < EPD_WIDTH; x_pos++) {
if (x_pos < x || x_pos >= x + new_w) continue;
int src_x = (int)((x_pos - x) / scale_w);
if (src_x >= w) continue;
uint8_t r, g, b;
if (bitDepth == 24) {
int pixel_offset = src_x * 3;
b = lineBuffer[pixel_offset + 0];
g = lineBuffer[pixel_offset + 1];
r = lineBuffer[pixel_offset + 2];
} else {
uint32_t c = 0;
if (bitDepth == 8) {
c = palette[lineBuffer[src_x]];
} else if (bitDepth == 4) {
c = palette[(lineBuffer[src_x/2] >> ((src_x & 1) ? 0 : 4)) & 0x0F];
} else { // bitDepth == 1
c = palette[(lineBuffer[src_x/8] >> (7 - (src_x & 7))) & 0x01];
}
b = c;
g = c >> 8;
r = c >> 16;
}
uint8_t color;
int indexColor = depalette(r, g, b);
switch (indexColor) {
case 0: color = EPD_5IN65F_BLACK; break;
case 1: color = EPD_5IN65F_WHITE; break;
case 2: color = EPD_5IN65F_GREEN; break;
case 3: color = EPD_5IN65F_BLUE; break;
case 4: color = EPD_5IN65F_RED; break;
case 5: color = EPD_5IN65F_YELLOW; break;
case 6: color = EPD_5IN65F_ORANGE; break;
default: color = EPD_5IN65F_WHITE; break;
}
// Show battery warning if needed
if (batteryVolts <= 3500 && batteryVolts >= 1000 &&
x_pos <= 50 && y_pos >= EPD_HEIGHT-50) {
color = EPD_5IN65F_RED;
}
// Store in half buffer (adjust y_pos to be relative to current half)
uint32_t buf_pos = (y_pos - startY) * EPD_WIDTH + x_pos;
if (buf_pos & 0x01) {
halfBuffer[buf_pos >> 1] = (halfBuffer[buf_pos >> 1] & 0xF0) | color;
} else {
halfBuffer[buf_pos >> 1] = (halfBuffer[buf_pos >> 1] & 0x0F) | (color << 4);
}
}
}
// Display this half of the image
epd.EPD_5IN65F_Display_part(halfBuffer, 0, startY, EPD_WIDTH, HALF_HEIGHT);
Serial.printf("Displayed half %d\n", half + 1);
}
free(halfBuffer);
free(lineBuffer);
bmpFS.close();
Serial.println("Image display complete");
return true;
}
int depalette(uint8_t r, uint8_t g, uint8_t b) {
int p;
int mindiff = 100000000;
int bestc = 0;
for(p = 0; p < sizeof(colorPallete)/3; p++) {
int diffr = ((int)r) - ((int)colorPallete[p*3+0]);
int diffg = ((int)g) - ((int)colorPallete[p*3+1]);
int diffb = ((int)b) - ((int)colorPallete[p*3+2]);
int diff = (diffr*diffr) + (diffg*diffg) + (diffb * diffb);
if(diff < mindiff) {
mindiff = diff;
bestc = p;
}
}
return bestc;
}
void checkSDFiles() {
Serial.println("Checking info.txt File");
File infoFile = SD.open("/info.txt");
if (!infoFile) {
Serial.println("info.txt not found");
return;
}
String infoText = "";
while (infoFile.available()) {
infoText += (char)infoFile.read();
}
infoFile.close();
Serial.println("Content of info.txt: " + infoText);
if(preferences.getString("checker", "") != infoText) {
Serial.println("Check SD File");
File root = SD.open("/");
String fileString = "";
std::vector<String> bmpFiles;
while (true) {
File entry = root.openNextFile();
if (!entry) {
root.close();
break;
}
uint8_t nameSize = String(entry.name()).length();
String str1 = String(entry.name()).substring(nameSize - 4);
if (str1.equalsIgnoreCase(".bmp")) {
bmpFiles.push_back(entry.name());
Serial.println(String(entry.name()));
}
entry.close();
}
std::sort(bmpFiles.begin(), bmpFiles.end());
for (const String& file : bmpFiles) {
fileString += file + ",";
}
preferences.putUInt("fileCount", bmpFiles.size());
preferences.putUInt("imageIndex", 0);
preferences.putString("checker", infoText);
File file = SD.open("/fileString.txt", FILE_WRITE);
if(!file) {
Serial.println("Failed to open file for writing");
return;
}
file.print(fileString);
file.close();
}
}
String getNextFile() {
File file = SD.open("/fileString.txt");
if(!file) {
Serial.println("Failed to open file for reading");
return "";
}
String fileString = "";
while(file.available()) {
fileString += (char)file.read();
}
file.close();
String date;
if(timeWorking) {
if (timeinfo.tm_hour < 9) {
time_t previousDay = mktime(&timeinfo) - 24 * 60 * 60;
struct tm* previousDayInfo = localtime(&previousDay);
date = String(previousDayInfo->tm_mday < 10 ? "0" : "") +
String(previousDayInfo->tm_mday) + "." +
String((previousDayInfo->tm_mon + 1) < 10 ? "0" : "") +
String(previousDayInfo->tm_mon + 1);
} else {
date = String(timeinfo.tm_mday < 10 ? "0" : "") +
String(timeinfo.tm_mday) + "." +
String((timeinfo.tm_mon + 1) < 10 ? "0" : "") +
String(timeinfo.tm_mon + 1);
}
} else {
date = preferences.getString("date", "01.01");
int day = date.substring(0, 2).toInt();
int month = date.substring(3, 5).toInt();
struct tm timeinfoTemp = {0};
timeinfoTemp.tm_year = 2000 - 1900;
timeinfoTemp.tm_mon = month - 1;
timeinfoTemp.tm_mday = day + 1;
mktime(&timeinfoTemp);
char newDate[6];
snprintf(newDate, sizeof(newDate), "%02d.%02d", timeinfoTemp.tm_mday, timeinfoTemp.tm_mon + 1);
date = String(newDate);
}
Serial.println("Looking for date: " + date);
preferences.putString("date", date);
int start = 0;
int end = fileString.indexOf(",", start);
String nextFile = "";
while (end != -1) {
String currentFile = fileString.substring(start, end);
if (currentFile.indexOf(date) != -1) {
nextFile = currentFile;
break;
}
start = end + 1;
end = fileString.indexOf(",", start);
}
if (nextFile != "") {
Serial.println("Found matching file: " + nextFile);
return "/" + nextFile;
}
Serial.println("No matching file found, using index-based selection");
unsigned int fileCount = preferences.getUInt("fileCount", 0);
unsigned int imageIndex = preferences.getUInt("imageIndex", 0);
if(imageIndex >= fileCount - 1) {
imageIndex = 0;
} else {
imageIndex++;
}
preferences.putUInt("imageIndex", imageIndex);
start = 0;
end = fileString.indexOf(",", start);
for(unsigned int i = 0; i < imageIndex; i++) {
start = end + 1;
end = fileString.indexOf(",", start);
}
nextFile = fileString.substring(start, end);
Serial.println("Selected file by index: " + nextFile);
return "/" + nextFile;
}
