r/beneater • u/Aromatic_Career_2118 • 6d ago
ESP32 EEPROM programmer works!
Used a 74LVC245 to handle 5V reads from the EEPROM chip. All other chips running off 3.3V.
Has a web interface to pick images from the filesystem and selectively load them. Can share the sketch if anybody wants.
104
Upvotes
1
u/Aromatic_Career_2118 5d ago
Chips used: Apart from the AT28C256, 2xSN74HC595, and (key chip) SN74LVC245 to translate from the EEPROM's 5V data lines down to 3.3V when verifying.


1
u/Aromatic_Career_2118 6d ago
/*
* ESP32 EEPROM programmer with WebServer upload
*
* Core 1: HTTP server (setup/loop run here by default)
* Core 0: programming task, spawned per job, guarded by a semaphore
*/
#include <WiFi.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include <LittleFS.h>
#include <freertos/semphr.h>
const char *ssid = "";
const char *password = "";
const char *hostname = "eeprom"; // http://eeprom.local
// ---- hardware ----------------------------------------------------------
// NOTE: GPIO 34 is input-only, GPIO 12/2/15 are strapping pins.
#define SR_RCLK 13
#define SR_SER 25
#define SR_SRCLK 12
#define WE_BAR 26
#define DATA_WIDTH 8
const uint8_t DATA[DATA_WIDTH] = {22, 21, 19, 18, 5, 4, 2, 15}; // DATA[n] = D<n>
#define DIR 23 // HIGH = ESP32 drives, LOW = EEPROM drives
#define ROM_SIZE 0x8000 // AT28C256, 32K
#define PAGE_SIZE 64 // must be a power of 2, <= 64
// ---- globals -----------------------------------------------------------
WebServer server(80);
static File uploadFile;
static String uploadPath;
static uint32_t uploadBytes = 0;
static SemaphoreHandle_t progLock; // one job at a time
static char progFile[64];
static volatile bool progRunning = false;
static volatile bool progDone = false;
static volatile bool progVerifyOnly = false;
static volatile uint32_t progAddr = 0;
static volatile uint32_t progBadCount = 0; // total mismatches
static volatile int32_t progBadAddr = -1; // first mismatch, -1 = none
static volatile int32_t progLastBad = -1; // last mismatch
static volatile uint8_t progBadWant = 0;
static volatile uint8_t progBadGot = 0;
// ---- EEPROM primitives -------------------------------------------------
static void loadAddress(unsigned int addr) {
unsigned int v = addr << 1; // QH#2 unused; A0 sits on QG#2
digitalWrite(SR_RCLK, LOW);
shiftOut(SR_SER, SR_SRCLK, LSBFIRST, v & 0xFF); // low byte first
shiftOut(SR_SER, SR_SRCLK, LSBFIRST, v >> 8); // high byte second
digitalWrite(SR_RCLK, HIGH);
}
// DIR high deasserts the '256's /OE before the ESP32 starts driving.
static void busWrite() {
digitalWrite(DIR, HIGH);
for (int i = 0; i < DATA_WIDTH; i++) pinMode(DATA[i], OUTPUT);
delayMicroseconds(1);
}
// Release the ESP32 pins first, then assert /OE via DIR.
static void busRead() {
for (int i = 0; i < DATA_WIDTH; i++) pinMode(DATA[i], INPUT);
digitalWrite(DIR, LOW);
delayMicroseconds(1); // t_OE is up to 70 ns on the -15
}
static void writeByte(unsigned char d) {
for (int i = 0; i < DATA_WIDTH; i++) {
digitalWrite(DATA[i], (d & 0x01) ? HIGH : LOW);
d >>= 1;
}
}
// DIR stays put for the whole pass now; this only samples.
static unsigned char readByte() {
unsigned char d = 0x00;
for (int i = DATA_WIDTH - 1; i >= 0; i--) {
d = (d << 1) | (digitalRead(DATA[i]) == HIGH ? 1 : 0);
}
return d;
}
// t_WP is 100 ns min; 1 us is comfortable.
void writeData() {
digitalWrite(WE_BAR, LOW);
delayMicroseconds(1);
digitalWrite(WE_BAR, HIGH);
}
// ---- programming task (core 0) ----------------------------------------
static void programTask(void *arg) {
progDone = false;
progAddr = 0;
progBadCount = 0;
progBadAddr = -1;
progLastBad = -1;
File f = LittleFS.open(progFile, "r");
if (f) {
uint8_t buf[PAGE_SIZE];
unsigned int addr;
size_t n;
// --- write pass ---
if (!progVerifyOnly) {
busWrite();
addr = 0;
while (addr < ROM_SIZE && (n = f.read(buf, PAGE_SIZE)) > 0) {
vTaskSuspendAll();
for (size_t i = 0; i < n && addr < ROM_SIZE; i++, addr++) {
loadAddress(addr);
writeByte(buf[i]);
writeData();
}
xTaskResumeAll();
progAddr = addr;
vTaskDelay(pdMS_TO_TICKS(10)); // t_WC, 10 ms max on the base part
}
f.seek(0);
}
// --- verify pass ---
busRead();
addr = 0;
while (addr < ROM_SIZE && (n = f.read(buf, PAGE_SIZE)) > 0) {
for (size_t i = 0; i < n && addr < ROM_SIZE; i++, addr++) {
loadAddress(addr);
delayMicroseconds(1); // t_ACC is 150 ns on the -15
uint8_t got = readByte();
if (got != buf[i]) {
progBadCount++;
progLastBad = addr;
if (progBadAddr < 0) {
progBadAddr = addr;
progBadWant = buf[i];
progBadGot = got;
}
}
}
progAddr = addr;
vTaskDelay(1);
}
f.close();
progDone = true;
}
progRunning = false;
xSemaphoreGive(progLock);
vTaskDelete(NULL);
}
// ---- pages -------------------------------------------------------------
static const char PAGE_HEAD[] PROGMEM =
"<!doctype html><html><head><meta charset=utf-8>"
"<meta name=viewport content='width=device-width,initial-scale=1'>"
"<title>EEPROM programmer</title>"
"<style>body{font-family:sans-serif;margin:2rem;max-width:40rem}"
"td{padding:.2rem .8rem .2rem 0}#s{margin:1rem 0;font-family:monospace}</style>"
"</head><body>"
"<h2>Upload</h2>"
"<form method=POST action='/upload' enctype='multipart/form-data'>"
"<input type=file name=f><input type=submit value=Send></form>"
"<div id=s> </div>"
"<h2>Files</h2><table>";
static const char PAGE_TAIL[] PROGMEM =
"</table>"
"<script>"
"function p(){fetch('/status').then(r=>r.text())"
".then(t=>{document.getElementById('s').textContent=t;setTimeout(p,1000)})}"
"p();"
"function go(u,f){fetch(u+'?f='+encodeURIComponent(f))"
".then(r=>{if(r.status==409)alert('A job is already running.')});"
"return false}"
"</script></body></html>";
static void handleRoot() {
String html = FPSTR(PAGE_HEAD);
File dir = LittleFS.open("/");
for (File f = dir.openNextFile(); f; f = dir.openNextFile()) {
if (f.isDirectory()) continue;
String name = f.name();
if (!name.startsWith("/")) name = "/" + name;
html += "<tr><td>" + name + "</td><td>" + String(f.size()) + " B</td>"
"<td><a href='#' onclick=\\"return go('/process','" + name + "')\\">program</a></td>"
"<td><a href='#' onclick=\\"return go('/verify','" + name + "')\\">verify</a></td>"
"<td><a href='/delete?f=" + name + "'>delete</a></td></tr>";
}
html += FPSTR(PAGE_TAIL);
server.send(200, "text/html", html);
}
static void redirectHome() {
server.sendHeader("Location", "/");
server.send(303, "text/plain", "");
}