// this sketch turns the Arduino into a AVRISP // using the following pins: // 10: target reset // 11: MOSI // 12: MISO // 13: SCK // Put an LED (with resistor) on the following pins: // 6: Heartbeat - shows the programmer is running // 8: Error - Lights up if something goes wrong (use red if that makes sense) // 5: Programming - In communication with the slave // 7: Configured for output at LOW // The above arrangement makes it easy to attach a common-cathode RGB LED directly // into the arduino pin header. // // April 2012 by Joey Morin // - Integrated code from http://pastebin.com/Aw5BD0zy probably authored by // smeezekitty, referenced in http://arduino.cc/forum/index.php?topic=89918.0 // to include support for slow software SPI. This allows for programming of // targets with clk < 500KHz, which is the lower limit for hardware SPI on a // 16MHz Arduino (SPI clock of 125KHz). // - Minor code cleanup. // - Mode selection is made on powerup or reset. Switch or short the // MODE_BUTTON pin to GND to enable slow software SPI mode. Current mode is // indicated by a fast or slow heartbeat. // // 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 // These set the correct timing delays for programming a target with clk < 500KHz. // Be sure to specify the lowest clock frequency of any target you plan to burn. // The rule is the programing clock (on SCK) must be less than 1/4 the clock // speed of the target (or 1/6 for speeds above 12MHz). For example, if using // the 128KHz RC oscillator, with a prescaler of 8, the target's clock frequency // would be 16KHz, and the maximum programming clock would be 4KHz, or a clock // period of 250uS. The algorithm uses a quarter of the clock period for sync // purposes, so quarter_period would be set to 63uS. Be aware that internal RC // oscillators can be off by as much as 10%, so you might have to force a slower // clock speed. #define DEFAULT_MINIMUM_TARGET_CLOCK_SPEED 128000 #define DEFAULT_SCK_FREQUENCY (DEFAULT_MINIMUM_TARGET_CLOCK_SPEED/4) #define DEFAULT_QUARTER_PERIOD ((1000000/DEFAULT_SCK_FREQUENCY/4)+1) #define MINIMUM_SCK_FREQUENCY 50 #define QUARTER_PERIOD_EEPROM_ADDRESS 0 #define HWVER 2 #define SWMAJ 1 #define SWMIN 18 #define LED_HB 6 #define LED_ERR 8 #define LED_PMODE 5 #define LED_COMMON 7 #define MODE_BUTTON 9 #define MAX_COMMAND_LENGTH 64 // As per http://arduino.cc/en/Tutorial/ArduinoISP #if ARDUINO >= 100 #define HB_DELAY 20 #else #define HB_DELAY 40 #endif #include #define RESET SS // STK Definitions #define STK_OK 0x10 #define STK_FAILED 0x11 #define STK_UNKNOWN 0x12 #define STK_INSYNC 0x14 #define STK_NOSYNC 0x15 #define CRC_EOP 0x20 //ok it is a space... #define PTIME 30 #define HW_SPI_MODE 1 #define SW_SPI_MODE 0 uint8_t spi_mode; // safe EEPROM write (only writes if value changed) void safe_EEPROM_write(int address, byte value) { if (value != EEPROM.read(address)) { EEPROM.write(address, value); } } // save a 16 bit value to eeprom void save_quarter_period_to_eeprom(unsigned int value) { safe_EEPROM_write(QUARTER_PERIOD_EEPROM_ADDRESS, (uint8_t)(value & 0x00FF)); safe_EEPROM_write(QUARTER_PERIOD_EEPROM_ADDRESS+1, (uint8_t)((value & 0xFF00) >> 8)); } unsigned int load_quarter_period_from_eeprom() { unsigned int value; value = EEPROM.read(QUARTER_PERIOD_EEPROM_ADDRESS) + (EEPROM.read(QUARTER_PERIOD_EEPROM_ADDRESS+1) << 8); if ((value == 0) || (value > 1000000/MINIMUM_SCK_FREQUENCY/4)) value = DEFAULT_QUARTER_PERIOD; return value; } // configure programming speed unsigned int quarter_period = load_quarter_period_from_eeprom(); // set programming clock from a terminal void change_sck() { char command[MAX_COMMAND_LENGTH]; uint8_t command_length = 0; int key; uint32_t sck_frequency; Serial.print("Current software SPI quarter-period delay: "); Serial.print(quarter_period); Serial.print(" microseconds,\n\r"); Serial.print("Resulting in a programming speed of "); Serial.print(1000000/quarter_period/4); Serial.print(" Hz.\n\r"); Serial.print("Enter a new programming clock speed (SCK) in HZ for software SPI.\n\r"); Serial.print("(NOTE THAT THIS SHOULD BE LESS THAN 1/4 OF TARGET'S CLOCK SPEED):\n\r"); while(true) { heartbeat(); key = Serial.read(); if (key != -1) { Serial.write(key); if (key == '\r') { Serial.println(); command[command_length] = 0; sck_frequency = atoi(command); if (sck_frequency < 50) { Serial.print("\n\rERROR: MINIMUM FREQUENCY IS 50 HZ, TRY AGAIN.\n\r"); } else { quarter_period = (1000000/sck_frequency/4)+1; Serial.print("Setting software SPI quarter-period delay to "); Serial.print(quarter_period); Serial.print(" microseconds,\n\r"); Serial.print("Resulting in a programming speed of "); Serial.print(1000000/quarter_period/4); Serial.print(" Hz.\n\r"); save_quarter_period_to_eeprom(quarter_period); Serial.print("Saved to EEPROM.\n\r"); return; } } else if ((key == 8) || (key == 127)) { if (command_length) { command[command_length] = 0; command[--command_length] = ' '; Serial.print("\r"); Serial.write(command); command[command_length] = 0; Serial.print("\r"); Serial.write(command); } } else { command[command_length++] = key; } if (command_length >= MAX_COMMAND_LENGTH-1) { Serial.print("\n\rERROR: COMMAND LINE TOO LONG, START OVER.\n\r"); command_length = 0; } } } } uint8_t hbval=12; int8_t hbdelta; // this provides a heartbeat on pin 9, so you can tell the software is running. void heartbeat() { if ((hbval > 24) || (hbval < 2)) hbdelta = -hbdelta; hbval += hbdelta; analogWrite(LED_HB, hbval); delay(HB_DELAY); } void pulse(int pin, int times) { do { digitalWrite(pin, HIGH); delay(PTIME); digitalWrite(pin, LOW); delay(PTIME); } while (times--); } void (*spi_init)(); uint8_t (*spi_send)(uint8_t); void setup() { Serial.begin(19200); pinMode(MODE_BUTTON, INPUT); digitalWrite(MODE_BUTTON, HIGH); pinMode(LED_PMODE, OUTPUT); pinMode(LED_ERR, OUTPUT); pinMode(LED_HB, OUTPUT); pinMode(LED_COMMON, OUTPUT); digitalWrite(LED_COMMON, LOW); pulse(LED_PMODE, 2); pulse(LED_ERR, 2); pulse(LED_HB, 2); pinMode(MODE_BUTTON, INPUT); digitalWrite(MODE_BUTTON, HIGH); if (digitalRead(MODE_BUTTON)){ spi_mode = HW_SPI_MODE; spi_init = hw_spi_init; spi_send = hw_spi_send; hbdelta = 2; } else { spi_mode = SW_SPI_MODE; spi_init = sw_spi_init; spi_send = sw_spi_send; hbdelta = 1; } } int error=0; int pmode=0; // address for reading and writing, set by 'U' command int here; uint8_t buff[256]; // global block storage #define beget16(addr) (*addr * 256 + *(addr+1) ) typedef struct param { uint8_t devicecode; uint8_t revision; uint8_t progtype; uint8_t parmode; uint8_t polling; uint8_t selftimed; uint8_t lockbytes; uint8_t fusebytes; int flashpoll; int eeprompoll; int pagesize; int eepromsize; int flashsize; } parameter; parameter param; #define LED_PWM_DUTY 10 void loop(void) { static uint8_t pwm; // is pmode active? if (pmode) analogWrite(LED_PMODE, (pwm++)%((LED_PWM_DUTY<<1))<<2); else digitalWrite(LED_PMODE, LOW); // is there an error? if (error) digitalWrite(LED_ERR, !((pwm++)%LED_PWM_DUTY)); else digitalWrite(LED_ERR, LOW); // light the heartbeat LED heartbeat(); if (Serial.available()) { avrisp(); } } uint8_t getch() { while(!Serial.available()); return Serial.read(); } void readbytes(int n) { for (int x = 0; x < n; x++) { buff[x] = Serial.read(); } } void hw_spi_init() { uint8_t x; SPCR = 0x53; x=SPSR; x=SPDR; } void sw_spi_init() { } void spi_wait() { do { } while (!(SPSR & (1 << SPIF))); } uint8_t hw_spi_send(uint8_t b) { uint8_t reply; SPDR=b; spi_wait(); reply = SPDR; return reply; } #define PCK() (bits[0] << 7 | bits[1] << 6 | bits[2] << 5 | bits[3] << 4 | bits[4] << 3 | bits[5] << 2 | bits[6] << 1 | bits[7]) uint8_t sw_spi_send(uint8_t b) { unsigned static char msk[] = { 0x80, 0x40, 0x20, 0x10, 0x8, 0x4, 0x2, 0x1 }; uint8_t reply; char bits[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; for(uint8_t _bit = 0;_bit < 8;_bit++){ digitalWrite(MOSI, !!(b & msk[_bit])); delayMicroseconds(quarter_period); digitalWrite(SCK, HIGH); delayMicroseconds(quarter_period); bits[_bit] = digitalRead(MISO); delayMicroseconds(quarter_period); digitalWrite(SCK, LOW); delayMicroseconds(quarter_period); } reply = PCK(); return reply; } uint8_t spi_transaction(uint8_t a, uint8_t b, uint8_t c, uint8_t d) { uint8_t n; spi_send(a); n=spi_send(b); //if (n != a) error = -1; n=spi_send(c); return spi_send(d); } void empty_reply() { if (CRC_EOP == getch()) { Serial.print((char)STK_INSYNC); Serial.print((char)STK_OK); } else { Serial.print((char)STK_NOSYNC); } } void breply(uint8_t b) { if (CRC_EOP == getch()) { Serial.print((char)STK_INSYNC); Serial.print((char)b); Serial.print((char)STK_OK); } else { Serial.print((char)STK_NOSYNC); } } void get_version(uint8_t 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); digitalWrite(RESET, LOW); delay(50); 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; uint8_t ch; for (w = 0; w < 4; w++) { buff[w] = getch(); } ch = spi_transaction(buff[0], buff[1], buff[2], buff[3]); breply(ch); } void flash(uint8_t hilo, int addr, uint8_t 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 here & 0xFFFFFFF0; if (param.pagesize == 64) return here & 0xFFFFFFE0; if (param.pagesize == 128) return here & 0xFFFFFFC0; if (param.pagesize == 256) return here & 0xFFFFFF80; return here; } uint8_t write_flash(int length) { if (param.pagesize < 1) return STK_FAILED; //if (param.pagesize != 64) return STK_FAILED; int page = current_page(here); int x = 0; while (x < length) { if (page != current_page(here)) { commit(page); page = current_page(here); } flash(LOW, here, buff[x++]); flash(HIGH, here, buff[x++]); here++; } commit(page); return STK_OK; } uint8_t 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, here*2+x, buff[x]); delay(45); } return STK_OK; } void program_page() { char result = (char) STK_FAILED; int length = 256 * getch() + getch(); if (length > 256) { Serial.print((char) STK_FAILED); return; } char memtype = getch(); for (int x = 0; x < length; x++) { buff[x] = getch(); } if (CRC_EOP == getch()) { Serial.print((char) STK_INSYNC); if (memtype == 'F') result = (char)write_flash(length); if (memtype == 'E') result = (char)write_eeprom(length); Serial.print(result); } else { Serial.print((char) STK_NOSYNC); } } uint8_t flash_read(uint8_t 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) { uint8_t low = flash_read(LOW, here); Serial.print((char) low); uint8_t high = flash_read(HIGH, here); Serial.print((char) high); here++; } return STK_OK; } char eeprom_read_page(int length) { // here again we have a word address for (int x = 0; x < length; x++) { uint8_t ee = spi_transaction(0xA0, 0x00, here*2+x, 0xFF); Serial.print((char) ee); } return STK_OK; } void read_page() { char result = (char)STK_FAILED; int length = 256 * getch() + getch(); char memtype = getch(); if (CRC_EOP != getch()) { Serial.print((char) STK_NOSYNC); return; } Serial.print((char) STK_INSYNC); if (memtype == 'F') result = flash_read_page(length); if (memtype == 'E') result = eeprom_read_page(length); Serial.print(result); return; } void read_signature() { if (CRC_EOP != getch()) { Serial.print((char) STK_NOSYNC); return; } Serial.print((char) STK_INSYNC); uint8_t high = spi_transaction(0x30, 0x00, 0x00, 0x00); Serial.print((char) high); uint8_t middle = spi_transaction(0x30, 0x00, 0x01, 0x00); Serial.print((char) middle); uint8_t low = spi_transaction(0x30, 0x00, 0x02, 0x00); Serial.print((char) low); Serial.print((char) STK_OK); } ////////////////////////////////////////// ////////////////////////////////////////// //////////////////////////////////// //////////////////////////////////// int avrisp() { uint8_t data, low, high; uint8_t ch = getch(); uint8_t static cr = 0; // support for changing SCK speed via serial interface // issuing carriage return 5 times in a row enters SCK change mode if (spi_mode == SW_SPI_MODE) { if (ch == '\r') { cr++; if (cr >= 5) { cr = 0; change_sck(); } return 0; } else { cr = 0; } } switch (ch) { case '0': // signon empty_reply(); break; case '1': if (getch() == CRC_EOP) { Serial.print((char) STK_INSYNC); Serial.print("AVR ISP"); Serial.print((char) STK_OK); } break; case 'A': get_version(getch()); break; case 'B': readbytes(20); set_parameters(); empty_reply(); break; case 'E': // extended parameters - ignore for now readbytes(5); empty_reply(); break; case 'P': start_pmode(); empty_reply(); break; case 'U': here = getch() + 256 * getch(); empty_reply(); break; case 0x60: //STK_PROG_FLASH low = getch(); high = getch(); empty_reply(); break; case 0x61: //STK_PROG_DATA data = getch(); empty_reply(); break; case 0x64: //STK_PROG_PAGE program_page(); break; case 0x74: //STK_READ_PAGE read_page(); break; case 'V': universal(); break; case 'Q': error=0; end_pmode(); empty_reply(); break; case 0x75: //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.print((char) STK_NOSYNC); break; // anything else we will return STK_UNKNOWN default: if (CRC_EOP == getch()) Serial.print((char)STK_UNKNOWN); else Serial.print((char)STK_NOSYNC); } }