diff --git a/CmdStatus.cpp b/CmdStatus.cpp new file mode 100644 index 0000000..9bc21f9 --- /dev/null +++ b/CmdStatus.cpp @@ -0,0 +1,92 @@ +#include "Arduino.h" +#include "CmdStatus.h" + +CmdStatus::CmdStatus() +{ + clear(); +} + +void CmdStatus::clear() +{ + level = SL_NONE; + message = "OK"; + for (int ix = 0; ix < MAX_VALUES; ix++) + { + values[ix].valType = VT_NONE; + } +} + +void CmdStatus::info(const char * msg) +{ + level = SL_INFO; + message = msg; +} + +void CmdStatus::error(const char * msg) +{ + level = SL_ERROR; + message = msg; +} + +void CmdStatus::setValueDec(int index, const char * label, long value) +{ + setLongValue(index, label, value, VT_DEC); +} + +void CmdStatus::setValueHex(int index, const char * label, long value) +{ + setLongValue(index, label, value, VT_HEX); +} + +void CmdStatus::setLongValue(int index, const char * label, long value, ValueType vt) +{ + if ((index >= 0) && (index < MAX_VALUES)) + { + values[index].label = label; + values[index].value = value; + values[index].valType = vt; + } +} + +bool CmdStatus::isClear() +{ + return level == SL_NONE; +} + +void CmdStatus::printStatus() +{ + if (level == SL_NONE) + { + Serial.println("OK"); + return; + } + else if (level == SL_INFO) + { + Serial.print("INFO: "); + } + else { + Serial.print("ERROR: "); + } + Serial.print(message); + for (int ix = 0; ix < MAX_VALUES; ix++) + { + if (values[ix].valType != VT_NONE) + { + Serial.print(" "); + Serial.print(values[ix].label); + Serial.print("="); + if (values[ix].valType == VT_DEC) + { + Serial.print(values[ix].value, DEC); + } + else + { + Serial.print("0x"); + Serial.print(values[ix].value, HEX); + } + } + } + Serial.println(""); +} + + diff --git a/CmdStatus.h b/CmdStatus.h new file mode 100644 index 0000000..567c02e --- /dev/null +++ b/CmdStatus.h @@ -0,0 +1,44 @@ +/** + * Command Status class + * + * Creates an object that holds an error message and a number of + * optional numeric values. Each value has a format (hex, + * decimal) and a label. The printStatus call formats all of + * the data in the command status and prints it out to the + * serial port. + */ +class CmdStatus +{ + public: + CmdStatus(); + void clear(); + void info(const char * msg); + void error(const char * msg); + void setValueDec(int index, const char * label, long value); + void setValueHex(int index, const char * label, long value); + + bool isClear(); + void printStatus(); + +private: + enum + { + MAX_VALUES = 3 + }; + enum StatusLevel { SL_NONE, SL_INFO, SL_ERROR }; + enum ValueType { VT_NONE, VT_DEC, VT_HEX }; + + struct StatusValue + { + const char * label; + ValueType valType; + long value; + }; + + StatusLevel level; + const char * message; + StatusValue values[MAX_VALUES]; + + void setLongValue(int index, const char * label, long value, ValueType vt); +}; + diff --git a/Configure.h b/Configure.h new file mode 100644 index 0000000..b55aee2 --- /dev/null +++ b/Configure.h @@ -0,0 +1,24 @@ +// Uncomment only one of these 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 + +// Uncomment only one of these to choose the PROM device code that will be +// compiled in. + +#define PROM_IS_28C +//#define PROM_IS_8755 + + +#if defined(PROM_IS_28C) +#include "PromDevice28C.h" +#elif defined(PROM_IS_8755) +#include "PromDevice8755.h" +#else +#error "No Prom Device defined" +#endif diff --git a/PromAddressDriver.cpp b/PromAddressDriver.cpp new file mode 100644 index 0000000..ded82a4 --- /dev/null +++ b/PromAddressDriver.cpp @@ -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; + } +} + + diff --git a/PromAddressDriver.h b/PromAddressDriver.h new file mode 100644 index 0000000..c77a7f2 --- /dev/null +++ b/PromAddressDriver.h @@ -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 + diff --git a/PromDevice.cpp b/PromDevice.cpp new file mode 100644 index 0000000..d5dc155 --- /dev/null +++ b/PromDevice.cpp @@ -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(AUDUINO_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(AUDUINO_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(AUDUINO_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 +} + + diff --git a/PromDevice.h b/PromDevice.h new file mode 100644 index 0000000..a71fdda --- /dev/null +++ b/PromDevice.h @@ -0,0 +1,46 @@ +#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 void disableSoftwareWriteProtect() {} + + 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 + diff --git a/PromDevice28C.cpp b/PromDevice28C.cpp new file mode 100644 index 0000000..330bd2f --- /dev/null +++ b/PromDevice28C.cpp @@ -0,0 +1,210 @@ +#include "Configure.h" +#if defined(PROM_IS_28C) + +#include "PromAddressDriver.h" + +// 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 + +// Set the status of the device control pins +static void enableChip() { digitalWrite(CE, LOW); } +static void disableChip() { digitalWrite(CE, HIGH);} +static void enableOutput() { digitalWrite(OE, LOW); } +static void disableOutput() { digitalWrite(OE, HIGH);} +static void enableWrite() { digitalWrite(WE, LOW); } +static void disableWrite() { digitalWrite(WE, HIGH);} + + +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(); + enableChip(); + setDataBusMode(OUTPUT); + + setByte(0xaa, 0x5555); + setByte(0x55, 0x2aaa); + setByte(0x80, 0x5555); + setByte(0xaa, 0x5555); + setByte(0x55, 0x2aaa); + setByte(0x20, 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); + enableWrite(); + delayMicroseconds(1); + disableWrite(); +} + +#endif // #if defined(PROM_IS_28C) + diff --git a/PromDevice28C.h b/PromDevice28C.h new file mode 100644 index 0000000..edca69f --- /dev/null +++ b/PromDevice28C.h @@ -0,0 +1,35 @@ +#ifndef INCLUDE_PROM_DEVICE_28C_H +#define INCLUDE_PROM_DEVICE_28C_H + +#include "Arduino.h" +#include "PromDevice.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 byte writes. + */ +class PromDevice28C : public PromDevice +{ + public: + PromDevice28C(unsigned long size, word blockSize, unsigned maxWriteTime, bool polling); + void begin(); + void disableSoftwareWriteProtect(); + + 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); +}; + +#endif // #define INCLUDE_PROM_DEVICE_28C_H + diff --git a/PromDevice8755.cpp b/PromDevice8755.cpp new file mode 100644 index 0000000..8d0ad93 --- /dev/null +++ b/PromDevice8755.cpp @@ -0,0 +1,115 @@ +#include "Configure.h" +#if defined(PROM_IS_8755) + + +#define CE1 A0 +#define CE2 A1 +#define RD A2 +#define AD8 A5 +#define AD9 A4 +#define AD10 A3 +#define ALE 12 +#define VDDCTL 11 + + +PromDevice8755::PromDevice8755(unsigned long size) + : PromDevice(size, 0, 0, false) +{ +} + + +void PromDevice8755::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(RD, HIGH); + pinMode(RD, OUTPUT); + digitalWrite(VDDCTL, LOW); + pinMode(VDDCTL, OUTPUT); + digitalWrite(CE1, LOW); + pinMode(CE1, OUTPUT); + digitalWrite(CE2, LOW); + pinMode(CE2, OUTPUT); + digitalWrite(ALE, LOW); + pinMode(ALE, OUTPUT); + + // The address control pins are always outputs. + pinMode(AD8, OUTPUT); + pinMode(AD9, OUTPUT); + pinMode(AD10, OUTPUT); + digitalWrite(AD8, LOW); + digitalWrite(AD9, LOW); + digitalWrite(AD10, LOW); +} + + +// BEGIN PRIVATE METHODS +// + +// Set an 11 bit address using the 8 address/data bus lines and three more dedicated +// address lines. The read and burn code will take care of the ALE line +void PromDevice8755::setAddress(word address) +{ + writeDataBus(byte(address & 0xff)); + digitalWrite(AD8, address & 0x100 ? HIGH : LOW); + digitalWrite(AD9, address & 0x200 ? HIGH : LOW); + digitalWrite(AD10, address & 0x400 ? HIGH : LOW); +} + + +// Read a byte from a given address +byte PromDevice8755::readByte(word address) +{ + byte data = 0; + digitalWrite(RD, HIGH); + digitalWrite(CE1, LOW); + + // Put the address on the bus and latch it with ALE + digitalWrite(CE2, HIGH); + setDataBusMode(OUTPUT); + setAddress(address); + digitalWrite(ALE, HIGH); + digitalWrite(ALE, LOW); + + // Read a byte + setDataBusMode(INPUT); + setAddress(0xff); + digitalWrite(RD, LOW); + delayMicroseconds(1); + data = readDataBus(); + digitalWrite(RD, HIGH); + digitalWrite(CE2, LOW); + + return data; +} + + +// Burn a byte to the chip and verify that it was written. +bool PromDevice8755::burnByte(byte value, word address) +{ + // Latch the address and the CE lines + digitalWrite(ALE, HIGH); + digitalWrite(CE1, LOW); + digitalWrite(CE2, HIGH); + setAddress(address); + digitalWrite(ALE, LOW); + + // Burn the byte value by setting CE1 high and then setting VDD to +25V for 50ms. + setDataBusMode(OUTPUT); + writeDataBus(value); + digitalWrite(CE1, HIGH); + digitalWrite(VDDCTL, HIGH); + delay(50); + digitalWrite(VDDCTL, LOW); + digitalWrite(CE1, LOW); + + // Read back the value and return success if it matches + return readByte(address) == value; +} + +#endif // #if defined(PROM_IS_8755) + diff --git a/PromDevice8755.h b/PromDevice8755.h new file mode 100644 index 0000000..3b601d4 --- /dev/null +++ b/PromDevice8755.h @@ -0,0 +1,32 @@ +#ifndef INCLUDE_PROM_DEVICE_8755_H +#define INCLUDE_PROM_DEVICE_8755_H + +#include "Arduino.h" +#include "PromDevice.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 byte writes. + */ +class PromDevice8755 : public PromDevice +{ + public: + PromDevice8755(unsigned long size); + void begin(); + + protected: + void setAddress(word address); + byte readByte(word address); + bool burnByte(byte value, word address); +}; + + +#endif // #define INCLUDE_PROM_DEVICE_8755_H + diff --git a/TommyPROM.ino b/TommyPROM.ino new file mode 100644 index 0000000..151b492 --- /dev/null +++ b/TommyPROM.ino @@ -0,0 +1,668 @@ +/** +* Read and write ATMEL 28C series EEPROMs. Support block writes for better +* performance. Read-only is supported for most parallel EPROM/EEPROMs. +* +* 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 hardware uses two 74LS164 shift registers as the low and +* high address registers. +**/ + +#include "Configure.h" +#include "CmdStatus.h" +#include "XModem.h" + + +// Global status +CmdStatus cmdStatus; + + +// Declare a global PROM device depending on the device type that is +// defined in Configure.h +#if defined(PROM_IS_28C) +// Define a device for a 28C256 EEPROM with the following parameters: +// 32K byte device capacity +// 64 byte block writes +// 10ms max write time +// Data polling supported +PromDevice28C prom(32 * 1024L, 64, 10, true); + +#elif defined(PROM_IS_8755) +// Define a device for an 8755. This has a fixed size of 2K and no +// other parameters. +PromDevice8755 prom(2 * 1024L); + +#else +#error "Must define a PROM type in Configure.h" +#endif + +// Global XModem driver +XModem xmodem(prom, cmdStatus); + + +/*****************************************************************************/ +/*****************************************************************************/ + +/** + * CLI parse functions + */ +const char hex[] = "0123456789abcdef"; + +enum { + // CLI Commands + CMD_INVALID, + CMD_CHECKSUM, + CMD_DUMP, + CMD_ERASED, + CMD_FILL, + CMD_READ, + CMD_UNLOCK, + CMD_WRITE, + + CMD_SCAN, + CMD_TEST, + CMD_ZAP, + CMD_LAST_STATUS +}; + + +// 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; +} + + +byte parseCommand(char c) +{ + byte cmd = CMD_INVALID; + + // Convert the command to lowercase. + if ((c >= 'A') && (c <= 'Z')) { + c |= 0x20; + } + + switch (c) + { + case 'c': cmd = CMD_CHECKSUM; break; + case 'd': cmd = CMD_DUMP; break; + case 'e': cmd = CMD_ERASED; break; + case 'f': cmd = CMD_FILL; break; + case 'r': cmd = CMD_READ; break; + case 'u': cmd = CMD_UNLOCK; break; + case 'w': cmd = CMD_WRITE; break; + + case 's': cmd = CMD_SCAN; break; + case 't': cmd = CMD_TEST; break; + case 'z': cmd = CMD_ZAP; break; + case '/': cmd = CMD_LAST_STATUS;break; + default: cmd = CMD_INVALID; break; + } + + return cmd; +} + + +/************************************************************ +* 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); +} + + +// If the user presses a key then pause until they press another. Return true if +// Ctrl-C is pressed. +bool checkForBreak() +{ + if (Serial.available()) + { + if (Serial.read() == 0x03) + { + return true; + } + while (!Serial.available()) + {;} + if (Serial.read() == 0x03) + { + return true; + } + } + + return false; +} + + + +/*****************************************************************************/ +/*****************************************************************************/ +/** + * Command implementations + */ + + +/** + * Compute a 16 bit checksum from PROM data + * + * Note that this always reads an even number of bytes from the + * device and will read one byte beyond the specified end + * address if an odd number of bytes is specified by start and + * end. + */ +word checksumBlock(word start, word end) +{ + word checksum = 0; + for (word addr = start; (addr <= end); addr += 2) + { + word w = prom.readData(addr); + w <<= 8; + w |= prom.readData(addr + 1); + checksum += w; + + if (addr >= 0xfffe) + { + // This is a really kludgy check to make sure the counter doesn't wrap + // around to zero. Could replace addr and end with longs to fix this, + // but that might not be any faster. + break; + } + } + + return checksum; +} + + +/** +* Read data from the device and dump it in hex and ascii. +**/ +void dumpBlock(word start, word end) +{ + char line[81]; +// 01234567891 234567892 234567893 234567894 234567895 234567896 234567897 23456789 +// 1234: 01 23 45 67 89 ab cf ef 01 23 45 67 89 ab cd ef 1.2.3.4. 5.6.7.8. + int count = 0; + + memset(line, ' ', sizeof(line)); + + char * pHex = line; + char * pChar = line + 58; + for (word addr = start; (addr <= end); addr++) + { + if (count == 0) + { + //print out the address at the beginning of the line + pHex = line; + pChar = line + 58; + *pHex++ = hex[(addr >> 12) & 0x0f]; + *pHex++ = hex[(addr >> 8) & 0x0f]; + *pHex++ = hex[(addr >> 4) & 0x0f]; + *pHex++ = hex[(addr) & 0x0f]; + *pHex++ = ':'; + *pHex++ = ' '; + } + + byte data = prom.readData(addr); + *pHex++ = hex[data >> 4]; + *pHex++ = hex[data & 0x0f]; + *pHex++ = ' '; + *pChar++ = ((data < 32) | (data >= 127)) ? '.' : data; + + if ((count & 3) == 3) + { + *pHex++ = ' '; + } + if ((count & 7) == 7) + { + *pChar++ = ' '; + } + if ((++count >= 16) || (addr == end)) + { + *pChar = '\0'; + Serial.println(line); + if (checkForBreak()) + { + return; + } + memset(line, ' ', sizeof(line)); + count = 0; + } + } + + if (count) + { + Serial.println(); + } +} + + +/** + * Fill a block of PROM data with a single value. + * + * @param start - start address + * @param end - end address + * @param val - data byte to write to all addresses + */ +void fillBlock(word start, word end, byte val) +{ + enum { BLOCK_SIZE = 32 }; + byte block[BLOCK_SIZE]; + + for (int ix = 0; ix < BLOCK_SIZE; ix++) + { + block[ix] = val; + } + + for (word addr = start; (addr <= end); addr += BLOCK_SIZE) + { + unsigned writeLen = ((end - addr + 1) < BLOCK_SIZE) ? (end - addr + 1) : BLOCK_SIZE; + if (!prom.writeData(block, writeLen, addr)) + { + cmdStatus.error("Write failed"); + return; + } + } +} + + +/** + * Verify that a block of PROM contains the all FF erased value. + * + * @param start - start address + * @param end - end address + */ +void erasedBlockCheck(word start, word end) +{ + for (word addr = start; (addr <= end); addr ++) + { + byte val = prom.readData(addr); + if (val != 0xff) + { + cmdStatus.error("Block is not erased"); + cmdStatus.setValueHex(0, "addr", addr); + cmdStatus.setValueHex(1, "value", val); + return; + } + } + cmdStatus.info("Block is erased"); +} + + +#ifdef ENABLE_DEBUG_COMMANDS +/** + * Runs through a range of addresses, reading a single address + * multiple times. Fails if all of the reads for an address do + * not produce that same value. + * + * @param start - start address + * @param end - end address + */ +void scanBlock(word start, word end) +{ + enum { SCAN_TESTS = 10 }; + + for (word addr = start; (addr <= end); addr++) + { + byte values[SCAN_TESTS]; + values[0] = prom.readData(addr); + bool fail = false; + for (int ix = 1; (ix < SCAN_TESTS); ix++) + { + values[ix] = prom.readData(addr); + if (values[ix] != values[0]) + { + fail = true; + } + } + if (fail) + { + printWord(addr); + Serial.print(": "); + for (int ix = 0; (ix < SCAN_TESTS); ix++) + { + printByte(values[ix]); + Serial.print(" "); + } + Serial.println(); + cmdStatus.error("Repeated reads returned different values"); + cmdStatus.setValueHex(0, "addr", addr); + break; + } + + if (addr == 0xffff) break; + } +} + + +/** + * Reads a single address in the PROM multiple times and fails + * if all of the reads do not produce the same value. + * + * @param addr - address to test + */ +void testAddr(word addr) +{ + enum { NUM_TESTS = 100 }; + + bool fail = false; + byte value; + byte firstValue = prom.readData(addr); + for (int ix = 1; (ix < NUM_TESTS); ix++) + { + value = prom.readData(addr); + if (value != firstValue) + { + fail = true; + } + } + if (fail) + { + cmdStatus.error("Repeated reads returned different values"); + cmdStatus.setValueHex(0, "addr", addr); + cmdStatus.setValueHex(1, "first read", firstValue); + cmdStatus.setValueHex(2, "last read", value); + } + else + { + cmdStatus.info("Read test passed"); + } +} + + +/** + * Write a 32 byte test pattern to the PROM device and verify it + * by reading back. The pattern includes a walking 1 and a + * walking zero, which may help to detect pins that are tied + * together or swapped. + * + * @param start - start address + */ +void zapTest(word start) +{ + byte testData[] = + { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, + 0x7f, 0xbf, 0xdf, 0xef, 0xf7, 0xfb, 0xfd, 0xfe, + 0x00, 0xff, 0x55, 0xaa, '0', '1', '2', '3' + }; + + + if (!prom.writeData(testData, sizeof(testData), start)) + { + cmdStatus.error("Write failed"); + return; + } + + delayMicroseconds(10000); + for (int ix = 0; ix < sizeof(testData); ix++) + { + byte val = prom.readData(start + ix); + if (val != testData[ix]) + { + cmdStatus.error("Verify failed"); + cmdStatus.setValueHex(0, "addr", start + ix); + cmdStatus.setValueHex(1, "read", val); + cmdStatus.setValueHex(2, "expected", testData[ix]); + return; + } + } + cmdStatus.info("Write test successful"); +} +#endif /* ENABLE_DEBUG_COMMANDS */ + + +/************************************************ +* 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); +} + + +/** +* main loop that runs infinite times, parsing a given command and +* executing read or write requestes. +**/ + +byte ledTest[] = +{ + 0xc3, 0x03, 0x80, 0x3e, 0xc0, 0x30, 0x3e, 0xff, + 0x47, 0x3d, 0x05, 0xc2, 0x0a, 0x80, 0xfe, 0x00, + 0xc2, 0x09, 0x80, 0x3e, 0x40, 0x30, 0x3e, 0xff, + 0x47, 0x3d, 0x05, 0xc2, 0x1a, 0x80, 0xfe, 0x00, + 0xc2, 0x19, 0x80, 0xc3, 0x03, 0x80 +}; +byte charTest[] = +{ + 0xc3, 0x03, 0x80, 0x0e, 0x55, 0xf3, 0x06, 0x0b, 0xaf, 0x3e, 0x80, 0x1f, + 0x3f, 0x30, 0x21, 0x19, 0x00, 0x2d, 0xc2, 0x11, 0x80, 0x25, 0xc2, 0x11, + 0x80, 0x37, 0x79, 0x1f, 0x4f, 0x05, 0xc2, 0x09, 0x80, 0x3e, 0xc0, 0x30, + 0x3e, 0x40, 0x30, 0x3e, 0xc0, 0x30, 0x3e, 0x40, 0x30, 0x21, 0xff, 0xff, + 0x2d, 0xc2, 0x30, 0x80, 0x25, 0xc2, 0x30, 0x80, 0xc3, 0x03, 0x80 +}; + +word start = 0; +word end = 0xff; +byte val = 0xff; + +void loop() +{ + word w; + char line[20]; + uint32_t numBytes; + + Serial.print("\n>"); + Serial.flush(); + readLine(line, sizeof(line)); + byte cmd = parseCommand(line[0]); + if (hexDigit(line[1]) <= 15) + start = hexWord(line + 1); + if (hexDigit(line[6]) <= 15) + end = hexWord(line + 6); + if (hexDigit(line[6]) <= 11) + val = hexByte(line + 11); + + if ((cmd != CMD_LAST_STATUS) && (cmd != CMD_INVALID)) + { + cmdStatus.clear(); + } + + switch (cmd) + { + case CMD_CHECKSUM: + w = checksumBlock(start, end); + Serial.print("Checksum "); + printWord(start); + Serial.print("-"); + printWord(end); + Serial.print(" = "); + printWord(w); + Serial.println(); + break; + + case CMD_DUMP: + dumpBlock(start, end); + break; + + case CMD_ERASED: + erasedBlockCheck(start, end); + break; + + case CMD_FILL: + fillBlock(start, end, val); + break; + + case CMD_READ: + Serial.println(F("Set the terminal to receive XMODEM CRC")); + if (xmodem.SendFile(start, uint32_t(end) - start + 1)) + { + cmdStatus.info("Send complete."); + cmdStatus.setValueDec(0, "NumBytes", uint32_t(end) - start + 1); + } + break; + + case CMD_UNLOCK: + Serial.println(F("Writing the unlock code to disable Software Write Protect mode.")); + prom.disableSoftwareWriteProtect(); + break; + + case CMD_WRITE: + Serial.println(F("Send the image file using XMODEM CRC")); + numBytes = xmodem.ReceiveFile(start); + if (numBytes) + { + cmdStatus.info("Success writing to EEPROM device."); + cmdStatus.setValueDec(0, "NumBytes", numBytes); + } + else + { + xmodem.Cancel(); + } + break; + +#ifdef ENABLE_DEBUG_COMMANDS + case CMD_SCAN: + scanBlock(start, end); + break; + + case CMD_TEST: + testAddr(start); + break; + + case CMD_ZAP: + zapTest(start); + break; +#endif /* ENABLE_DEBUG_COMMANDS */ + + case CMD_LAST_STATUS: + Serial.println(F("Status of last command:")); + break; + + default: + Serial.println(F("TommyPROM 1.5\n")); + Serial.println(F("Valid commands are:")); + Serial.println(F(" Cssss eeee - Compute checksum from device")); + Serial.println(F(" Dssss eeee - Dump bytes from device to terminal")); + Serial.println(F(" Essss eeee - Check to see if device range is Erased (all FF)")); + Serial.println(F(" Fssss eeee dd - Fill block on device with fixed value")); + Serial.println(F(" Rssss eeee - Read from device and save to XMODEM CRC file")); + Serial.println(F(" U - Unlock device Software Data Protection")); + Serial.println(F(" Wssss - Write to device from XMODEM CRC file")); +#ifdef ENABLE_DEBUG_COMMANDS + Serial.println(); + Serial.println(F(" Sssss eeee - Scan addresses (read each 10x)")); + Serial.println(F(" Tssss - Test read address (read 100x)")); + Serial.println(F(" Zssss - Zap (burn) a 32 byte test pattern")); +#endif /* ENABLE_DEBUG_COMMANDS */ + break; + } + + if (!cmdStatus.isClear() || (cmd == CMD_LAST_STATUS)) + { + Serial.println(); + cmdStatus.printStatus(); + } +} + diff --git a/XModem.cpp b/XModem.cpp new file mode 100644 index 0000000..9c52188 --- /dev/null +++ b/XModem.cpp @@ -0,0 +1,256 @@ +#include "XModem.h" +#include "CmdStatus.h" + +uint32_t XModem::ReceiveFile(uint16_t address) +{ + uint8_t buffer[PKTLEN]; + int c; + uint8_t seq = 1; + uint32_t numBytes = 0; + bool complete = false; + + if (!StartReceive()) + { + cmdStatus.error("Timeout waiting for transfer to start."); + return 0; + } + + while (!complete) + { + if ((c = GetChar()) < 0) + { + cmdStatus.error("Timeout waiting for start of next packet."); + cmdStatus.setValueDec(0, "seq", seq); + return 0; + } + + switch (c) + { + case XMDM_SOH: + // Start of a packet + if (ReceivePacket(buffer, PKTLEN, seq++, address)) + { + numBytes += PKTLEN; + address += PKTLEN; + } + else + { + return 0; + } + break; + + case XMDM_EOT: + // End of transfer + Serial.write(XMDM_ACK); + complete = true; + break; + + case XMDM_CAN: + case XMDM_ESC: + // Cancel from sender + cmdStatus.error("Transfer canceled by sender."); + cmdStatus.setValueDec(0, "seq", seq); + return 0; + break; + + default: + // Fail the transfer on anything else + cmdStatus.error("Unexpected character received waiting for next packet."); + cmdStatus.setValueDec(0, "char", c); + return 0; + break; + } + } + + return numBytes; +} + + +// This method it not very tolerant of communication errors. If the receiver +// does not send a positive ACK for each packet or does not ACK the packet +// within one second then the transfer will fail. Unlike in the dial-up +// days of old, this is designed to be run on a 3 foot cable betwee two fast +// hosts, so communication errors or timeouts are extremely unlikely. +bool XModem::SendFile(uint16_t address, uint32_t fileSize) +{ + uint8_t seq = 1; + int rxChar = -1; + uint32_t bytesSent = 0; + + while (rxChar == -1) + { + rxChar = GetChar(); + } + if (rxChar != XMDM_CRC) + { + cmdStatus.error("Expected XModem CRC start char."); + cmdStatus.setValueDec(0, "char", rxChar); + return false; + } + + while (bytesSent < fileSize) + { + SendPacket(address, seq++); + address += PKTLEN; + + rxChar = GetChar(5000); + if (rxChar != XMDM_ACK) + { + cmdStatus.error("Expected XModem ACK."); + cmdStatus.setValueDec(0, "char", rxChar); + return false; + } + bytesSent += PKTLEN; + } + + Serial.write(XMDM_EOT); + return true; +} + + +void XModem::Cancel() +{ + // Send a cancel and then eat input until the line is quiet for 3 seconds. + Serial.write(XMDM_CAN); + while (GetChar(3000) != -1) + {} +} + + +// Private functions +int XModem::GetChar(int msWaitTime) +{ + do + { + if (Serial.available() > 0) + { + return Serial.read(); + } + delay(1); + } while (msWaitTime--); + + return -1; +} + + +uint16_t XModem::UpdateCrc(uint16_t crc, uint8_t data) +{ + crc = crc ^ ((uint16_t)data << 8); + for (int ix = 0; (ix < 8); ix++) + { + if (crc & 0x8000) + { + crc = (crc << 1) ^ 0x1021; + } + else + { + crc <<= 1; + } + } + + return crc; +} + + +bool XModem::StartReceive() +{ + for (int retries = 30; (retries); --retries) + { + // Send the 'C' character, indicating a CRC16 XMODEM transfer, until the sender + // of the file responds with something. The start character will be sent once a + // second for a number of seconds. If nothing is received in that time then + // return false to indicate that the transfer did not start. + Serial.write('C'); + for (int ms = 1000; (ms); --ms) + { + if (Serial.available() > 0) + { + return true; + } + delay(1); + } + } + + return false; +} + + +bool XModem::ReceivePacket(uint8_t buffer[], unsigned bufferSize, uint8_t seq, uint16_t destAddr) +{ + int c; + uint8_t rxSeq1, rxSeq2; + uint16_t calcCrc = 0; + uint16_t rxCrc; + + rxSeq1 = (uint8_t) GetChar(); + rxSeq2 = (uint8_t) GetChar(); + + for (unsigned ix = 0; (ix < bufferSize); ix++) + { + if ((c = GetChar()) < 0) + { + // If the read times out then fail this packet. Note that this check isn't + // done for the sequence and CRC. If they timeout then the values won't match + // so there is not point in the extra code to check for the error. The worst + // that will happen is that the transfer will need to wait 3 timeouts before + // realizing that something is wrong. + cmdStatus.error("Timeout waiting for next packet char."); + cmdStatus.setValueDec(0, "seq", seq); + Serial.write(XMDM_CAN); + return false; + } + buffer[ix] = (uint8_t) c; + calcCrc = UpdateCrc(calcCrc, buffer[ix]); + } + + rxCrc = ((uint16_t) GetChar()) << 8; + rxCrc |= GetChar(); + + if ((calcCrc != rxCrc) || (rxSeq1 != seq) || ((rxSeq1 ^ rxSeq2) != 0xff)) + { + // Fail if the CRC or sequence number is not correct or if the two received + // sequence numbers are not the complement of one another. + cmdStatus.error("Bad CRC or sequence number."); + cmdStatus.setValueDec(0, "seq", seq); + Serial.write(XMDM_CAN); + return false; + } + else + { + // The data is good. Process the packet then ACK it to the sender. + pinMode(13, OUTPUT); + digitalWrite(13, HIGH); + if (!promDevice.writeData(buffer, bufferSize, destAddr)) + { + cmdStatus.error("Write failed"); + cmdStatus.setValueHex(0, "address", destAddr); + return false; + } + digitalWrite(13, LOW); + + Serial.write(XMDM_ACK); + } + + return true; +} + + +void XModem::SendPacket(uint16_t address, uint8_t seq) +{ + uint16_t crc = 0; + + Serial.write(XMDM_SOH); + Serial.write(seq); + Serial.write(~seq); + for (int ix = 0; (ix < PKTLEN); ix++) + { + byte c = promDevice.readData(address++); + Serial.write(c); + crc = UpdateCrc(crc, c); + } + Serial.write(crc >> 8); + Serial.write(crc & 0xff); +} + + + diff --git a/XModem.h b/XModem.h new file mode 100644 index 0000000..a283b86 --- /dev/null +++ b/XModem.h @@ -0,0 +1,58 @@ +/*****************************************************************************/ +/*****************************************************************************/ +/** + * + * XMODEM CRC Communication + * + * Simple implementation of read and write using XMODEM CRC. This is tied + * directly to the PROM code, so the receive function writes the data to the + * PROM device as each packet is received. The complete file is not kept + * in memory. + */ +#ifndef INCLUDE_CONFIGURE_H +#define INCLUDE_CONFIGURE_H + +#include "Arduino.h" +#include "Configure.h" + +//class PromDevice; +class CmdStatus; + +class XModem +{ + public: + XModem(PromDevice & pd, CmdStatus & cs) : promDevice(pd), cmdStatus(cs) {} + uint32_t ReceiveFile(uint16_t address); + bool SendFile(uint16_t address, uint32_t fileSize); + void Cancel(); + + private: + enum + { + // XMODEM control characters. + XMDM_SOH = 0x01, + XMDM_EOT = 0x04, + XMDM_ACK = 0x06, + XMDM_NAK = 0x15, + XMDM_CAN = 0x18, + XMDM_ESC = 0x1b, + XMDM_CRC = 'C' + }; + enum + { + // Misc constants for XMODEM. + PKTLEN = 128 + }; + + PromDevice & promDevice; + CmdStatus & cmdStatus; + + int GetChar(int msWaitTime = 3000); + uint16_t UpdateCrc(uint16_t crc, uint8_t data); + bool StartReceive(); + bool ReceivePacket(uint8_t buffer[], unsigned bufferSize, uint8_t seq, uint16_t destAddr); + void SendPacket(uint16_t address, uint8_t seq); +}; + +#endif // #define INCLUDE_CONFIGURE_H + diff --git a/docs/TommyPROM-8755-sch.png b/docs/TommyPROM-8755-sch.png new file mode 100644 index 0000000..2a7760e Binary files /dev/null and b/docs/TommyPROM-8755-sch.png differ diff --git a/docs/TommyPROM-8755.jpg b/docs/TommyPROM-8755.jpg new file mode 100644 index 0000000..c00a786 Binary files /dev/null and b/docs/TommyPROM-8755.jpg differ diff --git a/software/TommyPROM.ino b/software/TommyPROM.ino deleted file mode 100644 index 93e6dda..0000000 --- a/software/TommyPROM.ino +++ /dev/null @@ -1,1488 +0,0 @@ -/** -* Read and write ATMEL 28C series EEPROMs. Support block writes for better -* performance. Read-only is supported for most parallel EPROM/EEPROMs. -* -* 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 hardware uses two 74LS164 shift registers as the low and -* high address registers. -**/ - - -// Uncomment only one of these to match the Arduino model - -//#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 - - -/*****************************************************************************/ -/*****************************************************************************/ -/** - * Command Status class - * - * Creates an object that holds an error message and a number of - * optional numeric values. Each value has a format (hex, - * decimal) and a label. The printStatus call formats all of - * the data in the command status and prints it out to the - * serial port. - */ -class CmdStatus -{ - public: - CmdStatus(); - void clear(); - void info(char * msg); - void error(char * msg); - void setValueDec(int index, char * label, long value); - void setValueHex(int index, char * label, long value); - - bool isClear(); - void printStatus(); - -private: - enum - { - MAX_VALUES = 3 - }; - enum StatusLevel { SL_NONE, SL_INFO, SL_ERROR }; - enum ValueType { VT_NONE, VT_DEC, VT_HEX }; - - struct StatusValue - { - const char * label; - ValueType valType; - long value; - }; - - StatusLevel level; - const char * message; - StatusValue values[MAX_VALUES]; - - void setLongValue(int index, char * label, long value, ValueType vt); -}; - -CmdStatus::CmdStatus() -{ - clear(); -} - -void CmdStatus::clear() -{ - level = SL_NONE; - message = "OK"; - for (int ix = 0; ix < MAX_VALUES; ix++) - { - values[ix].valType = VT_NONE; - } -} - -void CmdStatus::info(char * msg) -{ - level = SL_INFO; - message = msg; -} - -void CmdStatus::error(char * msg) -{ - level = SL_ERROR; - message = msg; -} - -void CmdStatus::setValueDec(int index, char * label, long value) -{ - setLongValue(index, label, value, VT_DEC); -} - -void CmdStatus::setValueHex(int index, char * label, long value) -{ - setLongValue(index, label, value, VT_HEX); -} - -void CmdStatus::setLongValue(int index, char * label, long value, ValueType vt) -{ - if ((vt >= 0) && (vt < MAX_VALUES)) - { - values[index].label = label; - values[index].value = value; - values[index].valType = vt; - } -} - -bool CmdStatus::isClear() -{ - return level == SL_NONE; -} - -void CmdStatus::printStatus() -{ - if (level == SL_NONE) - { - Serial.println("OK"); - return; - } - else if (level == SL_INFO) - { - Serial.print("INFO: "); - } - else { - Serial.print("ERROR: "); - } - Serial.print(message); - for (int ix = 0; ix < MAX_VALUES; ix++) - { - if (values[ix].valType != VT_NONE) - { - Serial.print(" "); - Serial.print(values[ix].label); - Serial.print("="); - if (values[ix].valType == VT_DEC) - { - Serial.print(values[ix].value, DEC); - } - else - { - Serial.print("0x"); - Serial.print(values[ix].value, HEX); - } - } - } - Serial.println(""); -} - - -// Global status -CmdStatus cmdStatus; - - - -/*****************************************************************************/ -/*****************************************************************************/ -/** - * 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 byte writes. - */ -class PromDevice -{ - public: - PromDevice(unsigned long size, word blockSize, unsigned maxWriteTime, bool polling); - void begin(); - byte readByte(word address); - bool writeData(byte data[], word len, word address); - void disableSoftwareWriteProtect(); - - private: - 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 enableChip(); - void disableChip(); - void enableOutput(); - void disableOutput(); - void enableWrite(); - void disableWrite(); - - void setDataBusMode(uint8_t mode); - byte readDataBus(); - void writeDataBus(byte data); - void setAddress(word address); - void setAddressRegister(uint8_t clkPin, byte addr); - bool burnByte(byte value, word address); - bool burnBlock(byte data[], word len, word address); - bool waitForWriteCycleEnd(byte lastValue); - void setByte(byte value, word address); -}; - -// Define a device for a 28C256 EEPROM with the following parameters: -// 32K byte device capacity -// 64 byte block writes -// 10ms max write time -// Data polling supported -PromDevice prom(32 * 1024, 64, 10, true); - -// 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 -#define ADDR_CLK_HI A3 -#define ADDR_CLK_LO A4 -#define ADDR_DATA A5 - - -PromDevice::PromDevice(unsigned long size, word blockSize, unsigned maxWriteTime, bool polling) -{ - mSize = size; - mBlockSize = blockSize; - mMaxWriteTime = maxWriteTime; - mSupportsDataPoll = polling; -} - - -void PromDevice::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); - - // 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); -} - - -// Read a byte from a given address -byte PromDevice::readByte(word address) -{ - byte data = 0; - setAddress(address); - setDataBusMode(INPUT); - disableOutput(); - disableWrite(); - enableChip(); - enableOutput(); - data = readDataBus(); - disableOutput(); - disableChip(); - - return data; -} - - -// 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; -} - - -// Write the special six-byte code to turn off Software Data Protection. -void PromDevice::disableSoftwareWriteProtect() -{ - disableOutput(); - disableWrite(); - enableChip(); - setDataBusMode(OUTPUT); - - setByte(0xaa, 0x5555); - setByte(0x55, 0x2aaa); - setByte(0x80, 0x5555); - setByte(0xaa, 0x5555); - setByte(0x55, 0x2aaa); - setByte(0x20, 0x5555); - - setDataBusMode(INPUT); - disableChip(); -} - - -// BEGIN PRIVATE METHODS -// -// Set the status of the device control pins -void PromDevice::enableChip() { digitalWrite(CE, LOW); } -void PromDevice::disableChip() { digitalWrite(CE, HIGH);} -void PromDevice::enableOutput() { digitalWrite(OE, LOW); } -void PromDevice::disableOutput() { digitalWrite(OE, HIGH);} -void PromDevice::enableWrite() { digitalWrite(WE, LOW); } -void PromDevice::disableWrite() { digitalWrite(WE, HIGH);} - - -// 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(AUDUINO_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; - } -#endif -#ifdef 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; - } -#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(AUDUINO_IS_UNO) || defined(ARDUINO_IS_NANO) - return (PINB << 6) | (PIND >> 2); -#endif -#ifdef ARDUINO_IS_MICRO - return (PINB & 0xfe) | (PIND & 0x01); -#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(AUDUINO_IS_UNO) || defined(ARDUINO_IS_NANO) - PORTB = (PORTB & 0xfc) | (data >> 6); - PORTD = (PORTD & 0x03) | (data << 2); -#endif -#ifdef ARDUINO_IS_MICRO - PORTB = (PORTB & 0x01) | (data & 0xfe); - PORTD = (PORTD & 0xfe) | (data & 0x01); -#endif -} - - -// Set a 16 bit address in the two address shift registers. -void PromDevice::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 PromDevice::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; - } -} - - -// Burn a byte to the chip and verify that it was written. -bool PromDevice::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 PromDevice::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 PromDevice::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 PromDevice::setByte(byte value, word address) -{ - setAddress(address); - writeDataBus(value); - - delayMicroseconds(1); - enableWrite(); - delayMicroseconds(1); - disableWrite(); -} - - - -/*****************************************************************************/ -/*****************************************************************************/ -/** - * - * XMODEM CRC Communication - * - * Simple implementation of read and write using XMODEM CRC. This is tied - * directly to the PROM code, so the receive function writes the data to the - * PROM device as each packet is received. The complete file is not kept - * in memory. - */ -class XModem -{ - public: - XModem(PromDevice * pd) : pProm(pd) {} - uint32_t ReceiveFile(uint16_t address); - boolean SendFile(uint16_t address, uint32_t fileSize); - void Cancel(); - - private: - enum - { - // XMODEM control characters. - XMDM_SOH = 0x01, - XMDM_EOT = 0x04, - XMDM_ACK = 0x06, - XMDM_NAK = 0x15, - XMDM_CAN = 0x18, - XMDM_ESC = 0x1b, - XMDM_CRC = 'C' - }; - enum - { - // Misc constants for XMODEM. - PKTLEN = 128 - }; - - PromDevice * pProm; - - int GetChar(int msWaitTime = 3000); - uint16_t UpdateCrc(uint16_t crc, uint8_t data); - boolean StartReceive(); - boolean ReceivePacket(uint8_t buffer[], unsigned bufferSize, uint8_t seq, uint16_t destAddr); - void SendPacket(uint16_t address, uint8_t seq); -}; - -XModem xmodem(&prom); - -uint32_t XModem::ReceiveFile(uint16_t address) -{ - uint8_t buffer[PKTLEN]; - int c; - uint8_t seq = 1; - uint32_t numBytes = 0; - bool complete = false; - - if (!StartReceive()) - { - cmdStatus.error("Timeout waiting for transfer to start."); - return 0; - } - - while (!complete) - { - if ((c = GetChar()) < 0) - { - cmdStatus.error("Timeout waiting for start of next packet."); - cmdStatus.setValueDec(0, "seq", seq); - return 0; - } - - switch (c) - { - case XMDM_SOH: - // Start of a packet - if (ReceivePacket(buffer, PKTLEN, seq++, address)) - { - numBytes += PKTLEN; - address += PKTLEN; - } - else - { - return 0; - } - break; - - case XMDM_EOT: - // End of transfer - Serial.write(XMDM_ACK); - complete = true; - break; - - case XMDM_CAN: - case XMDM_ESC: - // Cancel from sender - cmdStatus.error("Transfer canceled by sender."); - cmdStatus.setValueDec(0, "seq", seq); - return 0; - break; - - default: - // Fail the transfer on anything else - cmdStatus.error("Unexpected character received waiting for next packet."); - cmdStatus.setValueDec(0, "char", c); - return 0; - break; - } - } - - return numBytes; -} - - -// This method it not very tolerant of communication errors. If the receiver -// does not send a positive ACK for each packet or does not ACK the packet -// within one second then the transfer will fail. Unlike in the dial-up -// days of old, this is designed to be run on a 3 foot cable betwee two fast -// hosts, so communication errors or timeouts are extremely unlikely. -boolean XModem::SendFile(uint16_t address, uint32_t fileSize) -{ - uint8_t seq = 1; - int rxChar = -1; - uint32_t bytesSent = 0; - - while (rxChar == -1) - { - rxChar = GetChar(); - } - if (rxChar != XMDM_CRC) - { - cmdStatus.error("Expected XModem CRC start char."); - cmdStatus.setValueDec(0, "char", rxChar); - return false; - } - - while (bytesSent < fileSize) - { - SendPacket(address, seq++); - address += PKTLEN; - - rxChar = GetChar(5000); - if (rxChar != XMDM_ACK) - { - cmdStatus.error("Expected XModem ACK."); - cmdStatus.setValueDec(0, "char", rxChar); - return false; - } - bytesSent += PKTLEN; - } - - Serial.write(XMDM_EOT); - return true; -} - - -void XModem::Cancel() -{ - // Send a cancel and then eat input until the line is quiet for 3 seconds. - Serial.write(XMDM_CAN); - while (GetChar(3000) != -1) - {} -} - - -// Private functions -int XModem::GetChar(int msWaitTime) -{ - do - { - if (Serial.available() > 0) - { - return Serial.read(); - } - delay(1); - } while (msWaitTime--); - - return -1; -} - - -uint16_t XModem::UpdateCrc(uint16_t crc, uint8_t data) -{ - crc = crc ^ ((uint16_t)data << 8); - for (int ix = 0; (ix < 8); ix++) - { - if (crc & 0x8000) - { - crc = (crc << 1) ^ 0x1021; - } - else - { - crc <<= 1; - } - } - - return crc; -} - - -boolean XModem::StartReceive() -{ - for (int retries = 20; (retries); --retries) - { - // Send the 'C' character, indicating a CRC16 XMODEM transfer, until the sender - // of the file responds with something. The start character will be sent once a - // second for a number of seconds. If nothing is received in that time then - // return false to indicate that the transfer did not start. - Serial.write('C'); - for (int ms = 1000; (ms); --ms) - { - if (Serial.available() > 0) - { - return true; - } - delay(1); - } - } - - return false; -} - - -boolean XModem::ReceivePacket(uint8_t buffer[], unsigned bufferSize, uint8_t seq, uint16_t destAddr) -{ - int c; - uint8_t rxSeq1, rxSeq2; - uint16_t calcCrc = 0; - uint16_t rxCrc; - - rxSeq1 = (uint8_t) GetChar(); - rxSeq2 = (uint8_t) GetChar(); - - for (unsigned ix = 0; (ix < bufferSize); ix++) - { - if ((c = GetChar()) < 0) - { - // If the read times out then fail this packet. Note that this check isn't - // done for the sequence and CRC. If they timeout then the values won't match - // so there is not point in the extra code to check for the error. The worst - // that will happen is that the transfer will need to wait 3 timeouts before - // realizing that something is wrong. - cmdStatus.error("Timeout waiting for next packet char."); - cmdStatus.setValueDec(0, "seq", seq); - Serial.write(XMDM_CAN); - return false; - } - buffer[ix] = (uint8_t) c; - calcCrc = UpdateCrc(calcCrc, buffer[ix]); - } - - rxCrc = ((uint16_t) GetChar()) << 8; - rxCrc |= GetChar(); - - if ((calcCrc != rxCrc) || (rxSeq1 != seq) || ((rxSeq1 ^ rxSeq2) != 0xff)) - { - // Fail if the CRC or sequence number is not correct or if the two received - // sequence numbers are not the complement of one another. - cmdStatus.error("Bad CRC or sequence number."); - cmdStatus.setValueDec(0, "seq", seq); - Serial.write(XMDM_CAN); - return false; - } - else - { - // The data is good. Process the packet then ACK it to the sender. - pinMode(13, OUTPUT); - digitalWrite(13, HIGH); - if (!pProm->writeData(buffer, bufferSize, destAddr)) - { - cmdStatus.error("Write failed"); - cmdStatus.setValueHex(0, "address", destAddr); - return false; - } - digitalWrite(13, LOW); - - Serial.write(XMDM_ACK); - } - - return true; -} - - -void XModem::SendPacket(uint16_t address, uint8_t seq) -{ - uint16_t crc = 0; - - Serial.write(XMDM_SOH); - Serial.write(seq); - Serial.write(~seq); - for (int ix = 0; (ix < PKTLEN); ix++) - { - byte c = pProm->readByte(address++); - Serial.write(c); - crc = UpdateCrc(crc, c); - } - Serial.write(crc >> 8); - Serial.write(crc & 0xff); -} - - - -/*****************************************************************************/ -/*****************************************************************************/ - -/** - * CLI parse functions - */ -const char hex[] = "0123456789abcdef"; - -enum { - // CLI Commands - CMD_INVALID, - CMD_CHECKSUM, - CMD_DUMP, - CMD_FILL, - CMD_READ, - CMD_UNLOCK, - CMD_WRITE, - - CMD_SCAN, - CMD_TEST, - CMD_ZAP, - CMD_LAST_STATUS -}; - - -// 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; -} - - -byte parseCommand(char c) -{ - byte cmd = CMD_INVALID; - - // Convert the command to lowercase. - if ((c >= 'A') && (c <= 'Z')) { - c |= 0x20; - } - - switch (c) - { - case 'c': cmd = CMD_CHECKSUM; break; - case 'd': cmd = CMD_DUMP; break; - case 'f': cmd = CMD_FILL; break; - case 'r': cmd = CMD_READ; break; - case 'u': cmd = CMD_UNLOCK; break; - case 'w': cmd = CMD_WRITE; break; - - case 's': cmd = CMD_SCAN; break; - case 't': cmd = CMD_TEST; break; - case 'z': cmd = CMD_ZAP; break; - case '/': cmd = CMD_LAST_STATUS;break; - default: cmd = CMD_INVALID; break; - } - - return cmd; -} - - -/************************************************************ -* 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); -} - - -// If the user presses a key then pause until they press another. Return true if -// Ctrl-C is pressed. -bool checkForBreak() -{ - if (Serial.available()) - { - if (Serial.read() == 0x03) - { - return true; - } - while (!Serial.available()) - {;} - if (Serial.read() == 0x03) - { - return true; - } - } - - return false; -} - - - -/*****************************************************************************/ -/*****************************************************************************/ -/** - * Command implementations - */ - - -/** - * Compute a 16 bit checksum from PROM data - * - * Note that this always reads an even number of bytes from the - * device and will read one byte beyond the specified end - * address if an odd number of bytes is specified by start and - * end. - */ -word checksumBlock(word start, word end) -{ - word checksum = 0; - for (word addr = start; (addr <= end); addr += 2) - { - word w = prom.readByte(addr); - w <<= 8; - w |= prom.readByte(addr + 1); - checksum += w; - - if (addr >= 0xfffe) - { - // This is a really kludgy check to make sure the counter doesn't wrap - // around to zero. Could replace addr and end with longs to fix this, - // but that might not be any faster. - break; - } - } - - return checksum; -} - - -/** -* Read data from the device and dump it in hex and ascii. -**/ -void dumpBlock(word start, word end) -{ - char line[81]; -// 01234567891 234567892 234567893 234567894 234567895 234567896 234567897 23456789 -// 1234: 01 23 45 67 89 ab cf ef 01 23 45 67 89 ab cd ef 1.2.3.4. 5.6.7.8. - int count = 0; - - memset(line, ' ', sizeof(line)); - - char * pHex = line; - char * pChar = line + 58; - for (word addr = start; (addr <= end); addr++) - { - if (count == 0) - { - //print out the address at the beginning of the line - pHex = line; - pChar = line + 58; - *pHex++ = hex[(addr >> 12) & 0x0f]; - *pHex++ = hex[(addr >> 8) & 0x0f]; - *pHex++ = hex[(addr >> 4) & 0x0f]; - *pHex++ = hex[(addr) & 0x0f]; - *pHex++ = ':'; - *pHex++ = ' '; - } - - byte data = prom.readByte(addr); - *pHex++ = hex[data >> 4]; - *pHex++ = hex[data & 0x0f]; - *pHex++ = ' '; - *pChar++ = ((data < 32) | (data >= 127)) ? '.' : data; - - if ((count & 3) == 3) - { - *pHex++ = ' '; - } - if ((count & 7) == 7) - { - *pChar++ = ' '; - } - if ((++count >= 16) || (addr == end)) - { - *pChar = '\0'; - Serial.println(line); - if (checkForBreak()) - { - return; - } - memset(line, ' ', sizeof(line)); - count = 0; - } - } - - if (count) - { - Serial.println(); - } -} - - -/** - * Fill a block of PROM data with a single value. - * - * @param start - start address - * @param end - end address - * @param val - data byte to write to all addresses - */ -void fillBlock(word start, word end, byte val) -{ - enum { BLOCK_SIZE = 32 }; - byte block[BLOCK_SIZE]; - - for (int ix = 0; ix < BLOCK_SIZE; ix++) - { - block[ix] = val; - } - - for (word addr = start; (addr <= end); addr += BLOCK_SIZE) - { - unsigned writeLen = ((end - addr + 1) < BLOCK_SIZE) ? (end - addr + 1) : BLOCK_SIZE; - if (!prom.writeData(block, writeLen, addr)) - { - cmdStatus.error("Write failed"); - return; - } - } -} - - -#ifdef ENABLE_DEBUG_COMMANDS -/** - * Runs through a range of addresses, reading a single address - * multiple times. Fails if all of the reads for an address do - * not produce that same value. - * - * @param start - start address - * @param end - end address - */ -void scanBlock(word start, word end) -{ - enum { SCAN_TESTS = 10 }; - - for (word addr = start; (addr <= end); addr++) - { - byte values[SCAN_TESTS]; - values[0] = prom.readByte(addr); - bool fail = false; - for (int ix = 1; (ix < SCAN_TESTS); ix++) - { - values[ix] = prom.readByte(addr); - if (values[ix] != values[0]) - { - fail = true; - } - } - if (fail) - { - printWord(addr); - Serial.print(": "); - for (int ix = 0; (ix < SCAN_TESTS); ix++) - { - printByte(values[ix]); - Serial.print(" "); - } - Serial.println(); - cmdStatus.error("Repeated reads returned different values"); - cmdStatus.setValueHex(0, "addr", addr); - break; - } - - if (addr == 0xffff) break; - } -} - - -/** - * Reads a single address in the PROM multiple times and fails - * if all of the reads do not produce the same value. - * - * @param addr - address to test - */ -void testAddr(word addr) -{ - enum { NUM_TESTS = 100 }; - - bool fail = false; - byte value; - byte firstValue = prom.readByte(addr); - for (int ix = 1; (ix < NUM_TESTS); ix++) - { - value = prom.readByte(addr); - if (value != firstValue) - { - fail = true; - } - } - if (fail) - { - cmdStatus.error("Repeated reads returned different values"); - cmdStatus.setValueHex(0, "addr", addr); - cmdStatus.setValueHex(1, "first read", firstValue); - cmdStatus.setValueHex(2, "last read", value); - } - else - { - cmdStatus.info("Read test passed"); - } -} - - -/** - * Write a 32 byte test pattern to the PROM device and verify it - * by reading back. The pattern includes a walking 1 and a - * walking zero, which may help to detect pins that are tied - * together or swapped. - * - * @param start - start address - */ -void zapTest(word start) -{ - byte testData[] = - { - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', - 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, - 0x7f, 0xbf, 0xdf, 0xef, 0xf7, 0xfb, 0xfd, 0xfe, - 0x00, 0xff, 0x55, 0xaa, '0', '1', '2', '3' - }; - - - if (!prom.writeData(testData, sizeof(testData), start)) - { - cmdStatus.error("Write failed"); - return; - } - - delayMicroseconds(10000); - for (int ix = 0; ix < sizeof(testData); ix++) - { - byte val = prom.readByte(start + ix); - if (val != testData[ix]) - { - cmdStatus.error("Verify failed"); - cmdStatus.setValueHex(0, "addr", start + ix); - cmdStatus.setValueHex(1, "read", val); - cmdStatus.setValueHex(2, "expected", testData[ix]); - return; - } - } - cmdStatus.info("Write test successful"); -} -#endif /* ENABLE_DEBUG_COMMANDS */ - - -/************************************************ -* 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); -} - - -/** -* main loop that runs infinite times, parsing a given command and -* executing read or write requestes. -**/ - -byte ledTest[] = -{ - 0xc3, 0x03, 0x80, 0x3e, 0xc0, 0x30, 0x3e, 0xff, - 0x47, 0x3d, 0x05, 0xc2, 0x0a, 0x80, 0xfe, 0x00, - 0xc2, 0x09, 0x80, 0x3e, 0x40, 0x30, 0x3e, 0xff, - 0x47, 0x3d, 0x05, 0xc2, 0x1a, 0x80, 0xfe, 0x00, - 0xc2, 0x19, 0x80, 0xc3, 0x03, 0x80 -}; -byte charTest[] = -{ - 0xc3, 0x03, 0x80, 0x0e, 0x55, 0xf3, 0x06, 0x0b, 0xaf, 0x3e, 0x80, 0x1f, - 0x3f, 0x30, 0x21, 0x19, 0x00, 0x2d, 0xc2, 0x11, 0x80, 0x25, 0xc2, 0x11, - 0x80, 0x37, 0x79, 0x1f, 0x4f, 0x05, 0xc2, 0x09, 0x80, 0x3e, 0xc0, 0x30, - 0x3e, 0x40, 0x30, 0x3e, 0xc0, 0x30, 0x3e, 0x40, 0x30, 0x21, 0xff, 0xff, - 0x2d, 0xc2, 0x30, 0x80, 0x25, 0xc2, 0x30, 0x80, 0xc3, 0x03, 0x80 -}; - -word start = 0; -word end = 0xff; -byte val = 0xff; - -void loop() -{ - word w; - char line[20]; - uint32_t numBytes; - - Serial.print("\n>"); - Serial.flush(); - readLine(line, sizeof(line)); - byte cmd = parseCommand(line[0]); - if (hexDigit(line[1]) <= 15) - start = hexWord(line + 1); - if (hexDigit(line[6]) <= 15) - end = hexWord(line + 6); - if (hexDigit(line[6]) <= 11) - val = hexByte(line + 11); - - if ((cmd != CMD_LAST_STATUS) && (cmd != CMD_INVALID)) - { - cmdStatus.clear(); - } - - switch (cmd) - { - case CMD_CHECKSUM: - w = checksumBlock(start, end); - Serial.print("Checksum "); - printWord(start); - Serial.print("-"); - printWord(end); - Serial.print(" = "); - printWord(w); - Serial.println(); - break; - - case CMD_DUMP: - dumpBlock(start, end); - break; - - case CMD_FILL: - fillBlock(start, end, val); - break; - - case CMD_READ: - Serial.println(F("Set the terminal to receive XMODEM CRC")); - if (xmodem.SendFile(start, uint32_t(end) - start + 1)) - { - cmdStatus.info("Send complete."); - cmdStatus.setValueDec(0, "NumBytes", uint32_t(end) - start + 1); - } - break; - - case CMD_UNLOCK: - Serial.println(F("Writing the unlock code to disable Software Write Protect mode.")); - prom.disableSoftwareWriteProtect(); - break; - - case CMD_WRITE: - Serial.println(F("Send the image file using XMODEM CRC")); - numBytes = xmodem.ReceiveFile(start); - if (numBytes) - { - cmdStatus.info("Success writing to EEPROM device."); - cmdStatus.setValueDec(0, "NumBytes", numBytes); - } - else - { - xmodem.Cancel(); - } - break; - -#ifdef ENABLE_DEBUG_COMMANDS - case CMD_SCAN: - scanBlock(start, end); - break; - - case CMD_TEST: - testAddr(start); - break; - - case CMD_ZAP: - zapTest(start); - break; -#endif /* ENABLE_DEBUG_COMMANDS */ - - case CMD_LAST_STATUS: - Serial.println(F("Status of last command:")); - break; - - default: - Serial.println(F("TommyPROM 1.3\n")); - Serial.println(F("Valid commands are:")); - Serial.println(F(" Cssss eeee - Compute checksum from device")); - Serial.println(F(" Dssss eeee - Dump bytes from device to terminal")); - Serial.println(F(" Fssss eeee dd - Fill block on device with fixed value")); - Serial.println(F(" Rssss eeee - Read from device and save to XMODEM CRC file")); - Serial.println(F(" U - Unlock device Software Data Protection")); - Serial.println(F(" Wssss - Write to device from XMODEM CRC file")); -#ifdef ENABLE_DEBUG_COMMANDS - Serial.println(); - Serial.println(F(" Sssss eeee - Scan addresses (read each 10x)")); - Serial.println(F(" Tssss - Test read address (read 100x)")); - Serial.println(F(" Zssss - Zap (burn) a 32 byte test pattern")); -#endif /* ENABLE_DEBUG_COMMANDS */ - break; - } - - if (!cmdStatus.isClear() || (cmd == CMD_LAST_STATUS)) - { - Serial.println(); - cmdStatus.printStatus(); - } -} -