From c4907c6896943eb3ff778cb007a9891ac7b68248 Mon Sep 17 00:00:00 2001 From: cpldcpu Date: Sat, 4 Jan 2014 15:22:34 +0100 Subject: just log current state --- firmware/main.c | 110 ++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 91 insertions(+), 19 deletions(-) (limited to 'firmware/main.c') diff --git a/firmware/main.c b/firmware/main.c index 16cca65..1fdda03 100644 --- a/firmware/main.c +++ b/firmware/main.c @@ -27,9 +27,6 @@ #include #include "bootloaderconfig.h" - - - #include "usbdrv/usbdrv.c" // verify the bootloader address aligns with page size @@ -100,9 +97,9 @@ static inline void eraseApplication(void) { // simply write currently stored page in to already erased flash memory static void writeFlashPage(void) { - cli(); + // cli(); boot_page_write(currentAddress.w - 2); // will halt CPU, no waiting required - sei(); + // sei(); } // clear memory which stores data to be written by next writeFlashPage call @@ -147,14 +144,14 @@ static void writeWordToPageBuffer(uint16_t data) { #endif } - previous_sreg=SREG; - cli(); // ensure interrupts are disabled + // 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; + // SREG=previous_sreg; } // This function is never called, it is just here to suppress a compiler warning. @@ -231,11 +228,14 @@ static void initHardware (void) #endif usbDeviceDisconnect(); /* do this while interrupts are disabled */ - _delay_ms(500); + _delay_ms(300); usbDeviceConnect(); + + // Todo: timeout if no reset is found + calibrateOscillatorASM(); usbInit(); // Initialize INT settings after reconnect - - sei(); + + // sei(); } /* ------------------------------------------------------------------------ */ @@ -244,7 +244,7 @@ static void leaveBootloader(void) __attribute__((__noreturn__)); static inline void leaveBootloader(void) { bootLoaderExit(); - cli(); + //cli(); usbDeviceDisconnect(); /* Disconnect micronucleus */ USB_INTR_ENABLE = 0; @@ -271,6 +271,33 @@ static inline void leaveBootloader(void) { for (;;); // Make sure function does not return to help compiler optimize } + +static void wait_usb_interrupt( void ) +{ + // Clear any stale pending interrupt, then wait for interrupt flag + USB_INTR_PENDING = 1< Date: Sun, 5 Jan 2014 12:09:34 +0100 Subject: firmware: first working version with polled usb --- firmware/main.c | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 58 insertions(+), 6 deletions(-) (limited to 'firmware/main.c') diff --git a/firmware/main.c b/firmware/main.c index 1fdda03..a09bb4c 100644 --- a/firmware/main.c +++ b/firmware/main.c @@ -125,19 +125,21 @@ static void writeWordToPageBuffer(uint16_t data) { vectorTemp[0] = data; data = 0xC000 + (BOOTLOADER_ADDRESS/2) - 1; } - +/* if (currentAddress.w == 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.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; @@ -286,7 +288,7 @@ static void wait_usb_interrupt( void ) USB_INTR_VECTOR(); // Wait a little while longer in case another one comes - uchar n = 250; // about 90us timeout + uchar n = 50; // about 90us timeout do { if ( !--n ) goto handled; @@ -302,6 +304,8 @@ int main(void) { bootLoaderInit(); + DDRB|=3; + if (bootLoaderStartCondition()||(pgm_read_byte(BOOTLOADER_ADDRESS - TINYVECTOR_RESET_OFFSET + 1)==0xff)) { initHardware(); @@ -314,7 +318,9 @@ int main(void) { } do { - +// USB_INTR_PENDING = 1< Date: Sun, 5 Jan 2014 13:04:08 +0100 Subject: firmware: cleaning up --- firmware/main.c | 81 ++++++--------------------------------------------------- 1 file changed, 8 insertions(+), 73 deletions(-) (limited to 'firmware/main.c') diff --git a/firmware/main.c b/firmware/main.c index a09bb4c..d3b1ce4 100644 --- a/firmware/main.c +++ b/firmware/main.c @@ -273,33 +273,6 @@ static inline void leaveBootloader(void) { for (;;); // Make sure function does not return to help compiler optimize } - -static void wait_usb_interrupt( void ) -{ - // Clear any stale pending interrupt, then wait for interrupt flag - USB_INTR_PENDING = 1< Date: Sun, 5 Jan 2014 16:30:52 +0100 Subject: firmware: Removed trampoline and INT patching --- firmware/main.c | 114 +++++++++++++++++++++++++------------------------------- 1 file changed, 50 insertions(+), 64 deletions(-) (limited to 'firmware/main.c') diff --git a/firmware/main.c b/firmware/main.c index d3b1ce4..19ed37a 100644 --- a/firmware/main.c +++ b/firmware/main.c @@ -13,10 +13,10 @@ */ #define MICRONUCLEUS_VERSION_MAJOR 1 -#define MICRONUCLEUS_VERSION_MINOR 11 +#define MICRONUCLEUS_VERSION_MINOR 99 // 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 +#define MICRONUCLEUS_WRITE_SLEEP 5 // Use the old delay routines without NOP padding. This saves memory. #define __DELAY_BACKWARD_COMPATIBLE__ @@ -47,7 +47,7 @@ register uint16_union_t idlePolls asm("r6"); // r6/r7 idlecounter 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 vectorTemp; // remember data to create tinyVector table before BOOTLOADER_ADDRESS enum { cmd_local_nop=0, // also: get device info @@ -55,7 +55,7 @@ enum { cmd_transfer_page=1, cmd_erase_application=2, cmd_exit=4, - cmd_write_page=5, + cmd_write_page=64, // internal commands start at 64 }; // Definition of sei and cli without memory barrier keyword to prevent reloading of memory variables @@ -83,7 +83,6 @@ static inline void eraseApplication(void) { uint8_t i; uint16_t ptr = BOOTLOADER_ADDRESS; - cli(); while (ptr) { ptr -= SPM_PAGESIZE; @@ -92,14 +91,15 @@ static inline void eraseApplication(void) { currentAddress.w = 0; for (i=0; i<8; i++) writeWordToPageBuffer(0xFFFF); // Write first 8 words to fill in vectors. - writeFlashPage(); // enables interrupts -} + writeFlashPage(); + } + // simply write currently stored page in to already erased flash memory -static void writeFlashPage(void) { - // cli(); +static inline void writeFlashPage(void) { + 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 @@ -117,29 +117,20 @@ static void writeFlashPage(void) { // 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.w == RESET_VECTOR_OFFSET * 2) { - vectorTemp[0] = data; - data = 0xC000 + (BOOTLOADER_ADDRESS/2) - 1; - } -/* - if (currentAddress.w == USBPLUS_VECTOR_OFFSET * 2) { - vectorTemp[1] = data; + vectorTemp = data; data = 0xC000 + (BOOTLOADER_ADDRESS/2) - 1; } - */ - // at end of page just before bootloader, write in tinyVector table + + // 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; -*/ + data = vectorTemp + ((FLASHEND + 1) - BOOTLOADER_ADDRESS)/2 + 2 + RESET_VECTOR_OFFSET; + #if (!OSCCAL_RESTORE) && OSCCAL_16_5MHz } else if (currentAddress.w == BOOTLOADER_ADDRESS - TINYVECTOR_OSCCAL_OFFSET) { data = OSCCAL; @@ -206,17 +197,6 @@ static uint8_t usbFunctionWrite(uint8_t *data, uint8_t length) { return isLast; // let V-USB know we're done with this request } -/* ------------------------------------------------------------------------ */ -void PushMagicWord (void) __attribute__ ((naked)) __attribute__ ((section (".init3"))); - -// 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"::); -} - static void initHardware (void) { // Disable watchdog and set timeout to maximum in case the WDT is fused on @@ -244,20 +224,18 @@ static void initHardware (void) // reset system to a normal state and launch user program static void leaveBootloader(void) __attribute__((__noreturn__)); static inline void leaveBootloader(void) { - + bootLoaderExit(); - //cli(); + + _delay_ms(10); // Bus needs to see a few more SOFs before it can be disconnected usbDeviceDisconnect(); /* Disconnect micronucleus */ 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=osccal_default; - nop(); // NOP to avoid CPU hickup during oscillator stabilization + 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 @@ -268,13 +246,15 @@ static inline void leaveBootloader(void) { } #endif - asm volatile ("rjmp __vectors - 4"); // jump to application reset vector at end of flash + asm volatile ("rjmp __vectors - 2"); // jump to application reset vector at end of flash for (;;); // Make sure function does not return to help compiler optimize } +void USB_INTR_VECTOR(void); + int main(void) { - + uint8_t ackSent=0; bootLoaderInit(); DDRB|=3; @@ -291,6 +271,8 @@ int main(void) { } do { + + USB_INTR_PENDING = 1< Date: Sun, 5 Jan 2014 17:29:43 +0100 Subject: firmware: reset vector patching in commandline tool --- firmware/main.c | 64 +++++++++++++++++++-------------------------------------- 1 file changed, 21 insertions(+), 43 deletions(-) (limited to 'firmware/main.c') diff --git a/firmware/main.c b/firmware/main.c index 19ed37a..513a956 100644 --- a/firmware/main.c +++ b/firmware/main.c @@ -1,10 +1,10 @@ /* - * Project: Micronucleus - v1.11 - * - * Original author (c) 2012 Jenna Fox + * Project: Micronucleus - v2.0 * - * Optimizations v1.10/v1.11 (c) 2013 Tim Bo"scke - cpldcpu@gmail.com - * v1.11 (c) 2013 Shay Green + * Micronucleus V2.0 (c) 2014 Tim Bo"scke - cpldcpu@gmail.com + * V2.0 (c) 2014 Shay Green + * + * Original Micronucleus (c) 2012 Jenna Fox * * Based on USBaspLoader-tiny85 (c) 2012 Louis Beaudoin * Based on USBaspLoader (c) 2007 by OBJECTIVE DEVELOPMENT Software GmbH @@ -12,8 +12,8 @@ * License: GNU GPL v2 (see License.txt) */ -#define MICRONUCLEUS_VERSION_MAJOR 1 -#define MICRONUCLEUS_VERSION_MINOR 99 +#define MICRONUCLEUS_VERSION_MAJOR 2 +#define MICRONUCLEUS_VERSION_MINOR 0 // 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 5 @@ -47,8 +47,6 @@ register uint16_union_t idlePolls asm("r6"); // r6/r7 idlecounter register uint8_t osccal_default asm("r2"); #endif -static uint16_t vectorTemp; // remember data to create tinyVector table before BOOTLOADER_ADDRESS - enum { cmd_local_nop=0, // also: get device info cmd_device_info=0, @@ -88,7 +86,8 @@ static inline void eraseApplication(void) { ptr -= SPM_PAGESIZE; boot_page_erase(ptr); } - + + // Code size is increase if this stuff is removed?!? currentAddress.w = 0; for (i=0; i<8; i++) writeWordToPageBuffer(0xFFFF); // Write first 8 words to fill in vectors. writeFlashPage(); @@ -118,33 +117,25 @@ static inline void writeFlashPage(void) { // write a word in to the page buffer, doing interrupt table modifications where they're required static void writeWordToPageBuffer(uint16_t data) { - // first two interrupt vectors get replaced with a jump to the bootloader's vector table - // remember vectors or the tinyvector table + // Patch the bootloader reset vector into the main vectortable to ensure + // the device can not be bricked. + // Saving user-reset-vector is done in the host tool, starting with + // firmware V2 + if (currentAddress.w == RESET_VECTOR_OFFSET * 2) { - vectorTemp = 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.w == BOOTLOADER_ADDRESS - TINYVECTOR_RESET_OFFSET) { - data = vectorTemp + ((FLASHEND + 1) - BOOTLOADER_ADDRESS)/2 + 2 + RESET_VECTOR_OFFSET; - #if (!OSCCAL_RESTORE) && OSCCAL_16_5MHz - } else if (currentAddress.w == BOOTLOADER_ADDRESS - TINYVECTOR_OSCCAL_OFFSET) { + if (currentAddress.w == BOOTLOADER_ADDRESS - TINYVECTOR_OSCCAL_OFFSET) { data = OSCCAL; -#endif - } - - // previous_sreg=SREG; - // cli(); // ensure interrupts are disabled + } +#endif 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. @@ -216,8 +207,6 @@ static void initHardware (void) // Todo: timeout if no reset is found calibrateOscillatorASM(); usbInit(); // Initialize INT settings after reconnect - - // sei(); } /* ------------------------------------------------------------------------ */ @@ -301,13 +290,12 @@ int main(void) { // an ACK packet by the client (10.5µs on D+) but not as long as bus // time out (12µs) // - // TODO: Fix usb receiver to discard DATA1/0 packets without preceding OUT or SETUP + // TODO: Fix usb receiver to ignore DATA1/0 packets without preceding OUT or SETUP - if (USB_INTR_PENDING & (1< Date: Sun, 5 Jan 2014 19:35:47 +0100 Subject: commandline: Support for new v2 transmission protocol The block transfer is now done in the address and indexfield of a setup-packet to save a lot of memory in the firmware: This requires twice the number of transmissions, but is effectively faster due to less bus congestion and resends. --- firmware/main.c | 226 ++++++++++++++++++++++++++------------------------------ 1 file changed, 106 insertions(+), 120 deletions(-) (limited to 'firmware/main.c') diff --git a/firmware/main.c b/firmware/main.c index 513a956..246b2f9 100644 --- a/firmware/main.c +++ b/firmware/main.c @@ -52,6 +52,7 @@ enum { cmd_device_info=0, cmd_transfer_page=1, cmd_erase_application=2, + cmd_write_data=3, cmd_exit=4, cmd_write_page=64, // internal commands start at 64 }; @@ -66,9 +67,21 @@ static inline void eraseApplication(void); static void writeFlashPage(void); static void writeWordToPageBuffer(uint16_t data); static uint8_t usbFunctionSetup(uint8_t data[8]); -static uint8_t usbFunctionWrite(uint8_t *data, uint8_t length); static inline void leaveBootloader(void); +// 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))) \ + ); \ +})) + // erase any existing application and write in jumps for usb interrupt and reset to bootloader // - Because flash can be erased once and programmed several times, we can write the bootloader // - vectors in now, and write in the application stuff around them later. @@ -86,34 +99,24 @@ static inline void eraseApplication(void) { ptr -= SPM_PAGESIZE; boot_page_erase(ptr); } + + // 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(); - // Code size is increase if this stuff is removed?!? - currentAddress.w = 0; - for (i=0; i<8; i++) writeWordToPageBuffer(0xFFFF); // Write first 8 words to fill in vectors. - writeFlashPage(); + // Write reset vector into first page. + currentAddress.w = 0; + writeWordToPageBuffer(0xffff); + command=cmd_write_page; } // simply write currently stored page in to already erased flash memory static inline void writeFlashPage(void) { - - boot_page_write(currentAddress.w - 2); // will halt CPU, no waiting required - + if (currentAddress.w<=BOOTLOADER_ADDRESS) + boot_page_write(currentAddress.w - 2); // will halt CPU, no waiting required } -// 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))) \ - ); \ -})) - // write a word in to the page buffer, doing interrupt table modifications where they're required static void writeWordToPageBuffer(uint16_t data) { @@ -121,10 +124,10 @@ static void writeWordToPageBuffer(uint16_t data) { // the device can not be bricked. // Saving user-reset-vector is done in the host tool, starting with // firmware V2 - - if (currentAddress.w == RESET_VECTOR_OFFSET * 2) { - data = 0xC000 + (BOOTLOADER_ADDRESS/2) - 1; - } + + if (currentAddress.w == RESET_VECTOR_OFFSET * 2) { + data = 0xC000 + (BOOTLOADER_ADDRESS/2) - 1; + } #if (!OSCCAL_RESTORE) && OSCCAL_16_5MHz if (currentAddress.w == BOOTLOADER_ADDRESS - TINYVECTOR_OSCCAL_OFFSET) { @@ -134,58 +137,41 @@ static void writeWordToPageBuffer(uint16_t data) { boot_page_fill(currentAddress.w, data); - // increment progmem address by one word currentAddress.w += 2; } // 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 uint8_t usbFunctionSetup(uint8_t data[8]) { - usbRequest_t *rq = (void *)data; - - static uint8_t replyBuffer[4] = { + PROGMEM const uint8_t replyBuffer[4] = { (((uint16_t)PROGMEM_SIZE) >> 8) & 0xff, ((uint16_t)PROGMEM_SIZE) & 0xff, SPM_PAGESIZE, MICRONUCLEUS_WRITE_SLEEP - }; + }; + +/* ------------------------------------------------------------------------ */ +static uint8_t usbFunctionSetup(uint8_t data[8]) { + usbRequest_t *rq = (void *)data; + idlePolls.b[1]=0; // reset idle polls when we get usb traffic if (rq->bRequest == cmd_device_info) { // get device info - usbMsgPtr = replyBuffer; + usbMsgPtr = (usbMsgPtr_t)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 if (rq->bRequest == cmd_transfer_page) { // initialize write page + currentAddress.w = rq->wIndex.word; + } else if (rq->bRequest == cmd_write_data) { // Write data + writeWordToPageBuffer(rq->wValue.word); + writeWordToPageBuffer(rq->wIndex.word); + if ((currentAddress.b[0] % SPM_PAGESIZE) == 0) + command=cmd_write_page; // ask runloop to write our page } 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 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; - - 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 + return 0; } static void initHardware (void) @@ -235,13 +221,12 @@ static inline void leaveBootloader(void) { } #endif - asm volatile ("rjmp __vectors - 2"); // jump to application reset vector at end of flash + asm volatile ("rjmp __vectors - 2"); // jump to application reset vector at end of flash - for (;;); // Make sure function does not return to help compiler optimize + for (;;); // Make sure function does not return to help compiler optimize } void USB_INTR_VECTOR(void); - int main(void) { uint8_t ackSent=0; bootLoaderInit(); @@ -263,73 +248,74 @@ int main(void) { USB_INTR_PENDING = 1< Date: Sun, 5 Jan 2014 21:50:58 +0100 Subject: firmware: clean up of main.c --- firmware/main.c | 63 +++++++++++++++++++++++++++++++-------------------------- 1 file changed, 34 insertions(+), 29 deletions(-) (limited to 'firmware/main.c') diff --git a/firmware/main.c b/firmware/main.c index 246b2f9..6bcc642 100644 --- a/firmware/main.c +++ b/firmware/main.c @@ -1,9 +1,8 @@ /* * Project: Micronucleus - v2.0 * - * Micronucleus V2.0 (c) 2014 Tim Bo"scke - cpldcpu@gmail.com - * V2.0 (c) 2014 Shay Green - * + * Micronucleus V2.0 (c) 2014 Tim Bo"scke - cpldcpu@gmail.com + * (c) 2014 Shay Green * Original Micronucleus (c) 2012 Jenna Fox * * Based on USBaspLoader-tiny85 (c) 2012 Louis Beaudoin @@ -14,11 +13,6 @@ #define MICRONUCLEUS_VERSION_MAJOR 2 #define MICRONUCLEUS_VERSION_MINOR 0 -// 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 5 -// Use the old delay routines without NOP padding. This saves memory. -#define __DELAY_BACKWARD_COMPATIBLE__ #include #include @@ -29,6 +23,10 @@ #include "bootloaderconfig.h" #include "usbdrv/usbdrv.c" +// 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 5 + // 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" @@ -38,15 +36,28 @@ #error "Micronucleus only supports pagesizes up to 256 bytes" #endif -// 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 +// Device configuration reply +// Length: 4 bytes +// Byte 0: User program memory size, high byte +// Byte 1: User program memory size, low byte +// Byte 2: Flash Pagesize in bytes +// Byte 3: Page write timing in ms + +PROGMEM const uint8_t configurationReply[4] = { + (((uint16_t)PROGMEM_SIZE) >> 8) & 0xff, + ((uint16_t)PROGMEM_SIZE) & 0xff, + SPM_PAGESIZE, + MICRONUCLEUS_WRITE_SLEEP +}; #if OSCCAL_RESTORE register uint8_t osccal_default asm("r2"); #endif +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 + +// command system schedules functions to run in the main loop enum { cmd_local_nop=0, // also: get device info cmd_device_info=0, @@ -56,12 +67,16 @@ enum { cmd_exit=4, cmd_write_page=64, // internal commands start at 64 }; +register uint8_t command asm("r3"); // bind command to r3 // 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") +// Use the old delay routines without NOP padding. This saves memory. +#define __DELAY_BACKWARD_COMPATIBLE__ + /* ------------------------------------------------------------------------ */ static inline void eraseApplication(void); static void writeFlashPage(void); @@ -69,6 +84,9 @@ static void writeWordToPageBuffer(uint16_t data); static uint8_t usbFunctionSetup(uint8_t data[8]); static inline void leaveBootloader(void); +// This function is never called, it is just here to suppress a compiler warning. +USB_PUBLIC usbMsgLen_t usbFunctionDescriptor(struct usbRequest *rq) { return 0; } + // clear memory which stores data to be written by next writeFlashPage call #define __boot_page_fill_clear() \ (__extension__({ \ @@ -108,16 +126,15 @@ static inline void eraseApplication(void) { currentAddress.w = 0; writeWordToPageBuffer(0xffff); command=cmd_write_page; - } - +} // simply write currently stored page in to already erased flash memory static inline void writeFlashPage(void) { if (currentAddress.w<=BOOTLOADER_ADDRESS) - boot_page_write(currentAddress.w - 2); // will halt CPU, no waiting required + boot_page_write(currentAddress.w - 2); // will halt CPU, no waiting required } -// write a word in to the page buffer, doing interrupt table modifications where they're required +// write a word into the page buffer, doing interrupt table modifications where they're required static void writeWordToPageBuffer(uint16_t data) { // Patch the bootloader reset vector into the main vectortable to ensure @@ -136,21 +153,9 @@ static void writeWordToPageBuffer(uint16_t data) { #endif boot_page_fill(currentAddress.w, data); - currentAddress.w += 2; } -// This function is never called, it is just here to suppress a compiler warning. -USB_PUBLIC usbMsgLen_t usbFunctionDescriptor(struct usbRequest *rq) { return 0; } - - PROGMEM const uint8_t replyBuffer[4] = { - (((uint16_t)PROGMEM_SIZE) >> 8) & 0xff, - ((uint16_t)PROGMEM_SIZE) & 0xff, - SPM_PAGESIZE, - MICRONUCLEUS_WRITE_SLEEP - }; - - /* ------------------------------------------------------------------------ */ static uint8_t usbFunctionSetup(uint8_t data[8]) { usbRequest_t *rq = (void *)data; @@ -158,7 +163,7 @@ static uint8_t usbFunctionSetup(uint8_t data[8]) { idlePolls.b[1]=0; // reset idle polls when we get usb traffic if (rq->bRequest == cmd_device_info) { // get device info - usbMsgPtr = (usbMsgPtr_t)replyBuffer; + usbMsgPtr = (usbMsgPtr_t)configurationReply; return 4; } else if (rq->bRequest == cmd_transfer_page) { // initialize write page currentAddress.w = rq->wIndex.word; -- cgit v1.2.3 From 5f2ae4c3f0e3a8d7e5a1379f6dfebe3d38351ee6 Mon Sep 17 00:00:00 2001 From: cpldcpu Date: Sun, 5 Jan 2014 23:15:15 +0100 Subject: firmware: clean up --- firmware/main.c | 150 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 75 insertions(+), 75 deletions(-) (limited to 'firmware/main.c') diff --git a/firmware/main.c b/firmware/main.c index 6bcc642..9f0ebe5 100644 --- a/firmware/main.c +++ b/firmware/main.c @@ -130,7 +130,7 @@ static inline void eraseApplication(void) { // simply write currently stored page in to already erased flash memory static inline void writeFlashPage(void) { - if (currentAddress.w<=BOOTLOADER_ADDRESS) + if (currentAddress.w - 2 bRequest == cmd_device_info) { // get device info usbMsgPtr = (usbMsgPtr_t)configurationReply; - return 4; + return sizeof(configurationReply); } else if (rq->bRequest == cmd_transfer_page) { // initialize write page currentAddress.w = rq->wIndex.word; } else if (rq->bRequest == cmd_write_data) { // Write data @@ -250,80 +250,80 @@ int main(void) { } do { - - USB_INTR_PENDING = 1< Date: Mon, 6 Jan 2014 00:47:20 +0100 Subject: firmware: readded timout-loop --- firmware/main.c | 63 ++++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 40 insertions(+), 23 deletions(-) (limited to 'firmware/main.c') diff --git a/firmware/main.c b/firmware/main.c index 9f0ebe5..c15afab 100644 --- a/firmware/main.c +++ b/firmware/main.c @@ -196,7 +196,7 @@ static void initHardware (void) usbDeviceConnect(); // Todo: timeout if no reset is found - calibrateOscillatorASM(); + // calibrateOscillatorASM(); usbInit(); // Initialize INT settings after reconnect } @@ -248,21 +248,44 @@ int main(void) { } else { idlePolls.b[1]=0; } - do { - - USB_INTR_PENDING = 1< Date: Mon, 6 Jan 2014 01:59:31 +0100 Subject: firmware: single buffer usb 1578 bytes, yay.. --- firmware/main.c | 46 +++++++++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 19 deletions(-) (limited to 'firmware/main.c') diff --git a/firmware/main.c b/firmware/main.c index c15afab..be31176 100644 --- a/firmware/main.c +++ b/firmware/main.c @@ -92,13 +92,15 @@ USB_PUBLIC usbMsgLen_t usbFunctionDescriptor(struct usbRequest *rq) { return 0; (__extension__({ \ __asm__ __volatile__ \ ( \ - "sts %0, %1\n\t" \ + "out %0, %1\n\t" \ "spm\n\t" \ : \ - : "i" (_SFR_MEM_ADDR(__SPM_REG)), \ + : "i" (_SFR_IO_ADDR(__SPM_REG)), \ "r" ((uint8_t)(__BOOT_PAGE_FILL | (1 << CTPB))) \ ); \ })) +// : "i" (_SFR_MEM_ADDR(__SPM_REG)), \ + // erase any existing application and write in jumps for usb interrupt and reset to bootloader // - Because flash can be erased once and programmed several times, we can write the bootloader @@ -109,8 +111,7 @@ static inline void eraseApplication(void) { // 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; while (ptr) { @@ -194,9 +195,7 @@ static void initHardware (void) usbDeviceDisconnect(); /* do this while interrupts are disabled */ _delay_ms(300); usbDeviceConnect(); - - // Todo: timeout if no reset is found - // calibrateOscillatorASM(); + usbInit(); // Initialize INT settings after reconnect } @@ -248,6 +247,7 @@ int main(void) { } else { idlePolls.b[1]=0; } + do { // 15 clockcycles per loop. // adjust fastctr for 1ms timeout @@ -274,18 +274,31 @@ int main(void) { PORTB|=_BV(PB1); command=cmd_local_nop; - usbPoll(); - + { + // This is usbpoll() minus reset logic and double buffering + int8_t len; + len = usbRxLen - 3; + if(len >= 0){ + usbProcessRx(usbRxBuf + 1, len); // only single buffer due to in-order processing + usbRxLen = 0; /* mark rx buffer as available */ + } + if(usbTxLen & 0x10){ /* transmit system idle */ + if(usbMsgLen != USB_NO_MSG){ /* transmit data pending? */ + usbBuildTxBlock(); + } + } + } + idlePolls.w++; - // Try to execute program when bootlodaer times out + // Try to execute program when bootloader times out if (AUTO_EXIT_MS&&(idlePolls.w==AUTO_EXIT_MS)) { if (pgm_read_byte(BOOTLOADER_ADDRESS - TINYVECTOR_RESET_OFFSET + 1)!=0xff) break; } LED_MACRO( idlePolls.b[1] ); - // Test whether another interrupt occured during the processing of USBpoll and commands. + // Test whether another interrupt occured during the processing of USBpoll and commands. // If yes, we missed a data packet on the bus. This is not a big issue, since // USB seems to allow timeout of up the two packets. (On my machine an USB // error is triggered after the third missed packet.) @@ -304,7 +317,7 @@ int main(void) { // time out (12µs) // - if (USB_INTR_PENDING & (1< Date: Sun, 2 Feb 2014 16:47:01 +0100 Subject: small fixed --- firmware/main.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'firmware/main.c') diff --git a/firmware/main.c b/firmware/main.c index be31176..aab826c 100644 --- a/firmware/main.c +++ b/firmware/main.c @@ -99,8 +99,6 @@ USB_PUBLIC usbMsgLen_t usbFunctionDescriptor(struct usbRequest *rq) { return 0; "r" ((uint8_t)(__BOOT_PAGE_FILL | (1 << CTPB))) \ ); \ })) -// : "i" (_SFR_MEM_ADDR(__SPM_REG)), \ - // erase any existing application and write in jumps for usb interrupt and reset to bootloader // - Because flash can be erased once and programmed several times, we can write the bootloader -- cgit v1.2.3 From d2cb718727dbe19f135dd51f1d9989d8cbcbc4f9 Mon Sep 17 00:00:00 2001 From: cpldcpu Date: Sun, 2 Feb 2014 18:34:47 +0100 Subject: firmware: added OSCCAL_HAVE_XTAL --- firmware/main.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'firmware/main.c') diff --git a/firmware/main.c b/firmware/main.c index aab826c..5381cee 100644 --- a/firmware/main.c +++ b/firmware/main.c @@ -259,7 +259,9 @@ int main(void) { if (!--resetctr) { // reset encountered usbNewDeviceAddr = 0; // bits from the reset handling of usbpoll() usbDeviceAddr = 0; +#if (OSCCAL_HAVE_XTAL == 0) calibrateOscillatorASM(); +#endif } if (USB_INTR_PENDING & (1< Date: Mon, 3 Feb 2014 18:39:10 +0100 Subject: firmware: removed debugging output, clean up logic --- firmware/main.c | 53 +++++++++++++++++++++-------------------------------- 1 file changed, 21 insertions(+), 32 deletions(-) (limited to 'firmware/main.c') diff --git a/firmware/main.c b/firmware/main.c index 5381cee..f85e9ca 100644 --- a/firmware/main.c +++ b/firmware/main.c @@ -204,7 +204,7 @@ static inline void leaveBootloader(void) { bootLoaderExit(); - _delay_ms(10); // Bus needs to see a few more SOFs before it can be disconnected + // _delay_ms(10); // Bus needs to see a few more SOFs before it can be disconnected usbDeviceDisconnect(); /* Disconnect micronucleus */ USB_INTR_ENABLE = 0; @@ -230,27 +230,26 @@ static inline void leaveBootloader(void) { void USB_INTR_VECTOR(void); int main(void) { - uint8_t ackSent=0; bootLoaderInit(); - - DDRB|=3; - + if (bootLoaderStartCondition()||(pgm_read_byte(BOOTLOADER_ADDRESS - TINYVECTOR_RESET_OFFSET + 1)==0xff)) { initHardware(); LED_INIT(); if (AUTO_EXIT_NO_USB_MS>0) { - idlePolls.b[1]=((AUTO_EXIT_MS-AUTO_EXIT_NO_USB_MS) * 10UL)>>8; + idlePolls.b[1]=((AUTO_EXIT_MS-AUTO_EXIT_NO_USB_MS)/5)>>8; } else { idlePolls.b[1]=0; } + command=cmd_local_nop; + do { // 15 clockcycles per loop. - // adjust fastctr for 1ms timeout + // adjust fastctr for 5ms timeout - uint16_t fastctr=(uint16_t)(F_CPU/(1000.0f*15.0f)); + uint16_t fastctr=(uint16_t)(F_CPU/(1000.0f*15.0f/5.0f)); uint8_t resetctr=20; do { @@ -270,8 +269,15 @@ int main(void) { } } while(--fastctr); - - PORTB|=_BV(PB1); + + // commands are only evaluated after next USB transmission or after 5ms passed + if (command==cmd_exit) break; + if (command==cmd_erase_application) + eraseApplication(); + // Attention: eraseApplication will set command=cmd_write_page! + if (command==cmd_write_page) + writeFlashPage(); + command=cmd_local_nop; { @@ -292,16 +298,15 @@ int main(void) { idlePolls.w++; // Try to execute program when bootloader times out - if (AUTO_EXIT_MS&&(idlePolls.w==AUTO_EXIT_MS)) { + if (AUTO_EXIT_MS&&(idlePolls.w==(AUTO_EXIT_MS/5))) { if (pgm_read_byte(BOOTLOADER_ADDRESS - TINYVECTOR_RESET_OFFSET + 1)!=0xff) break; } LED_MACRO( idlePolls.b[1] ); - // Test whether another interrupt occured during the processing of USBpoll and commands. + // Test whether another interrupt occurred during the processing of USBpoll and commands. // If yes, we missed a data packet on the bus. This is not a big issue, since - // USB seems to allow timeout of up the two packets. (On my machine an USB - // error is triggered after the third missed packet.) + // USB seems to allow time-out of up the two packets. // The most critical situation occurs when a PID IN packet is missed due to // it's short length. Each packet + timeout takes around 45µs, meaning that // usbpoll must take less than 90µs or resyncing is not possible. @@ -319,7 +324,6 @@ int main(void) { if (USB_INTR_PENDING & (1< Date: Tue, 18 Feb 2014 13:17:51 +0100 Subject: firmware: update reset offset to -4 --- firmware/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'firmware/main.c') diff --git a/firmware/main.c b/firmware/main.c index f85e9ca..981c8c1 100644 --- a/firmware/main.c +++ b/firmware/main.c @@ -223,7 +223,7 @@ static inline void leaveBootloader(void) { } #endif - asm volatile ("rjmp __vectors - 2"); // jump to application reset vector at end of flash + 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 } -- cgit v1.2.3 From b3c8d599b16f50aff21938c4ee0dcf3d4a0e47d4 Mon Sep 17 00:00:00 2001 From: cpldcpu Date: Tue, 18 Feb 2014 14:35:33 +0100 Subject: firmware: far jmp support for reset vector patching --- firmware/main.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'firmware/main.c') diff --git a/firmware/main.c b/firmware/main.c index 981c8c1..5342498 100644 --- a/firmware/main.c +++ b/firmware/main.c @@ -124,6 +124,9 @@ static inline void eraseApplication(void) { // Write reset vector into first page. currentAddress.w = 0; writeWordToPageBuffer(0xffff); +#if BOOTLOADER_ADDRESS >= 8192 + writeWordToPageBuffer(0xffff); // far jmp +#endif command=cmd_write_page; } @@ -140,10 +143,19 @@ static void writeWordToPageBuffer(uint16_t data) { // the device can not be bricked. // Saving user-reset-vector is done in the host tool, starting with // firmware V2 - +#if BOOTLOADER_ADDRESS < 8192 + // rjmp if (currentAddress.w == RESET_VECTOR_OFFSET * 2) { data = 0xC000 + (BOOTLOADER_ADDRESS/2) - 1; } +#else + // far jmp + if (currentAddress.w == RESET_VECTOR_OFFSET * 2) { + data = 0x940c; + } else if (currentAddress.w == (RESET_VECTOR_OFFSET +1 ) * 2) { + data = (BOOTLOADER_ADDRESS/2); + } +#endif #if (!OSCCAL_RESTORE) && OSCCAL_16_5MHz if (currentAddress.w == BOOTLOADER_ADDRESS - TINYVECTOR_OSCCAL_OFFSET) { -- cgit v1.2.3 From 9da799190ee21df5b48c425ac3df0d52613f0c95 Mon Sep 17 00:00:00 2001 From: cpldcpu Date: Thu, 20 Feb 2014 01:07:08 +0100 Subject: firmware: fixed LED --- firmware/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'firmware/main.c') diff --git a/firmware/main.c b/firmware/main.c index 5342498..b0aaa6b 100644 --- a/firmware/main.c +++ b/firmware/main.c @@ -314,7 +314,7 @@ int main(void) { if (pgm_read_byte(BOOTLOADER_ADDRESS - TINYVECTOR_RESET_OFFSET + 1)!=0xff) break; } - LED_MACRO( idlePolls.b[1] ); + LED_MACRO( idlePolls.b[0] ); // Test whether another interrupt occurred during the processing of USBpoll and commands. // If yes, we missed a data packet on the bus. This is not a big issue, since -- cgit v1.2.3 From eef6c9736b07669aee8965533dba963a363b8494 Mon Sep 17 00:00:00 2001 From: cpldcpu Date: Fri, 21 Feb 2014 07:25:00 +0100 Subject: firmware: Fix USB error during --run firmware will wait for 5ms after reception of last packet before exiting to user program. This prevents collisions with the last ACK from host PC. --- firmware/main.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'firmware/main.c') diff --git a/firmware/main.c b/firmware/main.c index b0aaa6b..cf61d3e 100644 --- a/firmware/main.c +++ b/firmware/main.c @@ -282,15 +282,18 @@ int main(void) { } while(--fastctr); - // commands are only evaluated after next USB transmission or after 5ms passed - if (command==cmd_exit) break; + // commands are only evaluated after next USB transmission or after 5 ms passed if (command==cmd_erase_application) eraseApplication(); // Attention: eraseApplication will set command=cmd_write_page! if (command==cmd_write_page) writeFlashPage(); - command=cmd_local_nop; + if (command==cmd_exit) { + if (!fastctr) break; // Only exit after 5 ms timeout + } else { + command=cmd_local_nop; + } { // This is usbpoll() minus reset logic and double buffering -- cgit v1.2.3 From 2297c1546d74a3cdeed5e3a3c064ca75f122ee4a Mon Sep 17 00:00:00 2001 From: cpldcpu Date: Fri, 21 Feb 2014 09:09:03 +0100 Subject: firmware: clean up configuration file --- firmware/main.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'firmware/main.c') diff --git a/firmware/main.c b/firmware/main.c index cf61d3e..d60798a 100644 --- a/firmware/main.c +++ b/firmware/main.c @@ -50,6 +50,11 @@ PROGMEM const uint8_t configurationReply[4] = { MICRONUCLEUS_WRITE_SLEEP }; + typedef union { + uint16_t w; + uint8_t b[2]; + } uint16_union_t; + #if OSCCAL_RESTORE register uint8_t osccal_default asm("r2"); #endif @@ -59,7 +64,7 @@ register uint16_union_t idlePolls asm("r6"); // r6/r7 idlecounter // command system schedules functions to run in the main loop enum { - cmd_local_nop=0, // also: get device info + cmd_local_nop=0, cmd_device_info=0, cmd_transfer_page=1, cmd_erase_application=2, @@ -216,8 +221,7 @@ static inline void leaveBootloader(void) { bootLoaderExit(); - // _delay_ms(10); // Bus needs to see a few more SOFs before it can be disconnected - usbDeviceDisconnect(); /* Disconnect micronucleus */ + usbDeviceDisconnect(); /* Disconnect micronucleus */ USB_INTR_ENABLE = 0; USB_INTR_CFG = 0; /* also reset config bits */ @@ -287,8 +291,7 @@ int main(void) { eraseApplication(); // Attention: eraseApplication will set command=cmd_write_page! if (command==cmd_write_page) - writeFlashPage(); - + writeFlashPage(); if (command==cmd_exit) { if (!fastctr) break; // Only exit after 5 ms timeout } else { -- cgit v1.2.3 From e41772b00b450ecb67a6d2e1eaf28647a9df4a54 Mon Sep 17 00:00:00 2001 From: cpldcpu Date: Fri, 21 Feb 2014 09:10:05 +0100 Subject: firmware: dont test for 00 in store calibration. This is an impossible combination and will never occur, so no testing is needed. Saves 4 bytes. --- firmware/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'firmware/main.c') diff --git a/firmware/main.c b/firmware/main.c index d60798a..59e4616 100644 --- a/firmware/main.c +++ b/firmware/main.c @@ -233,7 +233,7 @@ static inline void leaveBootloader(void) { // 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) { + if (stored_osc_calibration != 0xFF) { OSCCAL=stored_osc_calibration; nop(); } -- cgit v1.2.3 From 7a1beac2f9daf4e538453b86db2311f85038ccb0 Mon Sep 17 00:00:00 2001 From: cpldcpu Date: Thu, 6 Mar 2014 11:32:18 +0100 Subject: firmware: Extend timeout with any USB traffic, set timeout to 6s fixed weird bug with USB3.0 hub where MN is only recognized after 5s --- firmware/main.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'firmware/main.c') diff --git a/firmware/main.c b/firmware/main.c index 59e4616..1c265c7 100644 --- a/firmware/main.c +++ b/firmware/main.c @@ -176,8 +176,6 @@ static void writeWordToPageBuffer(uint16_t data) { static uint8_t usbFunctionSetup(uint8_t data[8]) { usbRequest_t *rq = (void *)data; - idlePolls.b[1]=0; // reset idle polls when we get usb traffic - if (rq->bRequest == cmd_device_info) { // get device info usbMsgPtr = (usbMsgPtr_t)configurationReply; return sizeof(configurationReply); @@ -281,7 +279,8 @@ int main(void) { if (USB_INTR_PENDING & (1< Date: Thu, 6 Mar 2014 12:43:22 +0100 Subject: firmware: move device configureation to header file --- firmware/main.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'firmware/main.c') diff --git a/firmware/main.c b/firmware/main.c index 1c265c7..2b9e359 100644 --- a/firmware/main.c +++ b/firmware/main.c @@ -23,10 +23,6 @@ #include "bootloaderconfig.h" #include "usbdrv/usbdrv.c" -// 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 5 - // 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" @@ -148,6 +144,7 @@ static void writeWordToPageBuffer(uint16_t data) { // the device can not be bricked. // Saving user-reset-vector is done in the host tool, starting with // firmware V2 + #if BOOTLOADER_ADDRESS < 8192 // rjmp if (currentAddress.w == RESET_VECTOR_OFFSET * 2) { -- cgit v1.2.3 From 37db25e1c902172be1abf883ec0aaa210a1e0f3d Mon Sep 17 00:00:00 2001 From: cpldcpu Date: Fri, 7 Mar 2014 11:03:56 +0100 Subject: firmware: Simplified memory corruption mechanism --- firmware/main.c | 61 +++++++++++++++++---------------------------------------- 1 file changed, 18 insertions(+), 43 deletions(-) (limited to 'firmware/main.c') diff --git a/firmware/main.c b/firmware/main.c index 2b9e359..7f200ee 100644 --- a/firmware/main.c +++ b/firmware/main.c @@ -88,47 +88,19 @@ static inline void leaveBootloader(void); // This function is never called, it is just here to suppress a compiler warning. USB_PUBLIC usbMsgLen_t usbFunctionDescriptor(struct usbRequest *rq) { return 0; } -// clear memory which stores data to be written by next writeFlashPage call -#define __boot_page_fill_clear() \ -(__extension__({ \ - __asm__ __volatile__ \ - ( \ - "out %0, %1\n\t" \ - "spm\n\t" \ - : \ - : "i" (_SFR_IO_ADDR(__SPM_REG)), \ - "r" ((uint8_t)(__BOOT_PAGE_FILL | (1 << CTPB))) \ - ); \ -})) - -// erase any existing application and write in jumps for usb interrupt and reset to bootloader -// - Because flash can be erased once and programmed several times, we can write the bootloader -// - vectors in now, and write in the application stuff around them later. -// - if vectors weren't written back in immediately, usb would fail. +// erase all pages until bootloader, in reverse order (so our vectors stay in place for as long as possible) +// to minimise the chance of leaving the device in a state where the bootloader wont run, if there's power failure +// during upload 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 - uint16_t ptr = BOOTLOADER_ADDRESS; while (ptr) { ptr -= SPM_PAGESIZE; boot_page_erase(ptr); } - - // 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(); - // Write reset vector into first page. - currentAddress.w = 0; - writeWordToPageBuffer(0xffff); -#if BOOTLOADER_ADDRESS >= 8192 - writeWordToPageBuffer(0xffff); // far jmp -#endif - command=cmd_write_page; + // Reset address to ensure the reset vector is written first. + currentAddress.w = 0; } // simply write currently stored page in to already erased flash memory @@ -137,14 +109,12 @@ static inline void writeFlashPage(void) { boot_page_write(currentAddress.w - 2); // will halt CPU, no waiting required } -// write a word into the page buffer, doing interrupt table modifications where they're required +// Write a word into the page buffer. +// Will patch the bootloader reset vector into the main vectortable to ensure +// the device can not be bricked. Saving user-reset-vector is done in the host +// tool, starting with firmware V2 static void writeWordToPageBuffer(uint16_t data) { - - // Patch the bootloader reset vector into the main vectortable to ensure - // the device can not be bricked. - // Saving user-reset-vector is done in the host tool, starting with - // firmware V2 - + #if BOOTLOADER_ADDRESS < 8192 // rjmp if (currentAddress.w == RESET_VECTOR_OFFSET * 2) { @@ -176,8 +146,13 @@ static uint8_t usbFunctionSetup(uint8_t data[8]) { if (rq->bRequest == cmd_device_info) { // get device info usbMsgPtr = (usbMsgPtr_t)configurationReply; return sizeof(configurationReply); - } else if (rq->bRequest == cmd_transfer_page) { // initialize write page - currentAddress.w = rq->wIndex.word; + } else if (rq->bRequest == cmd_transfer_page) { + // Set page address. Address zero always has to be written first to ensure reset vector patching. + // Mask to page boundary to prevent vulnerability to partial page write "attacks" + if ( currentAddress.w != 0 ) { + currentAddress.b[0]=rq->wIndex.bytes[0] & (~ (SPM_PAGESIZE-1)); + currentAddress.b[1]=rq->wIndex.bytes[1]; + } } else if (rq->bRequest == cmd_write_data) { // Write data writeWordToPageBuffer(rq->wValue.word); writeWordToPageBuffer(rq->wIndex.word); @@ -255,6 +230,7 @@ int main(void) { } command=cmd_local_nop; + currentAddress.w = 0; do { // 15 clockcycles per loop. @@ -285,7 +261,6 @@ int main(void) { // commands are only evaluated after next USB transmission or after 5 ms passed if (command==cmd_erase_application) eraseApplication(); - // Attention: eraseApplication will set command=cmd_write_page! if (command==cmd_write_page) writeFlashPage(); if (command==cmd_exit) { -- cgit v1.2.3 From 8bfc02c68ac25c42e64b7880132c1a19ef46f7da Mon Sep 17 00:00:00 2001 From: cpldcpu Date: Thu, 13 Mar 2014 09:50:37 +0100 Subject: firmware: added WDR --- firmware/main.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'firmware/main.c') diff --git a/firmware/main.c b/firmware/main.c index 7f200ee..486a26b 100644 --- a/firmware/main.c +++ b/firmware/main.c @@ -74,6 +74,7 @@ register uint8_t command asm("r3"); // bind command to r3 #define sei() asm volatile("sei") #define cli() asm volatile("cli") #define nop() asm volatile("nop") +#define wdr() asm volatile("wdr") // Use the old delay routines without NOP padding. This saves memory. #define __DELAY_BACKWARD_COMPATIBLE__ @@ -217,7 +218,7 @@ static inline void leaveBootloader(void) { void USB_INTR_VECTOR(void); int main(void) { bootLoaderInit(); - + if (bootLoaderStartCondition()||(pgm_read_byte(BOOTLOADER_ADDRESS - TINYVECTOR_RESET_OFFSET + 1)==0xff)) { initHardware(); @@ -258,6 +259,8 @@ int main(void) { } while(--fastctr); + wdr(); + // commands are only evaluated after next USB transmission or after 5 ms passed if (command==cmd_erase_application) eraseApplication(); -- cgit v1.2.3 From ca5b4534b621a0100cc5aa2ea9babb6f7bafd037 Mon Sep 17 00:00:00 2001 From: cpldcpu Date: Sun, 16 Mar 2014 08:13:29 +0100 Subject: firmware: Attiny841 first working config --- firmware/main.c | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'firmware/main.c') diff --git a/firmware/main.c b/firmware/main.c index 486a26b..13484d3 100644 --- a/firmware/main.c +++ b/firmware/main.c @@ -169,9 +169,16 @@ static uint8_t usbFunctionSetup(uint8_t data[8]) { static void initHardware (void) { // Disable watchdog and set timeout to maximum in case the WDT is fused on +#ifdef WDTCSR + // New ATtinies841/441 use a different unlock sequence and renamed registers + MCUSR=0; + CCP = 0xD8; + WDTCSR = 1< Date: Sun, 16 Mar 2014 08:23:24 +0100 Subject: firmware: page erase timing modification for ATtiny841 --- firmware/main.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'firmware/main.c') diff --git a/firmware/main.c b/firmware/main.c index 13484d3..187ef54 100644 --- a/firmware/main.c +++ b/firmware/main.c @@ -37,13 +37,15 @@ // Byte 0: User program memory size, high byte // Byte 1: User program memory size, low byte // Byte 2: Flash Pagesize in bytes -// Byte 3: Page write timing in ms +// Byte 3: Page write timing in ms. +// Bit 7 '0': Page erase time equals page write time +// Bit 7 '1': Page erase time equals page write time divided by 4 PROGMEM const uint8_t configurationReply[4] = { (((uint16_t)PROGMEM_SIZE) >> 8) & 0xff, ((uint16_t)PROGMEM_SIZE) & 0xff, SPM_PAGESIZE, - MICRONUCLEUS_WRITE_SLEEP + MICRONUCLEUS_WRITE_SLEEP }; typedef union { -- cgit v1.2.3 From 8773da4c5ff13ced369e8bc169956dd36a613918 Mon Sep 17 00:00:00 2001 From: cpldcpu Date: Sun, 16 Mar 2014 09:16:27 +0100 Subject: firmware: ATtiny841 - proper page erasing --- firmware/main.c | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) (limited to 'firmware/main.c') diff --git a/firmware/main.c b/firmware/main.c index 187ef54..8a86b33 100644 --- a/firmware/main.c +++ b/firmware/main.c @@ -24,8 +24,14 @@ #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" +#if (defined __AVR_ATtiny841__)||(defined __AVR_ATtiny441__) + #if BOOTLOADER_ADDRESS % ( SPM_PAGESIZE * 4 ) != 0 + #error "BOOTLOADER_ADDRESS in makefile must be a multiple of chip's pagesize" + #endif +#else + #if BOOTLOADER_ADDRESS % SPM_PAGESIZE != 0 + #error "BOOTLOADER_ADDRESS in makefile must be a multiple of chip's pagesize" + #endif #endif #if SPM_PAGESIZE>256 @@ -98,7 +104,11 @@ static inline void eraseApplication(void) { uint16_t ptr = BOOTLOADER_ADDRESS; while (ptr) { +#if (defined __AVR_ATtiny841__)||(defined __AVR_ATtiny441__) + ptr -= SPM_PAGESIZE * 4; +#else ptr -= SPM_PAGESIZE; +#endif boot_page_erase(ptr); } @@ -306,22 +316,8 @@ int main(void) { LED_MACRO( idlePolls.b[0] ); // Test whether another interrupt occurred during the processing of USBpoll and commands. - // If yes, we missed a data packet on the bus. This is not a big issue, since - // USB seems to allow time-out of up the two packets. - // The most critical situation occurs when a PID IN packet is missed due to - // it's short length. Each packet + timeout takes around 45µs, meaning that - // usbpoll must take less than 90µs or resyncing is not possible. - // To avoid synchronizing of the interrupt routine, we must not call it while - // a packet is transmitted. Therefore we have to wait until the bus is idle again. - // - // Just waiting for EOP (SE0) or no activity for 6 bus cycles is not enough, - // as the host may have been sending a multi-packet transmission (eg. OUT or SETUP) - // In that case we may resynch within a transmission, causing errors. - // - // A safer way is to wait until the bus was idle for the time it takes to send - // an ACK packet by the client (10.5µs on D+) but not as long as bus - // time out (12µs) - // + // If yes, we missed a data packet on the bus. Wait until the bus was idle for 10µs to + // allow synchronising to the next incoming packet. if (USB_INTR_PENDING & (1< Date: Sun, 16 Mar 2014 17:57:32 +0100 Subject: firmware: config update t841,t85 --- firmware/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'firmware/main.c') diff --git a/firmware/main.c b/firmware/main.c index 8a86b33..63979ac 100644 --- a/firmware/main.c +++ b/firmware/main.c @@ -181,7 +181,7 @@ static uint8_t usbFunctionSetup(uint8_t data[8]) { static void initHardware (void) { // Disable watchdog and set timeout to maximum in case the WDT is fused on -#ifdef WDTCSR +#ifdef CCP // New ATtinies841/441 use a different unlock sequence and renamed registers MCUSR=0; CCP = 0xD8; -- cgit v1.2.3 From 0b653a2d0d74c1ad0ed6e2d0b54cdf6e46ab67a4 Mon Sep 17 00:00:00 2001 From: cpldcpu Date: Thu, 1 May 2014 19:14:52 +0200 Subject: firmware: Added signature to configurationreply --- firmware/main.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'firmware/main.c') diff --git a/firmware/main.c b/firmware/main.c index 63979ac..4d51c10 100644 --- a/firmware/main.c +++ b/firmware/main.c @@ -39,19 +39,23 @@ #endif // Device configuration reply -// Length: 4 bytes +// Length: 6 bytes // Byte 0: User program memory size, high byte // Byte 1: User program memory size, low byte // Byte 2: Flash Pagesize in bytes // Byte 3: Page write timing in ms. // Bit 7 '0': Page erase time equals page write time // Bit 7 '1': Page erase time equals page write time divided by 4 +// Byte 4: SIGNATURE_1 +// Byte 5: SIGNATURE_2 -PROGMEM const uint8_t configurationReply[4] = { +PROGMEM const uint8_t configurationReply[6] = { (((uint16_t)PROGMEM_SIZE) >> 8) & 0xff, ((uint16_t)PROGMEM_SIZE) & 0xff, SPM_PAGESIZE, - MICRONUCLEUS_WRITE_SLEEP + MICRONUCLEUS_WRITE_SLEEP, + SIGNATURE_1, + SIGNATURE_2 }; typedef union { @@ -74,7 +78,7 @@ enum { cmd_erase_application=2, cmd_write_data=3, cmd_exit=4, - cmd_write_page=64, // internal commands start at 64 + cmd_write_page=64 // internal commands start at 64 }; register uint8_t command asm("r3"); // bind command to r3 -- cgit v1.2.3 From 37bbaa85b6752c5f54fa6f026c1c8ade5e984347 Mon Sep 17 00:00:00 2001 From: cpldcpu Date: Sun, 22 Jun 2014 22:26:22 +0200 Subject: firmware: Fix loophole in protocol This fix prevents the host program from issuing page writes without transferring data first. --- firmware/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'firmware/main.c') diff --git a/firmware/main.c b/firmware/main.c index 4d51c10..6a091a3 100644 --- a/firmware/main.c +++ b/firmware/main.c @@ -177,7 +177,7 @@ static uint8_t usbFunctionSetup(uint8_t data[8]) { command=cmd_write_page; // ask runloop to write our page } else { // Handle cmd_erase_application and cmd_exit - command=rq->bRequest; + command=rq->bRequest&0x3f; } return 0; } -- cgit v1.2.3 From dd4264ead7cdf63af84e68eaeb9470bee6f6b147 Mon Sep 17 00:00:00 2001 From: cpldcpu Date: Sun, 22 Jun 2014 23:06:20 +0200 Subject: firmware: Changed handling of OSCCAL calibration If the calibrated OSCAL was saved during programming it will be reloaded before starting the bootloader, regardless whether it was entered or not. This allows calibrating devices without PLL to 12 MHz. --- firmware/main.c | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) (limited to 'firmware/main.c') diff --git a/firmware/main.c b/firmware/main.c index 6a091a3..625f026 100644 --- a/firmware/main.c +++ b/firmware/main.c @@ -146,7 +146,7 @@ static void writeWordToPageBuffer(uint16_t data) { } #endif -#if (!OSCCAL_RESTORE) && OSCCAL_16_5MHz +#if OSCCAL_SAVE_CALIB if (currentAddress.w == BOOTLOADER_ADDRESS - TINYVECTOR_OSCCAL_OFFSET) { data = OSCCAL; } @@ -196,11 +196,21 @@ static void initHardware (void) WDTCR = 1< Date: Thu, 3 Jul 2014 22:41:08 +0200 Subject: firmware: Stored OSCCAL can now be used by userprogram --- firmware/main.c | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) (limited to 'firmware/main.c') diff --git a/firmware/main.c b/firmware/main.c index 625f026..0de341a 100644 --- a/firmware/main.c +++ b/firmware/main.c @@ -196,20 +196,6 @@ static void initHardware (void) WDTCR = 1< Date: Mon, 14 Jul 2014 21:56:25 +0200 Subject: firmware: Fix corruption of USB pins when bootloader is not activated see: https://github.com/micronucleus/micronucleus/issues/52 --- firmware/main.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'firmware/main.c') diff --git a/firmware/main.c b/firmware/main.c index 0de341a..194156c 100644 --- a/firmware/main.c +++ b/firmware/main.c @@ -211,11 +211,6 @@ static inline void leaveBootloader(void) { bootLoaderExit(); - usbDeviceDisconnect(); /* Disconnect micronucleus */ - - USB_INTR_ENABLE = 0; - USB_INTR_CFG = 0; /* also reset config bits */ - #if OSCCAL_RESTORE_DEFAULT OSCCAL=osccal_default; nop(); // NOP to avoid CPU hickup during oscillator stabilization @@ -344,6 +339,11 @@ int main(void) { } while(1); LED_EXIT(); + + usbDeviceDisconnect(); /* Disconnect micronucleus */ + USB_INTR_ENABLE = 0; + USB_INTR_CFG = 0; /* also reset config bits */ + } leaveBootloader(); -- cgit v1.2.3 From 42f2d8cc38064c67a9478826f563f5110c3848ee Mon Sep 17 00:00:00 2001 From: cpldcpu Date: Mon, 12 Jan 2015 00:46:06 +0100 Subject: #ENABLE_UNSAFE_OPTIMIZATIONS Added new global flag to enable unsafe optimizations: This will disable several safety features in microncleus to save around 40 more bytes Disabled features: * Stack pointer and SREG initialization in CRT * Client side reset vector patching * USB collision detection. Micronucleus will not work reliability with hubs if this is disabled. See t85_aggressive configuration for usage examples. --- firmware/main.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'firmware/main.c') diff --git a/firmware/main.c b/firmware/main.c index 194156c..0cd0d26 100644 --- a/firmware/main.c +++ b/firmware/main.c @@ -131,19 +131,21 @@ static inline void writeFlashPage(void) { // the device can not be bricked. Saving user-reset-vector is done in the host // tool, starting with firmware V2 static void writeWordToPageBuffer(uint16_t data) { - -#if BOOTLOADER_ADDRESS < 8192 + +#ifndef ENABLE_UNSAFE_OPTIMIZATIONS + #if BOOTLOADER_ADDRESS < 8192 // rjmp if (currentAddress.w == RESET_VECTOR_OFFSET * 2) { data = 0xC000 + (BOOTLOADER_ADDRESS/2) - 1; } -#else + #else // far jmp if (currentAddress.w == RESET_VECTOR_OFFSET * 2) { data = 0x940c; } else if (currentAddress.w == (RESET_VECTOR_OFFSET +1 ) * 2) { data = (BOOTLOADER_ADDRESS/2); } + #endif #endif #if OSCCAL_SAVE_CALIB @@ -177,7 +179,7 @@ static uint8_t usbFunctionSetup(uint8_t data[8]) { command=cmd_write_page; // ask runloop to write our page } else { // Handle cmd_erase_application and cmd_exit - command=rq->bRequest&0x3f; + command=rq->bRequest&0x3f; } return 0; } @@ -278,7 +280,7 @@ int main(void) { } } while(--fastctr); - + wdr(); // commands are only evaluated after next USB transmission or after 5 ms passed -- cgit v1.2.3 From 49217b956286d37a89265120b6275a2a383c561d Mon Sep 17 00:00:00 2001 From: cpldcpu Date: Sun, 8 Feb 2015 16:05:55 +0100 Subject: firmware: Fixed bug with osccal_restore --- firmware/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'firmware/main.c') diff --git a/firmware/main.c b/firmware/main.c index 0cd0d26..403c4cc 100644 --- a/firmware/main.c +++ b/firmware/main.c @@ -63,7 +63,7 @@ PROGMEM const uint8_t configurationReply[6] = { uint8_t b[2]; } uint16_union_t; -#if OSCCAL_RESTORE +#if OSCCAL_RESTORE_DEFAULT register uint8_t osccal_default asm("r2"); #endif -- cgit v1.2.3 From e67eb5000ccc6befd04cd1cb8115b5c06e45a76e Mon Sep 17 00:00:00 2001 From: cpldcpu Date: Sat, 18 Apr 2015 12:05:40 +0200 Subject: firmware: emit error when auto_exit_ms is too low --- firmware/main.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'firmware/main.c') diff --git a/firmware/main.c b/firmware/main.c index 403c4cc..a6bb8c6 100644 --- a/firmware/main.c +++ b/firmware/main.c @@ -38,6 +38,10 @@ #error "Micronucleus only supports pagesizes up to 256 bytes" #endif +#if ((AUTO_EXIT_MS>0) && (AUTO_EXIT_MS<1000)) + #error "Do not set AUTO_EXIT_MS to below 1s to allow Micronucleus to function properly" +#endif + // Device configuration reply // Length: 6 bytes // Byte 0: User program memory size, high byte -- cgit v1.2.3 From 616f5810f1ba98f3f963e4e8b947ad3c6bd9ea57 Mon Sep 17 00:00:00 2001 From: cpldcpu Date: Sat, 30 May 2015 18:39:45 +0200 Subject: firmware: added OSCCAL_SLOW_PROGRAMMING --- firmware/main.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'firmware/main.c') diff --git a/firmware/main.c b/firmware/main.c index a6bb8c6..fdf22e4 100644 --- a/firmware/main.c +++ b/firmware/main.c @@ -229,6 +229,8 @@ static inline void leaveBootloader(void) { void USB_INTR_VECTOR(void); int main(void) { + uint8_t osccal_tmp; + bootLoaderInit(); /* save default OSCCAL calibration */ @@ -286,12 +288,22 @@ int main(void) { } while(--fastctr); wdr(); - + + #if OSCCAL_SLOW_PROGRAMMING + osccal_tmp = OSCCAL; + OSCCAL = osccal_default; + #endif // commands are only evaluated after next USB transmission or after 5 ms passed if (command==cmd_erase_application) eraseApplication(); if (command==cmd_write_page) writeFlashPage(); + #if OSCCAL_SLOW_PROGRAMMING + OSCCAL = osccal_tmp; + #endif + + + if (command==cmd_exit) { if (!fastctr) break; // Only exit after 5 ms timeout } else { -- cgit v1.2.3