Skip to content

Commit

Permalink
Arduino-Based AT28C256 EEPROM Programmer.
Browse files Browse the repository at this point in the history
Initial commit with working reads and full dumps.
  • Loading branch information
erikvanzijst committed May 13, 2019
0 parents commit 6158aa1
Show file tree
Hide file tree
Showing 6 changed files with 384 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.idea/
13 changes: 13 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Copyright 2019, Erik van Zijst <[email protected]>

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
158 changes: 158 additions & 0 deletions adruino/adruino.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
* AT28C256 EEPROM Reader and Programmer
*
* This code implements the serial wire protocol as described in protocol.txt
*
* Pin Layout
*
* Pin | Circuit
* ----+--------------
* 2 | EEPROM IO0
* 3 | EEPROM IO1
* 4 | EEPROM IO2
* 5 | EEPROM IO3
* 6 | EEPROM IO4
* 7 | EEPROM IO5
* 8 | EEPROM IO6
* 9 | EEPROM IO7
* ----+--------------
* 10 | 74HC595 SER
* 11 | 74HC595 RCLK
* 12 | 74HC595 SCLK
* ----+--------------
* A0 | EEPROM CE
* A1 | EEPROM OE
* A2 | EEPROM WE
* ----+--------------
* 13 | Activity LED
* ----+--------------
*
* Copyright 2019, Erik van Zijst <[email protected]>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

// AT28C256 contol lines
const unsigned int EEPROM_CE = A0;
const unsigned int EEPROM_OE = A1;
const unsigned int EEPROM_WE = A2;

// 74HC595 control lines
const unsigned int SHIFT_SER = 10;
const unsigned int SHIFT_RCLK = 11;
const unsigned int SHIFT_SCLK = 12;

// Activity indicator LED
const unsigned int ACT_LED = 13;

const unsigned int dataPins[] = {2, 3, 4, 5, 6, 7, 8, 9};

unsigned int len;
byte buf[4];

void setup() {
Serial.begin(19200);

pinMode(EEPROM_CE, OUTPUT);
pinMode(EEPROM_OE, OUTPUT);
pinMode(EEPROM_WE, OUTPUT);

pinMode(SHIFT_SER, OUTPUT);
pinMode(SHIFT_RCLK, OUTPUT);
pinMode(SHIFT_SCLK, OUTPUT);

pinMode(ACT_LED, OUTPUT);
digitalWrite(ACT_LED, LOW);

for (unsigned int i = 0; i < 8; i++) {
pinMode(dataPins[i], INPUT);
}
}

void pulse(int pin) {
digitalWrite(pin, HIGH);
delayMicroseconds(1);
digitalWrite(pin, LOW);
}

/*
* Loads the specified 16 bit address into the 595 shift register.
*/
void loadShiftAddr(unsigned int addr) {
for (int i = 15; i >= 0; i--) {
digitalWrite(SHIFT_SER, ((addr >> i) & 1) ? HIGH : LOW);
pulse(SHIFT_SCLK);
}
pulse(SHIFT_RCLK);
}

void readAddr(unsigned int addr) {
loadShiftAddr(addr);

byte val = 0;
for (unsigned int i = 0; i < 8; i++) {
val |= (digitalRead(dataPins[i]) << i);
}

Serial.write(val);
}

void writeAddr(unsigned int addr, byte val) {
Serial.write(0);
}

void dump() {
for (unsigned int addr = 0; addr < 32768; addr++) {
byte val = 0;
loadShiftAddr(addr);
delayMicroseconds(1);
for (unsigned int i = 0; i < 8; i++) {
val |= (digitalRead(dataPins[i]) << i);
}
Serial.write(val);
}
}

void load() {
}

void loop() {
if (Serial.available() > 0) {
digitalWrite(ACT_LED, HIGH);

len = Serial.read();
Serial.readBytes(buf, len);

if (buf[0] == 0x72 && len == 3) {
readAddr((buf[1] << 8) + buf[2]);

} else if (buf[0] == 0x77 && len == 4) {
writeAddr((buf[1] << 8) + buf[2], buf[3]);

} else if (buf[0] == 0x64 && len == 1) {
dump();

} else if (buf[0] == 0x6c && len == 3) {
load();

} else {
for (int i = 0; i < 5; i++) {
digitalWrite(ACT_LED, LOW);
delay(200);
digitalWrite(ACT_LED, HIGH);
delay(200);
}
}
digitalWrite(ACT_LED, LOW);
}
}
121 changes: 121 additions & 0 deletions eeprom.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
#!/usr/bin/env python3
#
# Copyright 2019, Erik van Zijst <[email protected]>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import argparse
import sys
from struct import pack

from serial import Serial
from serial.tools import list_ports


def atoi(val: str) -> int:
"""Parses an address string into an integer.
Supports decimal, hexadecimal and octal notation.
"""
return int(val,
16 if val.startswith('0x') else
8 if val.startswith('0o') else
10)


def read(port: Serial, addr: str) -> None:
port.write(pack('>BcH', 3, b'r', atoi(addr)))
val = port.read(1)
print(int.from_bytes(val, 'big'), '/', '0x' + val.hex())


def write(port: Serial, addr: str, val: str) -> None:
port.write(pack('>BcHB', 4, b'w', atoi(addr), atoi(val)))
print(port.read(1))
print('OK')


def dump(port: Serial, filename: str) -> None:
with open(filename, 'wb') as f:
port.write(pack('>Bc', 1, b'd'))

for i in range(2**15):
f.write(port.read(1))
f.flush()
if i % 100 == 0:
print('\r%d%%' % ((i / 2**15) * 100), end='')
print('\nComplete.')


def quit(*args) -> None:
raise EOFError()


if __name__ == '__main__':
usage = """AT28C256 EEPROM Programmer
Read or write individual addresses, dump out the full contents to a file, or\
load an image file onto the ERPROM.
To read a single byte:
> [r|read] [addr]
To write a byte to a specific address:
> [w|write] [addr] [value]
To dump the entire EEPROM to a file:
> [d|dump] [filename]
To load a local file into the EEPROM:
> [l|load] [filename]
Address supports hex (0xFF) and octal (0o7) notation.
"""
parser = argparse.ArgumentParser(description='AT28C256 EEPROM Programmer')
parser.add_argument('port', nargs='?',
help='the serial port the Arduino is '
'connected to (on OSX typically '
'/dev/tty.usbmodemXXXX)')
args = parser.parse_args()

dev = args.port
if not args.port:
try:
# attempt to autodetect the Arduino
dev = next(
filter(lambda p: p.product and 'arduino' in p.product.lower(),
list_ports.comports())).device
print('Found Arduino at port', dev)
except StopIteration:
print('Cannot find Arduino. If it is connected, specify the port '
'manually.', file=sys.stderr)
exit(1)

port = Serial(dev, 19200, timeout=3)
print(usage)
while True:
try:
expr = input('> ').split()
{'r': read,
'read': read,
'w': write,
'write': write,
'd': dump,
'dump': dump,
'quit': quit,
'q': quit}[expr[0]](port, *expr[1:])

except EOFError:
break
except (ValueError, KeyError, IndexError, TypeError) as e:
print('Invalid command:', e)
90 changes: 90 additions & 0 deletions protocol.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
Protocol

The protocol between the Python client and Arduino is a synchronous request
response based system. The client initiates all communication, one request
at a time.

Each request yields a response from the Arduino which must be consumed fully
before the next request can be sent.

Each request is packed into a structured binary envelope consisting of a
leading octet indicating the length of the message (excluding the leading
octet), followed by a single octet describing the command that is to be
executed by the Arduino, followed by 0 up to and including 253 bytes of
command-specific payload data:

0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length | Command | Data |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

The following commands are defined:

1. Read

Command byte: 'r' (0x72 / ASCII 114)
Data: 16 bit address in network byte order. Since the AT28C256 has only 15
address lines, only the 15 lower bits are used. The leading, MSB is unused.

0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 3 | 'r' |0| 15 bit address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

The response is a single byte containing the value at the specified address,
without header or delimiter.

2. Write

Command byte: 'w' (0x77 / ASCII 119)
Data: 3 bytes, broken down as follows: 2 bytes containing a 15 bit address
in network byte order, followed by one octet of data to be written to the
specified address.

0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 4 | 'w' |0| 15 bit address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data |
+-+-+-+-+-+-+-+-+

The response is a single 0 byte to indicate the operation finished and a new
command can be sent.

3. Dump

Command byte: 'd' (0x64 / ASCII 100)
Data: No payload following the command byte.

0 1
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 0 | 'd' |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

The response is the raw contents of the entire EEPROM. This is exactly 32KB
of raw data without header or delimiter. The response is done after the
32768th byte.

4. Load

Command byte: 'l' (0x6C / ASCII 108)
Data: 2 bytes indicating the size of the upload that follows the request
message. Since the AT28C256 has only 15 address lines, only the lower 15 bits
are used. The leading, MSB is ignored.

0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 3 | 'l' |0| 15 bit length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Directly following the command message is the stream of bytes to be uploaded
to the EEPROM. Writing always begins at address 0. The length of the stream
must match the length advertised in the command message.

The response is a single 0 byte to indicate the operation finished and a new
command can be sent.
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pyserial==3.4

0 comments on commit 6158aa1

Please sign in to comment.