mirror of
https://github.com/TomNisbet/TommyPROM.git
synced 2025-02-16 11:30:32 +00:00
1489 lines
37 KiB
C++
1489 lines
37 KiB
C++
/**
|
|
* 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();
|
|
}
|
|
}
|
|
|