summaryrefslogtreecommitdiffstats
path: root/firmware/main.c
diff options
context:
space:
mode:
Diffstat (limited to 'firmware/main.c')
-rw-r--r--firmware/main.c484
1 files changed, 227 insertions, 257 deletions
diff --git a/firmware/main.c b/firmware/main.c
index 6f7f180..16cca65 100644
--- a/firmware/main.c
+++ b/firmware/main.c
@@ -1,22 +1,22 @@
-/* Name: main.c
- * Project: Micronucleus
- * Author: Jenna Fox
- * Creation Date: 2007-12-08
- * Tabsize: 4
- * Copyright: (c) 2012 Jenna Fox
- * All changes past revision 1.06 authored by http://github.com/cpldcpu
- * Portions Copyright: (c) 2007 by OBJECTIVE DEVELOPMENT Software GmbH (USBaspLoader)
- * Portions Copyright: (c) 2012 Louis Beaudoin (USBaspLoader-tiny85)
- * License: GNU GPL v2 (see License.txt)
+/*
+ * Project: Micronucleus - v1.11
+ *
+ * Original author (c) 2012 Jenna Fox
+ *
+ * Optimizations v1.10/v1.11 (c) 2013 Tim Bo"scke - cpldcpu@gmail.com
+ * v1.11 (c) 2013 Shay Green
+ *
+ * Based on USBaspLoader-tiny85 (c) 2012 Louis Beaudoin
+ * Based on USBaspLoader (c) 2007 by OBJECTIVE DEVELOPMENT Software GmbH
*
+ * License: GNU GPL v2 (see License.txt)
*/
#define MICRONUCLEUS_VERSION_MAJOR 1
-#define MICRONUCLEUS_VERSION_MINOR 10
+#define MICRONUCLEUS_VERSION_MINOR 11
// how many milliseconds should host wait till it sends another erase or write?
// needs to be above 4.5 (and a whole integer) as avr freezes for 4.5ms
#define MICRONUCLEUS_WRITE_SLEEP 8
-
// Use the old delay routines without NOP padding. This saves memory.
#define __DELAY_BACKWARD_COMPATIBLE__
@@ -26,59 +26,52 @@
#include <avr/boot.h>
#include <util/delay.h>
-static void leaveBootloader() __attribute__((__noreturn__));
-
#include "bootloaderconfig.h"
-#include "usbdrv/usbdrv.c"
-/* ------------------------------------------------------------------------ */
-// postscript are the few bytes at the end of programmable memory which store tinyVectors
-// and used to in USBaspLoader-tiny85 store the checksum iirc
-#define POSTSCRIPT_SIZE 6
-#define PROGMEM_SIZE (BOOTLOADER_ADDRESS - POSTSCRIPT_SIZE) /* max size of user program */
+
+
+#include "usbdrv/usbdrv.c"
// verify the bootloader address aligns with page size
#if BOOTLOADER_ADDRESS % SPM_PAGESIZE != 0
-# error "BOOTLOADER_ADDRESS in makefile must be a multiple of chip's pagesize"
+ #error "BOOTLOADER_ADDRESS in makefile must be a multiple of chip's pagesize"
#endif
-#ifdef AUTO_EXIT_MS
-# if AUTO_EXIT_MS < (MICRONUCLEUS_WRITE_SLEEP * (BOOTLOADER_ADDRESS / SPM_PAGESIZE))
-# warning "AUTO_EXIT_MS is shorter than the time it takes to perform erase function - might affect reliability?"
-# warning "Try increasing AUTO_EXIT_MS if you have stability problems"
-# endif
+#if SPM_PAGESIZE>256
+ #error "Micronucleus only supports pagesizes up to 256 bytes"
#endif
-// events system schedules functions to run in the main loop
-static uchar events = 0; // bitmap of events to run
-#define EVENT_ERASE_APPLICATION 1
-#define EVENT_WRITE_PAGE 2
-#define EVENT_EXECUTE 4
-
-// controls state of events
-#define fireEvent(event) events |= (event)
-#define isEvent(event) (events & (event))
-#define clearEvents() events = 0
-
-// Definition of sei and cli without memory barrier keyword to prevent reloading of memory variables
-#define sei() __asm__ __volatile__ ("sei")
-#define cli() __asm__ __volatile__ ("cli")
+// command system schedules functions to run in the main loop
+register uint8_t command asm("r3"); // bind command to r3
+register uint16_union_t currentAddress asm("r4"); // r4/r5 current progmem address, used for erasing and writing
+register uint16_union_t idlePolls asm("r6"); // r6/r7 idlecounter
-uint16_t idlePolls = 0; // how long have we been idle?
+#if OSCCAL_RESTORE
+ register uint8_t osccal_default asm("r2");
+#endif
static uint16_t vectorTemp[2]; // remember data to create tinyVector table before BOOTLOADER_ADDRESS
-static uint16_t currentAddress; // current progmem address, used for erasing and writing
-#if OSCCAL_RESTORE
- static uint8_t osccal_default; // due to compiler insanity, having this as global actually saves memory
-#endif
+enum {
+ cmd_local_nop=0, // also: get device info
+ cmd_device_info=0,
+ cmd_transfer_page=1,
+ cmd_erase_application=2,
+ cmd_exit=4,
+ cmd_write_page=5,
+};
+
+// Definition of sei and cli without memory barrier keyword to prevent reloading of memory variables
+#define sei() asm volatile("sei")
+#define cli() asm volatile("cli")
+#define nop() asm volatile("nop")
/* ------------------------------------------------------------------------ */
static inline void eraseApplication(void);
static void writeFlashPage(void);
static void writeWordToPageBuffer(uint16_t data);
-static uchar usbFunctionSetup(uchar data[8]);
-static uchar usbFunctionWrite(uchar *data, uchar length);
+static uint8_t usbFunctionSetup(uint8_t data[8]);
+static uint8_t usbFunctionWrite(uint8_t *data, uint8_t length);
static inline void leaveBootloader(void);
// erase any existing application and write in jumps for usb interrupt and reset to bootloader
@@ -86,142 +79,132 @@ static inline void leaveBootloader(void);
// - vectors in now, and write in the application stuff around them later.
// - if vectors weren't written back in immediately, usb would fail.
static inline void eraseApplication(void) {
- // erase all pages until bootloader, in reverse order (so our vectors stay in place for as long as possible)
- // while the vectors don't matter for usb comms as interrupts are disabled during erase, it's important
- // to minimise the chance of leaving the device in a state where the bootloader wont run, if there's power failure
- // during upload
-
- uint8_t i;
- uint16_t ptr = BOOTLOADER_ADDRESS;
- cli();
- while (ptr) {
- ptr -= SPM_PAGESIZE;
- boot_page_erase(ptr);
- }
+ // erase all pages until bootloader, in reverse order (so our vectors stay in place for as long as possible)
+ // while the vectors don't matter for usb comms as interrupts are disabled during erase, it's important
+ // to minimise the chance of leaving the device in a state where the bootloader wont run, if there's power failure
+ // during upload
+
+ uint8_t i;
+ uint16_t ptr = BOOTLOADER_ADDRESS;
+ cli();
+
+ while (ptr) {
+ ptr -= SPM_PAGESIZE;
+ boot_page_erase(ptr);
+ }
- currentAddress = 0;
- for (i=0; i<8; i++) writeWordToPageBuffer(0xFFFF); // Write first 8 words to fill in vectors.
- writeFlashPage(); // enables interrupts
+ currentAddress.w = 0;
+ for (i=0; i<8; i++) writeWordToPageBuffer(0xFFFF); // Write first 8 words to fill in vectors.
+ writeFlashPage(); // enables interrupts
}
// simply write currently stored page in to already erased flash memory
static void writeFlashPage(void) {
- cli();
- boot_page_write(currentAddress - 2); // will halt CPU, no waiting required
- sei();
+ cli();
+ boot_page_write(currentAddress.w - 2); // will halt CPU, no waiting required
+ sei();
}
// clear memory which stores data to be written by next writeFlashPage call
-#define __boot_page_fill_clear() \
-(__extension__({ \
- __asm__ __volatile__ \
- ( \
- "sts %0, %1\n\t" \
- "spm\n\t" \
- : \
- : "i" (_SFR_MEM_ADDR(__SPM_REG)), \
- "r" ((uint8_t)(__BOOT_PAGE_FILL | (1 << CTPB))) \
- ); \
+#define __boot_page_fill_clear() \
+(__extension__({ \
+ __asm__ __volatile__ \
+ ( \
+ "sts %0, %1\n\t" \
+ "spm\n\t" \
+ : \
+ : "i" (_SFR_MEM_ADDR(__SPM_REG)), \
+ "r" ((uint8_t)(__BOOT_PAGE_FILL | (1 << CTPB))) \
+ ); \
}))
// write a word in to the page buffer, doing interrupt table modifications where they're required
static void writeWordToPageBuffer(uint16_t data) {
- uint8_t previous_sreg;
-
- // first two interrupt vectors get replaced with a jump to the bootloader's vector table
- // remember vectors or the tinyvector table
- if (currentAddress == RESET_VECTOR_OFFSET * 2) {
- vectorTemp[0] = data;
- data = 0xC000 + (BOOTLOADER_ADDRESS/2) - 1;
- }
-
- if (currentAddress == USBPLUS_VECTOR_OFFSET * 2) {
- vectorTemp[1] = data;
- data = 0xC000 + (BOOTLOADER_ADDRESS/2) - 1;
- }
-
- // at end of page just before bootloader, write in tinyVector table
- // see http://embedded-creations.com/projects/attiny85-usb-bootloader-overview/avr-jtag-programmer/
- // for info on how the tiny vector table works
- if (currentAddress == BOOTLOADER_ADDRESS - TINYVECTOR_RESET_OFFSET) {
- data = vectorTemp[0] + ((FLASHEND + 1) - BOOTLOADER_ADDRESS)/2 + 2 + RESET_VECTOR_OFFSET;
- } else if (currentAddress == BOOTLOADER_ADDRESS - TINYVECTOR_USBPLUS_OFFSET) {
- data = vectorTemp[1] + ((FLASHEND + 1) - BOOTLOADER_ADDRESS)/2 + 1 + USBPLUS_VECTOR_OFFSET;
-#if (!OSCCAL_RESTORE) && OSCCAL_16_5MHz
- } else if (currentAddress == BOOTLOADER_ADDRESS - TINYVECTOR_OSCCAL_OFFSET) {
- data = OSCCAL;
-#endif
+ uint8_t previous_sreg;
+
+ // first two interrupt vectors get replaced with a jump to the bootloader's vector table
+ // remember vectors or the tinyvector table
+ if (currentAddress.w == RESET_VECTOR_OFFSET * 2) {
+ vectorTemp[0] = data;
+ data = 0xC000 + (BOOTLOADER_ADDRESS/2) - 1;
}
-
- previous_sreg=SREG;
- cli(); // ensure interrupts are disabled
- // clear page buffer as a precaution before filling the buffer on the first page
- // in case the bootloader somehow ran after user program and there was something
- // in the page buffer already
- if (currentAddress == 0x0000) __boot_page_fill_clear();
- boot_page_fill(currentAddress, data);
+ if (currentAddress.w == USBPLUS_VECTOR_OFFSET * 2) {
+ vectorTemp[1] = data;
+ data = 0xC000 + (BOOTLOADER_ADDRESS/2) - 1;
+ }
- // increment progmem address by one word
- currentAddress += 2;
- SREG=previous_sreg;
+ // at end of page just before bootloader, write in tinyVector table
+ // see http://embedded-creations.com/projects/attiny85-usb-bootloader-overview/avr-jtag-programmer/
+ // for info on how the tiny vector table works
+ if (currentAddress.w == BOOTLOADER_ADDRESS - TINYVECTOR_RESET_OFFSET) {
+ data = vectorTemp[0] + ((FLASHEND + 1) - BOOTLOADER_ADDRESS)/2 + 2 + RESET_VECTOR_OFFSET;
+ } else if (currentAddress.w == BOOTLOADER_ADDRESS - TINYVECTOR_USBPLUS_OFFSET) {
+ data = vectorTemp[1] + ((FLASHEND + 1) - BOOTLOADER_ADDRESS)/2 + 1 + USBPLUS_VECTOR_OFFSET;
+#if (!OSCCAL_RESTORE) && OSCCAL_16_5MHz
+ } else if (currentAddress.w == BOOTLOADER_ADDRESS - TINYVECTOR_OSCCAL_OFFSET) {
+ data = OSCCAL;
+#endif
+ }
+
+ previous_sreg=SREG;
+ cli(); // ensure interrupts are disabled
+
+ boot_page_fill(currentAddress.w, data);
+
+ // increment progmem address by one word
+ currentAddress.w += 2;
+ SREG=previous_sreg;
}
+// This function is never called, it is just here to suppress a compiler warning.
+USB_PUBLIC usbMsgLen_t usbFunctionDescriptor(struct usbRequest *rq) { return 0; }
+
/* ------------------------------------------------------------------------ */
-static uchar usbFunctionSetup(uchar data[8]) {
- usbRequest_t *rq = (void *)data;
- ((uint8_t*)&idlePolls)[1] = 0; // reset idle polls when we get usb traffic
-
- static uchar replyBuffer[4] = {
- (((uint16_t)PROGMEM_SIZE) >> 8) & 0xff,
- ((uint16_t)PROGMEM_SIZE) & 0xff,
- SPM_PAGESIZE,
- MICRONUCLEUS_WRITE_SLEEP
- };
-
- if (rq->bRequest == 0) { // get device info
- usbMsgPtr = replyBuffer;
- return 4;
-
- } else if (rq->bRequest == 1) { // write page
- currentAddress = rq->wIndex.word;
- return USB_NO_MSG; // hands off work to usbFunctionWrite
-
- } else if (rq->bRequest == 2) { // erase application
- fireEvent(EVENT_ERASE_APPLICATION);
-
- } else { // exit bootloader
-# if BOOTLOADER_CAN_EXIT
- fireEvent(EVENT_EXECUTE);
-# endif
- }
-
+static uint8_t usbFunctionSetup(uint8_t data[8]) {
+ usbRequest_t *rq = (void *)data;
+
+ static uint8_t replyBuffer[4] = {
+ (((uint16_t)PROGMEM_SIZE) >> 8) & 0xff,
+ ((uint16_t)PROGMEM_SIZE) & 0xff,
+ SPM_PAGESIZE,
+ MICRONUCLEUS_WRITE_SLEEP
+ };
+
+ idlePolls.b[1]=0; // reset idle polls when we get usb traffic
+
+ if (rq->bRequest == cmd_device_info) { // get device info
+ usbMsgPtr = replyBuffer;
+ return 4;
+ } else if (rq->bRequest == cmd_transfer_page) { // transfer page
+ // clear page buffer as a precaution before filling the buffer in case
+ // a previous write operation failed and there is still something in the buffer.
+ __boot_page_fill_clear();
+ currentAddress.w = rq->wIndex.word;
+ return USB_NO_MSG; // hands off work to usbFunctionWrite
+ } else {
+ // Handle cmd_erase_application and cmd_exit
+ command=rq->bRequest;
return 0;
+ }
}
// read in a page over usb, and write it in to the flash write buffer
-static uchar usbFunctionWrite(uchar *data, uchar length) {
- do {
- // make sure we don't write over the bootloader!
- if (currentAddress >= BOOTLOADER_ADDRESS) break;
-
- writeWordToPageBuffer(*(uint16_t *) data);
- data += 2; // advance data pointer
- length -= 2;
- } while(length);
+static uint8_t usbFunctionWrite(uint8_t *data, uint8_t length) {
+ do {
+ // make sure we don't write over the bootloader!
+ if (currentAddress.w >= BOOTLOADER_ADDRESS) break;
- // if we have now reached another page boundary, we're done
-#if SPM_PAGESIZE<256
- // Hack to reduce code size
- uchar isLast = ((((uchar)currentAddress) % SPM_PAGESIZE) == 0);
-#else
- uchar isLast = ((currentAddress % SPM_PAGESIZE) == 0);
-#endif
-
- // definitely need this if! seems usbFunctionWrite gets called again in future usbPoll's in the runloop!
- if (isLast) fireEvent(EVENT_WRITE_PAGE); // ask runloop to write our page
-
- return isLast; // let vusb know we're done with this request
+ writeWordToPageBuffer(*(uint16_t *) data);
+ data += 2; // advance data pointer
+ length -= 2;
+ } while(length);
+
+ // if we have now reached another page boundary, we're done
+ uint8_t isLast = ((currentAddress.b[0] % SPM_PAGESIZE) == 0);
+ if (isLast) command=cmd_write_page; // ask runloop to write our page
+
+ return isLast; // let V-USB know we're done with this request
}
/* ------------------------------------------------------------------------ */
@@ -229,122 +212,109 @@ void PushMagicWord (void) __attribute__ ((naked)) __attribute__ ((section (".ini
// put the word "B007" at the bottom of the stack (RAMEND - RAMEND-1)
void PushMagicWord (void) {
- asm volatile("ldi r16, 0xB0"::);
- asm volatile("push r16"::);
- asm volatile("ldi r16, 0x07"::);
- asm volatile("push r16"::);
+ asm volatile("ldi r16, 0xB0"::);
+ asm volatile("push r16"::);
+ asm volatile("ldi r16, 0x07"::);
+ asm volatile("push r16"::);
+}
+
+static void initHardware (void)
+{
+ // Disable watchdog and set timeout to maximum in case the WDT is fused on
+ MCUSR=0;
+ WDTCR = 1<<WDCE | 1<<WDE;
+ WDTCR = 1<<WDP2 | 1<<WDP1 | 1<<WDP0;
+
+ /* initialize */
+ #if OSCCAL_RESTORE
+ osccal_default = OSCCAL;
+ #endif
+
+ usbDeviceDisconnect(); /* do this while interrupts are disabled */
+ _delay_ms(500);
+ usbDeviceConnect();
+ usbInit(); // Initialize INT settings after reconnect
+
+ sei();
}
/* ------------------------------------------------------------------------ */
// reset system to a normal state and launch user program
+static void leaveBootloader(void) __attribute__((__noreturn__));
static inline void leaveBootloader(void) {
- _delay_ms(10); // removing delay causes USB errors
-
- bootLoaderExit();
- cli();
- usbDeviceDisconnect(); /* Disconnect micronucleus */
-
- USB_INTR_ENABLE = 0;
- USB_INTR_CFG = 0; /* also reset config bits */
+
+ bootLoaderExit();
+ cli();
+ usbDeviceDisconnect(); /* Disconnect micronucleus */
- // clear magic word from bottom of stack before jumping to the app
- *(uint8_t*)(RAMEND) = 0x00; // A single write is sufficient to invalidate magic word
+ USB_INTR_ENABLE = 0;
+ USB_INTR_CFG = 0; /* also reset config bits */
+
+ // clear magic word from bottom of stack before jumping to the app
+ *(uint8_t*)(RAMEND) = 0x00; // A single write is sufficient to invalidate magic word
-#if (!OSCCAL_RESTORE) && OSCCAL_16_5MHz
+ #if OSCCAL_RESTORE
+ OSCCAL=osccal_default;
+ nop(); // NOP to avoid CPU hickup during oscillator stabilization
+ #elif OSCCAL_16_5MHz
// adjust clock to previous calibration value, so user program always starts with same calibration
// as when it was uploaded originally
unsigned char stored_osc_calibration = pgm_read_byte(BOOTLOADER_ADDRESS - TINYVECTOR_OSCCAL_OFFSET);
if (stored_osc_calibration != 0xFF && stored_osc_calibration != 0x00) {
- OSCCAL=stored_osc_calibration;
- asm volatile("nop");
+ OSCCAL=stored_osc_calibration;
+ nop();
}
-#endif
- // jump to application reset vector at end of flash
- asm volatile ("rjmp __vectors - 4");
+ #endif
+
+ asm volatile ("rjmp __vectors - 4"); // jump to application reset vector at end of flash
+
+ for (;;); // Make sure function does not return to help compiler optimize
}
int main(void) {
- /* initialize */
- #if OSCCAL_RESTORE
- osccal_default = OSCCAL;
- #endif
- #if (!SET_CLOCK_PRESCALER) && LOW_POWER_MODE
- uint8_t prescaler_default = CLKPR;
- #endif
-
- bootLoaderInit();
-# if AUTO_EXIT_NO_USB_MS
- ((uint8_t*)&idlePolls)[1]=((AUTO_EXIT_MS-AUTO_EXIT_NO_USB_MS) * 10UL)>>8; // write only high byte to save 6 bytes
-# endif
+ bootLoaderInit();
+
+ if (bootLoaderStartCondition()||(pgm_read_byte(BOOTLOADER_ADDRESS - TINYVECTOR_RESET_OFFSET + 1)==0xff)) {
+
+ initHardware();
+ LED_INIT();
- if (bootLoaderStartCondition()) {
-
- MCUSR=0; /* need this to properly disable watchdog */
- wdt_disable();
-
- #if LOW_POWER_MODE
- // turn off clock prescalling - chip must run at full speed for usb
- // if you might run chip at lower voltages, detect that in bootLoaderStartCondition
- CLKPR = 1 << CLKPCE;
- CLKPR = 0;
- #endif
-
-# if LED_PRESENT
- LED_INIT();
-# endif
-
- usbDeviceDisconnect(); /* do this while interrupts are disabled */
- _delay_ms(500);
- usbDeviceConnect();
- usbInit(); // Initialize INT settings after reconnect
- sei();
-
- do {
- usbPoll();
- _delay_us(100);
-
- // these next two freeze the chip for ~ 4.5ms, breaking usb protocol
- // and usually both of these will activate in the same loop, so host
- // needs to wait > 9ms before next usb request
- if (isEvent(EVENT_ERASE_APPLICATION)) eraseApplication();
- if (isEvent(EVENT_WRITE_PAGE)) {
- _delay_us(2000); // Wait for USB traffic to finish before halting CPU with write-
- writeFlashPage();
- }
-
-# if BOOTLOADER_CAN_EXIT
- if (isEvent(EVENT_EXECUTE)) break; // when host requests device run uploaded program
-# endif
- clearEvents();
-
-# if LED_PRESENT
- LED_MACRO( ((uint8_t*)&idlePolls)[1] )
-# endif
-
- } while(bootLoaderCondition()); /* main event loop runs so long as bootLoaderCondition remains truthy */
+ if (AUTO_EXIT_NO_USB_MS>0) {
+ idlePolls.b[1]=((AUTO_EXIT_MS-AUTO_EXIT_NO_USB_MS) * 10UL)>>8;
+ } else {
+ idlePolls.b[1]=0;
}
- // set clock prescaler to desired clock speed (changing from clkdiv8, or no division, depending on fuses)
- #if LOW_POWER_MODE
- #ifdef SET_CLOCK_PRESCALER
- CLKPR = 1 << CLKPCE;
- CLKPR = SET_CLOCK_PRESCALER;
- #else
- CLKPR = 1 << CLKPCE;
- CLKPR = prescaler_default;
- #endif
- #endif
-
-# if LED_PRESENT
- LED_EXIT();
-# endif
-
-# if OSCCAL_RESTORE
- OSCCAL=osccal_default;
- asm volatile("nop"); // NOP to avoid CPU hickup during oscillator stabilization
-# endif
+ do {
+ _delay_us(100);
+ wdt_reset(); // Only necessary if WDT is fused on
+
+ command=cmd_local_nop;
+ usbPoll();
+
+ idlePolls.w++;
+
+ // Try to execute program if bootloader exit condition is met
+ if (AUTO_EXIT_MS&&(idlePolls.w==AUTO_EXIT_MS*10L)) command=cmd_exit;
+
+ LED_MACRO( idlePolls.b[1] );
- leaveBootloader();
+ // Wait for USB traffic to finish before a blocking event is executed
+ // All events will render the MCU unresponsive to USB traffic for a while.
+ if (command!=cmd_local_nop) _delay_ms(2);
+
+ if (command==cmd_erase_application)
+ eraseApplication();
+ else if (command==cmd_write_page)
+ writeFlashPage();
+
+ /* main event loop runs as long as no problem is uploaded or existing program is not executed */
+ } while((command!=cmd_exit)||(pgm_read_byte(BOOTLOADER_ADDRESS - TINYVECTOR_RESET_OFFSET + 1)==0xff));
+
+ LED_EXIT();
+ }
+
+ leaveBootloader();
}
/* ------------------------------------------------------------------------ */