Add Hardware Verify sketch

This commit is contained in:
Tom Nisbet 2019-06-09 16:13:39 -04:00
parent ed539fe310
commit 4529343228
9 changed files with 886 additions and 0 deletions

View File

@ -0,0 +1,9 @@
// 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
#include "PromDevice28C.h"

View File

@ -0,0 +1,262 @@
/**
* Test hardware for ATMEL 28C series EEPROMs.
*
* The hardware uses two 74LS164 shift registers as the low and
* high address registers.
**/
#include "Configure.h"
#define LED 13
PromDevice28C prom(32 * 1024L, 64, 10, true);
/*****************************************************************************/
/*****************************************************************************/
/**
* CLI parse functions
*/
const char hex[] = "0123456789abcdef";
// Read a line of data from the serial connection.
char * readLine(char * buffer, int len)
{
for (int ix = 0; (ix < len); ix++)
{
buffer[ix] = 0;
}
// read serial data until linebreak or buffer is full
char c = ' ';
int ix = 0;
do {
if (Serial.available())
{
c = Serial.read();
if ((c == '\b') && (ix > 0))
{
// Backspace, forget last character
--ix;
}
buffer[ix++] = c;
Serial.write(c);
}
} while ((c != '\n') && (ix < len));
buffer[ix - 1] = 0;
return buffer;
}
/************************************************************
* convert a single hex character [0-9a-fA-F] to its value
* @param char c single character (digit)
* @return byte value of the digit (0-15)
************************************************************/
byte hexDigit(char c)
{
if ((c >= '0') && (c <= '9'))
{
return c - '0';
}
else if ((c >= 'a') && (c <= 'f'))
{
return c - 'a' + 10;
}
else if ((c >= 'A') && (c <= 'F'))
{
return c - 'A' + 10;
}
else
{
return 0xff;
}
}
/************************************************************
* convert a hex byte (00 - ff) to byte
* @param c-string with the hex value of the byte
* @return byte represented by the digits
************************************************************/
byte hexByte(char * a)
{
return (hexDigit(a[0]) << 4) | hexDigit(a[1]);
}
/************************************************************
* convert a hex word (0000 - ffff) to unsigned int
* @param c-string with the hex value of the word
* @return unsigned int represented by the digits
************************************************************/
unsigned int hexWord(char * data)
{
return (hexDigit(data[0]) << 12) |
(hexDigit(data[1]) << 8) |
(hexDigit(data[2]) << 4) |
(hexDigit(data[3]));
}
void printByte(byte b)
{
char line[3];
line[0] = hex[b >> 4];
line[1] = hex[b & 0x0f];
line[2] = '\0';
Serial.print(line);
}
void printWord(word w)
{
char line[5];
line[0] = hex[(w >> 12) & 0x0f];
line[1] = hex[(w >> 8) & 0x0f];
line[2] = hex[(w >> 4) & 0x0f];
line[3] = hex[(w) & 0x0f];
line[4] = '\0';
Serial.print(line);
}
/************************************************
* MAIN
*************************************************/
word addr = 0;
void setup()
{
// Do this first so that it initializes all of the hardware pins into a
// non-harmful state. The Arduino or the target EEPROM could be damaged
// if both writing to the data bus at the same time.
prom.begin();
Serial.begin(115200);
}
word start = 0;
word end = 0xff;
byte val = 0xff;
void loop()
{
byte b;
word w;
bool error = false;
char line[20];
uint32_t numBytes;
Serial.print("\n#");
Serial.flush();
readLine(line, sizeof(line));
byte c = tolower(line[0]);
if ((c >= 'A') && (c <= 'Z')) {
c |= 0x20;
}
/*
* Note that the comamnds here allow for direct writing of the 28C control lines with some exceptions to
* protect the chip and the host arduino:
* 1) When the O command is used to enable chip output, the arduino data bus us set to INPUT
* 2) When the D command is used to write data from the arduino, the chip output is disabled
* 3) The R command sets to output enable (OE) on the chip (but not the chip enable (CE)) */
switch (c)
{
case 'a':
if (hexDigit(line[1]) <= 15)
{
w = hexWord(line + 1);
prom.setAddress(w);
}
else
error = true;
break;
case 'd':
if (hexDigit(line[1]) <= 15)
{
prom.disableOutput();
prom.setDataBusMode(OUTPUT);
b = hexByte(line + 1);
prom.writeDataBus(b);
}
else
error = true;
break;
case 'c':
case 'o':
case 'w':
if ((line[1] == 'd') || (line[1] == 'e')) {
bool enable = line[1] == 'e';
if (c == 'c')
if (enable) prom.enableChip(); else prom.disableChip();
else if (c == 'w')
if (enable) prom.enableWrite(); else prom.disableWrite();
else { // c == 'o'
if (enable)
{
// Don't allow the prom and the data bus to output at the same time
prom.setDataBusMode(INPUT);
prom.enableOutput();
}
else prom.disableOutput();
}
}
else
{
error = true;
}
break;
case 'r':
prom.setDataBusMode(INPUT);
prom.enableOutput();
b = prom.readDataBus();
printByte(b);
Serial.println();
prom.disableOutput();
break;
case 'l':
Serial.println(F("Writing the lock code to enable Software Write Protect mode."));
prom.enableSoftwareWriteProtect();
break;
case 'u':
Serial.println(F("Writing the unlock code to disable Software Write Protect mode."));
prom.disableSoftwareWriteProtect();
break;
default:
error = true;
break;
}
if (error) {
Serial.print(F("Hardware Verifier - "));
Serial.println(prom.getName());
Serial.println();
Serial.println(F("Valid commands are:"));
Serial.println(F(" Axxxx - Set address bus to xxxx"));
Serial.println(F(" Dxx - Set Data bus to xx"));
Serial.println(F(" Cs - Set Chip enable to state (e=enable, d=disable)"));
Serial.println(F(" Os - Set Output enable to state (e=enable, d=disable)"));
Serial.println(F(" Ws - Set Write enable to state (e=enable, d=disable)"));
Serial.println(F(" R - Read and print the value on the data bus"));
Serial.println(F(" L - Send Lock sequence to enable device Software Data Protection"));
Serial.println(F(" U - Send Unlock sequence to disable device Software Data Protection"));
}
}

View File

@ -0,0 +1,70 @@
#include "PromAddressDriver.h"
#define ADDR_CLK_HI A3
#define ADDR_CLK_LO A4
#define ADDR_DATA A5
void PromAddressDriver::begin()
{
// The address control pins are always outputs.
pinMode(ADDR_DATA, OUTPUT);
pinMode(ADDR_CLK_LO, OUTPUT);
pinMode(ADDR_CLK_HI, OUTPUT);
digitalWrite(ADDR_DATA, LOW);
digitalWrite(ADDR_CLK_LO, LOW);
digitalWrite(ADDR_CLK_HI, LOW);
// To save time, the setAddress only writes the hi byte if it has changed.
// The value used to detect the change is initialized to a non-zero value,
// so set an initial address to avoid the the case where the first address
// written is the 'magic' initial address.
setAddress(0x0000);
}
// Set a 16 bit address in the two address shift registers.
void PromAddressDriver::setAddress(word address)
{
static byte lastHi = 0xca;
byte hi = address >> 8;
byte lo = address & 0xff;
if (hi != lastHi)
{
setAddressRegister(ADDR_CLK_HI, hi);
lastHi = hi;
}
setAddressRegister(ADDR_CLK_LO, lo);
}
// Shift an 8-bit value into one of the address shift registers. Note that
// the data pins are tied together, selecting the high or low address register
// is a matter of using the correct clock pin to shift the data in.
void PromAddressDriver::setAddressRegister(uint8_t clkPin, byte addr)
{
// Make sure the clock is low to start.
digitalWrite(clkPin, LOW);
// Shift 8 bits in, starting with the MSB.
for (int ix = 0; (ix < 8); ix++)
{
// Set the data bit
if (addr & 0x80)
{
digitalWrite(ADDR_DATA, HIGH);
}
else
{
digitalWrite(ADDR_DATA, LOW);
}
digitalWrite(clkPin, HIGH); // Clock in a bit
digitalWrite(clkPin, LOW); // Reset the clock pin
addr <<= 1;
}
}

View File

@ -0,0 +1,17 @@
#ifndef INCLUDE_PROM_ADDRESS_DRIVER_H
#define INCLUDE_PROM_ADDRESS_DRIVER_H
#include "Arduino.h"
class PromAddressDriver {
public:
static void begin();
static void setAddress(word address);
private:
static void setAddressRegister(uint8_t clkPin, byte addr);
};
#endif // #define INCLUDE_PROM_ADDRESS_DRIVER_H

View File

@ -0,0 +1,155 @@
#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
}

View File

@ -0,0 +1,48 @@
#ifndef INCLUDE_PROM_DEVICE_H
#define INCLUDE_PROM_DEVICE_H
#include "Arduino.h"
/*****************************************************************************/
/*****************************************************************************/
/**
* PromDevice class
*
* Provides the interface to read and write data from a parallel PROM using the
* Arduino.
*
* Block writes are supported on compatible devices by specifying a blockSize
* in the constructor. Use zero for devices that only support byte writes.
*/
class PromDevice
{
public:
PromDevice(unsigned long size, word blockSize, unsigned maxWriteTime, bool polling);
bool writeData(byte data[], word len, word address);
byte readData(word address) { return readByte(address); }
virtual void begin() = 0;
virtual const char * getName() = 0;
virtual void disableSoftwareWriteProtect() {}
virtual void enableSoftwareWriteProtect() {}
//protected:
unsigned int mSize; // Size of the device, in bytes
unsigned int mBlockSize; // Block size for page writes, zero if N/A
unsigned int mMaxWriteTime; // Max time (in ms) to wait for write cycle to complete
bool mSupportsDataPoll; // End of write detected by data polling
void setDataBusMode(uint8_t mode);
byte readDataBus();
void writeDataBus(byte data);
virtual void setAddress(word address) = 0;
virtual byte readByte(word address) = 0;
virtual bool burnByte(byte value, word address) = 0;
virtual bool burnBlock(byte data[], word len, word address) { return false; }
};
#endif // #define INCLUDE_PROM_DEVICE_H

View File

@ -0,0 +1,211 @@
#include "Configure.h"
#include "PromAddressDriver.h"
PromDevice28C::PromDevice28C(unsigned long size, word blockSize, unsigned maxWriteTime, bool polling)
: PromDevice(size, blockSize, maxWriteTime, polling)
{
}
void PromDevice28C::begin()
{
// Define the data bus as input initially so that it does not put out a
// signal that could collide with output on the data pins of the EEPROM.
setDataBusMode(INPUT);
// Define the EEPROM control pins as output, making sure they are all
// in the disabled state.
digitalWrite(OE, HIGH);
pinMode(OE, OUTPUT);
digitalWrite(CE, HIGH);
pinMode(CE, OUTPUT);
digitalWrite(WE, HIGH);
pinMode(WE, OUTPUT);
// This chip uses the shift register hardware for addresses, so initialize that.
PromAddressDriver::begin();
}
// Write the special six-byte code to turn off Software Data Protection.
void PromDevice28C::disableSoftwareWriteProtect()
{
disableOutput();
disableWrite();
setDataBusMode(OUTPUT);
setByte(0xaa, 0x5555);
setByte(0x55, 0x2aaa);
setByte(0x80, 0x5555);
setByte(0xaa, 0x5555);
setByte(0x55, 0x2aaa);
setByte(0x20, 0x5555);
setDataBusMode(INPUT);
disableChip();
}
// Write the special three-byte code to turn on Software Data Protection.
void PromDevice28C::enableSoftwareWriteProtect()
{
disableOutput();
disableWrite();
setDataBusMode(OUTPUT);
setByte(0xaa, 0x5555);
setByte(0x55, 0x2aaa);
setByte(0xa0, 0x5555);
setDataBusMode(INPUT);
disableChip();
}
// BEGIN PRIVATE METHODS
//
// Use the PromAddressDriver to set a 16 bit address in the two address shift registers.
void PromDevice28C::setAddress(word address)
{
PromAddressDriver::setAddress(address);
}
// Read a byte from a given address
byte PromDevice28C::readByte(word address)
{
byte data = 0;
setAddress(address);
setDataBusMode(INPUT);
disableOutput();
disableWrite();
enableChip();
enableOutput();
data = readDataBus();
disableOutput();
disableChip();
return data;
}
// Burn a byte to the chip and verify that it was written.
bool PromDevice28C::burnByte(byte value, word address)
{
bool status = false;
disableOutput();
disableWrite();
setAddress(address);
setDataBusMode(OUTPUT);
writeDataBus(value);
enableChip();
delayMicroseconds(1);
enableWrite();
delayMicroseconds(1);
disableWrite();
status = waitForWriteCycleEnd(value);
disableChip();
return status;
}
bool PromDevice28C::burnBlock(byte data[], word len, word address)
{
bool status = false;
if (len == 0) return true;
disableOutput();
disableWrite();
enableChip();
// Write all of the bytes in the block out to the chip. The chip will
// program them all at once as long as they are written fast enough.
setDataBusMode(OUTPUT);
for (word ix = 0; (ix < len); ix++)
{
setAddress(address + ix);
writeDataBus(data[ix]);
delayMicroseconds(1);
enableWrite();
delayMicroseconds(1);
disableWrite();
}
status = waitForWriteCycleEnd(data[len - 1]);
disableChip();
return status;
}
bool PromDevice28C::waitForWriteCycleEnd(byte lastValue)
{
if (mSupportsDataPoll)
{
// Verify programming complete by reading the last value back until it matches the
// value written twice in a row. The D7 bit will read the inverse of last written
// data and the D6 bit will toggle on each read while in programming mode.
//
// Note that the max readcount is set to the device's maxReadTime (in uSecs)
// divided by two because there are two 1 uSec delays in the loop. In reality,
// the loop could run for longer because this does not account for the time needed
// to run all of the loop code. In actual practice, the loop will terminate much
// earlier because it will detect the end of the write well before the max time.
setDataBusMode(INPUT);
delayMicroseconds(1);
for (int readCount = mMaxWriteTime * 1000 / 2; (readCount > 0); readCount--)
{
enableOutput();
delayMicroseconds(1);
byte b1 = readDataBus();
disableOutput();
enableOutput();
delayMicroseconds(1);
byte b2 = readDataBus();
disableOutput();
if ((b1 == b2) && (b1 == lastValue))
{
return true;
}
}
return false;
}
else
{
// No way to detect success. Just wait the max write time.
delayMicroseconds(mMaxWriteTime * 1000L);
return true;
}
}
// Set an address and data value and toggle the write control. This is used
// to write control sequences, like the software write protect. This is not a
// complete byte write function because it does not set the chip enable or the
// mode of the data bus.
void PromDevice28C::setByte(byte value, word address)
{
setAddress(address);
writeDataBus(value);
delayMicroseconds(1);
enableChip();
enableWrite();
delayMicroseconds(1);
disableChip();
disableWrite();
}

View File

@ -0,0 +1,53 @@
#ifndef INCLUDE_PROM_DEVICE_28C_H
#define INCLUDE_PROM_DEVICE_28C_H
#include "Arduino.h"
#include "PromDevice.h"
/*****************************************************************************/
/*****************************************************************************/
/**
* PromDevice28C class
*
* Provides the device-specific interface to read and write data from a
* 28C series parallel EEPROM using the Arduino.
*
* Block writes are supported on compatible devices by specifying a blockSize
* in the constructor. Use zero for byte writes.
*/
// IO lines for the EEPROM device control
// Pins D2..D9 are used for the data bus.
#define WE A0
#define CE A1
#define OE A2
class PromDevice28C : public PromDevice
{
public:
PromDevice28C(unsigned long size, word blockSize, unsigned maxWriteTime, bool polling);
void begin();
const char * getName() { return "28C series EEPROM"; }
void disableSoftwareWriteProtect();
void enableSoftwareWriteProtect();
//protected:
void setAddress(word address);
byte readByte(word address);
bool burnByte(byte value, word address);
bool burnBlock(byte data[], word len, word address);
bool waitForWriteCycleEnd(byte lastValue);
void setByte(byte value, word address);
// Set the status of the device control pins
void enableChip() { digitalWrite(CE, LOW); }
void disableChip() { digitalWrite(CE, HIGH);}
void enableOutput() { digitalWrite(OE, LOW); }
void disableOutput() { digitalWrite(OE, HIGH);}
void enableWrite() { digitalWrite(WE, LOW);}
void disableWrite() { digitalWrite(WE, HIGH);}
};
#endif // #define INCLUDE_PROM_DEVICE_28C_H

61
HardwareVerify/README.md Normal file
View File

@ -0,0 +1,61 @@
## hardware Verifier Tool
This tools allows access to individual control lines from the Arduino to verify that the hardware was assembled correctly.
It can be used without a chip installed to scope out address and data lines. It also offers low-level control when the chip
is installed.
Note that the comamnds allow for direct writing of the 28C control lines with some exceptions to protect the chip and the host arduino:
* When the O command is used to enable chip output, the arduino data bus is set to INPUT
* When the D command is used to write data from the arduino, the chip output is disabled
* The R command sets the output enable (OE) on the chip, but not the chip enable (CE)
The session below shows how a write fails to a locked chip and then succeeds once the chip is unlocked.
```
Hardware Verifier - 28C series EEPROM
Valid commands are:
Axxxx - Set address bus to xxxx
Dxx - Set Data bus to xx
Cs - Set Chip enable to state (e=enable, d=disable)
Os - Set Output enable to state (e=enable, d=disable)
Ws - Set Write enable to state (e=enable, d=disable)
R - Read and print the value on the data bus
L - Send Lock sequence to enable device Software Data Protection
U - Send Unlock sequence to disable device Software Data Protection
#l
Writing the lock code to enable Software Write Protect mode.
#a0000
#ce
#r
22
#d55
#we
#wd
#r
22
#u
Writing the unlock code to disable Software Write Protect mode.
#d33
#we
#wd
#r
33
#cd
```