// this sketch turns the Arduino into a AVRISP
// using the following pins:
// 10: slave reset
// 11: MOSI
// 12: MISO
// 13: SCK
// Put an LED (with resistor) on the following pins:
// 8: Error - Lights up if something goes wrong (use red if that makes sense)
// A0: Programming - In communication with the slave
// 6: Heartbeat - shows the programmer is running (removed, see notes below)
// Optional - Piezo speaker on pin A3
//
// October 2009 by David A. Mellis
// - Added support for the read signature command
//
// February 2009 by Randall Bohn
// - Added support for writing to EEPROM (what took so long?)
// Windows users should consider WinAVR's avrdude instead of the
// avrdude included with Arduino software.
//
// January 2008 by Randall Bohn
// - Thanks to Amplificar for helping me with the STK500 protocol
// - The AVRISP/STK500 (mk I) protocol is used in the arduino bootloader
// - The SPI functions herein were developed for the AVR910_ARD programmer
// - More information at http://code.google.com/p/mega-isp
//
// March 2012 - William Phelps
// modify to work with Arduino IDE 1.0 which has a shorter serial port receive buffer
// getEOP() now gets entire request before avrisp() is called to process it
// Serial.print((char) xxx) changed to Serial.write(xxx)
// uint8_t changed to byte
// added support for Piezo speaker
// moved Pmode LED to A0
// removed "heartbeat" on pin 6, added short blip of ERROR LED instead
// Why is it that PROG_FLASH and PROG_DATA don't actually do anything???
// Tested with Arduino IDE 22 and 1.0
// IDE 22 - 5148 bytes
// IDE 1.0 - 5524 bytes!
// SLOW SPEED CHIP ERASE AND FUSE BURNING
//
// Enable LOW_SPEED to allow you to erase chips that would fail otherwise,
// for being running with a clock too slow for the programmer.
//
// This allowed me to recover several ATMega328 that had no boot loader and the
// first instruction was to set the clock to the slowest speed. Usually this
// kind of recovery requires high voltage programming, but this trick will do
// just fine.
//
// How to proceed:
// 1. Enable LOW_SPEED, and load it to the programmer.
// 2. Erase and burn the fuses on the target uC. Example for ATMega328:
// arduino-1.0.1/hardware/tools/avrdude -Carduino-1.0.1/hardware/tools/avrdude.conf -patmega328p -cstk500v1 -P /dev/serial/by-id/usb-FTDI_FT232R_USB_UART_A900cf1Q-if00-port0 -b19200 -e -Ulock:w:0x3F:m -Uefuse:w:0x05:m -Uhfuse:w:0xDA:m -Ulfuse:w:0xF7:m
// 3. Comment LOW_SPEED and load it back to the programmer.
// 4. Program the target uC as usual. Example:
// arduino-1.0.1/hardware/tools/avrdude -Carduino-1.0.1/hardware/tools/avrdude.conf -patmega328p -cstk500v1 -P /dev/serial/by-id/usb-FTDI_FT232R_USB_UART_A900cf1Q-if00-port0 -b19200 -Uflash:w:firmware.hex:i
//
// Note 1: EXTRA_SPI_DELAY was added to let you slow down SPI even more. You can
// play with the value if it does not work with the default.
// Note 2: LOW_SPEED will alow you only to erase the chip and burn the fuses! It
// will fail if you try to program the target uC this way!
//#define LOW_SPEED
#ifdef LOW_SPEED
#define EXTRA_SPI_DELAY 125
#else
#define EXTRA_SPI_DELAY 0
#endif
#include "pins_arduino.h" // defines SS,MOSI,MISO,SCK
#define RESET SS
#define LED_ERR 8
#define LED_PMODE A0
//#define LED_HB 6
#define PIEZO A3
#define HWVER 2
#define SWMAJ 1
#define SWMIN 18
// STK Definitions
const byte STK_OK = 0x10;
const byte STK_FAILED = 0x11;
const byte STK_UNKNOWN = 0x12;
const byte STK_INSYNC = 0x14;
const byte STK_NOSYNC = 0x15;
const byte CRC_EOP = 0x20; //ok it is a space...
const byte STK_GET_SYNC = 0x30;
const byte STK_GET_SIGNON = 0x31;
const byte STK_GET_PARM = 0x41;
const byte STK_SET_PARM = 0x42;
const byte STK_SET_PARM_EXT = 0x45;
const byte STK_PMODE_START = 0x50;
const byte STK_PMODE_END = 0x51;
const byte STK_SET_ADDR = 0x55;
const byte STK_UNIVERSAL = 0x56;
const byte STK_PROG_FLASH = 0x60;
const byte STK_PROG_DATA = 0x61;
const byte STK_PROG_PAGE = 0x64;
const byte STK_READ_PAGE = 0x74;
const byte STK_READ_SIGN = 0x75;
//// TONES ==========================================
//// Start by defining the relationship between
//// note, period, & frequency.
//#define c 3830 // 261 Hz
//#define d 3400 // 294 Hz
//#define e 3038 // 329 Hz
//#define f 2864 // 349 Hz
//#define g 2550 // 392 Hz
//#define a 2272 // 440 Hz
//#define b 2028 // 493 Hz
//#define C 1912 // 523 Hz
//void pulse(int pin, int times);
int error=0;
int pmode=0;
// address for reading and writing, set by STK_SET_ADDR command
int _addr;
byte _buffer[256]; // serial port buffer
int pBuffer = 0; // buffer pointer
int iBuffer = 0; // buffer index
byte buff[256]; // temporary buffer
boolean EOP_SEEN = false;
void setup() {
Serial.begin(19200);
pinMode(PIEZO, OUTPUT);
beep(1700, 40);
EOP_SEEN = false;
iBuffer = pBuffer = 0;
pinMode(LED_PMODE, OUTPUT);
pulse(LED_PMODE, 2);
pinMode(LED_ERR, OUTPUT);
pulse(LED_ERR, 2);
// pinMode(LED_HB, OUTPUT);
// pulse(LED_HB, 2);
pinMode(9, OUTPUT);
// setup high freq PWM on pin 9 (timer 1)
// 50% duty cycle -> 8 MHz
OCR1A = 0;
ICR1 = 1;
// OC1A output, fast PWM
TCCR1A = _BV(WGM11) | _BV(COM1A1);
TCCR1B = _BV(WGM13) | _BV(WGM12) | _BV(CS10); // no clock prescale
}
#define beget16(addr) (*addr * 256 + *(addr+1) )
typedef struct param {
byte devicecode;
byte revision;
byte progtype;
byte parmode;
byte polling;
byte selftimed;
byte lockbytes;
byte fusebytes;
int flashpoll;
int eeprompoll;
int pagesize;
int eepromsize;
int flashsize;
}
parameter;
parameter param;
// this provides a heartbeat on pin 6, so you can tell the software is running.
//byte hbval=128;
//int8_t hbdelta=4;
//void heartbeat() {
//// if (hbval > 192) hbdelta = -hbdelta;
//// if (hbval < 32) hbdelta = -hbdelta;
// if (hbval > 250) hbdelta = -hbdelta;
// if (hbval < 10) hbdelta = -hbdelta;
// hbval += hbdelta;
// analogWrite(LED_HB, hbval);
// delay(20);
//}
void getEOP() {
int minL = 0;
byte avrch = 0;
byte bl = 0;
while (!EOP_SEEN) {
while (Serial.available()>0) {
byte ch = Serial.read();
_buffer[iBuffer] = ch;
iBuffer = (++iBuffer)%256; // increment and wrap
if (iBuffer == 1) avrch = ch; // save command
if ((avrch == STK_PROG_PAGE) && (iBuffer==3)) {
minL = 256*_buffer[1] + _buffer[2] + 4;
}
if ((iBuffer>minL) && (ch == CRC_EOP)) {
EOP_SEEN = true;
}
}
if (!EOP_SEEN) {
// heartbeat(); // light the heartbeat LED
if (bl == 100) {
pulse(LED_ERR,1,10); // blink the red LED
bl = 0;
}
bl++;
delay(10);
}
}
}
// serialEvent not used so sketch would be compatible with older IDE versions
//void serialEvent() {
// int minL = 0;
// byte avrch = 0;
// while (Serial.available()>0)
// {
// byte ch = Serial.read();
// _buffer[iBuffer] = ch;
// iBuffer = (++iBuffer)%256; // increment and wrap
// if (iBuffer == 1) avrch = ch; // save command
// if ((avrch == STK_PROG_PAGE) && (iBuffer==3)) {
// minL = 256*_buffer[1] + _buffer[2] + 4;
// }
// if ((iBuffer>minL) && (ch == CRC_EOP)) {
// EOP_SEEN = true;
// }
// }
//}
void loop(void) {
// is pmode active?
// if (pmode) digitalWrite(LED_PMODE, HIGH);
// else digitalWrite(LED_PMODE, LOW);
digitalWrite(LED_PMODE, LOW);
// is there an error?
if (error) digitalWrite(LED_ERR, HIGH);
else digitalWrite(LED_ERR, LOW);
getEOP();
// have we received a complete request? (ends with CRC_EOP)
if (EOP_SEEN) {
digitalWrite(LED_PMODE, HIGH);
EOP_SEEN = false;
avrisp();
iBuffer = pBuffer = 0; // restart buffer
}
}
byte getch() {
if (pBuffer == iBuffer) { // spin until data available ???
pulse(LED_ERR, 1);
beep(1700, 20);
error++;
return -1;
}
byte ch = _buffer[pBuffer]; // get next char
pBuffer = (++pBuffer)%256; // increment and wrap
return ch;
}
void readbytes(int n) {
for (int x = 0; x < n; x++) {
buff[x] = getch();
}
}
//#define PTIME 20
void pulse(int pin, int times, int ptime) {
do {
digitalWrite(pin, HIGH);
delay(ptime);
digitalWrite(pin, LOW);
delay(ptime);
times--;
}
while (times > 0);
}
void pulse(int pin, int times) {
pulse(pin, times, 50);
}
void spi_init() {
byte x;
SPCR = 0x53;
#ifdef LOW_SPEED
SPCR=SPCR|B00000011;
#endif
x=SPSR;
x=SPDR;
}
void spi_wait() {
do {
}
while (!(SPSR & (1 << SPIF)));
}
byte spi_send(byte b) {
byte reply;
#ifdef LOW_SPEED
cli();
CLKPR=B10000000;
CLKPR=B00000011;
sei();
#endif
SPDR=b;
spi_wait();
reply = SPDR;
#ifdef LOW_SPEED
cli();
CLKPR=B10000000;
CLKPR=B00000000;
sei();
#endif
return reply;
}
byte spi_transaction(byte a, byte b, byte c, byte d) {
byte n;
spi_send(a);
n=spi_send(b);
//if (n != a) error = -1;
n=spi_send(c);
return spi_send(d);
}
void replyOK() {
// if (EOP_SEEN == true) {
if (CRC_EOP == getch()) { // EOP should be next char
Serial.write(STK_INSYNC);
Serial.write(STK_OK);
}
else {
pulse(LED_ERR, 2);
Serial.write(STK_NOSYNC);
error++;
}
}
void breply(byte b) {
if (CRC_EOP == getch()) { // EOP should be next char
Serial.write(STK_INSYNC);
Serial.write(b);
Serial.write(STK_OK);
}
else {
Serial.write(STK_NOSYNC);
error++;
}
}
void get_parameter(byte c) {
switch(c) {
case 0x80:
breply(HWVER);
break;
case 0x81:
breply(SWMAJ);
break;
case 0x82:
breply(SWMIN);
break;
case 0x93:
breply('S'); // serial programmer
break;
default:
breply(0);
}
}
void set_parameters() {
// call this after reading paramter packet into buff[]
param.devicecode = buff[0];
param.revision = buff[1];
param.progtype = buff[2];
param.parmode = buff[3];
param.polling = buff[4];
param.selftimed = buff[5];
param.lockbytes = buff[6];
param.fusebytes = buff[7];
param.flashpoll = buff[8];
// ignore buff[9] (= buff[8])
//getch(); // discard second value
// WARNING: not sure about the byte order of the following
// following are 16 bits (big endian)
param.eeprompoll = beget16(&buff[10]);
param.pagesize = beget16(&buff[12]);
param.eepromsize = beget16(&buff[14]);
// 32 bits flashsize (big endian)
param.flashsize = buff[16] * 0x01000000
+ buff[17] * 0x00010000
+ buff[18] * 0x00000100
+ buff[19];
}
void start_pmode() {
spi_init();
// following delays may not work on all targets...
pinMode(RESET, OUTPUT);
digitalWrite(RESET, HIGH);
pinMode(SCK, OUTPUT);
digitalWrite(SCK, LOW);
delay(50+EXTRA_SPI_DELAY);
digitalWrite(RESET, LOW);
delay(50+EXTRA_SPI_DELAY);
pinMode(MISO, INPUT);
pinMode(MOSI, OUTPUT);
spi_transaction(0xAC, 0x53, 0x00, 0x00);
pmode = 1;
}
void end_pmode() {
pinMode(MISO, INPUT);
pinMode(MOSI, INPUT);
pinMode(SCK, INPUT);
pinMode(RESET, INPUT);
pmode = 0;
}
void universal() {
// int w;
byte ch;
// for (w = 0; w < 4; w++) {
// buff[w] = getch();
// }
readbytes(4);
ch = spi_transaction(buff[0], buff[1], buff[2], buff[3]);
breply(ch);
}
void flash(byte hilo, int addr, byte data) {
spi_transaction(0x40+8*hilo, addr>>8 & 0xFF, addr & 0xFF, data);
}
void commit(int addr) {
spi_transaction(0x4C, (addr >> 8) & 0xFF, addr & 0xFF, 0);
}
//#define _current_page(x) (here & 0xFFFFE0)
int current_page(int addr) {
if (param.pagesize == 32) return addr & 0xFFFFFFF0;
if (param.pagesize == 64) return addr & 0xFFFFFFE0;
if (param.pagesize == 128) return addr & 0xFFFFFFC0;
if (param.pagesize == 256) return addr & 0xFFFFFF80;
return addr;
}
byte write_flash(int length) {
if (param.pagesize < 1) {
return STK_FAILED;
}
//if (param.pagesize != 64) return STK_FAILED;
int page = current_page(_addr);
int x = 0;
while (x < length) {
if (page != current_page(_addr)) {
commit(page);
page = current_page(_addr);
}
flash(LOW, _addr, buff[x++]);
flash(HIGH, _addr, buff[x++]);
_addr++;
}
commit(page);
return STK_OK;
}
byte write_eeprom(int length) {
// here is a word address, so we use here*2
// this writes byte-by-byte,
// page writing may be faster (4 bytes at a time)
for (int x = 0; x < length; x++) {
spi_transaction(0xC0, 0x00, _addr*2+x, buff[x]);
delay(45);
}
return STK_OK;
}
void program_page() {
byte result = STK_FAILED;
int length = 256 * getch() + getch();
if (length > 256) {
Serial.write(STK_FAILED);
error++;
return;
}
char memtype = (char)getch();
// for (int x = 0; x < length; x++) {
// buff[x] = getch();
// }
readbytes(length);
if (CRC_EOP == getch()) {
Serial.write(STK_INSYNC);
switch (memtype) {
case 'E':
result = (byte)write_eeprom(length);
break;
case 'F':
result = (byte)write_flash(length);
break;
}
Serial.write(result);
if (result != STK_OK) {
error++;
}
}
else {
Serial.write(STK_NOSYNC);
error++;
}
}
byte flash_read(byte hilo, int addr) {
return spi_transaction(0x20 + hilo * 8,
(addr >> 8) & 0xFF,
addr & 0xFF,
0);
}
char flash_read_page(int length) {
for (int x = 0; x < length; x+=2) {
byte low = flash_read(LOW, _addr);
Serial.write( low);
byte high = flash_read(HIGH, _addr);
Serial.write( high);
_addr++;
}
return STK_OK;
}
char eeprom_read_page(int length) {
// here again we have a word address
for (int x = 0; x < length; x++) {
byte ee = spi_transaction(0xA0, 0x00, _addr*2+x, 0xFF);
Serial.write( ee);
}
return STK_OK;
}
void read_page() {
byte result = (byte)STK_FAILED;
int length = 256 * getch() + getch();
char memtype = getch();
if (CRC_EOP != getch()) {
Serial.write(STK_NOSYNC);
return;
}
Serial.write(STK_INSYNC);
if (memtype == 'F') result = flash_read_page(length);
if (memtype == 'E') result = eeprom_read_page(length);
Serial.write(result);
return;
}
void read_signature() {
if (CRC_EOP != getch()) {
Serial.write(STK_NOSYNC);
error++;
return;
}
Serial.write(STK_INSYNC);
byte high = spi_transaction(0x30, 0x00, 0x00, 0x00);
Serial.write(high);
byte middle = spi_transaction(0x30, 0x00, 0x01, 0x00);
Serial.write(middle);
byte low = spi_transaction(0x30, 0x00, 0x02, 0x00);
Serial.write(low);
Serial.write(STK_OK);
}
//////////////////////////////////////////
//////////////////////////////////////////
////////////////////////////////////
////////////////////////////////////
int avrisp() {
byte data, low, high;
byte avrch = getch();
switch (avrch) {
case STK_GET_SYNC: // get in sync
replyOK();
break;
case STK_GET_SIGNON: // get sign on
if (getch() == CRC_EOP) {
Serial.write(STK_INSYNC);
Serial.write("AVR ISP");
Serial.write(STK_OK);
}
break;
case STK_GET_PARM: // 0x41
get_parameter(getch());
break;
case STK_SET_PARM: // 0x42
readbytes(20);
set_parameters();
replyOK();
break;
case STK_SET_PARM_EXT: // extended parameters - ignore for now
readbytes(5);
replyOK();
break;
case STK_PMODE_START: // 0x50
beep(2272, 20);
start_pmode();
replyOK();
break;
case STK_PMODE_END: //0x51
beep(1912, 50);
error=0;
end_pmode();
replyOK();
break;
case STK_SET_ADDR: // 0x55
_addr = getch() + 256 * getch();
replyOK();
break;
case STK_UNIVERSAL: //UNIVERSAL 0x56
universal();
break;
case STK_PROG_FLASH: //STK_PROG_FLASH ???
low = getch();
high = getch();
replyOK();
break;
case STK_PROG_DATA: //STK_PROG_DATA ???
data = getch();
replyOK();
break;
case STK_PROG_PAGE: //STK_PROG_PAGE
// beep(1912, 20);
program_page();
break;
case STK_READ_PAGE: //STK_READ_PAGE
read_page();
break;
case STK_READ_SIGN: //STK_READ_SIGN
read_signature();
break;
// expecting a command, not CRC_EOP
// this is how we can get back in sync
case CRC_EOP:
Serial.write(STK_NOSYNC);
break;
// anything else we will return STK_UNKNOWN
default:
if (CRC_EOP == getch())
Serial.write(STK_UNKNOWN);
else
Serial.write(STK_NOSYNC);
}
}
// beep without using PWM
void beep(int tone, long duration){
long elapsed = 0;
while (elapsed < (duration * 10000)) {
digitalWrite(PIEZO, HIGH);
delayMicroseconds(tone / 2);
digitalWrite(PIEZO, LOW);
delayMicroseconds(tone / 2);
// Keep track of how long we pulsed
elapsed += tone;
}
}