Optimize Address Register code for 28C timing requirements

Also remove slower generic Arduino code so that the project is now tied
to specific Arduino versions.
Add readme for 28C chips.
This commit is contained in:
Tom Nisbet 2019-06-15 22:20:02 -04:00
parent 702de2ba96
commit 23454b9dad
9 changed files with 173 additions and 183 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.vscode/

25
README-28C.md Normal file
View File

@ -0,0 +1,25 @@
# Notes on 28C EEPROMs
The 28C series parallel EEPROMS, like the 28C256, support fast block writes and algorithms to implement Software Data Protection (SDP). The SDP feature seems to be a leading cause of problems for people trying to program these chips with Arduino or other homebrew hardware.
The first problem many people encounter is that new chips are often locked, even though the datasheet states that they should ship unlocked. It isn't clear if the manufacturing practices have changed or if this might be due to used or conterfeit chips. In any case, the chips may need to be unlocked before they can be programmed for the first time.
The unlock is accomplished by sending a sequence of bytes to specific addresses. Many people have reported problems with this step when using DIY programmers. Some programmers may not be writing the SDP sequences quickly enough to successfully unlock the chips and yet others will report that the same hardware works correctly. This may be due to variences in the behavior of chips from different manufacturers.
When writing SDP lock/unlock sequences, the datasheets note that the timing between bytes must follow the same restrictions as page writes. In particular, the bytes must be written within the tBLC (Byte Load Cycle time). On Atmel parts, this is specified as 150us max, so each write pulse must occur within 150us of the previous write. The tBLC value is even shorter on the Xicor and ON Semi datasheets, stating that the writes must occur within 100us of each other.
In practice, the Xicor chips seem very forgiving of the timing, doing successful SDP and page write operations even when the tBLC is close to 200us. Atmel chips, on the other hand, will refuse to unlock when the timing is outside the acceptable maximum.
# Solution
The TommyProm programmer uses direct port access to control the data bus and addressing shift register. This is much faster than doing individual DigitalWrite calls and allows the unlock and page write code to run comfortably with the tBLC constraints. It has been tested with Atmel chips and multiple batches of Xicor 28C256 chips with success.
The capture below shows an unlock command sequence where the tBLC us within 80us for each byte.
![Unlock Timing](docs/Unlock-Timing.png)
# References
1. [Atmel AT28C256 Data Sheet, 0006MPEEPR12/09](http://ww1.microchip.com/downloads/en/DeviceDoc/doc0006.pdf)
1. [Xicor, Intersil, Renesas X28C256 Data Sheet, 3855-1.9 8/1/97 T1/C0/D8 EW](https://www.renesas.com/us/en/www/doc/datasheet/x28hc256.pdf)
1. [ON Semiconductor CAT28C256 Data Sheet, CAT28C256/D, December, 2009 Rev. 6](https://www.onsemi.com/pub/Collateral/CAT28C256-D.PDF)
1. [Parallel EEPROM Data Protection Application Note, Atmel, Rev. 0543C10/98](http://ww1.microchip.com/downloads/en/AppNotes/DOC0543.PDF)

View File

@ -1,5 +1,5 @@
# TommyPROM - An Arduino-based EEPROM programmer
This is a simple EEPROM programmer and reader that can be assembled using an Arduino and a few additional parts. It has been sucessfully built using the Arduino UNO, Micro, and Nano models.
This is a simple EEPROM programmer and reader that can be assembled using an Arduino and a few additional parts. It has been sucessfully built using the Arduino UNO, Nano and Boarduino models.
The original code was specific to the 28C256 32Kx8 EEPROM, but it has been extended to also support Intel 8755A EPROMS.
@ -11,6 +11,7 @@ Features include:
* Simple hardware design that can be assembled on a breadboard.
* ROM images transfers using XMODEM - no special host client needed.
* Support for fast block EEPROM writes - a 32K EEPROM will program in just a few seconds.
* Optimized code that supports the timing requirements needed to unlock the 28C series Software Protection Algorithm.
* Modular software design to easily support other EEPROM and EPROM families.
The [hardware readme](hardware/README.md) has schematics and more information on the hardware design. The [software readme](TommyPROM/README.md) has class definitions and more information on the software design.
@ -24,9 +25,9 @@ The project was inspired by the [MEEPROMMER programmer](http://www.ichbinzustaen
Open the TommyPROM.ino file in the Arduino IDE. It should automatically open the cpp and h files as well. The default code programs 28C series chips using Arduino Nano hardware. To use this version, just compile and upload it to the Arduino.
For different Arduino hardware, like UNO or Micro, edit the Configure.h file and uncomment the appropriate ARDUINO_IS_xx line. Only one of these lines should be uncommented. If all of these lines are commented out, the generic bit-at-a-time code is used to write to the data bus. This will work on all Arduinos, but it is slower that the model-specific code.
**Note well** that this code has been optimized for the Aduino UNO and Nano hardware so that it can run quickly enough to meet 28C series chip timing reqirements for SDP unlocking. To use different Arduino hardware, like the Micro, the board-specific code in PromDevice.cpp and PromAddressDriver.cpp must be change to match the port mappings between the ATmega chip and the Arduino I/O pins.
To use the 8755A version of the code and matching hardware, uncomment PROM_IS_8755A and comment out the other PROM_IS_xx choices.
To use the 8755A version of the code and matching hardware, uncomment PROM_IS_8755A and comment out the other PROM_IS_xx choices in Configure.h.
## Operation
![TommyPROM Screenshot](docs/tp05.png)
@ -48,11 +49,11 @@ The READ and WRITE command both use XMODEM CRC to complete the file transfers.
The files used for READ and WRITE are simple binary images. This can be created directly by [asm85](http://github.com/TomNisbet/asm85) or can be converted from S-record or Intel HEX using an external utility.
## Troubleshooting
* Verify that the Arduino type you are using is a supported board or that its I/O port definitions match one of the supported boards. Some other Arduino boards, like the Duemilanove, appear to be compatible but have not been tested. Others, like the Micro, have different port mappings and definitely will not work without software changes.
* If the code doesn't appear to be working, concentrate on the read operations first to verify that the data and address paths are good.
* 28C series EEPROMS, like the X28C256, sometimes ship from the factory with Data Protection enabled. Use the UNLOCK command to disable this. See the [28C Readme](README-28C.md) for more information.
* Re-check all hardware connections and verify the the control pins are going to the Arduino pins that match the definitions in the code.
* Verify that the ARDUINO_IS_xxx line in Configure.h matches the Arduino type you are using. Many Arduino boards other than those listed in
the file may work as well by commenting out all of the ARDUINO_IS_xxx lines. This will use the slower bit-at-a-time code for that data bus instead of the board-specific code.
* 28C series EEPROMS, like the 28C256, sometimes ship from the factory with Data Protection enabled. Use the UNLOCK command to disable this.
* This repo contains a standalone program called HardwareVerify that allows low-level access to the address, data, and control lines through a menu-driven interface. See the [readme](HardwareVerify/README.md) for that code for more tips.
## Further Work
* Add a new PromDevice class for 27 series EPROMS.

View File

@ -1,11 +1,3 @@
// Uncomment only one of the ARDUINO_IS_ lines to use the fast I/O code for
// the data bus, or comment them all out to use the slower bit-at-a-time code.
//#define ARDUINO_IS_MICRO
#define ARDUINO_IS_UNO
//#define ARDUINO_IS_NANO
// Comment this out to remove extra debugging commands and code
#define ENABLE_DEBUG_COMMANDS

View File

@ -45,8 +45,14 @@ void PromAddressDriver::setAddress(word address)
// is a matter of using the correct clock pin to shift the data in.
void PromAddressDriver::setAddressRegister(uint8_t clkPin, byte addr)
{
byte mask = 0;
if (clkPin == ADDR_CLK_HI)
mask = 0x08;
else if (clkPin == ADDR_CLK_LO)
mask = 0x10;
// Make sure the clock is low to start.
digitalWrite(clkPin, LOW);
PORTC &= ~mask;
// Shift 8 bits in, starting with the MSB.
for (int ix = 0; (ix < 8); ix++)
@ -54,17 +60,18 @@ void PromAddressDriver::setAddressRegister(uint8_t clkPin, byte addr)
// Set the data bit
if (addr & 0x80)
{
digitalWrite(ADDR_DATA, HIGH);
PORTC |= 0x20;
}
else
{
digitalWrite(ADDR_DATA, LOW);
PORTC &= 0xdf;
}
digitalWrite(clkPin, HIGH); // Clock in a bit
digitalWrite(clkPin, LOW); // Reset the clock pin
// Toggle the clock high then low
PORTC |= mask;
delayMicroseconds(3);
PORTC &= ~mask;
addr <<= 1;
}
}

View File

@ -1,155 +1,109 @@
#include "Configure.h"
#include "PromDevice.h"
PromDevice::PromDevice(unsigned long size, word blockSize, unsigned maxWriteTime, bool polling)
: mSize(size),
mBlockSize(blockSize),
mMaxWriteTime(maxWriteTime),
mSupportsDataPoll(polling)
{
}
// Write a block of data to the device. If the device supports block writes,
// the data will be broken into chunks and written using the block mode.
// Otherwise, each byte will be individually written and verified.
bool PromDevice::writeData(byte data[], word len, word address)
{
bool status = true;
if (mBlockSize == 0)
{
// Device does not support block writes.
for (word ix = 0; (ix < len); ix++)
{
if (burnByte(data[ix], address + ix) == false)
{
status = false;
break;
}
}
}
else
{
word offset = 0;
word chunkSize;
if (address & (mBlockSize - 1))
{
// Address does not start on a block boundary. Adjust the size of
// the first block to fit within a single block.
chunkSize = mBlockSize - (address & (mBlockSize - 1));
chunkSize = (chunkSize > len) ? len : chunkSize;
if (burnBlock(data, chunkSize, address) == false)
{
return false;
}
offset += chunkSize;
len -= chunkSize;
}
// All writes are now aligned to block boundaries, so write full blocks
// or remaining length, whichever is smaller.
while (len > 0)
{
chunkSize = (len > mBlockSize) ? mBlockSize : len;
if (burnBlock(data + offset, chunkSize, address + offset) == false)
{
status = false;
break;
}
offset += chunkSize;
len -= chunkSize;
}
}
return status;
}
// BEGIN PRIVATE METHODS
//
// Set the I/O state of the data bus.
// The first two bits of port D are used for serial, so the 8 bits data bus are
// on pins D2..D9.
void PromDevice::setDataBusMode(uint8_t mode)
{
#if defined(ARDUINO_IS_UNO) || defined(ARDUINO_IS_NANO)
// On the Uno and Nano, D2..D9 maps to the upper 6 bits of port D and the
// lower 2 bits of port B.
if (mode == OUTPUT)
{
DDRB |= 0x03;
DDRD |= 0xfc;
}
else
{
DDRB &= 0xfc;
DDRD &= 0x03;
}
#elif defined(ARDUINO_IS_MICRO)
// On the Micro, D2..D9 maps to the upper 7 bits of port B and the
// lower bit of port D.
if (mode == OUTPUT)
{
DDRB |= 0xfe;
DDRD |= 0x01;
}
else
{
DDRB &= 0x01;
DDRD &= 0xfe;
}
#else
byte bit = 0x01;
for (int pin = 2; (pin <= 9); pin++) {
pinMode(pin, mode);
bit <<= 1;
}
#endif
}
// Read a byte from the data bus. The caller must set the bus to input_mode
// before calling this or no useful data will be returned.
byte PromDevice::readDataBus()
{
#if defined(ARDUINO_IS_UNO) || defined(ARDUINO_IS_NANO)
return (PINB << 6) | (PIND >> 2);
#elif defined(ARDUINO_IS_MICRO)
return (PINB & 0xfe) | (PIND & 0x01);
#else
byte data = 0;
byte bit = 0x01;
for (int pin = 2; (pin <= 9); pin++) {
if (digitalRead(pin) == HIGH) {
data |= bit;
}
bit <<= 1;
}
return data;
#endif
}
// Write a byte to the data bus. The caller must set the bus to output_mode
// before calling this or no data will be written.
void PromDevice::writeDataBus(byte data)
{
#if defined(ARDUINO_IS_UNO) || defined(ARDUINO_IS_NANO)
PORTB = (PORTB & 0xfc) | (data >> 6);
PORTD = (PORTD & 0x03) | (data << 2);
#elif defined(ARDUINO_IS_MICRO)
PORTB = (PORTB & 0x01) | (data & 0xfe);
PORTD = (PORTD & 0xfe) | (data & 0x01);
#else
byte bit = 0x01;
for (int pin = 2; (pin <= 9); pin++) {
digitalWrite(pin, (data & bit) ? HIGH : LOW);
bit <<= 1;
}
#endif
}
#include "Configure.h"
#include "PromDevice.h"
PromDevice::PromDevice(unsigned long size, word blockSize, unsigned maxWriteTime, bool polling)
: mSize(size),
mBlockSize(blockSize),
mMaxWriteTime(maxWriteTime),
mSupportsDataPoll(polling)
{
}
// Write a block of data to the device. If the device supports block writes,
// the data will be broken into chunks and written using the block mode.
// Otherwise, each byte will be individually written and verified.
bool PromDevice::writeData(byte data[], word len, word address)
{
bool status = true;
if (mBlockSize == 0)
{
// Device does not support block writes.
for (word ix = 0; (ix < len); ix++)
{
if (burnByte(data[ix], address + ix) == false)
{
status = false;
break;
}
}
}
else
{
word offset = 0;
word chunkSize;
if (address & (mBlockSize - 1))
{
// Address does not start on a block boundary. Adjust the size of
// the first block to fit within a single block.
chunkSize = mBlockSize - (address & (mBlockSize - 1));
chunkSize = (chunkSize > len) ? len : chunkSize;
if (burnBlock(data, chunkSize, address) == false)
{
return false;
}
offset += chunkSize;
len -= chunkSize;
}
// All writes are now aligned to block boundaries, so write full blocks
// or remaining length, whichever is smaller.
while (len > 0)
{
chunkSize = (len > mBlockSize) ? mBlockSize : len;
if (burnBlock(data + offset, chunkSize, address + offset) == false)
{
status = false;
break;
}
offset += chunkSize;
len -= chunkSize;
}
}
return status;
}
// BEGIN PRIVATE METHODS
//
// Set the I/O state of the data bus.
// The first two bits of port D are used for serial, so the 8 bits data bus are
// on pins D2..D9.
void PromDevice::setDataBusMode(uint8_t mode)
{
// On the Uno and Nano, D2..D9 maps to the upper 6 bits of port D and the
// lower 2 bits of port B.
if (mode == OUTPUT)
{
DDRB |= 0x03;
DDRD |= 0xfc;
}
else
{
DDRB &= 0xfc;
DDRD &= 0x03;
}
}
// Read a byte from the data bus. The caller must set the bus to input_mode
// before calling this or no useful data will be returned.
byte PromDevice::readDataBus()
{
return (PINB << 6) | (PIND >> 2);
}
// Write a byte to the data bus. The caller must set the bus to output_mode
// before calling this or no data will be written.
void PromDevice::writeDataBus(byte data)
{
PORTB = (PORTB & 0xfc) | (data >> 6);
PORTD = (PORTD & 0x03) | (data << 2);
}

View File

@ -16,7 +16,7 @@ A compile-time switch in Configure.h enables additional debug commands that are
## PromDevice class
The PromDevice class and its subclasses encapsulate all of the communication between the Arduino and the target PROM device.
The PromDevice class can access the data bus using direct port writes instead of 8 individual pin accesses. This greatly increases performance, but it makes the code dependent on the particular flavor of Arduino being used. The code can currently be compiled for Uno, Nano, or Micro versions of Arduino hardware or in a slower hardware-indpendent mode.
To meet the timing requirements for block writes and 28C chip unlocking, the PromDevice class accesses the shift registers and data bus using direct port writes instead of 8 individual pin accesses. This greatly increases performance, but it makes the code dependent on the particular flavor of Arduino being used. The code supports the Uno, Nano, and Boarduino versions of Arduino hardware or any other variant that uses that same mapping of ATMega ports to I/O pins. To support a different Arduino board, either change the pins used to match the mapping in the software, or change the hardware-specific code in PromDevice.cpp and PromAddressDriver.cpp.
The PromDevice class contains common code used by all devices, including the block write code that will break a large write request into a set of properly-aligned smaller blocks for devices that support block writing, or a sequence of infividual byte writes for devices that do not.

View File

@ -1,12 +1,16 @@
/**
* Read and write ATMEL 28C series EEPROMs. Support block writes for better
* performance. Read-only is supported for most parallel EPROM/EEPROMs.
* Read and write parallel EEPROMS with an interctive command-line interface.
* Modules are available for ATMEL 28C series EEPROMs and Intel 8755A EPROMS.
* Many other parallel EPROM/EEPROMs can be read, but not written, using the
* 28C code.
*
* ROM images are moved to and from a host computer using XMODEM.
* This is available in a number of terminal programs, such as
* TeraTerm and Minicom.
* The 28C module supports block writes for better performance and
* Software Data Protection (SDP) unlocking.
*
* The hardware uses two 74LS164 shift registers as the low and
* ROM images are moved to and from a host computer using XMODEM. This is
* available in a number of terminal programs, such as TeraTerm and Minicom.
*
* The default hardware uses two 74LS164 shift registers as the low and
* high address registers.
**/
@ -15,6 +19,9 @@
#include "XModem.h"
static const char * MY_VERSION = "1.8";
// Global status
CmdStatus cmdStatus;
@ -574,6 +581,7 @@ void loop()
Serial.print("\n>");
Serial.flush();
readLine(line, sizeof(line));
Serial.println();
byte cmd = parseCommand(line[0]);
if (hexDigit(line[1]) <= 15)
start = hexWord(line + 1);
@ -664,7 +672,9 @@ void loop()
break;
default:
Serial.print(F("TommyPROM 1.7 - "));
Serial.print(F("TommyPROM "));
Serial.print(MY_VERSION);
Serial.print(F(" - "));
Serial.println(prom.getName());
Serial.println();
Serial.println(F("Valid commands are:"));

BIN
docs/Unlock-Timing.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB