/*
* Copyright (C) 2012 Southern Storm Software, Pty Ltd.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include // For PROGMEM
typedef void PROGMEM prog_void;
typedef char PROGMEM prog_char;
typedef unsigned char PROGMEM prog_uchar;
typedef int8_t PROGMEM prog_int8_t;
typedef uint8_t PROGMEM prog_uint8_t;
typedef int16_t PROGMEM prog_int16_t;
typedef uint16_t PROGMEM prog_uint16_t;
typedef int32_t PROGMEM prog_int32_t;
typedef uint32_t PROGMEM prog_uint32_t;
void processCommand(const char *buf);
void exitProgramMode();
unsigned int readConfigWord(unsigned long addr);
unsigned int readWord(unsigned long addr);
void setErasePC();
bool matchString(const prog_char *name, const char *str, int len);
void beginProgramCycle(unsigned long addr, bool isData);
bool writeWord(unsigned long addr, unsigned int word);
bool writeWordForced(unsigned long addr, unsigned int word);
void sendSimpleCommand(unsigned char cmd);
void sendWriteCommand(unsigned char cmd, unsigned int data);
void enterProgramMode();
void cmdHelp(const char *args);
#include
// Pin mappings for the PIC programming shield.
#define PIN_MCLR 2 // 0: MCLR is VPP voltage, 1: Reset PIC
#define PIN_ACTIVITY 3 // LED that indicates read/write activity
#define PIN_VDD 21 // Controls the power to the PIC
#define PIN_CLOCK 5 // Clock pin (PIC pin 13)
#define PIN_DATA 4 // Data pin (PIC pin 12)
#define MCLR_RESET HIGH // PIN_MCLR state to reset the PIC
#define MCLR_VPP LOW // PIN_MCLR state to apply 13v to MCLR/VPP pin
// All delays are in microseconds.
#define DELAY_SETTLE 1*50 // Delay for lines to settle for reset
#define DELAY_TPPDP 1*5 // Hold time after raising MCLR
#define DELAY_THLD0 1*5 // Hold time after raising VDD
#define DELAY_TSET1 1*1 // Data in setup time before lowering clock
#define DELAY_THLD1 1*1 // Data in hold time after lowering clock
#define DELAY_TDLY2 1*1 // Delay between commands or data
#define DELAY_TDLY3 1*1 // Delay until data bit read will be valid
#define DELAY_TPROG 1*4000 // Time for a program memory write to complete
#define DELAY_TDPROG 1*6000 // Time for a data memory write to complete
#define DELAY_TERA 1*6000 // Time for a word erase to complete
#define DELAY_TPROG5 1*1000 // Time for program write on FLASH5 systems
#define DELAY_TFULLERA 1*50000 // Time for a full chip erase
#define DELAY_TFULL84 1*20000 // Intermediate wait for PIC16F84/PIC16F84A
// Commands that may be sent to the device.
#define CMD_LOAD_CONFIG 0x00 // Load (write) to config memory
#define CMD_LOAD_PROGRAM_MEMORY 0x02 // Load to program memory
#define CMD_LOAD_DATA_MEMORY 0x03 // Load to data memory
#define CMD_INCREMENT_ADDRESS 0x06 // Increment the PC
#define CMD_READ_PROGRAM_MEMORY 0x04 // Read from program memory
#define CMD_READ_DATA_MEMORY 0x05 // Read from data memory
#define CMD_BEGIN_PROGRAM 0x08 // Begin programming with erase cycle
#define CMD_BEGIN_PROGRAM_ONLY 0x18 // Begin programming only cycle
#define CMD_END_PROGRAM_ONLY 0x17 // End programming only cycle
#define CMD_BULK_ERASE_PROGRAM 0x09 // Bulk erase program memory
#define CMD_BULK_ERASE_DATA 0x0B // Bulk erase data memory
#define CMD_CHIP_ERASE 0x1F // Erase the entire chip
// States this application may be in.
#define STATE_IDLE 0 // Idle, device is held in the reset state
#define STATE_PROGRAM 1 // Active, reading and writing program memory
#define STATE_CONFIG 2 // Active, reading and writing config memory
int state = STATE_IDLE;
// Flash types. Uses a similar naming system to picprog.
#define EEPROM 0
#define FLASH 1
#define FLASH4 4
#define FLASH5 5
unsigned long pc = 0; // Current program counter.
// Flat address ranges for the various memory spaces. Defaults to the values
// for the PIC16F628A. "DEVICE" command updates to the correct values later.
unsigned long programEnd = 0x07FF;
unsigned long configStart = 0x2000;
unsigned long configEnd = 0x2007;
unsigned long dataStart = 0x2100;
unsigned long dataEnd = 0x217F;
unsigned long reservedStart = 0x0800;
unsigned long reservedEnd = 0x07FF;
unsigned int configSave = 0x0000;
byte progFlashType = FLASH4;
byte dataFlashType = EEPROM;
// Device names, forced out into PROGMEM.
const char s_pic12f629[] PROGMEM = "pic12f629";
const char s_pic12f630[] PROGMEM = "pic12f630";
const char s_pic12f675[] PROGMEM = "pic12f675";
const char s_pic12f676[] PROGMEM = "pic12f676";
const char s_pic16f84[] PROGMEM = "pic16f84";
const char s_pic16f84a[] PROGMEM = "pic16f84a";
const char s_pic16f87[] PROGMEM = "pic16f87";
const char s_pic16f88[] PROGMEM = "pic16f88";
const char s_pic16f627[] PROGMEM = "pic16f627";
const char s_pic16f627a[] PROGMEM = "pic16f627a";
const char s_pic16f628[] PROGMEM = "pic16f628";
const char s_pic16f628a[] PROGMEM = "pic16f628a";
const char s_pic16f648a[] PROGMEM = "pic16f648a";
// List of devices that are currently supported and their properties.
// Note: most of these are based on published information and have not
// been tested by the author. Patches welcome to improve the list.
struct deviceInfo
{
const prog_char *name; // User-readable name of the device.
prog_int16_t deviceId; // Device ID for the PIC (-1 if no id).
prog_uint32_t programSize; // Size of program memory (words).
prog_uint32_t configStart; // Flat address start of configuration memory.
prog_uint32_t dataStart; // Flat address start of EEPROM data memory.
prog_uint16_t configSize; // Number of configuration words.
prog_uint16_t dataSize; // Size of EEPROM data memory (bytes).
prog_uint16_t reservedWords;// Reserved program words (e.g. for OSCCAL).
prog_uint16_t configSave; // Bits in config word to be saved.
prog_uint8_t progFlashType; // Type of flash for program memory.
prog_uint8_t dataFlashType; // Type of flash for data memory.
};
struct deviceInfo const devices[] PROGMEM = {
// http://ww1.microchip.com/downloads/en/DeviceDoc/41191D.pdf
{s_pic12f629, 0x0F80, 1024, 0x2000, 0x2100, 8, 128, 1, 0x3000, FLASH4, EEPROM},
{s_pic12f630, 0x10C0, 1024, 0x2000, 0x2100, 8, 128, 1, 0x3000, FLASH4, EEPROM},
{s_pic12f675, 0x0FC0, 1024, 0x2000, 0x2100, 8, 128, 1, 0x3000, FLASH4, EEPROM},
{s_pic12f676, 0x10E0, 1024, 0x2000, 0x2100, 8, 128, 1, 0x3000, FLASH4, EEPROM},
// http://ww1.microchip.com/downloads/en/DeviceDoc/30262e.pdf
{s_pic16f84, -1, 1024, 0x2000, 0x2100, 8, 64, 0, 0, FLASH, EEPROM},
{s_pic16f84a, 0x0560, 1024, 0x2000, 0x2100, 8, 64, 0, 0, FLASH, EEPROM},
// http://ww1.microchip.com/downloads/en/DeviceDoc/39607c.pdf
{s_pic16f87, 0x0720, 4096, 0x2000, 0x2100, 9, 256, 0, 0, FLASH5, EEPROM},
{s_pic16f88, 0x0760, 4096, 0x2000, 0x2100, 9, 256, 0, 0, FLASH5, EEPROM},
// 627/628: http://ww1.microchip.com/downloads/en/DeviceDoc/30034d.pdf
// A series: http://ww1.microchip.com/downloads/en/DeviceDoc/41196g.pdf
{s_pic16f627, 0x07A0, 1024, 0x2000, 0x2100, 8, 128, 0, 0, FLASH, EEPROM},
{s_pic16f627a, 0x1040, 1024, 0x2000, 0x2100, 8, 128, 0, 0, FLASH4, EEPROM},
{s_pic16f628, 0x07C0, 2048, 0x2000, 0x2100, 8, 128, 0, 0, FLASH, EEPROM},
{s_pic16f628a, 0x1060, 2048, 0x2000, 0x2100, 8, 128, 0, 0, FLASH4, EEPROM},
{s_pic16f648a, 0x1100, 4096, 0x2000, 0x2100, 8, 256, 0, 0, FLASH4, EEPROM},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
};
// Buffer for command-line character input and READBIN data packets.
#define BINARY_TRANSFER_MAX 64
#define BUFFER_MAX (BINARY_TRANSFER_MAX + 1)
char buffer[BUFFER_MAX];
int buflen = 0;
unsigned long lastActive = 0;
void setup()
{
// Need a serial link to the host.
Serial.begin(38400);
// Hold the PIC in the powered down/reset state until we are ready for it.
pinMode(PIN_MCLR, OUTPUT);
pinMode(PIN_VDD, OUTPUT);
digitalWrite(PIN_MCLR, MCLR_RESET);
digitalWrite(PIN_VDD, LOW);
// Clock and data are floating until the first PIC command.
pinMode(PIN_CLOCK, INPUT);
pinMode(PIN_DATA, INPUT);
// Turn off the activity LED initially.
pinMode(PIN_ACTIVITY, OUTPUT);
digitalWrite(PIN_ACTIVITY, LOW);
}
void loop()
{
if (Serial.available()) {
// Process serial input for commands from the host.
int ch = Serial.read();
if (ch == 0x0A || ch == 0x0D) {
// End of the current command. Blank lines are ignored.
if (buflen > 0) {
buffer[buflen] = '\0';
buflen = 0;
digitalWrite(PIN_ACTIVITY, HIGH); // Turn on activity LED.
processCommand(buffer);
digitalWrite(PIN_ACTIVITY, LOW); // Turn off activity LED.
}
} else if (ch == 0x08) {
// Backspace over the last character.
if (buflen > 0)
--buflen;
} else if (buflen < (BUFFER_MAX - 1)) {
// Add the character to the buffer after forcing to upper case.
if (ch >= 'a' && ch <= 'z')
buffer[buflen++] = ch - 'a' + 'A';
else
buffer[buflen++] = ch;
}
lastActive = millis();
} else if (state != STATE_IDLE) {
// Power off the programming socket if no activity for 2 seconds.
// Normally the host will issue the "PWROFF" command, but if we are
// operating in interactive mode or the host has crashed, then this
// timeout will ensure that the system eventually enters safe mode.
if ((millis() - lastActive) >= 2000)
exitProgramMode();
}
}
void printHex1(unsigned int value)
{
if (value >= 10)
Serial.print((char)('A' + value - 10));
else
Serial.print((char)('0' + value));
}
void printHex4(unsigned int word)
{
printHex1((word >> 12) & 0x0F);
printHex1((word >> 8) & 0x0F);
printHex1((word >> 4) & 0x0F);
printHex1(word & 0x0F);
}
void printHex8(unsigned long word)
{
unsigned int upper = (unsigned int)(word >> 16);
if (upper)
printHex4(upper);
printHex4((unsigned int)word);
}
void printProgString(const prog_char *str)
{
for (;;) {
char ch = (char)(pgm_read_byte(str));
if (ch == '\0')
break;
Serial.print(ch);
++str;
}
}
// PROGRAM_PIC_VERSION command.
void cmdVersion(const char *args)
{
Serial.println("ProgramPIC 1.0");
}
// Initialize device properties from the "devices" list and
// print them to the serial port. Note: "dev" is in PROGMEM.
void initDevice(const struct deviceInfo *dev)
{
// Update the global device details.
programEnd = pgm_read_dword(&(dev->programSize)) - 1;
configStart = pgm_read_dword(&(dev->configStart));
configEnd = configStart + pgm_read_word(&(dev->configSize)) - 1;
dataStart = pgm_read_dword(&(dev->dataStart));
dataEnd = dataStart + pgm_read_word(&(dev->dataSize)) - 1;
reservedStart = programEnd - pgm_read_word(&(dev->reservedWords)) + 1;
reservedEnd = programEnd;
configSave = pgm_read_word(&(dev->configSave));
progFlashType = pgm_read_byte(&(dev->progFlashType));
dataFlashType = pgm_read_byte(&(dev->dataFlashType));
// Print the extra device information.
Serial.print("DeviceName: ");
printProgString((const prog_char *)(pgm_read_word(&(dev->name))));
Serial.println();
Serial.print("ProgramRange: 0000-");
printHex8(programEnd);
Serial.println();
Serial.print("ConfigRange: ");
printHex8(configStart);
Serial.print('-');
printHex8(configEnd);
Serial.println();
if (configSave != 0) {
Serial.print("ConfigSave: ");
printHex4(configSave);
Serial.println();
}
Serial.print("DataRange: ");
printHex8(dataStart);
Serial.print('-');
printHex8(dataEnd);
Serial.println();
if (reservedStart <= reservedEnd) {
Serial.print("ReservedRange: ");
printHex8(reservedStart);
Serial.print('-');
printHex8(reservedEnd);
Serial.println();
}
}
// Offsets of interesting config locations that contain device information.
#define DEV_USERID0 0
#define DEV_USERID1 1
#define DEV_USERID2 2
#define DEV_USERID3 3
#define DEV_ID 6
#define DEV_CONFIG_WORD 7
// DEVICE command.
void cmdDevice(const char *args)
{
// Make sure the device is reset before we start.
exitProgramMode();
// Read identifiers and configuration words from config memory.
unsigned int userid0 = readConfigWord(DEV_USERID0);
unsigned int userid1 = readConfigWord(DEV_USERID1);
unsigned int userid2 = readConfigWord(DEV_USERID2);
unsigned int userid3 = readConfigWord(DEV_USERID3);
unsigned int deviceId = readConfigWord(DEV_ID);
unsigned int configWord = readConfigWord(DEV_CONFIG_WORD);
/*{ char Buf[128];
sprintf(Buf, "id: %04X-%04X-%04X-%04X did=%04X cfg=%04X",
userid0,userid1,userid2,userid3,deviceId,configWord);
Serial.println(Buf);
}*/
// If the device ID is all-zeroes or all-ones, then it could mean
// one of the following:
//
// 1. There is no PIC in the programming socket.
// 2. The VPP programming voltage is not available.
// 3. Code protection is enabled and the PIC is unreadable.
// 4. The PIC is an older model with no device identifier.
//
// Case 4 is the interesting one. We look for any word in configuration
// memory or the first 16 words of program memory that is non-zero.
// If we find a non-zero word, we assume that we have a PIC but we
// cannot detect what type it is.
if (deviceId == 0 || deviceId == 0x3FFF) {
unsigned int word = userid0 | userid1 | userid2 | userid3 | configWord;
unsigned int addr = 0;
while (!word && addr < 16) {
word |= readWord(addr);
++addr;
}
if (!word) {
Serial.println("ERROR");
exitProgramMode();
return;
}
deviceId = 0;
}
Serial.println("OK");
Serial.print("DeviceID: ");
printHex4(deviceId);
Serial.println();
// Find the device in the built-in list if we have details for it.
int index = 0;
for (;;) {
const prog_char *name = (const prog_char *)
(pgm_read_word(&(devices[index].name)));
if (!name) {
index = -1;
break;
}
int id = pgm_read_word(&(devices[index].deviceId));
if (id == (deviceId & 0xFFE0))
break;
++index;
}
if (index >= 0) {
initDevice(&(devices[index]));
} else {
// Reset the global parameters to their defaults. A separate
// "SETDEVICE" command will be needed to set the correct values.
programEnd = 0x07FF;
configStart = 0x2000;
configEnd = 0x2007;
dataStart = 0x2100;
dataEnd = 0x217F;
reservedStart = 0x0800;
reservedEnd = 0x07FF;
configSave = 0x0000;
progFlashType = FLASH4;
dataFlashType = EEPROM;
}
Serial.print("ConfigWord: ");
printHex4(configWord);
Serial.println();
Serial.println(".");
// Don't need programming mode once the details have been read.
exitProgramMode();
}
// DEVICES command.
void cmdDevices(const char *args)
{
Serial.println("OK");
int index = 0;
for (;;) {
const prog_char *name = (const prog_char *)
(pgm_read_word(&(devices[index].name)));
if (!name)
break;
if (index > 0) {
Serial.print(',');
if ((index % 6) == 0)
Serial.println();
else
Serial.print(' ');
}
printProgString(name);
int id = (int)(pgm_read_word(&(devices[index].deviceId)));
if (id != -1)
Serial.print('*');
++index;
}
Serial.println();
Serial.println(".");
}
// SETDEVICE command.
void cmdSetDevice(const char *args)
{
// Extract the name of the device from the command arguments.
int len = 0;
for (;;) {
char ch = args[len];
if (ch == '\0' || ch == ' ' || ch == '\t')
break;
++len;
}
// Look for the name in the devices list.
int index = 0;
for (;;) {
const prog_char *name = (const prog_char *)
(pgm_read_word(&(devices[index].name)));
if (!name)
break;
if (matchString(name, args, len)) {
Serial.println("OK");
initDevice(&(devices[index]));
Serial.println(".");
exitProgramMode(); // Force a reset upon the next command.
return;
}
++index;
}
Serial.println("ERROR");
}
int parseHex(const char *args, unsigned long *value)
{
int size = 0;
*value = 0;
for (;;) {
char ch = *args;
if (ch >= '0' && ch <= '9')
*value = (*value << 4) | (ch - '0');
else if (ch >= 'A' && ch <= 'F')
*value = (*value << 4) | (ch - 'A' + 10);
else if (ch >= 'a' && ch <= 'f')
*value = (*value << 4) | (ch - 'a' + 10);
else
break;
++size;
++args;
}
if (*args != '\0' && *args != '-' && *args != ' ' && *args != '\t')
return 0;
return size;
}
// Parse a range of addresses of the form START or START-END.
bool parseRange(const char *args, unsigned long *start, unsigned long *end)
{
int size = parseHex(args, start);
if (!size)
return false;
args += size;
while (*args == ' ' || *args == '\t')
++args;
if (*args != '-') {
*end = *start;
return true;
}
++args;
while (*args == ' ' || *args == '\t')
++args;
if (!parseHex(args, end))
return false;
return *end >= *start;
}
bool parseCheckedRange(const char *args, unsigned long *start, unsigned long *end)
{
// Parse the basic values and make sure that start <= end.
if (!parseRange(args, start, end))
return false;
// Check that both start and end are within the same memory area
// and within the bounds of that memory area.
if (*start <= programEnd) {
if (*end > programEnd)
return false;
} else if (*start >= configStart && *start <= configEnd) {
if (*end < configStart || *end > configEnd)
return false;
} else if (*start >= dataStart && *start <= dataEnd) {
if (*end < dataStart || *end > dataEnd)
return false;
} else {
return false;
}
return true;
}
// READ command.
void cmdRead(const char *args)
{
unsigned long start;
unsigned long end;
if (!parseCheckedRange(args, &start, &end)) {
Serial.println("ERROR");
return;
}
Serial.println("OK");
int count = 0;
bool activity = true;
while (start <= end) {
unsigned int word = readWord(start);
if (count > 0) {
if ((count % 8) == 0)
Serial.println();
else
Serial.print(' ');
}
printHex4(word);
++start;
++count;
if ((count % 32) == 0) {
// Toggle the activity LED to make it blink during long reads.
activity = !activity;
if (activity)
digitalWrite(PIN_ACTIVITY, HIGH);
else
digitalWrite(PIN_ACTIVITY, LOW);
}
}
Serial.println();
Serial.println(".");
}
// READBIN command.
void cmdReadBinary(const char *args)
{
unsigned long start;
unsigned long end;
if (!parseCheckedRange(args, &start, &end)) {
Serial.println("ERROR");
return;
}
Serial.println("OK");
int count = 0;
bool activity = true;
size_t offset = 0;
while (start <= end) {
unsigned int word = readWord(start);
buffer[++offset] = (char)word;
buffer[++offset] = (char)(word >> 8);
if (offset >= BINARY_TRANSFER_MAX) {
// Buffer is full - flush it to the host.
buffer[0] = (char)offset;
Serial.write((const uint8_t *)buffer, offset + 1);
offset = 0;
}
++start;
++count;
if ((count % 64) == 0) {
// Toggle the activity LED to make it blink during long reads.
activity = !activity;
if (activity)
digitalWrite(PIN_ACTIVITY, HIGH);
else
digitalWrite(PIN_ACTIVITY, LOW);
}
}
if (offset > 0) {
// Flush the final packet before the terminator.
buffer[0] = (char)offset;
Serial.write((const uint8_t *)buffer, offset + 1);
}
// Write the terminator (a zero-length packet).
Serial.write((uint8_t)0x00);
}
const char s_force[] PROGMEM = "FORCE";
// WRITE command.
void cmdWrite(const char *args)
{
unsigned long addr;
unsigned long limit;
unsigned long value;
int size;
// Was the "FORCE" option given?
int len = 0;
while (args[len] != '\0' && args[len] != ' ' && args[len] != '\t')
++len;
bool force = matchString(s_force, args, len);
if (force) {
args += len;
while (*args == ' ' || *args == '\t')
++args;
}
size = parseHex(args, &addr);
if (!size) {
Serial.println("ERROR");
return;
}
args += size;
if (addr <= programEnd) {
limit = programEnd;
} else if (addr >= configStart && addr <= configEnd) {
limit = configEnd;
} else if (addr >= dataStart && addr <= dataEnd) {
limit = dataEnd;
} else {
// Address is not within one of the valid ranges.
Serial.println("ERROR");
return;
}
int count = 0;
for (;;) {
while (*args == ' ' || *args == '\t')
++args;
if (*args == '\0')
break;
if (*args == '-') {
Serial.println("ERROR");
return;
}
size = parseHex(args, &value);
if (!size) {
Serial.println("ERROR");
return;
}
args += size;
if (addr > limit) {
// We've reached the limit of this memory area, so fail.
Serial.println("ERROR");
return;
}
if (!force) {
if (!writeWord(addr, (unsigned int)value)) {
// The actual write to the device failed.
Serial.println("ERROR");
return;
}
} else {
if (!writeWordForced(addr, (unsigned int)value)) {
// The actual write to the device failed.
Serial.println("ERROR");
return;
}
}
++addr;
++count;
}
if (!count) {
// Missing word argument.
Serial.println("ERROR");
} else {
Serial.println("OK");
}
}
// Blocking serial read for use by WRITEBIN.
int readBlocking()
{
while (!Serial.available())
; // Do nothing.
return Serial.read();
}
// WRITEBIN command.
void cmdWriteBinary(const char *args)
{
unsigned long addr;
unsigned long limit;
int size;
// Was the "FORCE" option given?
int len = 0;
while (args[len] != '\0' && args[len] != ' ' && args[len] != '\t')
++len;
bool force = matchString(s_force, args, len);
if (force) {
args += len;
while (*args == ' ' || *args == '\t')
++args;
}
size = parseHex(args, &addr);
if (!size) {
Serial.println("ERROR");
return;
}
args += size;
if (addr <= programEnd) {
limit = programEnd;
} else if (addr >= configStart && addr <= configEnd) {
limit = configEnd;
} else if (addr >= dataStart && addr <= dataEnd) {
limit = dataEnd;
} else {
// Address is not within one of the valid ranges.
Serial.println("ERROR");
return;
}
Serial.println("OK");
int count = 0;
bool activity = true;
for (;;) {
// Read in the next binary packet.
int len = readBlocking();
while (len == 0x0A && count == 0) {
// Skip 0x0A bytes before the first packet as they are
// probably part of a CRLF pair rather than a packet length.
len = readBlocking();
}
// Stop if we have a zero packet length - end of upload.
if (!len)
break;
// Read the contents of the packet from the serial input stream.
int offset = 0;
while (offset < len) {
if (offset < BINARY_TRANSFER_MAX) {
buffer[offset++] = (char)readBlocking();
} else {
readBlocking(); // Packet is too big - discard extra bytes.
++offset;
}
}
// Write the words to memory.
for (int posn = 0; posn < (len - 1); posn += 2) {
if (addr > limit) {
// We've reached the limit of this memory area, so fail.
Serial.println("ERROR");
return;
}
unsigned int value =
(((unsigned int)buffer[posn]) & 0xFF) |
((((unsigned int)buffer[posn + 1]) & 0xFF) << 8);
if (!force) {
if (!writeWord(addr, (unsigned int)value)) {
// The actual write to the device failed.
Serial.println("ERROR");
return;
}
} else {
if (!writeWordForced(addr, (unsigned int)value)) {
// The actual write to the device failed.
Serial.println("ERROR");
return;
}
}
++addr;
++count;
if ((count % 24) == 0) {
// Toggle the activity LED to make it blink during long writes.
activity = !activity;
if (activity)
digitalWrite(PIN_ACTIVITY, HIGH);
else
digitalWrite(PIN_ACTIVITY, LOW);
}
}
// All words in this packet have been written successfully.
Serial.println("OK");
}
Serial.println("OK");
}
const char s_noPreserve[] PROGMEM = "NOPRESERVE";
// ERASE command.
void cmdErase(const char *args)
{
// Was the "NOPRESERVE" option given?
int len = 0;
while (args[len] != '\0' && args[len] != ' ' && args[len] != '\t')
++len;
bool preserve = !matchString(s_noPreserve, args, len);
// Preserve reserved words if necessary.
unsigned int *reserved = 0;
unsigned int configWord = 0x3FFF;
if (preserve && reservedStart <= reservedEnd) {
size_t size = ((size_t)(reservedEnd - reservedStart + 1))
* sizeof(unsigned int);
reserved = (unsigned int *)malloc(size);
if (reserved) {
unsigned long addr = reservedStart;
int offset = 0;
while (addr <= reservedEnd) {
reserved[offset] = readWord(addr);
++addr;
++offset;
}
} else {
// If we cannot preserve the reserved words, then abort now.
Serial.println("ERROR");
return;
}
}
if (configSave != 0 && preserve) {
// Some of the bits in the configuration word must also be saved.
configWord &= ~configSave;
configWord |= readWord(configStart + DEV_CONFIG_WORD) & configSave;
}
// Perform the memory type specific erase sequence.
switch (progFlashType) {
case FLASH4:
setErasePC();
sendSimpleCommand(CMD_BULK_ERASE_PROGRAM);
delayMicroseconds(DELAY_TERA);
sendSimpleCommand(CMD_BULK_ERASE_DATA);
break;
case FLASH5:
setErasePC();
sendSimpleCommand(CMD_CHIP_ERASE);
break;
default:
// Details for disabling code protection and erasing all memory
// for PIC16F84/PIC16F84A comes from this doc, section 4.1:
// http://ww1.microchip.com/downloads/en/DeviceDoc/30262e.pdf
setErasePC();
for (int count = 0; count < 7; ++count)
sendSimpleCommand(CMD_INCREMENT_ADDRESS); // Advance to 0x2007
sendSimpleCommand(0x01); // Command 1
sendSimpleCommand(0x07); // Command 7
sendSimpleCommand(CMD_BEGIN_PROGRAM);
delayMicroseconds(DELAY_TFULL84);
sendSimpleCommand(0x01); // Command 1
sendSimpleCommand(0x07); // Command 7
// Some FLASH devices need the data memory to be erased separately.
sendWriteCommand(CMD_LOAD_DATA_MEMORY, 0x3FFF);
sendSimpleCommand(CMD_BULK_ERASE_DATA);
sendSimpleCommand(CMD_BEGIN_PROGRAM);
break;
}
// Wait until the chip is fully erased.
delayMicroseconds(DELAY_TFULLERA);
// Force the device to reset after it has been erased.
exitProgramMode();
enterProgramMode();
// Write the reserved words back to program memory.
if (reserved) {
unsigned long addr = reservedStart;
int offset = 0;
bool ok = true;
while (addr <= reservedEnd) {
if (!writeWord(addr, reserved[offset]))
ok = false;
++addr;
++offset;
}
free(reserved);
if (!ok) {
// Reserved words did not read back correctly.
Serial.println("ERROR");
return;
}
}
// Forcibly write 0x3FFF over the configuration words as erase
// sometimes won't reset the words (e.g. PIC16F628A). If the
// write fails, then leave the words as-is - don't report the failure.
for (unsigned long configAddr = configStart + DEV_CONFIG_WORD;
configAddr <= configEnd; ++configAddr)
writeWordForced(configAddr, configWord);
// Done.
Serial.println("OK");
}
// PWROFF command.
void cmdPowerOff(const char *args)
{
exitProgramMode();
Serial.println("OK");
}
// List of all commands that are understood by the programmer.
typedef void (*commandFunc)(const char *args);
typedef struct
{
char shortkey;
const prog_char *name;
commandFunc func;
const prog_char *desc;
const prog_char *args;
} command_t;
const char s_cmdRead[] PROGMEM = "READ";
const char s_cmdReadDesc[] PROGMEM =
"Reads program and data words from device memory (text)";
const char s_cmdReadArgs[] PROGMEM = "STARTADDR[-ENDADDR]";
const char s_cmdReadBinary[] PROGMEM = "READBIN";
const char s_cmdReadBinaryDesc[] PROGMEM =
"Reads program and data words from device memory (binary)";
const char s_cmdWrite[] PROGMEM = "WRITE";
const char s_cmdWriteDesc[] PROGMEM =
"Writes program and data words to device memory (text)";
const char s_cmdWriteArgs[] PROGMEM = "STARTADDR WORD [WORD ...]";
const char s_cmdWriteBinary[] PROGMEM = "WRITEBIN";
const char s_cmdWriteBinaryDesc[] PROGMEM =
"Writes program and data words to device memory (binary)";
const char s_cmdWriteBinaryArgs[] PROGMEM = "STARTADDR";
const char s_cmdErase[] PROGMEM = "ERASE";
const char s_cmdEraseDesc[] PROGMEM =
"Erases the contents of program, configuration, and data memory";
const char s_cmdDevice[] PROGMEM = "DEVICE";
const char s_cmdDeviceDesc[] PROGMEM =
"Probes the device and returns information about it";
const char s_cmdDevices[] PROGMEM = "DEVICES";
const char s_cmdDevicesDesc[] PROGMEM =
"Returns a list of all supported device types";
const char s_cmdSetDevice[] PROGMEM = "SETDEVICE";
const char s_cmdSetDeviceDesc[] PROGMEM =
"Sets a specific device type manually";
const char s_cmdSetDeviceArgs[] PROGMEM = "DEVTYPE";
const char s_cmdPowerOff[] PROGMEM = "PWROFF";
const char s_cmdPowerOffDesc[] PROGMEM =
"Powers off the device in the programming socket";
const char s_cmdVersion[] PROGMEM = "PROGRAM_PIC_VERSION";
const char s_cmdVersionDesc[] PROGMEM =
"Prints the version of ProgramPIC";
const char s_cmdHelp[] PROGMEM = "HELP";
const char s_cmdHelpDesc[] PROGMEM =
"Prints this help message";
const command_t commands[] PROGMEM = {
{'R', s_cmdRead, cmdRead, s_cmdReadDesc, s_cmdReadArgs},
{0, s_cmdReadBinary, cmdReadBinary, s_cmdReadBinaryDesc, s_cmdReadArgs},
{'W', s_cmdWrite, cmdWrite, s_cmdWriteDesc, s_cmdWriteArgs},
{0, s_cmdWriteBinary, cmdWriteBinary, s_cmdWriteBinaryDesc, s_cmdWriteBinaryArgs},
{'E', s_cmdErase, cmdErase, s_cmdEraseDesc, 0},
{'D', s_cmdDevice, cmdDevice, s_cmdDeviceDesc, 0},
{0, s_cmdDevices, cmdDevices, s_cmdDevicesDesc, 0},
{'S', s_cmdSetDevice, cmdSetDevice, s_cmdSetDeviceDesc, s_cmdSetDeviceArgs},
{'P', s_cmdPowerOff, cmdPowerOff, s_cmdPowerOffDesc, 0},
{'V', s_cmdVersion, cmdVersion, s_cmdVersionDesc, 0},
{'H', s_cmdHelp, cmdHelp, s_cmdHelpDesc, 0},
{0, 0, 0, 0,0}
};
// "HELP" command.
void cmdHelp(const char *args)
{
Serial.println("OK");
int index = 0;
for (;;) {
char c = pgm_read_byte(&(commands[index].shortkey));
const prog_char *name = (const prog_char *)
(pgm_read_word(&(commands[index].name)));
if (!name)
break;
const prog_char *desc = (const prog_char *)
(pgm_read_word(&(commands[index].desc)));
const prog_char *args = (const prog_char *)
(pgm_read_word(&(commands[index].args)));
printProgString(name);
if (args) {
Serial.print(' ');
printProgString(args);
}
Serial.println();
Serial.print(" ");
printProgString(desc);
Serial.println();
++index;
}
Serial.println(".");
}
// Match a data-space string where the name comes from PROGMEM.
bool matchString(const prog_char *name, const char *str, int len)
{
for (;;) {
char ch1 = (char)(pgm_read_byte(name));
if (ch1 == '\0')
return len == 0;
else if (len == 0)
break;
if (ch1 >= 'a' && ch1 <= 'z')
ch1 = ch1 - 'a' + 'A';
char ch2 = *str;
if (ch2 >= 'a' && ch2 <= 'z')
ch2 = ch2 - 'a' + 'A';
if (ch1 != ch2)
break;
++name;
++str;
--len;
}
return false;
}
// Process commands from the host.
void processCommand(const char *buf)
{
// Skip white space at the start of the command.
while (*buf == ' ' || *buf == '\t')
++buf;
if (*buf == '\0')
return; // Ignore blank lines.
// Extract the command portion of the line.
const char *cmd = buf;
int len = 0;
for (;;) {
char ch = *buf;
if (ch == '\0' || ch == ' ' || ch == '\t')
break;
++buf;
++len;
}
// Skip white space after the command name and before the arguments.
while (*buf == ' ' || *buf == '\t')
++buf;
// Find the command and execute it.
int index = 0;
/*{ char Buf[128];
sprintf(Buf, "Is command '%s', key '%c', len %d", cmd, cmd[0], len);
Serial.println(Buf);
}*/
for (;;) {
char c = pgm_read_byte(&(commands[index].shortkey));
const prog_char *name = (const prog_char *)
(pgm_read_word(&(commands[index].name)));
if (!name)
break;
if (matchString(name, cmd, len)
|| (c && cmd[0] == c && (cmd[1] == ' ' || cmd[1] == 0 || len == 1))
) {
/*{ char Buf[128];
sprintf(Buf, "Found command '%s', key '%c'", cmd, c);
Serial.println(Buf);
}*/
commandFunc func =
(commandFunc)(pgm_read_word(&(commands[index].func)));
(*func)(buf);
return;
}
++index;
}
// Unknown command.
Serial.println("NOTSUPPORTED");
}
// Enter high voltage programming mode.
void enterProgramMode()
{
// Bail out if already in programming mode.
if (state != STATE_IDLE)
return;
// Lower MCLR, VDD, DATA, and CLOCK initially. This will put the
// PIC into the powered-off, reset state just in case.
digitalWrite(PIN_MCLR, MCLR_RESET);
digitalWrite(PIN_VDD, LOW);
digitalWrite(PIN_DATA, LOW);
digitalWrite(PIN_CLOCK, LOW);
// Wait for the lines to settle.
delayMicroseconds(DELAY_SETTLE);
// Switch DATA and CLOCK into outputs.
pinMode(PIN_DATA, OUTPUT);
pinMode(PIN_CLOCK, OUTPUT);
// Raise MCLR, then VDD.
digitalWrite(PIN_MCLR, MCLR_VPP);
delayMicroseconds(DELAY_TPPDP);
digitalWrite(PIN_VDD, HIGH);
delayMicroseconds(DELAY_THLD0);
// Now in program mode, starting at the first word of program memory.
state = STATE_PROGRAM;
pc = 0;
}
// Exit programming mode and reset the device.
void exitProgramMode()
{
// Nothing to do if already out of programming mode.
if (state == STATE_IDLE)
return;
// Lower MCLR, VDD, DATA, and CLOCK.
digitalWrite(PIN_MCLR, MCLR_RESET);
digitalWrite(PIN_VDD, LOW);
digitalWrite(PIN_DATA, LOW);
digitalWrite(PIN_CLOCK, LOW);
// Float the DATA and CLOCK pins.
pinMode(PIN_DATA, INPUT);
pinMode(PIN_CLOCK, INPUT);
// Now in the idle state with the PIC powered off.
state = STATE_IDLE;
pc = 0;
}
// Send a command to the PIC.
void sendCommand(byte cmd)
{
for (byte bit = 0; bit < 6; ++bit) {
digitalWrite(PIN_CLOCK, HIGH);
if (cmd & 1)
digitalWrite(PIN_DATA, HIGH);
else
digitalWrite(PIN_DATA, LOW);
delayMicroseconds(DELAY_TSET1);
digitalWrite(PIN_CLOCK, LOW);
delayMicroseconds(DELAY_THLD1);
cmd >>= 1;
}
}
// Send a command to the PIC that has no arguments.
void sendSimpleCommand(byte cmd)
{
sendCommand(cmd);
delayMicroseconds(DELAY_TDLY2);
}
// Send a command to the PIC that writes a data argument.
void sendWriteCommand(byte cmd, unsigned int data)
{
sendCommand(cmd);
delayMicroseconds(DELAY_TDLY2);
for (byte bit = 0; bit < 16; ++bit) {
digitalWrite(PIN_CLOCK, HIGH);
if (data & 1)
digitalWrite(PIN_DATA, HIGH);
else
digitalWrite(PIN_DATA, LOW);
delayMicroseconds(DELAY_TSET1);
digitalWrite(PIN_CLOCK, LOW);
delayMicroseconds(DELAY_THLD1);
data >>= 1;
}
delayMicroseconds(DELAY_TDLY2);
}
// Send a command to the PIC that reads back a data value.
unsigned int sendReadCommand(byte cmd)
{
unsigned long data = 0;
sendCommand(cmd);
digitalWrite(PIN_DATA, LOW);
pinMode(PIN_DATA, INPUT);
delayMicroseconds(DELAY_TDLY2);
for (byte bit = 0; bit < 16; ++bit) {
digitalWrite(PIN_CLOCK, HIGH);
delayMicroseconds(DELAY_TDLY3);
if (digitalRead(PIN_DATA))
data |= 1UL << bit;
digitalWrite(PIN_CLOCK, LOW);
delayMicroseconds(DELAY_THLD1);
}
/* { char Buf[128]; sprintf(Buf, "sendReadCommand(%02X->%08lX)", cmd,data);
Serial.println(Buf); }*/
/*{
char Buf[129];
for (byte bit = 0; bit < 128; ++bit) {
digitalWrite(PIN_CLOCK, HIGH);
delayMicroseconds(DELAY_TDLY3);
Buf[bit] = '0' + digitalRead(PIN_DATA);
digitalWrite(PIN_CLOCK, LOW);
delayMicroseconds(DELAY_THLD1);
}
Buf[128] = 0;
Serial.println(Buf);
}*/
pinMode(PIN_DATA, OUTPUT);
delayMicroseconds(DELAY_TDLY2);
return data;
}
// Set the program counter to a specific "flat" address.
void setPC(unsigned long addr)
{
if (addr >= dataStart && addr <= dataEnd) {
// Data memory.
addr -= dataStart;
if (state != STATE_PROGRAM || addr < pc) {
// Device is off, currently looking at configuration memory,
// or the address is further back. Reset the device.
exitProgramMode();
enterProgramMode();
}
} else if (addr >= configStart && addr <= configEnd) {
// Configuration memory.
addr -= configStart;
if (state == STATE_IDLE) {
// Enter programming mode and switch to config memory.
enterProgramMode();
sendWriteCommand(CMD_LOAD_CONFIG, 0);
state = STATE_CONFIG;
} else if (state == STATE_PROGRAM) {
// Switch from program memory to config memory.
sendWriteCommand(CMD_LOAD_CONFIG, 0);
state = STATE_CONFIG;
pc = 0;
} else if (addr < pc) {
// Need to go backwards in config memory, so reset the device.
exitProgramMode();
enterProgramMode();
sendWriteCommand(CMD_LOAD_CONFIG, 0);
state = STATE_CONFIG;
}
} else {
// Program memory.
if (state != STATE_PROGRAM || addr < pc) {
// Device is off, currently looking at configuration memory,
// or the address is further back. Reset the device.
exitProgramMode();
enterProgramMode();
}
}
while (pc < addr) {
sendSimpleCommand(CMD_INCREMENT_ADDRESS);
++pc;
}
}
// Sets the PC for "erase mode", which is activated by loading the
// data value 0x3FFF into location 0 of configuration memory.
void setErasePC()
{
// Forcibly reset the device so we know what state it is in.
exitProgramMode();
enterProgramMode();
// Load 0x3FFF for the configuration.
sendWriteCommand(CMD_LOAD_CONFIG, 0x3FFF);
state = STATE_CONFIG;
}
// Read a word from memory (program, config, or data depending upon addr).
// The start and stop bits will be stripped from the raw value from the PIC.
unsigned int readWord(unsigned long addr)
{
setPC(addr);
if (addr >= dataStart && addr <= dataEnd)
return (sendReadCommand(CMD_READ_DATA_MEMORY) >> 1) & 0x00FF;
else
return (sendReadCommand(CMD_READ_PROGRAM_MEMORY) >> 1) & 0x3FFF;
}
// Read a word from config memory using relative, non-flat, addressing.
// Used by the "DEVICE" command to fetch information about devices whose
// flat address ranges are presently unknown.
unsigned int readConfigWord(unsigned long addr)
{
if (state == STATE_IDLE) {
// Enter programming mode and switch to config memory.
enterProgramMode();
sendWriteCommand(CMD_LOAD_CONFIG, 0);
state = STATE_CONFIG;
} else if (state == STATE_PROGRAM) {
// Switch from program memory to config memory.
sendWriteCommand(CMD_LOAD_CONFIG, 0);
state = STATE_CONFIG;
pc = 0;
} else if (addr < pc) {
// Need to go backwards in config memory, so reset the device.
exitProgramMode();
enterProgramMode();
sendWriteCommand(CMD_LOAD_CONFIG, 0);
state = STATE_CONFIG;
}
while (pc < addr) {
sendSimpleCommand(CMD_INCREMENT_ADDRESS);
++pc;
}
return (sendReadCommand(CMD_READ_PROGRAM_MEMORY) >> 1) & 0x3FFF;
}
// Begin a programming cycle, depending upon the type of flash being written.
void beginProgramCycle(unsigned long addr, bool isData)
{
switch (isData ? dataFlashType : progFlashType) {
case FLASH:
case EEPROM:
sendSimpleCommand(CMD_BEGIN_PROGRAM);
delayMicroseconds(DELAY_TDPROG + DELAY_TERA);
break;
case FLASH4:
sendSimpleCommand(CMD_BEGIN_PROGRAM);
delayMicroseconds(DELAY_TPROG);
break;
case FLASH5:
sendSimpleCommand(CMD_BEGIN_PROGRAM_ONLY);
delayMicroseconds(DELAY_TPROG5);
sendSimpleCommand(CMD_END_PROGRAM_ONLY);
break;
}
}
// Write a word to memory (program, config, or data depending upon addr).
// Returns true if the write succeeded, false if read-back failed to match.
bool writeWord(unsigned long addr, unsigned int word)
{
unsigned int readBack;
setPC(addr);
if (addr >= dataStart && addr <= dataEnd) {
word &= 0x00FF;
sendWriteCommand(CMD_LOAD_DATA_MEMORY, word << 1);
beginProgramCycle(addr, true);
readBack = sendReadCommand(CMD_READ_DATA_MEMORY);
readBack = (readBack >> 1) & 0x00FF;
} else if (!configSave || addr != (configStart + DEV_CONFIG_WORD)) {
word &= 0x3FFF;
sendWriteCommand(CMD_LOAD_PROGRAM_MEMORY, word << 1);
beginProgramCycle(addr, false);
readBack = sendReadCommand(CMD_READ_PROGRAM_MEMORY);
readBack = (readBack >> 1) & 0x3FFF;
} else {
// The configuration word has calibration bits within it that
// must be preserved when we write to it. Read the current value
// and preserve the necessary bits.
readBack = (sendReadCommand(CMD_READ_PROGRAM_MEMORY) >> 1) & 0x3FFF;
word = (readBack & configSave) | (word & 0x3FFF & ~configSave);
sendWriteCommand(CMD_LOAD_PROGRAM_MEMORY, word << 1);
beginProgramCycle(addr, false);
readBack = sendReadCommand(CMD_READ_PROGRAM_MEMORY);
readBack = (readBack >> 1) & 0x3FFF;
}
return readBack == word;
}
// Force a word to be written even if it normally would protect config bits.
bool writeWordForced(unsigned long addr, unsigned int word)
{
unsigned int readBack;
setPC(addr);
if (addr >= dataStart && addr <= dataEnd) {
word &= 0x00FF;
sendWriteCommand(CMD_LOAD_DATA_MEMORY, word << 1);
beginProgramCycle(addr, true);
readBack = sendReadCommand(CMD_READ_DATA_MEMORY);
readBack = (readBack >> 1) & 0x00FF;
} else {
word &= 0x3FFF;
sendWriteCommand(CMD_LOAD_PROGRAM_MEMORY, word << 1);
beginProgramCycle(addr, false);
readBack = sendReadCommand(CMD_READ_PROGRAM_MEMORY);
readBack = (readBack >> 1) & 0x3FFF;
}
return readBack == word;
}