Simple eeprom programmer
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
.pio
|
||||
.vscode/.browse.c_cpp.db*
|
||||
.vscode/c_cpp_properties.json
|
||||
.vscode/launch.json
|
||||
.vscode/ipch
|
||||
Vendored
+10
@@ -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
@@ -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)
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user