Compare commits
10 commits
59a9758a63
...
4e50929f9c
| Author | SHA1 | Date | |
|---|---|---|---|
| 4e50929f9c | |||
| 057c6f51fc | |||
| 2ecd603db9 | |||
| 81e30791b0 | |||
| adc5e6a078 | |||
| 7ef7a0bfef | |||
| 58c74ebc86 | |||
| e8bcd7c994 | |||
| eed427f986 | |||
| 9af30ef829 |
10 changed files with 431 additions and 47 deletions
6
.gitignore
vendored
6
.gitignore
vendored
|
|
@ -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
6
.vim/coc-settings.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"python.linting.pylintEnabled": true,
|
||||
"python.linting.mypyEnabled": false,
|
||||
"python.linting.enabled": true,
|
||||
"python.formatting.provider": "black"
|
||||
}
|
||||
|
|
@ -1,4 +1,7 @@
|
|||
# eeprom_programmer
|
||||
A very basic EEPROM programmer, compatible with chips with up to 15 address lines.
|
||||
|
||||
# Protoboard assembly
|
||||

|
||||
|
||||
##### This project was inspired by and based on [Ben Eater's programmer](https://github.com/beneater/eeprom-programmer)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
// 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;
|
||||
isFirstWrite = false;
|
||||
|
||||
if (cooldown) {
|
||||
delay(10);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -28,9 +43,15 @@ void onAddressClock() {
|
|||
|
||||
void onWriteClock() {
|
||||
Serial.print("Data: 0b");
|
||||
for (int pin = DATA_7; pin >= DATA_0; pin--) {
|
||||
Serial.print(digitalRead(pin));
|
||||
}
|
||||
// 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
160
cli/cli.py
Normal 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
17
cli/requirements.txt
Normal 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
|
||||
BIN
images/protoboard_assembly.JPG
Normal file
BIN
images/protoboard_assembly.JPG
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.1 MiB |
16
serial_monitor/requirements.txt
Normal file
16
serial_monitor/requirements.txt
Normal 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
|
||||
44
serial_monitor/serial_monitor.py
Normal file
44
serial_monitor/serial_monitor.py
Normal 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()
|
||||
Loading…
Add table
Add a link
Reference in a new issue