Simple eeprom programmer

This commit is contained in:
Joppe Blondel
2026-04-02 15:11:39 +02:00
commit b43c979b13
5 changed files with 521 additions and 0 deletions
+5
View File
@@ -0,0 +1,5 @@
.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch
+10
View File
@@ -0,0 +1,10 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"platformio.platformio-ide"
],
"unwantedRecommendations": [
"ms-vscode.cpptools-extension-pack"
]
}
+159
View File
@@ -0,0 +1,159 @@
#!/usr/bin/env python3
import argparse
import sys
import time
from pathlib import Path
import serial
FRAME_START = 0x55
CMD_INFO = ord("I")
CMD_READ = ord("R")
CMD_WRITE_PAGE = ord("W")
REPLY_OK = ord("K")
REPLY_DATA = ord("D")
REPLY_ERROR = ord("E")
MAX_SIZE = 32768
DEFAULT_PAGE_SIZE = 64
class ProtocolError(RuntimeError):
pass
def read_exact(port: serial.Serial, length: int) -> bytes:
data = bytearray()
while len(data) < length:
chunk = port.read(length - len(data))
if not chunk:
raise ProtocolError(f"timeout while reading {length} bytes")
data.extend(chunk)
return bytes(data)
def expect_frame_start(port: serial.Serial) -> int:
start = read_exact(port, 1)[0]
if start != FRAME_START:
raise ProtocolError(f"bad frame start: 0x{start:02x}")
return read_exact(port, 1)[0]
def send_command(port: serial.Serial, command: int, payload: bytes = b"") -> int:
port.write(bytes([FRAME_START, command]) + payload)
port.flush()
return expect_frame_start(port)
def query_info(port: serial.Serial) -> tuple[int, int]:
reply = send_command(port, CMD_INFO)
if reply != REPLY_DATA:
raise ProtocolError(f"unexpected INFO reply: {reply!r}")
length = read_exact(port, 1)[0]
payload = read_exact(port, length)
if length != 4:
raise ProtocolError(f"unexpected INFO payload length: {length}")
page_size = payload[0]
total_size = payload[1] | (payload[2] << 8)
return page_size, total_size
def read_chunk(port: serial.Serial, address: int, length: int) -> bytes:
payload = bytes([address & 0xFF, (address >> 8) & 0xFF, length])
reply = send_command(port, CMD_READ, payload)
if reply == REPLY_ERROR:
code = read_exact(port, 1)[0]
raise ProtocolError(f"READ failed with error code {code}")
if reply != REPLY_DATA:
raise ProtocolError(f"unexpected READ reply: {reply!r}")
returned_length = read_exact(port, 1)[0]
if returned_length != length:
raise ProtocolError(f"short READ reply: {returned_length} != {length}")
return read_exact(port, returned_length)
def write_page(port: serial.Serial, address: int, data: bytes) -> None:
if not data or len(data) > 255:
raise ValueError("invalid page length")
payload = bytes([address & 0xFF, (address >> 8) & 0xFF, len(data)]) + data
reply = send_command(port, CMD_WRITE_PAGE, payload)
if reply == REPLY_ERROR:
code = read_exact(port, 1)[0]
raise ProtocolError(f"WRITE failed with error code {code} at 0x{address:04x}")
if reply != REPLY_OK:
raise ProtocolError(f"unexpected WRITE reply: {reply!r}")
def read_image(port: serial.Serial, size: int, page_size: int) -> bytes:
image = bytearray()
for address in range(0, size, page_size):
chunk_len = min(page_size, size - address)
image.extend(read_chunk(port, address, chunk_len))
return bytes(image)
def write_image(port: serial.Serial, data: bytes, page_size: int) -> None:
for address in range(0, len(data), page_size):
page = data[address:address + page_size]
write_page(port, address, page)
verify = read_chunk(port, address, len(page))
if verify != page:
raise ProtocolError(f"verify failed at 0x{address:04x}")
print(f"wrote and verified page 0x{address:04x}")
def open_port(device: str, baud: int) -> serial.Serial:
port = serial.Serial(device, baudrate=baud, timeout=2)
time.sleep(2.0)
port.reset_input_buffer()
port.reset_output_buffer()
return port
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="CAT28C256 UART programmer")
parser.add_argument("mode", choices=["read", "write"])
parser.add_argument("serial_port")
parser.add_argument("file", type=Path)
parser.add_argument("--baud", type=int, default=115200)
parser.add_argument("--size", type=int, default=MAX_SIZE, help="bytes to read in read mode")
return parser.parse_args()
def main() -> int:
args = parse_args()
if args.mode == "write":
data = args.file.read_bytes()
if len(data) > MAX_SIZE:
raise SystemExit(f"input file too large: {len(data)} > {MAX_SIZE}")
else:
if args.size < 1 or args.size > MAX_SIZE:
raise SystemExit(f"--size must be between 1 and {MAX_SIZE}")
with open_port(args.serial_port, args.baud) as port:
page_size, total_size = query_info(port)
if total_size != MAX_SIZE:
raise ProtocolError(f"unexpected EEPROM size reported: {total_size}")
if page_size != DEFAULT_PAGE_SIZE:
print(f"using device page size {page_size}", file=sys.stderr)
if args.mode == "read":
image = read_image(port, args.size, page_size)
args.file.write_bytes(image)
print(f"read {len(image)} bytes to {args.file}")
return 0
write_image(port, data, page_size)
print(f"programmed {len(data)} bytes from {args.file}")
return 0
if __name__ == "__main__":
try:
raise SystemExit(main())
except ProtocolError as exc:
print(f"error: {exc}", file=sys.stderr)
raise SystemExit(1)
+14
View File
@@ -0,0 +1,14 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[env:nanoatmega328new]
platform = atmelavr
board = nanoatmega328new
framework = arduino
+333
View File
@@ -0,0 +1,333 @@
#include <Arduino.h>
constexpr uint8_t kShiftDataPin = 2; // PD2
constexpr uint8_t kShiftClockPin = 3; // PD3
constexpr uint8_t kShiftLatchPin = 4; // PD4
constexpr uint8_t kWriteEnablePin = 13; // PB5, active low
constexpr uint8_t kDataPins[8] = {5, 6, 7, 8, 9, 10, 11, 12};
constexpr uint16_t kAddressMask = 0x7fff;
constexpr uint16_t kOutputDisableBit = 0x8000;
constexpr uint16_t kMaxSize = 32768;
constexpr uint8_t kPageSize = 64;
constexpr uint8_t kShiftDataMask = _BV(PD2);
constexpr uint8_t kShiftClockMask = _BV(PD3);
constexpr uint8_t kShiftLatchMask = _BV(PD4);
constexpr uint8_t kWriteEnableMask = _BV(PB5);
constexpr uint8_t kDataPortDMask = _BV(PD5) | _BV(PD6) | _BV(PD7);
constexpr uint8_t kDataPortBMask = _BV(PB0) | _BV(PB1) | _BV(PB2) | _BV(PB3) | _BV(PB4);
constexpr uint8_t kFrameStart = 0x55;
constexpr uint8_t kReplyOk = 'K';
constexpr uint8_t kReplyData = 'D';
constexpr uint8_t kReplyError = 'E';
enum Command : uint8_t {
kCmdInfo = 'I',
kCmdRead = 'R',
kCmdWritePage = 'W',
};
enum ErrorCode : uint8_t {
kErrBadCommand = 1,
kErrBadLength = 2,
kErrOutOfRange = 3,
kErrVerify = 4,
kErrProtocol = 5,
};
uint8_t pageBuffer[kPageSize];
uint16_t makeShiftValue(uint16_t address, bool outputDisable) {
return (address & kAddressMask) | (outputDisable ? kOutputDisableBit : 0);
}
inline void setShiftDataLevel(bool high) {
if (high) {
PORTD |= kShiftDataMask;
} else {
PORTD &= ~kShiftDataMask;
}
}
inline void pulseShiftClock() {
PORTD |= kShiftClockMask;
PORTD &= ~kShiftClockMask;
}
inline void setShiftLatchLevel(bool high) {
if (high) {
PORTD |= kShiftLatchMask;
} else {
PORTD &= ~kShiftLatchMask;
}
}
inline void pulseWriteEnable() {
PORTB &= ~kWriteEnableMask;
delayMicroseconds(2);
PORTB |= kWriteEnableMask;
}
void writeShiftRegisters(uint16_t value) {
setShiftLatchLevel(false);
for (int8_t bit = 15; bit >= 0; --bit) {
setShiftDataLevel((value >> bit) & 0x01);
pulseShiftClock();
}
setShiftLatchLevel(true);
setShiftLatchLevel(false);
}
void setAddress(uint16_t address, bool outputDisable) {
writeShiftRegisters(makeShiftValue(address, outputDisable));
}
void dataBusMode(uint8_t mode) {
for (uint8_t i = 0; i < 8; ++i) {
pinMode(kDataPins[i], mode);
}
}
uint8_t readDataBus() {
uint8_t value = 0;
for (uint8_t i = 0; i < 8; ++i) {
value |= static_cast<uint8_t>(digitalRead(kDataPins[i])) << i;
}
return value;
}
void writeDataBus(uint8_t data) {
PORTD = (PORTD & ~kDataPortDMask) | ((data & 0x07) << PD5);
PORTB = (PORTB & ~kDataPortBMask) | ((data >> 3) & 0x1f);
}
uint8_t eepromRead(uint16_t address) {
dataBusMode(INPUT);
setAddress(address, true);
delayMicroseconds(1);
setAddress(address, false);
delayMicroseconds(1);
const uint8_t value = readDataBus();
setAddress(address, true);
return value;
}
void beginWriteCycle(uint16_t address, uint8_t data) {
setAddress(address, true);
dataBusMode(OUTPUT);
writeDataBus(data);
delayMicroseconds(1);
}
void endWriteCycle() {
dataBusMode(INPUT);
}
void rawWriteCycle(uint16_t address, uint8_t data) {
beginWriteCycle(address, data);
pulseWriteEnable();
}
bool waitWriteComplete(uint16_t address, uint8_t expectedData) {
const unsigned long start = millis();
while (millis() - start < 20) {
if (eepromRead(address) == expectedData) {
return true;
}
}
return false;
}
bool eepromWritePage(uint16_t address, const uint8_t* data, uint8_t length) {
if (length == 0 || length > kPageSize) {
return false;
}
if ((address / kPageSize) != ((address + length - 1) / kPageSize)) {
return false;
}
dataBusMode(OUTPUT);
for (uint8_t i = 0; i < length; ++i) {
setAddress(address + i, true);
writeDataBus(data[i]);
delayMicroseconds(1);
pulseWriteEnable();
}
endWriteCycle();
delayMicroseconds(150);
return waitWriteComplete(address + length - 1, data[length - 1]);
}
void disableSoftwareDataProtection() {
dataBusMode(OUTPUT);
rawWriteCycle(0x5555, 0xAA);
rawWriteCycle(0x2AAA, 0x55);
rawWriteCycle(0x5555, 0x80);
rawWriteCycle(0x5555, 0xAA);
rawWriteCycle(0x2AAA, 0x55);
rawWriteCycle(0x5555, 0x20);
endWriteCycle();
delay(10);
}
bool readExact(uint8_t* buffer, size_t length) {
const unsigned long start = millis();
size_t offset = 0;
while (offset < length) {
if (Serial.available() > 0) {
buffer[offset++] = static_cast<uint8_t>(Serial.read());
continue;
}
if (millis() - start > 1000) {
return false;
}
}
return true;
}
uint8_t readByteOrProtocolError(bool* ok) {
uint8_t value = 0;
*ok = readExact(&value, 1);
return value;
}
void sendOk() {
Serial.write(kFrameStart);
Serial.write(kReplyOk);
}
void sendError(uint8_t code) {
Serial.write(kFrameStart);
Serial.write(kReplyError);
Serial.write(code);
}
void sendInfo() {
Serial.write(kFrameStart);
Serial.write(kReplyData);
Serial.write(static_cast<uint8_t>(4));
Serial.write(static_cast<uint8_t>(kPageSize));
Serial.write(static_cast<uint8_t>(kMaxSize & 0xff));
Serial.write(static_cast<uint8_t>(kMaxSize >> 8));
Serial.write(static_cast<uint8_t>(1)); // protocol version
}
void handleRead() {
uint8_t header[3];
if (!readExact(header, sizeof(header))) {
sendError(kErrProtocol);
return;
}
const uint16_t address = static_cast<uint16_t>(header[0]) | (static_cast<uint16_t>(header[1]) << 8);
const uint8_t length = header[2];
if (length == 0) {
sendError(kErrBadLength);
return;
}
if (static_cast<uint32_t>(address) + length > kMaxSize) {
sendError(kErrOutOfRange);
return;
}
Serial.write(kFrameStart);
Serial.write(kReplyData);
Serial.write(length);
for (uint8_t i = 0; i < length; ++i) {
Serial.write(eepromRead(address + i));
}
}
void handleWritePage() {
uint8_t header[3];
if (!readExact(header, sizeof(header))) {
sendError(kErrProtocol);
return;
}
const uint16_t address = static_cast<uint16_t>(header[0]) | (static_cast<uint16_t>(header[1]) << 8);
const uint8_t length = header[2];
if (length == 0 || length > kPageSize) {
sendError(kErrBadLength);
return;
}
if (static_cast<uint32_t>(address) + length > kMaxSize) {
sendError(kErrOutOfRange);
return;
}
if ((address / kPageSize) != ((address + length - 1) / kPageSize)) {
sendError(kErrBadLength);
return;
}
if (!readExact(pageBuffer, length)) {
sendError(kErrProtocol);
return;
}
if (!eepromWritePage(address, pageBuffer, length)) {
sendError(kErrVerify);
return;
}
sendOk();
}
void processCommand(uint8_t command) {
switch (command) {
case kCmdInfo:
sendInfo();
return;
case kCmdRead:
handleRead();
return;
case kCmdWritePage:
handleWritePage();
return;
default:
sendError(kErrBadCommand);
return;
}
}
void setup() {
Serial.begin(115200);
pinMode(kShiftDataPin, OUTPUT);
pinMode(kShiftClockPin, OUTPUT);
pinMode(kShiftLatchPin, OUTPUT);
pinMode(kWriteEnablePin, OUTPUT);
digitalWrite(kShiftClockPin, LOW);
digitalWrite(kShiftLatchPin, LOW);
digitalWrite(kWriteEnablePin, HIGH);
dataBusMode(INPUT);
setAddress(0, true);
delay(100);
disableSoftwareDataProtection();
}
void loop() {
if (Serial.available() < 2) {
return;
}
const uint8_t start = static_cast<uint8_t>(Serial.read());
if (start != kFrameStart) {
return;
}
bool ok = false;
const uint8_t command = readByteOrProtocolError(&ok);
if (!ok) {
sendError(kErrProtocol);
return;
}
processCommand(command);
}