uint8_t Checksum;
/** Starting address of the last addressed FLASH page. */
uint32_t PageStartAddress;
+ /** Current 32-bit byte extended base address in FLASH being targeted. */
+ uint32_t CurrBaseAddress;
/** Current 32-bit byte address in FLASH being targeted. */
uint32_t CurrAddress;
} HEXParser =
*/
static bool PageDirty = false;
+
+/** Flag to indicate if the bootloader should be running, or should exit and allow the application code to run
+ * via a soft reset. When cleared, the bootloader will abort, the USB interface will shut down and the application
+ * started via a forced watchdog reset.
+ */
+static bool RunBootloader = true;
+
+/** Magic lock for forced application start. If the HWBE fuse is programmed and BOOTRST is unprogrammed, the bootloader
+ * will start if the /HWB line of the AVR is held low and the system is reset. However, if the /HWB line is still held
+ * low when the application attempts to start via a watchdog reset, the bootloader will re-start. If set to the value
+ * \ref MAGIC_BOOT_KEY the special init function \ref Application_Jump_Check() will force the application to start.
+ */
+uint16_t MagicBootKey ATTR_NO_INIT;
+
+
+/** Special startup routine to check if the bootloader was started via a watchdog reset, and if the magic application
+ * start key has been loaded into \ref MagicBootKey. If the bootloader started via the watchdog and the key is valid,
+ * this will force the user application to start via a software jump.
+ */
+void Application_Jump_Check(void)
+{
+ /* If the reset source was the bootloader and the key is correct, clear it and jump to the application */
+ if ((MCUSR & (1 << WDRF)) && (MagicBootKey == MAGIC_BOOT_KEY))
+ {
+ MagicBootKey = 0;
+
+ // cppcheck-suppress constStatement
+ ((void (*)(void))0x0000)();
+ }
+}
+
/**
* Determines if a given input byte of data is an ASCII encoded HEX value.
*
*
* \param[in] Byte ASCII byte of data to check
*
- * \return Boolean \c true if the input data is ASCII encoded HEX, false otherwise.
+ * \return Boolean \c true if the input data is ASCII encoded HEX, \c false otherwise.
*/
static bool IsHex(const char Byte)
{
*/
static void ParseIntelHEXByte(const char ReadCharacter)
{
+ /* Reset the line parser while waiting for a new line to start */
if ((HEXParser.ParserState == HEX_PARSE_STATE_WAIT_LINE) || (ReadCharacter == ':'))
{
HEXParser.Checksum = 0;
- HEXParser.CurrAddress &= ~0xFFFF;
+ HEXParser.CurrAddress = HEXParser.CurrBaseAddress;
HEXParser.ParserState = HEX_PARSE_STATE_WAIT_LINE;
HEXParser.ReadMSB = false;
+ /* ASCII ':' indicates the start of a new HEX record */
if (ReadCharacter == ':')
HEXParser.ParserState = HEX_PARSE_STATE_BYTE_COUNT;
return;
}
+ /* Only allow ASCII HEX encoded digits, ignore all other characters */
if (!IsHex(ReadCharacter))
return;
+ /* Read and convert the next nibble of data from the current character */
HEXParser.Data = (HEXParser.Data << 4) | HexToDecimal(ReadCharacter);
HEXParser.ReadMSB = !HEXParser.ReadMSB;
+ /* Only process further when a full byte (two nibbles) have been read */
if (HEXParser.ReadMSB)
return;
+ /* Intel HEX checksum is for all fields except starting character and the
+ * checksum itself
+ */
if (HEXParser.ParserState != HEX_PARSE_STATE_CHECKSUM)
HEXParser.Checksum += HEXParser.Data;
break;
case HEX_PARSE_STATE_ADDRESS_HIGH:
- HEXParser.CurrAddress |= ((uint16_t)HEXParser.Data << 8);
+ HEXParser.CurrAddress += ((uint16_t)HEXParser.Data << 8);
HEXParser.ParserState = HEX_PARSE_STATE_ADDRESS_LOW;
break;
case HEX_PARSE_STATE_ADDRESS_LOW:
- HEXParser.CurrAddress |= HEXParser.Data;
+ HEXParser.CurrAddress += HEXParser.Data;
HEXParser.ParserState = HEX_PARSE_STATE_RECORD_TYPE;
break;
break;
case HEX_PARSE_STATE_READ_DATA:
+ /* Track the number of read data bytes in the record */
HEXParser.DataRem--;
+ /* Protect the bootloader against being written to */
+ if (HEXParser.CurrAddress >= BOOT_START_ADDR)
+ {
+ HEXParser.ParserState = HEX_PARSE_STATE_WAIT_LINE;
+ PageDirty = false;
+ return;
+ }
+
+ /* Wait for a machine word (two bytes) of data to be read */
if (HEXParser.DataRem & 0x01)
{
HEXParser.PrevData = HEXParser.Data;
break;
}
+ /* Convert the last two received data bytes into a 16-bit word */
+ uint16_t NewDataWord = ((uint16_t)HEXParser.Data << 8) | HEXParser.PrevData;
+
switch (HEXParser.RecordType)
{
case HEX_RECORD_TYPE_Data:
+ /* If we are writing to a new page, we need to erase it
+ * first
+ */
if (!(PageDirty))
{
boot_page_erase(HEXParser.PageStartAddress);
PageDirty = true;
}
- boot_page_fill(HEXParser.CurrAddress, ((uint16_t)HEXParser.Data << 8) | HEXParser.PrevData);
+ /* Fill the FLASH memory buffer with the new word of data */
+ boot_page_fill(HEXParser.CurrAddress, NewDataWord);
HEXParser.CurrAddress += 2;
+ /* Flush the FLASH page to physical memory if we are crossing a page boundary */
uint32_t NewPageStartAddress = (HEXParser.CurrAddress & ~(SPM_PAGESIZE - 1));
if (PageDirty && (HEXParser.PageStartAddress != NewPageStartAddress))
{
}
break;
+ case HEX_RECORD_TYPE_ExtendedSegmentAddress:
+ /* Extended address data - store the upper 12-bits of the new address */
+ HEXParser.CurrBaseAddress = ((uint32_t)NewDataWord << 4);
+ break;
+
case HEX_RECORD_TYPE_ExtendedLinearAddress:
- HEXParser.CurrAddress |= (uint32_t)HEXParser.Data << (HEXParser.DataRem ? 24 : 16);
+ /* Extended address data - store the upper 16-bits of the new address */
+ HEXParser.CurrBaseAddress = ((uint32_t)NewDataWord << 16);
break;
}
break;
case HEX_PARSE_STATE_CHECKSUM:
+ /* Verify checksum of the completed record */
if (HEXParser.Data != ((~HEXParser.Checksum + 1) & 0xFF))
break;
+ /* Flush the FLASH page to physical memory if we are crossing a page boundary */
uint32_t NewPageStartAddress = (HEXParser.CurrAddress & ~(SPM_PAGESIZE - 1));
if (PageDirty && (HEXParser.PageStartAddress != NewPageStartAddress))
{
PageDirty = false;
}
+ /* If end of the HEX file reached, the bootloader should exit at next opportunity */
+ if (HEXParser.RecordType == HEX_RECORD_TYPE_EndOfFile)
+ RunBootloader = false;
+
break;
default:
LEDs_SetAllLEDs(LEDMASK_USB_NOTREADY);
GlobalInterruptEnable();
- for (;;)
+ while (RunBootloader)
{
USB_USBTask();
LEDs_SetAllLEDs(LEDMASK_USB_READY);
}
}
+
+ /* Disconnect from the host - USB interface will be reset later along with the AVR */
+ USB_Detach();
+
+ /* Unlock the forced application start mode of the bootloader if it is restarted */
+ MagicBootKey = MAGIC_BOOT_KEY;
+
+ /* Enable the watchdog and force a timeout to reset the AVR */
+ wdt_enable(WDTO_250MS);
+
+ for (;;);
}
/** Configures the board hardware and chip peripherals for the demo's functionality. */
-void SetupHardware(void)
+static void SetupHardware(void)
{
/* Disable watchdog if enabled by bootloader/fuses */
MCUSR &= ~(1 << WDRF);
/* Hardware Initialization */
LEDs_Init();
USB_Init();
+
+ /* Bootloader active LED toggle timer initialization */
+ TIMSK1 = (1 << TOIE1);
+ TCCR1B = ((1 << CS11) | (1 << CS10));
+}
+
+/** ISR to periodically toggle the LEDs on the board to indicate that the bootloader is active. */
+ISR(TIMER1_OVF_vect, ISR_BLOCK)
+{
+ LEDs_ToggleLEDs(LEDS_LED1 | LEDS_LED2);
}
/** Event handler for the USB_Connect event. This indicates that the device is enumerating via the status LEDs. */
}
/** Event handler for the USB_Disconnect event. This indicates that the device is no longer connected to a host via
- * the status LEDs and stops the Mass Storage management task.
+ * the status LEDs and stops the Printer management task.
*/
void EVENT_USB_Device_Disconnect(void)
{
"CLS:PRINTER";
Endpoint_ClearSETUP();
+
+ while (!(Endpoint_IsINReady()))
+ {
+ if (USB_DeviceState == DEVICE_STATE_Unattached)
+ return;
+ }
+
Endpoint_Write_16_BE(sizeof(PrinterIDString));
Endpoint_Write_Control_Stream_LE(PrinterIDString, strlen(PrinterIDString));
Endpoint_ClearStatusStage();
if (USB_ControlRequest.bmRequestType == (REQDIR_DEVICETOHOST | REQTYPE_CLASS | REQREC_INTERFACE))
{
Endpoint_ClearSETUP();
+
+ while (!(Endpoint_IsINReady()))
+ {
+ if (USB_DeviceState == DEVICE_STATE_Unattached)
+ return;
+ }
+
Endpoint_Write_8(PRNT_PORTSTATUS_NOTERROR | PRNT_PORTSTATUS_SELECT);
Endpoint_ClearStatusStage();
}