arduino-icsp
Arduino sketch that implements a programmer for PIC devices that use the low voltage In Circuit Serial Programming protocol.
// This sketch implents a bit-banged ICSP programmer for use with PIC micros.
// This is developed for the pic16lf1919x series so may not work for others.
// Github: https://github.com/Rex--/arduino-icsp
// Pin configuration for ICSP
#define ICSP_PIN_DAT 12
#define ICSP_PIN_CLK 11
#define ICSP_PIN_MCLR 10
// Delay configuration for ICSP
#define ICSP_DELAY_CLK 1
#define ICSP_DELAY_DLY 3
// Variables to hold command args and data
static char cmd_args[4];
static char cmd_data[128];
static bool programming_mode = false;
void command(char cmd);
void start_programming(void);
void read_words(void);
void icsp_pins_out(void);
void icsp_pins_low(void);
// Helper Macros
//#define shiftOutICSP(b) shiftOut(ICSP_PIN_DAT, ICSP_PIN_CLK, MSBFIRST, (b))
//#define shiftOutICSP(b) clockOut((b))
//#define shiftInICSP() shiftIn(ICSP_PIN_DAT, ICSP_PIN_CLK, MSBFIRST)
void setup() {
Serial.begin(115200);
Serial.setTimeout(5000); // 5sec timeout
while (!Serial) {
}
pinMode(13, OUTPUT);
digitalWrite(13, LOW);
}
void loop() {
char cmd;
// Read command byte
if (Serial.available()) {
cmd = Serial.read();
command(cmd);
}
}
void command(char cmd) {
switch (cmd) {
case 's': // Start programming mode
start_programming();
break;
case 'x': // Exit programming mode
exit_programming();
break;
case 'r': // Read words
read_words();
break;
case 'w': // Write words
write_words();
break;
case 'e': // Erase row
erase_row();
break;
case 'b': // Erase bulk
default:
// Serial.print(cmd);
Serial.print('U');
break;
}
}
// Programmer commands
void start_programming(void) {
char startup_seq[] = {'M', 'C', 'H', 'P'};
// Turn on LED
digitalWrite(13, HIGH);
// Set pins to outputs in a low state.
icsp_pins_out();
icsp_pins_low();
delay(1);
for (int i=0; i < 4; i++) { // Start with the most significant byte
// Shift out the character MSb first
clockOut(startup_seq[i]);
}
Serial.write('K');
}
void exit_programming(void) {
// Set MCLR high
// digitalWrite(ICSP_PIN_MCLR, HIGH);
// Turn LED off
digitalWrite(13, LOW);
// delay(1);
// Tristate pins
icsp_pins_in();
}
// This gets called before receiving any of the arugments
void read_words(void) {
if (Serial.readBytes(cmd_args, 4) != 4) {
// We didn't receive the correct amount of arguments in time
// Return an error
Serial.write('A');
return;
}
// Address to start reading from
int address = (cmd_args[0] << 8) | cmd_args[1];
// Number of words to read
int rLength = (cmd_args[2] << 8) | cmd_args[3];
// Load PC with address
icsp_load_pc(address);
delay(5);
// Read rLength number of words
while (rLength) {
int data = icsp_read_word(true);
Serial.write(data>>8);
Serial.write(data&0xFF);
rLength--;
}
// Delay for TDLY
}
void write_words(void) {
if (Serial.readBytes(cmd_args, 4) != 4) {
// We didn't receive the correct amount of arguments in time
// Return an error
Serial.write('A');
return;
}
// Address to start writing
int address = (cmd_args[0] << 8) | cmd_args[1];
// Number of words to write (len*2 bytes of data follows)
int wLength = (cmd_args[2] << 8) | cmd_args[3];
int wLenBytes = wLength * 2;
if (Serial.readBytes(cmd_data, wLenBytes) != wLenBytes) {
// We didn't receive the correct amount of data in time
// Return an error
Serial.write('D');
return;
}
icsp_load_pc(address);
// Erase row (required for write)
icsp_erase_row();
// Load all data latches except one
int i;
for (i=0; i < wLenBytes-2; i+=2) {
icsp_load_latch((cmd_data[i]<<8)|cmd_data[i+1], true);
// Delay for TDLY
}
// Load last data latch without incrementing PC
icsp_load_latch((cmd_data[i]<<8)|cmd_data[i+1], false);
// Begin internal timed write
icsp_begin_write();
Serial.write('K');
}
void erase_row(void) {
if (Serial.readBytes(cmd_args, 2) != 2) {
// We didn't receive the correct amount of arguments in time
// Return an error
Serial.write('A');
return;
}
int address = (cmd_args[0] << 8) | cmd_args[1];
icsp_load_pc(address);
icsp_erase_row();
}
// ICSP commands
#define ICSP_CMD_LOAD_PC 0x80
void icsp_load_pc(int addr) {
// Shift out command
clockOut(ICSP_CMD_LOAD_PC);
// Delay for TDLY
// Shift out address
clockOutData(addr);
// Delay for TDLY
}
#define ICSP_CMD_READ 0xFC
#define ICSP_CMD_READ_INC 0xFE
int icsp_read_word(bool inc) {
// Shift out command
if (inc) {
clockOut(ICSP_CMD_READ_INC);
} else {
clockOut(ICSP_CMD_READ);
}
// Set DAT pin to input
pinMode(ICSP_PIN_DAT, INPUT);
// Delay for TDLY
int ret = clockIn();
// Sack DAT pin back to output
pinMode(ICSP_PIN_DAT, OUTPUT);
return ret;
}
#define ICSP_CMD_LOAD 0
#define ICSP_CMD_LOAD_INC 2
void icsp_load_latch(int data, bool inc) {
// Shift out command
if (inc) {
clockOut(ICSP_CMD_LOAD_INC);
} else {
clockOut(ICSP_CMD_LOAD);
}
// Shift out word
clockOutData(data);
}
#define ICSP_CMD_ERASE_ROW 0xF0
void icsp_erase_row(void) {
// Shift out command
clockOut(ICSP_CMD_ERASE_ROW);
// delay for ERAR (2.8ms)
delay(3);
}
#define ICSP_CMD_WRITE 0xE0
void icsp_begin_write(void) {
// Shift out command
clockOut(ICSP_CMD_WRITE);
// Wait for TPINT: PFM 2.8ms / ConfWord 5.6ms
delay(5);
}
// Utils
void icsp_pins_out(void) {
pinMode(ICSP_PIN_DAT, OUTPUT);
pinMode(ICSP_PIN_CLK, OUTPUT);
pinMode(ICSP_PIN_MCLR, OUTPUT);
}
void icsp_pins_in(void) {
pinMode(ICSP_PIN_DAT, INPUT);
pinMode(ICSP_PIN_CLK, INPUT);
pinMode(ICSP_PIN_MCLR, INPUT);
}
void icsp_pins_low(void) {
digitalWrite(ICSP_PIN_DAT, LOW);
digitalWrite(ICSP_PIN_CLK, LOW);
digitalWrite(ICSP_PIN_MCLR, LOW);
}
#define CLK_HIGH() digitalWrite(ICSP_PIN_CLK, HIGH); delay(ICSP_DELAY_CLK)
#define CLK_LOW() digitalWrite(ICSP_PIN_CLK, LOW); delay(ICSP_DELAY_CLK)
#define CLK_CYCLE() CLK_HIGH(); CLK_LOW()
// Clocks out a byte MSb first
void clockOut(char data) {
for (int i=7; i >= 0; i--) {
// Set DAT to data bit
digitalWrite(ICSP_PIN_DAT, ((data>>i)&1));
// Cycle clock
CLK_CYCLE();
}
}
// Clock out a 16-bit payload MSb first, with 7 start/1 stop bits (0's)
void clockOutData(int data) {
// Clock out 1 start + 6 padding bits
digitalWrite(ICSP_PIN_DAT, LOW); // Use 0's as padding
for (int i=0; i < 7; i++) {
CLK_CYCLE();
}
// Clock out 16-bit data MSb first
for (int i=15; i >= 0; i--) {
// Set DAT to data bit
digitalWrite(ICSP_PIN_DAT, ((data>>i)&1));
CLK_CYCLE();
}
// Clock out stop bit
digitalWrite(ICSP_PIN_DAT, LOW);
CLK_CYCLE();
}
// Clocks in 24 bits, returns the a 16-bit payload
int clockIn(void) {
int ret = 0;
// 7 start bits
for (int i=0; i < 7; i++) {
CLK_CYCLE();
}
// 16-bit payload
for (int i=15; i >=0; i--) {
digitalWrite(ICSP_PIN_CLK, HIGH);
delay(ICSP_DELAY_CLK);
ret |= digitalRead(ICSP_PIN_DAT) << i;
digitalWrite(ICSP_PIN_CLK, LOW);
delay(ICSP_DELAY_CLK);
}
// Stop bit
CLK_CYCLE();
return ret;
}