commit b43c979b13b69ce60e3c58355babe70ff0e86623 Author: Joppe Blondel Date: Thu Apr 2 15:11:39 2026 +0200 Simple eeprom programmer diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..89cc49c --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..080e70d --- /dev/null +++ b/.vscode/extensions.json @@ -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" + ] +} diff --git a/eeprom_uart.py b/eeprom_uart.py new file mode 100644 index 0000000..7fbc9a5 --- /dev/null +++ b/eeprom_uart.py @@ -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) diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..8ba220f --- /dev/null +++ b/platformio.ini @@ -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 diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..149b417 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,333 @@ +#include + +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(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(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(4)); + Serial.write(static_cast(kPageSize)); + Serial.write(static_cast(kMaxSize & 0xff)); + Serial.write(static_cast(kMaxSize >> 8)); + Serial.write(static_cast(1)); // protocol version +} + +void handleRead() { + uint8_t header[3]; + if (!readExact(header, sizeof(header))) { + sendError(kErrProtocol); + return; + } + + const uint16_t address = static_cast(header[0]) | (static_cast(header[1]) << 8); + const uint8_t length = header[2]; + + if (length == 0) { + sendError(kErrBadLength); + return; + } + if (static_cast(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(header[0]) | (static_cast(header[1]) << 8); + const uint8_t length = header[2]; + + if (length == 0 || length > kPageSize) { + sendError(kErrBadLength); + return; + } + if (static_cast(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(Serial.read()); + if (start != kFrameStart) { + return; + } + + bool ok = false; + const uint8_t command = readByteOrProtocolError(&ok); + if (!ok) { + sendError(kErrProtocol); + return; + } + + processCommand(command); +}