/** * Read and write parallel EEPROMS with an interctive command-line interface. * Modules are available for ATMEL 28C series EEPROMs and Intel 8755A EPROMS. * Many other parallel EPROM/EEPROMs can be read, but not written, using the * 28C code. * * The 28C module supports block writes for better performance and * Software Data Protection (SDP) unlocking. * * ROM images are moved to and from a host computer using XMODEM. This is * available in a number of terminal programs, such as TeraTerm and Minicom. * * The default hardware uses two 74LS164 shift registers as the low and * high address registers. **/ #include "Configure.h" #include "CmdStatus.h" #include "XModem.h" static const char * MY_VERSION = "3.7"; // 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); //PromDevice28C prom(8 * 1024L, 0, 10, true); // 28C64 with no page writes //PromDevice28C prom(2 * 1024L, 0, 10, true); // 28C16 with no page writes #elif defined(PROM_IS_27) // Define a device for a 2764 EPROM with the following parameters: // 8K byte device capacity // Program using dedicated WR pin // 1000us (1ms) write pulse // Max 15 write attempts // 4x overwrite pulse (4 * writePulseLength * numberOfPulsesWritten) // (true) verify data byte after writing //PromDevice27 prom(8 * 1024L, E27C_PGM_WE, 1000L, 15, 4); // 2764 with SEEQ intelligent programming //PromDevice27 prom(32 * 1024L, E27C_PGM_WE, 1000L, 25, 3); // 27C256 with SEEQ intelligent programming PromDevice27 prom(32 * 1024L, E27C_PGM_CE, 100L, 25, 0); // M27C256 with Presto II intelligent programming //PromDevice27 prom(32 * 1024L, E27C_PGM_CE, 1000L, 25, 3); // M27256 with fast programming //PromDevice27 prom(2 * 1024L, E27C_PGM_WE, 50000L, 1, 0); // 2716 with single 50ms write //PromDevice27 prom(512 * 1024L, E27C_PGM_CE, 100L, 11, 0); // 27C040 with Atmel rapid programming, CE connected to CE#/PGM# //PromDevice27 prom(32 * 1024L, E27C_PGM_CE, 100L, 25, 0); // W27C257/W27E257 with 100uS program pulse on CE //PromDevice27 prom(64 * 1024L, E27C_PGM_CE, 100L, 1, 0, false); // W27C512 with single 100uS program pulse on CE, no verify //PromDevice27 prom(256 * 1024L, E27C_PGM_WE, 20L, 1, 0, false); // SST27SF020 with single 20us write, no verify #elif defined(PROM_IS_SST39SF) // Define a device for anSST39SF Flash with the following parameters: // 512K byte device capacity // 10us max write time // Data polling supported //PromDeviceSST39SF prom(128 * 1024L, 10, true); // SST39SF010 //PromDeviceSST39SF prom(256 * 1024L, 10, true); // SST39SF020 PromDeviceSST39SF prom(512 * 1024L, 10, true); // SST39SF040 #elif defined(PROM_IS_SST28SF) // Define a device for anSST28SF Flash with the following parameters: // 512K byte device capacity // 40us max write time // Data polling supported PromDeviceSST28SF prom(512 * 1024L, 40, true); #elif defined(PROM_IS_8755A) // Define a device for an Intel 8755A with a fixed size of 2K and no other parameters. PromDevice8755A prom(2 * 1024L); #elif defined(PROM_IS_23) PromDevice23 prom(2 * 1024L); // 2316 // Additional device-specific code goes here... //#elif defined(PROM_IS... #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"; const uint32_t unspec = ~0; inline uint32_t if_unspec(uint32_t val, uint32_t repl) { return val == unspec? repl: val; } enum { // CLI Commands CMD_INVALID, CMD_BLANK, CMD_CHECKSUM, CMD_DUMP, CMD_ERASE, CMD_FILL, CMD_INFO, CMD_LOCK, CMD_POKE, CMD_READ, CMD_UNLOCK, CMD_WRITE, CMD_ZAP, CMD_SCAN, CMD_TEST, CMD_PATTERN_FILL, 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') && (c != '\r') && (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 'b': cmd = CMD_BLANK; break; case 'c': cmd = CMD_CHECKSUM; break; case 'd': cmd = CMD_DUMP; break; case 'e': cmd = CMD_ERASE; break; case 'f': cmd = CMD_FILL; break; case 'i': cmd = CMD_INFO; break; case 'l': cmd = CMD_LOCK; break; case 'p': cmd = CMD_POKE; break; case 'r': cmd = CMD_READ; break; case 'u': cmd = CMD_UNLOCK; break; case 'w': cmd = CMD_WRITE; break; case 'z': cmd = CMD_ZAP; break; case 's': cmd = CMD_SCAN; break; case 't': cmd = CMD_TEST; break; case '!': cmd = CMD_PATTERN_FILL;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 string to a uint32_t value. * Skips leading spaces and terminates on the first non-hex * character. Leading zeroes are not required. * * No error checking is performed - if no hex is found then * defaultValue is returned. Similarly, a hex string of more than * 8 digits will return the value of the last 8 digits. * @param pointer to string with the hex value of the word (modified) * @return unsigned int represented by the digits ************************************************************/ uint32_t getHex32(char *& pData, uint32_t defaultValue=unspec) { uint32_t u32 = 0; while (isspace(*pData)) { ++pData; } if (isxdigit(*pData)) { while (isxdigit(*pData)) { u32 = (u32 << 4) | hexDigit(*pData++); } } else { u32 = defaultValue; } return u32; } 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); } /* * Prints a 32 bit value as a hex. * * Note that no values over 5 digits are used in * this appication, so only 5 digits are printed.*/ void printHex32(uint32_t u32) { char line[6]; line[0] = hex[(u32 >> 16) & 0x0f]; line[1] = hex[(u32 >> 12) & 0x0f]; line[2] = hex[(u32 >> 8) & 0x0f]; line[3] = hex[(u32 >> 4) & 0x0f]; line[4] = hex[(u32) & 0x0f]; line[5] = '\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(uint32_t start, uint32_t end) { word checksum = 0; #if 1 for (uint32_t addr = start; (addr <= end); addr += 2) { word w = prom.readData(addr); w <<= 8; w |= prom.readData(addr + 1); checksum += w; } #else uint16_t crc = 0xffff; for (uint32_t addr = start; (addr <= end); addr++) { crc = crc ^ (uint16_t(prom.readData(addr)) << 8); for (int ix = 0; (ix < 8); ix++) { if (crc & 0x8000) { crc = (crc << 1) ^ 0x1021; } else { crc <<= 1; } } } checksum = crc; #endif return checksum; } /** * Read data from the device and dump it in hex and ascii. **/ uint32_t dumpBlock(uint32_t start, uint32_t end) { char line[81]; // 01234567891 234567892 234567893 234567894 234567895 234567896 234567897 23456789 // 01234: 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 + 59; for (uint32_t addr = start; (addr <= end); addr++) { if (count == 0) { //print out the address at the beginning of the line pHex = line; pChar = line + 59; *pHex++ = hex[(addr >> 16) & 0x0f]; *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 addr; } memset(line, ' ', sizeof(line)); count = 0; } } if (count) { Serial.println(); } return end+1; } /** * 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(uint32_t start, uint32_t end, byte val) { enum { BLOCK_SIZE = 32 }; byte block[BLOCK_SIZE]; for (int ix = 0; ix < BLOCK_SIZE; ix++) { block[ix] = val; } for (uint32_t addr = start; (addr <= end); addr += BLOCK_SIZE) { uint32_t writeLen = ((end - addr + 1) < BLOCK_SIZE) ? (end - addr + 1) : uint32_t(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(uint32_t start, uint32_t end) { for (uint32_t 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"); } /** * Write a series of bytes from the command line to the PROM. * * @param cursor - pointer to command line text */ void pokeBytes(char * pCursor) { uint32_t val; uint32_t start; unsigned byteCtr = 0; enum { BLOCK_SIZE = 32 }; byte data[BLOCK_SIZE]; //first value returned is the starting address start = getHex32(pCursor, 0); while (((val = getHex32(pCursor)) != unspec) && (byteCtr < BLOCK_SIZE)) { data[byteCtr++] = byte(val); } if (byteCtr > 0) { if (!prom.writeData(data, byteCtr, start)) { cmdStatus.error("Write failed"); return; } } else { cmdStatus.error("Missing address or data"); return; } delay(100); if (!prom.is_readback_safe()) { // This chip uses the CE line for write control, so don't do the read because it // could cause a write operation that would corrupt the data. cmdStatus.info("Poke complete"); return; } for (unsigned ix = 0; ix < byteCtr ; ix++) { byte val = prom.readData(start + ix); if (val != data[ix]) { cmdStatus.error("Verify failed"); cmdStatus.setValueHex(0, "addr", start + ix); cmdStatus.setValueHex(1, "read", val); cmdStatus.setValueHex(2, "expected", data[ix]); return; } } cmdStatus.info("Poke successful"); } /** * 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(uint32_t 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; } delay(100); if (!prom.is_readback_safe()) { // This chip uses the CE line for write control, so don't do the read because it // could cause a write operation that would corrupt the data. cmdStatus.info("Write test complete"); return; } for (unsigned 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"); } void printRetStatus(ERET status) { switch (status) { case RET_OK: Serial.println(F("OK")); break; case RET_FAIL: Serial.println(F("FAILED")); break; case RET_NOT_SUPPORT: Serial.println(F("NOT SUPPORTED")); break; } } #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(uint32_t start, uint32_t end) { enum { SCAN_TESTS = 10 }; for (uint32_t 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) { printHex32(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(uint32_t 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"); } } /** * Fill a PROM with a test pattern * * Each 8-byte block contains two bytes of 256-byte block number * followed by six bytes of byte number. * 00000 00 00 02 03 04 05 06 07 00 00 0a 0b 0c 0d 0e 0f * 00010 00 00 12 13 14 15 16 17 00 00 1a 1b 1c 1d 1e 1f * 00020 00 00 22 23 24 25 26 27 00 00 2a 2b 2c 2d 2e 2f * ... * 000f0 00 00 f2 f3 f4 f5 f6 f7 00 00 fa fb fc fd fe ff * 00100 00 01 02 03 04 05 06 07 00 01 0a 0b 0c 0d 0e 0f * 00010 00 01 12 13 14 15 16 17 00 01 1a 1b 1c 1d 1e 1f * ... * 3fff0 03 ff f2 f3 f4 f5 f6 f7 03 ff fa fb fc fd fe ff */ void patternFill() { enum { BLOCK_SIZE = 32 }; byte block[BLOCK_SIZE]; Serial.print("Filling with pattern from 0 to "); printHex32(prom.end()); Serial.print("..."); for (uint32_t addr = 0; (addr <= prom.end()); addr += BLOCK_SIZE) { for (unsigned ix = 0; (ix < BLOCK_SIZE); ix+= 2) { if ((ix & 7) == 0) { block[ix] = (addr >> 16) & 0xff; block[ix+1] = (addr >> 8) & 0xff; } else { block[ix] = (addr + ix) & 0xff; block[ix+1] = (addr + ix + 1) & 0xff; } } if (!prom.writeData(block, BLOCK_SIZE, addr)) { cmdStatus.error("Write failed"); return; } } } #endif /* ENABLE_DEBUG_COMMANDS */ /************************************************ * MAIN *************************************************/ uint32_t 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. **/ char line[120]; void loop() { uint32_t w; uint32_t numBytes; Serial.print("\n>"); Serial.flush(); readLine(line, sizeof(line)); Serial.println(); byte cmd = parseCommand(line[0]); char * pCursor = line+1; uint32_t start = getHex32(pCursor); uint32_t end = getHex32(pCursor); uint32_t val = getHex32(pCursor); static uint32_t dump_next = 0; if ((cmd != CMD_LAST_STATUS) && (cmd != CMD_INVALID)) { cmdStatus.clear(); } switch (cmd) { case CMD_BLANK: erasedBlockCheck(if_unspec(start, 0), if_unspec(end, prom.end())); break; case CMD_CHECKSUM: start = if_unspec(start, 0); end = if_unspec(end, prom.end()); w = checksumBlock(start, end); Serial.print(F("Checksum ")); printWord(start); Serial.print(F("-")); printWord(end); Serial.print(F(" = ")); printWord(w); Serial.println(); break; case CMD_DUMP: start = if_unspec(start, dump_next); dump_next = dumpBlock(start, if_unspec(end, start + 0xff)); break; case CMD_ERASE: if (start == unspec || end == unspec) { Serial.println(F("Erase requires explicit start, end")); } else { printRetStatus(prom.erase(start, end)); } break; case CMD_FILL: if (start == unspec || end == unspec || val == unspec) { Serial.println(F("Fill requires explicit start, end and value")); } else { prom.resetDebugStats(); fillBlock(start, end, (byte)val); } break; case CMD_INFO: prom.printDebugStats(); break; case CMD_LOCK: Serial.print(F("Writing the lock code to enable Software Write Protect mode: ")); printRetStatus(prom.enableSoftwareWriteProtect()); break; case CMD_POKE: prom.resetDebugStats(); pokeBytes(line+1); break; case CMD_READ: start = if_unspec(start, 0); end = if_unspec(end, prom.end()); if (xmodem.SendFile(start, end - start + 1)) { cmdStatus.info("Send complete."); cmdStatus.setValueDec(0, "NumBytes", end - start + 1); } break; case CMD_UNLOCK: Serial.print(F("Writing the unlock code to disable Software Write Protect mode: ")); printRetStatus(prom.disableSoftwareWriteProtect()); break; case CMD_WRITE: prom.resetDebugStats(); start = if_unspec(start, 0); numBytes = xmodem.ReceiveFile(start); if (numBytes) { cmdStatus.info("Success writing to EEPROM device."); cmdStatus.setValueDec(0, "NumBytes", numBytes); } else { xmodem.Cancel(); } break; case CMD_ZAP: prom.resetDebugStats(); zapTest(if_unspec(start, 0)); break; #ifdef ENABLE_DEBUG_COMMANDS case CMD_SCAN: scanBlock(if_unspec(start, 0), if_unspec(end, prom.end())); break; case CMD_TEST: testAddr(if_unspec(start, 0)); break; case CMD_PATTERN_FILL: patternFill(); break; #endif /* ENABLE_DEBUG_COMMANDS */ case CMD_LAST_STATUS: Serial.println(F("Status of last command:")); break; default: Serial.print(F("TommyPROM ")); Serial.print(MY_VERSION); Serial.print(F(" - ")); Serial.println(prom.getName()); Serial.println(); Serial.println(F("Valid commands are:")); Serial.println(F(" Bsssss eeeee - Check to see if device range is Blank/erased (all FF)")); Serial.println(F(" Csssss eeeee - Compute Checksum from device")); Serial.println(F(" Dsssss eeeee - Dump bytes from device to terminal")); Serial.println(F(" Esssss eeeee - Erase address range on device (needed for some Flash)")); Serial.println(F(" Fsssss eeeee dd - Fill block on device with fixed value")); Serial.println(F(" I - Print debug Info")); Serial.println(F(" L - Lock (enable) device Software Data Protection")); Serial.println(F(" Psssss dd dd... - Poke (write) values to device (up to 32 values)")); Serial.println(F(" Rsssss eeeee - Read from device and save to XMODEM CRC file")); Serial.println(F(" U - Unlock (disable) device Software Data Protection")); Serial.println(F(" Wsssss - Write to device from XMODEM CRC file")); Serial.println(F(" Zsssss - Zap (burn) a 32 byte test pattern")); #ifdef ENABLE_DEBUG_COMMANDS Serial.println(); Serial.println(F(" Ssssss eeeee - Scan addresses (read each 10x)")); Serial.println(F(" Tsssss - Test read address (read 100x)")); Serial.println(F(" ! - Fill entire device with a test pattern")); #endif /* ENABLE_DEBUG_COMMANDS */ break; } if (!cmdStatus.isClear() || (cmd == CMD_LAST_STATUS)) { Serial.println(); cmdStatus.printStatus(); } }