Compare commits

...

10 commits

10 changed files with 431 additions and 47 deletions

6
.gitignore vendored
View file

@ -163,3 +163,9 @@ tags
[._]*.un~
# End of https://www.toptal.com/developers/gitignore/api/vim,python
# Binary files
*.bin
# Mac
/**/.DS_Store

6
.vim/coc-settings.json Normal file
View file

@ -0,0 +1,6 @@
{
"python.linting.pylintEnabled": true,
"python.linting.mypyEnabled": false,
"python.linting.enabled": true,
"python.formatting.provider": "black"
}

View file

@ -1,4 +1,7 @@
# eeprom_programmer
A very basic EEPROM programmer, compatible with chips with up to 15 address lines.
# Protoboard assembly
![Protoboard assembly](images/protoboard_assembly.JPG)
##### This project was inspired by and based on [Ben Eater's programmer](https://github.com/beneater/eeprom-programmer)

View file

@ -17,8 +17,70 @@
// Digital pins 8 to 13 correspond to PB0 to PB5
byte lastOp;
int lastAddressWritten;
byte lastDataWritten;
bool isFirstWrite = true;
static char printBuff[128];
void program();
void dump();
void erase();
void dumpFirts256Bytes() {
byte data;
Serial.println("Reading EEPROM");
for (int addr = 0; addr < 256; addr += 16) {
sprintf(printBuff, "%04x:", addr);
Serial.print(printBuff);
for (int offset = 0; offset < 16; offset++) {
sprintf(printBuff, " %02x", readEEPROM(addr + offset));
Serial.print(printBuff);
}
Serial.println();
}
}
void setup() {
// put your setup code here, to run once:
pinMode(SHIFT_DATA, OUTPUT);
pinMode(SHIFT_CLK, OUTPUT);
pinMode(SHIFT_LATCH, OUTPUT);
digitalWrite(EEPROM_WE, HIGH);
pinMode(EEPROM_WE, OUTPUT);
// Setting an invalid value.
lastOp = -1;
Serial.begin(115200);
}
void loop() {
static byte command;
// The loop function only process commands.
while (Serial.available() == 0);
command = Serial.read();
switch(command) {
case 0x00:
// Program the eeprom.
program();
break;
case 0x01:
// Dump the eeprom.
dump();
break;
case 0x02:
// Erase the EEPROM (fill with 0xFF).
erase();
default:
// Ignore invalid commands.
break;
}
}
void latchOutput() {
static byte latchMask = 0b00010000;
@ -27,6 +89,7 @@ void latchOutput() {
PORTD |= latchMask;
delayMicroseconds(1);
PORTD &= ~latchMask;
delayMicroseconds(1);
}
void commitWrite() {
@ -87,29 +150,43 @@ void setAddress(int address, bool outputEnable) {
latchOutput();
}
byte readEEPROM(int address) {
// TODO: Think about adding a delay if the last operation was a write and where it is appropriate to put it.
// If the last operation wasn't a read we need to set our pins as input.
if (lastOp != LAST_OP_READ) {
setBusMode(READ_MODE);
}
byte readBus(int address) {
// In contrast with the write function, it's okay to set output enable after setting the
// pins as inputs. This way, the arduino pins are already ready to sink current when the
// EEPROM starts driving the bus.
byte result = (PINB << 3) | (PIND >> 5) ;
lastOp = LAST_OP_READ;
return result;
setAddress(address, true);
// For some reason, PINB takes a long time to update, a delay of a minimum of 2 microsends
// Must be added. The datasheet of the AT28C256 specifies a time from address to output of 150ns max
// and a time from output enabled to output of 70ns max, thus it makes no sense to wait 2000ns, but
// if it is not done, the most significant nibble is always 0xF.
delayMicroseconds(2);
return (PINB << 3) | (PIND >> 5);
}
byte readEEPROM(int address) {
// If the last operation wasn't a read we need to set our pins as input
if (lastOp != LAST_OP_READ) {
setBusMode(READ_MODE);
// If last op was a write we need to poll for valid data.
if (lastOp == LAST_OP_WRITE) {
while (readBus(lastAddressWritten) != lastDataWritten);
}
}
lastOp = LAST_OP_READ;
return readBus(address);
}
/**
* Writes a single byte of data to the specified address.
* If cooldown is true, the function adds a delay to avoid
* reading incorrect data after a write.
*/
void writeEEPROM(int address, byte data, bool cooldown) {
void writeEEPROM(int address, byte data, bool pollOnPageChange) {
// Since we're performing page writes, we must poll the data when we change page.
if (
((address & 0xFFC0) != (lastAddressWritten & 0xFFC0))
&& isFirstWrite == false
&& pollOnPageChange
) {
while(readEEPROM(lastAddressWritten) != lastDataWritten);
}
// For the write case, must turn output enable off before setting the pins as outputs.
// If we don't do this, for a brief period of time, both the arduino and the EEPROM would
// drive the bus and this could case problems.
@ -122,37 +199,71 @@ void writeEEPROM(int address, byte data, bool cooldown) {
// EEPROM_D0 to D2 correspond to PD5 to PD7
// EEPROM_D3 to D7 correspond to PB0 to PB4
PORTD &= (data << 5) & 0b00011111;
PORTB &= (data >> 3) & 0b11100000;
PORTD = (PORTD & 0b00011111 ) | (data << 5);
PORTB = (PORTB & 0b11100000) | (data >> 3) ;
// Slow version.
//for (int pin = EEPROM_D0; pin < EEPROM_D7; pin++, data >>= 1) {
// digitalWrite(pin, data & 1);
//}
commitWrite();
lastOp = LAST_OP_WRITE;
if (cooldown) {
delay(10);
isFirstWrite = false;
lastAddressWritten = address;
lastDataWritten = data;
}
void disableSoftwareProtection() {
writeEEPROM(0x5555, 0xAA, false);
writeEEPROM(0x2AAA, 0x55, false);
writeEEPROM(0x5555, 0x80, false);
writeEEPROM(0x5555, 0xAA, false);
writeEEPROM(0x2AAA, 0x55, false);
writeEEPROM(0x5555, 0x20, false);
delay(10);
}
void erase() {
for (long addr = 0; addr < 32768; addr++) {
writeEEPROM(addr, 0xFF, true);
}
// Ack the operation.
Serial.write(0xFF);
}
void program() {
// For now, the programmer will always write to the whole eeprom.
byte value;
// The program will write byte by byte.
for (long addr = 0; addr < 32768; addr++) {
// Wait for next value.
while(Serial.available() == 0);
value = Serial.read();
writeEEPROM(addr, value, true);
}
}
void dump() {
unsigned long startAddress = 0;
unsigned long byteCount = 0;
byte value;
// Wait and read the starting address to dump.
while (Serial.available() < 2);
startAddress |= Serial.read();
startAddress |= Serial.read() << 8;
void setup() {
// put your setup code here, to run once:
pinMode(SHIFT_DATA, OUTPUT);
pinMode(SHIFT_CLK, OUTPUT);
pinMode(SHIFT_LATCH, OUTPUT);
digitalWrite(EEPROM_WE, HIGH);
pinMode(EEPROM_WE, OUTPUT);
// Setting an invalid value.
lastOp = -1;
Serial.begin(9600);
writeEEPROM(0x1234, 0x55, true);
writeEEPROM(0x4321, 0x7E, true);
}
void loop() {
// put your main code here, to run repeatedly:
// Wait and read the byte count.
while (Serial.available() < 2);
byteCount |= Serial.read();
byteCount |= Serial.read() << 8;
for (int address = startAddress; address < startAddress + byteCount; address++ ) {
value = readEEPROM(address);
Serial.write(value);
}
}

View file

@ -10,8 +10,23 @@
#define ADDRESS_CLOCK 18
#define WRITE_CLOCK 19
/*
PORT mappings for data pins:
2 PORTE 4
3 PORTE 5
4 PORTG 5
5 PORTE 3
6 PORTH 3
7 PORTH 4
8 PORTH 5
9 PORTH 6
*/
void setup() {
Serial.begin(9600);
Serial.begin(115200);
attachInterrupt(digitalPinToInterrupt(ADDRESS_CLOCK), onAddressClock, RISING);
attachInterrupt(digitalPinToInterrupt(WRITE_CLOCK), onWriteClock, FALLING);
}
@ -27,10 +42,16 @@ void onAddressClock() {
}
void onWriteClock() {
Serial.print("Data: 0b");
for (int pin = DATA_7; pin >= DATA_0; pin--) {
Serial.print(digitalRead(pin));
}
Serial.print("Data: 0b");
// TODO: Find a way of making this more readable.
Serial.print((PINH & 0x40) >> 6);
Serial.print((PINH & 0x20) >> 5);
Serial.print((PINH & 0x10) >> 4);
Serial.print((PINH & 0x08) >> 3);
Serial.print((PINE & 0x08) >> 3);
Serial.print((PING & 0x20) >> 5);
Serial.print((PINE & 0x20) >> 5);
Serial.print((PINE & 0x10) >> 4);
Serial.println();
}

160
cli/cli.py Normal file
View file

@ -0,0 +1,160 @@
import argparse
import time
import serial
from tqdm import tqdm
def get_parsed_args():
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(title="Operation to perform", dest="operation")
program_parser = subparsers.add_parser("program")
dump_parser = subparsers.add_parser("dump")
erase_parser = subparsers.add_parser("erase")
# Parser for the "program" subcommand.
program_parser.add_argument(
"-f",
"--file",
help="Binary file containing the program",
type=argparse.FileType("rb"),
required=True,
)
program_parser.add_argument(
"-p", "--port", help="Serial port where programmer is located", required=True
)
# Parser for the dump subcommand.
dump_parser.add_argument(
"-f",
"--file",
help="The file where the binary dump will be stored",
type=argparse.FileType("wb"),
required=True,
)
dump_parser.add_argument(
"-p", "--port", help="Serial port where programmer is located", required=True
)
dump_parser.add_argument(
"-s",
"--start",
help="The address where to start dumping",
type=int,
required=True,
)
dump_parser.add_argument(
"-c",
"--byte-count",
help="The amount of bytes to dump",
type=int,
required=True,
)
dump_parser.add_argument(
"--display",
help="Print the dump to the screen after storing it to the file",
action="store_true",
)
# Parser for the erase command .
erase_parser.add_argument(
"-p",
"--port",
help="Serial port where the programmer is located",
required=True,
)
return parser.parse_args()
def program(args):
print("Programming...")
with serial.Serial(args.port, 115200) as ser:
# Give a chance to the arduino to reset.
# TODO: Arduino resets by default when opening a serial
# connection, there's ways to avoid this. Investigate more.
time.sleep(2)
# Sending command
ser.write((0).to_bytes(1, "big"))
bytes_to_write = args.file.read()
# Forward the bytes from the selected file
for i in tqdm(range(32 * 1024)):
value = (bytes_to_write[i]).to_bytes(1, "big")
ser.write(value)
# This delay was found by experimentation.
# A byte write in the arduino takes about 80us.
# Every 64 bytes (a page) we do data polling and could
# take much longer. A byte send given a 115200 baud rate
# should take ~70us, a 100us delay should give the EEPROM enough time
# to internally write the page withouh the UART read buffer (64 bytes)
# overflowing.
time.sleep(100.0 * 1e-6)
print("Done programming!")
def dump(args):
print("Dumping...")
with serial.Serial(args.port, 115200) as ser:
address = int(args.start)
byte_count = int(args.byte_count)
# TODO: Avoid arduino autoreset.
print("Waiting for the arduino to reset...")
time.sleep(2)
# Sending command
ser.write((1).to_bytes(1, "big"))
# Sending address, low byte first.
ser.write((address & 0xFF).to_bytes(1, "big"))
ser.write(((address & 0xFF00) >> 8).to_bytes(1, "big"))
# Sending byte count, low byte first.
ser.write((byte_count & 0xFF).to_bytes(1, "big"))
ser.write(((byte_count & 0xFF00) >> 8).to_bytes(1, "big"))
# Read the stream coming from the Arduino and forward them
# to the user-selected dump file.
for _ in range(byte_count):
args.file.write(ser.read())
print("Done dumping!")
def erase(args):
print("Erasing...")
with serial.Serial(args.port, 115200) as ser:
# TODO: Avoid arduino autoreset.
print("Waiting for the arduino to reset...")
time.sleep(2)
ser.write((2).to_bytes(1, "big"))
# Wait for the ack.
if ser.read() == b"\xFF":
print("Erasing completed!")
else:
print("Erasing failed")
def main():
args = get_parsed_args()
if args.operation == "program":
program(args)
elif args.operation == "dump":
dump(args)
elif args.operation == "erase":
erase(args)
else:
print("Unrecognized command, exiting now...")
if __name__ == "__main__":
main()

17
cli/requirements.txt Normal file
View file

@ -0,0 +1,17 @@
appdirs==1.4.4
astroid==2.4.2
isort==5.6.4
jedi==0.17.2
lazy-object-proxy==1.4.3
mccabe==0.6.1
mypy-extensions==0.4.3
parso==0.7.1
pathspec==0.8.1
pylint==2.6.0
pyserial==3.5
rope==0.18.0
six==1.15.0
toml==0.10.2
tqdm==4.54.1
typing-extensions==3.7.4.3
wrapt==1.12.1

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

View file

@ -0,0 +1,16 @@
appdirs==1.4.4
astroid==2.4.2
isort==5.6.4
jedi==0.17.2
lazy-object-proxy==1.4.3
mccabe==0.6.1
mypy-extensions==0.4.3
parso==0.7.1
pathspec==0.8.1
pylint==2.6.0
pyserial==3.5
rope==0.18.0
six==1.15.0
toml==0.10.2
typing-extensions==3.7.4.3
wrapt==1.12.1

View file

@ -0,0 +1,44 @@
import glob
import argparse
import serial
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--port")
args = parser.parse_args()
# Get all the valid serial ports available.
available_ports = glob.glob("/dev/cu.*")
# Only do this if the user did not specify a port.
if args.port is None:
# Make the user select a port to start a listening session.
print("Available ports:")
for i, port in enumerate(available_ports):
print(f"{i} - {port}")
selected_port_index = None
while selected_port_index is None and args.port is None:
try:
selected_port_index = input(
"\nType port number to start listening section: "
)
selected_port_index = int(selected_port_index)
if selected_port_index < 0 or selected_port_index >= len(available_ports):
print("The port number you provided does not exist.")
selected_port_index = None
except:
print("The typed input is not valid, please enter a valid number.")
selected_port_index = None
port = args.port if args.port is not None else available_ports[selected_port_index]
# TODO: Allow selecting baud raute, 115200 by default for now.
with serial.Serial(port, 115200) as ser:
while True:
byte = ser.read()
print(byte.decode("utf-8"), end="")
if __name__ == "__main__":
main()