|
@@ -0,0 +1,411 @@
|
|
|
+/* simple USBasp compatible bootloader
|
|
|
+ * by Alexander Neumann <alexander@lochraster.org>
|
|
|
+ *
|
|
|
+ * inspired by USBasploader by Christian Starkjohann,
|
|
|
+ * see http://www.obdev.at/products/avrusb/usbasploader.html
|
|
|
+ *
|
|
|
+ * This program is free software; you can redistribute it and/or modify
|
|
|
+ * it under the terms of the GNU General Public License version 2 as
|
|
|
+ * published by the Free Software Foundation.
|
|
|
+ *
|
|
|
+ * 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, write to the Free Software
|
|
|
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
|
+ *
|
|
|
+ * For more information on the GPL, please go to:
|
|
|
+ * http://www.gnu.org/copyleft/gpl.html
|
|
|
+ */
|
|
|
+
|
|
|
+#include <avr/io.h>
|
|
|
+#include <avr/interrupt.h>
|
|
|
+#include <avr/pgmspace.h>
|
|
|
+#include <avr/boot.h>
|
|
|
+#include <avr/eeprom.h>
|
|
|
+#include <util/delay.h>
|
|
|
+#include <string.h>
|
|
|
+#include <avr/wdt.h>
|
|
|
+
|
|
|
+#include "config.h"
|
|
|
+#include "usbdrv/usbdrv.c"
|
|
|
+
|
|
|
+/* USBasp requests, taken from the original USBasp sourcecode */
|
|
|
+#define USBASP_FUNC_CONNECT 1
|
|
|
+#define USBASP_FUNC_DISCONNECT 2
|
|
|
+#define USBASP_FUNC_TRANSMIT 3
|
|
|
+#define USBASP_FUNC_READFLASH 4
|
|
|
+#define USBASP_FUNC_ENABLEPROG 5
|
|
|
+#define USBASP_FUNC_WRITEFLASH 6
|
|
|
+#define USBASP_FUNC_READEEPROM 7
|
|
|
+#define USBASP_FUNC_WRITEEEPROM 8
|
|
|
+#define USBASP_FUNC_SETLONGADDRESS 9
|
|
|
+
|
|
|
+/* additional functions */
|
|
|
+#define FUNC_ECHO 0x17
|
|
|
+
|
|
|
+/* atmel isp commands */
|
|
|
+#define ISP_CHIP_ERASE1 0xAC
|
|
|
+#define ISP_CHIP_ERASE2 0x80
|
|
|
+#define ISP_READ_SIGNATURE 0x30
|
|
|
+#define ISP_READ_EEPROM 0xa0
|
|
|
+#define ISP_WRITE_EEPROM 0xc0
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+#define LED_PORT PORTC
|
|
|
+#define LED_DIR DDRC
|
|
|
+#define LED_PIN PC7
|
|
|
+
|
|
|
+#define DLED_ON {((LED_PORT &=~ (1 << LED_PIN)),\
|
|
|
+ (LED_DIR &=~ (1 << LED_PIN))); }
|
|
|
+#define DLED_OFF {((LED_PORT &=~ (1 << LED_PIN)),\
|
|
|
+ (LED_DIR |= (1 << LED_PIN))); }
|
|
|
+#define DLED_TGL {((LED_PORT ^= (1 << LED_PIN)),\
|
|
|
+ (LED_DIR |= (1 << LED_PIN)));}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+/* some predefined signatures, taken from the original USBasp sourcecode */
|
|
|
+static const uint8_t signature[4] = {
|
|
|
+#ifdef SIGNATURE_BYTES
|
|
|
+ SIGNATURE_BYTES
|
|
|
+#elif defined (__AVR_ATmega8__) || defined (__AVR_ATmega8HVA__)
|
|
|
+ 0x1e, 0x93, 0x07, 0
|
|
|
+#elif defined (__AVR_ATmega48__) || defined (__AVR_ATmega48P__)
|
|
|
+ 0x1e, 0x92, 0x05, 0
|
|
|
+#elif defined (__AVR_ATmega88__) || defined (__AVR_ATmega88P__)
|
|
|
+ 0x1e, 0x93, 0x0a, 0
|
|
|
+#elif defined (__AVR_ATmega168__) || defined (__AVR_ATmega168P__)
|
|
|
+ 0x1e, 0x94, 0x06, 0
|
|
|
+#elif defined (__AVR_ATmega328P__)
|
|
|
+ 0x1e, 0x95, 0x0f, 0
|
|
|
+#elif defined (__AVR_ATmega644__)
|
|
|
+ 0x1e, 0x96, 0x06, 0
|
|
|
+#else
|
|
|
+# error "Device signature is not known, please edit config.h!"
|
|
|
+#endif
|
|
|
+};
|
|
|
+
|
|
|
+#ifndef BOOT_SECTION_START
|
|
|
+# error "BOOT_SECTION_START undefined!"
|
|
|
+#endif
|
|
|
+
|
|
|
+
|
|
|
+#ifdef DEBUG_UART
|
|
|
+static __attribute__ (( __noinline__ )) void putc(uint8_t data) {
|
|
|
+ while(!(UCSR0A & _BV(UDRE0)));
|
|
|
+ UDR0 = data;
|
|
|
+}
|
|
|
+#else
|
|
|
+#define putc(x)
|
|
|
+#endif
|
|
|
+
|
|
|
+/* supply custom usbDeviceConnect() and usbDeviceDisconnect() macros
|
|
|
+ * which turn the interrupt on and off at the right times,
|
|
|
+ * and prevent the execution of an interrupt while the pullup resistor
|
|
|
+ * is switched off */
|
|
|
+/*
|
|
|
+#ifdef USB_CFG_PULLUP_IOPORTNAME
|
|
|
+#undef usbDeviceConnect
|
|
|
+#define usbDeviceConnect() do { \
|
|
|
+ USB_PULLUP_DDR |= (1<<USB_CFG_PULLUP_BIT); \
|
|
|
+ USB_PULLUP_OUT |= (1<<USB_CFG_PULLUP_BIT); \
|
|
|
+ USB_INTR_ENABLE |= (1 << USB_INTR_ENABLE_BIT); \
|
|
|
+ } while(0);
|
|
|
+#undef usbDeviceDisconnect
|
|
|
+#define usbDeviceDisconnect() do { \
|
|
|
+ USB_INTR_ENABLE &= ~(1 << USB_INTR_ENABLE_BIT); \
|
|
|
+ USB_PULLUP_DDR &= ~(1<<USB_CFG_PULLUP_BIT); \
|
|
|
+ USB_PULLUP_OUT &= ~(1<<USB_CFG_PULLUP_BIT); \
|
|
|
+ } while(0);
|
|
|
+#endif
|
|
|
+*/
|
|
|
+/* prototypes */
|
|
|
+void __attribute__ (( __noreturn__, __noinline__, __naked__ )) leave_bootloader(void);
|
|
|
+
|
|
|
+/* we just support flash sizes <= 64kb, for code size reasons
|
|
|
+ * if you need to program bigger devices, have a look at USBasploader:
|
|
|
+ * http://www.obdev.at/products/avrusb/usbasploader.html */
|
|
|
+#if FLASHEND > 0xffff
|
|
|
+# error "usbload only supports up to 64kb of flash!"
|
|
|
+#endif
|
|
|
+
|
|
|
+/* we are just checking the lower byte of flash_address,
|
|
|
+ * so make sure SPM_PAGESIZE is <= 256
|
|
|
+ */
|
|
|
+#if SPM_PAGESIZE > 256
|
|
|
+# error "SPM_PAGESIZE is too big (just checking lower byte)"
|
|
|
+#endif
|
|
|
+
|
|
|
+/* start flash (byte address) read/write at this address */
|
|
|
+usbWord_t flash_address;
|
|
|
+uint8_t bytes_remaining;
|
|
|
+uint8_t request;
|
|
|
+uint8_t request_exit;
|
|
|
+
|
|
|
+uint8_t timeout;
|
|
|
+
|
|
|
+usbMsgLen_t usbFunctionSetup(uchar data[8])
|
|
|
+{
|
|
|
+ usbRequest_t *req = (void *)data;
|
|
|
+ uint8_t len = 0;
|
|
|
+ static uint8_t buf[4];
|
|
|
+
|
|
|
+ /* set global data pointer to local buffer */
|
|
|
+ usbMsgPtr = buf;
|
|
|
+
|
|
|
+ /* on enableprog just return one zero, which means success */
|
|
|
+ if (req->bRequest == USBASP_FUNC_ENABLEPROG) {
|
|
|
+ buf[0] = 0;
|
|
|
+ len = 1;
|
|
|
+ timeout = 255;
|
|
|
+ } else if (req->bRequest == USBASP_FUNC_CONNECT) {
|
|
|
+ /* turn on led */
|
|
|
+ DLED_ON;
|
|
|
+ } else if (req->bRequest == USBASP_FUNC_DISCONNECT) {
|
|
|
+ /* turn off led */
|
|
|
+ DLED_OFF;
|
|
|
+ request_exit = 1;
|
|
|
+ /* catch query for the devicecode, chip erase and eeprom byte requests */
|
|
|
+ } else if (req->bRequest == USBASP_FUNC_TRANSMIT) {
|
|
|
+
|
|
|
+ /* reset buffer with zeroes */
|
|
|
+ memset(buf, '\0', sizeof(buf));
|
|
|
+
|
|
|
+ /* read the address for eeprom operations */
|
|
|
+ usbWord_t address;
|
|
|
+ address.bytes[0] = data[4]; /* low byte is data[4] */
|
|
|
+ address.bytes[1] = data[3]; /* high byte is data[3] */
|
|
|
+
|
|
|
+ /* if this is a request to read the device signature, answer with the
|
|
|
+ * appropiate signature byte */
|
|
|
+ if (data[2] == ISP_READ_SIGNATURE) {
|
|
|
+ /* the complete isp data is reported back to avrdude, but we just need byte 4
|
|
|
+ * bits 0 and 1 of byte 3 determine the signature byte address */
|
|
|
+ buf[3] = signature[data[4] & 0x03];
|
|
|
+
|
|
|
+#ifdef ENABLE_CATCH_EEPROM_ISP
|
|
|
+ /* catch eeprom read */
|
|
|
+ } else if (data[2] == ISP_READ_EEPROM) {
|
|
|
+
|
|
|
+ buf[3] = eeprom_read_byte((uint8_t *)address.word);
|
|
|
+
|
|
|
+ /* catch eeprom write */
|
|
|
+ } else if (data[2] == ISP_WRITE_EEPROM) {
|
|
|
+
|
|
|
+ /* address is in data[4], data[3], and databyte is in data[5] */
|
|
|
+ eeprom_write_byte((uint8_t *)address.word, data[5]);
|
|
|
+#endif
|
|
|
+
|
|
|
+ /* catch a chip erase */
|
|
|
+ } else if (data[2] == ISP_CHIP_ERASE1 && data[3] == ISP_CHIP_ERASE2) {
|
|
|
+ for (flash_address.word = 0;
|
|
|
+ flash_address.word < BOOT_SECTION_START;
|
|
|
+ flash_address.word += SPM_PAGESIZE) {
|
|
|
+
|
|
|
+ /* wait and erase page */
|
|
|
+ boot_spm_busy_wait();
|
|
|
+ cli();
|
|
|
+ boot_page_erase(flash_address.word);
|
|
|
+ sei();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* in case no data has been filled in by the if's above, just return zeroes */
|
|
|
+ len = 4;
|
|
|
+
|
|
|
+#ifdef ENABLE_ECHO_FUNC
|
|
|
+ /* implement a simple echo function, for testing the usb connectivity */
|
|
|
+ } else if (req->bRequest == FUNC_ECHO) {
|
|
|
+ buf[0] = req->wValue.bytes[0];
|
|
|
+ buf[1] = req->wValue.bytes[1];
|
|
|
+ len = 2;
|
|
|
+#endif
|
|
|
+ } else if (req->bRequest >= USBASP_FUNC_READFLASH) {
|
|
|
+ /* && req->bRequest <= USBASP_FUNC_SETLONGADDRESS */
|
|
|
+
|
|
|
+ putc('R');
|
|
|
+ putc(req->bRequest);
|
|
|
+
|
|
|
+ /* extract address and length */
|
|
|
+ flash_address.word = req->wValue.word;
|
|
|
+ bytes_remaining = req->wLength.bytes[0];
|
|
|
+ request = req->bRequest;
|
|
|
+ /* hand control over to usbFunctionRead()/usbFunctionWrite() */
|
|
|
+ len = 0xff;
|
|
|
+ }
|
|
|
+
|
|
|
+ return len;
|
|
|
+}
|
|
|
+
|
|
|
+uchar usbFunctionWrite(uchar *data, uchar len)
|
|
|
+{
|
|
|
+ if (len > bytes_remaining)
|
|
|
+ len = bytes_remaining;
|
|
|
+ bytes_remaining -= len;
|
|
|
+
|
|
|
+ if (request == USBASP_FUNC_WRITEEEPROM) {
|
|
|
+ for (uint8_t i = 0; i < len; i++)
|
|
|
+ eeprom_write_byte((uint8_t *)flash_address.word++, *data++);
|
|
|
+ } else {
|
|
|
+ /* data is handled wordwise, adjust len */
|
|
|
+ len /= 2;
|
|
|
+ len -= 1;
|
|
|
+ for (uint8_t i = 0; i <= len; i++) {
|
|
|
+ uint16_t *w = (uint16_t *)data;
|
|
|
+ cli();
|
|
|
+ boot_page_fill(flash_address.word, *w);
|
|
|
+ sei();
|
|
|
+
|
|
|
+ usbWord_t next_address;
|
|
|
+ next_address.word = flash_address.word;
|
|
|
+ next_address.word += 2;
|
|
|
+ data += 2;
|
|
|
+
|
|
|
+ /* write page if page boundary is crossed or this is the last page */
|
|
|
+ if ( next_address.bytes[0] % SPM_PAGESIZE == 0 ||
|
|
|
+ (bytes_remaining == 0 && i == len) ) {
|
|
|
+ cli();
|
|
|
+ boot_page_write(flash_address.word);
|
|
|
+ sei();
|
|
|
+ boot_spm_busy_wait();
|
|
|
+ cli();
|
|
|
+ boot_rww_enable();
|
|
|
+ sei();
|
|
|
+ }
|
|
|
+
|
|
|
+ flash_address.word = next_address.word;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* flash led on activity */
|
|
|
+ DLED_TGL;
|
|
|
+
|
|
|
+ return (bytes_remaining == 0);
|
|
|
+}
|
|
|
+
|
|
|
+uchar usbFunctionRead(uchar *data, uchar len)
|
|
|
+{
|
|
|
+ if(len > bytes_remaining)
|
|
|
+ len = bytes_remaining;
|
|
|
+ bytes_remaining -= len;
|
|
|
+
|
|
|
+ for (uint8_t i = 0; i < len; i++) {
|
|
|
+ if(request == USBASP_FUNC_READEEPROM)
|
|
|
+ *data = eeprom_read_byte((void *)flash_address.word);
|
|
|
+ else
|
|
|
+ *data = pgm_read_byte_near((void *)flash_address.word);
|
|
|
+ data++;
|
|
|
+ flash_address.word++;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* flash led on activity */
|
|
|
+ DLED_TGL;
|
|
|
+
|
|
|
+ return len;
|
|
|
+}
|
|
|
+
|
|
|
+void (*jump_to_app)(void) = 0x0000;
|
|
|
+void leave_bootloader(void)
|
|
|
+{
|
|
|
+
|
|
|
+ cli();
|
|
|
+
|
|
|
+ /* disconnect usb */
|
|
|
+ usbDeviceDisconnect();
|
|
|
+ for (uint8_t i = 0; i < 38; i++)
|
|
|
+ _delay_loop_2(0); /* 0 means 0x10000, 38*1/f*0x10000 =~ 498ms */
|
|
|
+
|
|
|
+ /* enable watchdog to soft-reset the uC for clean startup of new application */
|
|
|
+ wdt_enable(WDTO_15MS);
|
|
|
+
|
|
|
+ /* let watchdog kick in and reset uC */
|
|
|
+ while(1);
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+int __attribute__ ((noreturn,OS_main)) main(void)
|
|
|
+{
|
|
|
+ /* start bootloader */
|
|
|
+
|
|
|
+ /* init LED-Pin */
|
|
|
+ PORTB = 0;
|
|
|
+
|
|
|
+
|
|
|
+#ifdef DEBUG_UART
|
|
|
+ /* init uart (115200 baud, at 20mhz) */
|
|
|
+ UBRR0L = 10;
|
|
|
+ UCSR0C = _BV(UCSZ00) | _BV(UCSZ01);
|
|
|
+ UCSR0B = _BV(TXEN0);
|
|
|
+ putc('b');
|
|
|
+#endif
|
|
|
+
|
|
|
+ uint8_t reset = MCUSR;
|
|
|
+
|
|
|
+ /* if power-on reset, quit bootloader via watchdog reset */
|
|
|
+ if (reset & _BV(PORF)){
|
|
|
+ MCUSR = 0;
|
|
|
+ leave_bootloader();
|
|
|
+ }
|
|
|
+ /* if watchdog reset, disable watchdog and jump to app */
|
|
|
+ else if(reset & _BV(WDRF)){
|
|
|
+ MCUSR = 0;
|
|
|
+ wdt_disable();
|
|
|
+ jump_to_app();
|
|
|
+ }
|
|
|
+
|
|
|
+ /* else: enter programming mode */
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ /* clear external reset flags */
|
|
|
+ MCUSR = 0;
|
|
|
+
|
|
|
+ /* init exit request state */
|
|
|
+ request_exit = 0;
|
|
|
+
|
|
|
+ /* move interrupts to boot section */
|
|
|
+ MCUCR = (1 << IVCE);
|
|
|
+ MCUCR = (1 << IVSEL);
|
|
|
+
|
|
|
+ /* enable interrupts */
|
|
|
+ sei();
|
|
|
+
|
|
|
+ /* initialize usb pins */
|
|
|
+ usbInit();
|
|
|
+
|
|
|
+ /* disconnect for ~500ms, so that the host re-enumerates this device */
|
|
|
+ usbDeviceDisconnect();
|
|
|
+ for (uint8_t i = 0; i < 38; i++)
|
|
|
+ _delay_loop_2(0); /* 0 means 0x10000, 38*1/f*0x10000 =~ 498ms */
|
|
|
+ usbDeviceConnect();
|
|
|
+
|
|
|
+ uint16_t delay;
|
|
|
+ timeout = TIMEOUT;
|
|
|
+
|
|
|
+ while(1) {
|
|
|
+ usbPoll();
|
|
|
+ delay++;
|
|
|
+
|
|
|
+ /* do some led blinking, so that it is visible that the bootloader is still running */
|
|
|
+ if (delay == 0) {
|
|
|
+ DLED_TGL;
|
|
|
+ if (timeout < 255)
|
|
|
+ timeout--;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (request_exit || timeout == 0) {
|
|
|
+ _delay_loop_2(0);
|
|
|
+ leave_bootloader();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ leave_bootloader();
|
|
|
+}
|