In one of the previous articles, we wrote about a
USB bootloader on a SAM D21 microcontroller with a Cortex M0 + core. Using a flash drive to update the firmware is very convenient, but not suitable for all cases. For example, if access to a device is limited or problematic, and communication with it is established remotely. In such cases, the development of a bootloader should be treated with the utmost care. Otherwise, in case of an error, the probability of getting a “brick” and a huge number of problems is high. An example of such a hard-to-reach device is the control board of a “smart” architectural lamp hanging on the facade of a building on the 7th floor.

Formulation of the problem
In the project, which formed the basis of this article, the connection with the remote device is carried out by power wires in accordance with the standard
G3 PLC (Power Line Communication). At first glance, the method of communication should not matter, because for a microcontroller, it still comes down to an exchange over UART / I2C / SPI. However, for example, in our case this imposes a limitation in the form of the impossibility of an online firmware update (when the bootloader establishes connection with the server to receive a new firmware), since Work with a PLC-modem resource-intensive task and the size of the bootloader section is not enough.
Thus, the following requirements were formed:- The update process takes place offline. Those. The firmware file is transferred in parts to the target device and saved to external flash memory. Starting the update process is a separate command.
- The head unit (gateway) is managing the remote device and transferring the file with the new firmware. The description of these processes in this article is not considered.
- An external EEPROM is used to exchange information between the application and the bootloader sections. For example, to start the update process, the application writes a corresponding flag to the EEPROM and transfers control to the bootloader section.
- Before updating, it is obligatory to check the checksum of the firmware located in the external flash. Otherwise, the update does not start, and control is returned to the application section.
- The first always starts bootloader. In addition to checking commands in the EEPROM, the bootloader checks the correctness of the current firmware in the controller's flash memory each time before transferring control to the application section using the CRC. CRC - stored as the last two bytes of the flash.

In the article on the
USB bootloader, a brief theory is given on the organization of the memory of the SAM D20 / D21 families, and we will not duplicate it here.
ATSAMD20G16 is used as the target microcontroller. We will build the project in IAR using the
ASF (Atmel Software Framework).
Algorithms of work
The operation of the device always starts from the bootloader, then, depending on the command in the EEPROM, either the firmware is updated or the correctness of the current firmware crc is simply checked. Upon completion of the bootloader, if there are no errors, a transition is made to the application:
#define APP_START_ADDRESS 0x00004000
The general algorithm of the bootloader is shown in the figure below:

The CRC check of its firmware is performed every time the microcontroller starts, in order to avoid incorrect operation in case of damage to the main code. What to do if the calculated CRC does not coincide with what is read, is solved in each specific application individually. You can blink infinitely red LED, you can fill in the backup firmware (then you need to provide a place where to store it).
The general algorithm of the application is shown in the figure below. Here is only what is directly related to the firmware update.

')
Project settings for application
To implement what was planned in the project with the application, you must do the following:
- use IAR as a checksum of firmware (CRC) and substitute the result in the form of the last two bytes of the received binary
- place the application not from the zero memory address of the microcontroller, in order to leave "space" for the bootloader.
This is done in the project settings.
CRC count

Such settings correspond to the polynomial 0x1021, while we should not forget to change the bytes in places when counting with the help of the code, and also that the CRC means IAR is considered throughout the memory, and not only for the filled code. After counting the CRC from the contents of the entire application memory, at the end you must flip the result by “adding” 2 more bytes to the checksum (see
EWARM_DevelopmentGuide.ENU ).
CRC verification code:
void TestChecksum() { unsigned short calc = 0; unsigned char zeros[2] = {0, 0}; calc = slow_crc16(0, (unsigned char *) ChecksumStart,(ChecksumEnd - ChecksumStart+1)); calc = slow_crc16(calc, zeros, 2); if (calc != checksum) { abort(); } } unsigned short slow_crc16(unsigned short sum, unsigned char *p, unsigned int len) { while (len--) { int i; unsigned char byte = *(p++); for (i = 0; i < 8; ++i) { unsigned long oSum = sum; sum <<= 1; if (byte & 0x80) sum |= 1; if (oSum & 0x8000) sum ^= 0x1021; byte <<= 1; } } return sum; }
Code location settings for application
Specify the beginning of the memory and the address of the interrupt vector table:


Code
To work we need the following modules ASF / MK:
- NVM (non-volatile memory controller),
- SERCOM SPI (for communication with external flash memory),
- SERCOM I2C (for communication with external EEPROM memory),
In addition to initialization and configuration of all modules, it is necessary to ensure the logic of operation.
Full code main.c#include <asf.h>
#include "twi_driver.h"
#include "MCP7941x.h"
#include "at45db041d.h"
#include "init.h"
#include "utils.h"
// ------------------------------------------------ -----------------------------
// --------------- global variables ------------------------------- ----------
// --------- structures of peripheral instances ------------------------------------ -
extern struct spi_module spi_master_instance;
extern struct spi_slave_inst slave;
extern struct tc_module tc_instance_tc2;
extern struct i2c_master_module i2c_master_instance;
//------------------------periphery----------------------- ---------------------
// ------------------------------ i2c ----------------- ---------------------------
unsigned char twi_out_data [9];
unsigned char twi_in_data [8];
// ---------------------------- SPI ------------------- ---------------------------
extern unsigned char spi_in_data [ext_flash_page_size];
extern unsigned char spi_out_data [ext_flash_page_size];
unsigned char temp_buffer_write [ext_flash_page_size];
unsigned long rtc_cnt;
// ------------------- counters for cycles -------------------------- -------------
unsigned int i, m;
unsigned char led_cnt;
// ------------------------ working with the firmware --------------------- -------------
unsigned int page_addr;
unsigned int CRC;
unsigned int CRC_read;
unsigned char zero [2] = {0,0};
unsigned int last_page_length;
unsigned int page_num;
unsigned int byte_amount;
unsigned int last_page_number;
const uint8_t flash_data [256];
// ------------------------------------------------ -----------------------------
// ------------------- init ---------------------------- ------------------------
void MC_init (void)
{
struct nvm_config config;
system_init ();
SystemClock_Init ();
configure_tc2 ();
configure_pins ();
configure_spi_master ();
configure_i2c_master ();
nvm_get_config_defaults (& config);
nvm_set_config (& config);
}
void init_variables (void)
{
// ------------------------------ i2c ----------------- ---------------------------
clear_twi_in_buffer ();
clear_twi_out_buffer ();
// ---------------------------- SPI ------------------- ---------------------------
for (i = 0; i <ext_flash_page_size; i ++) temp_buffer_write [i] = 0;
rtc_cnt = 0;
// ------------------- counters for cycles -------------------------- -------------
led_cnt = 0;
// ------------------------ other ----------------------- ------------------------
last_page_number = 0;
}
void external_init (void)
{
unsigned char temp;
// ---------------- external eeprom ---------------------
// check that the memory is not write protected
clear_twi_in_buffer ();
twi_read_bytes (twi_in_data, 1, EEPROM_ADR, 0xff);
if (twi_in_data [0]! = 0)
{
clear_twi_out_buffer ();
twi_out_data [0] = 0xff;
twi_out_data [1] = 0;
twi_write_bytes (twi_out_data, 2, EEPROM_ADR, EEPROM_OWN_METERS_1_STATUS);
}
// ---------------- FLASH -------------------------------
// check the page size of the external flash
temp = flash_wait (spi_delay);
// if 264, then install 256
if ((temp & 0x01)! = 0x01) set_page_size_256 ();
temp = 0;
}
// brief Function for programming data to Flash
// This is the function that will check whether the data is greater than the Flash page size.
// If it is greater, it splits and writeswise.
// param address of the flashmed page to be programmed
// param buffer pointer
// param len length
static void program_memory (uint32_t address, uint8_t * buffer, uint16_t len)
{
// Check if length is greater than Flash page size
if (len> NVMCTRL_PAGE_SIZE)
{
uint32_t offset = 0;
while (len> NVMCTRL_PAGE_SIZE)
{
// Check if it is the first page of a row
if ((address & 0xFF) == 0)
{
// Erase row
nvm_erase_row (address);
}
// Write one page data to flash
nvm_write_buffer (address, buffer + offset, NVMCTRL_PAGE_SIZE);
// Increment the address to be programmed
address + = NVMCTRL_PAGE_SIZE;
// Increment the offset of the buffer containing data
offset + = NVMCTRL_PAGE_SIZE;
// Decrement the length
len - = NVMCTRL_PAGE_SIZE;
}
// Check if there is data
if (len> 0)
{
// Write the data to flash
nvm_write_buffer (address, buffer + offset, len);
}
}
else
{
// Check if it is the first page of a row)
if ((address & 0xFF) == 0)
{
// Erase row
nvm_erase_row (address);
}
// Write the data to flash
nvm_write_buffer (address, buffer, len);
}
}
// brief Function for starting application
// This function will configure the WDT module and enable it. The LED is
// kept toggling till wdt reset occurs.
static void start_application (void)
{
// Pointer to the Application Section
void (* application_code_entry) (void);
// Rebase the Stack Pointer
__set_MSP (* (uint32_t *) APP_START_ADDRESS);
// Rebase the vector table base address
SCB-> VTOR = ((uint32_t) APP_START_ADDRESS & SCB_VTOR_TBLOFF_Msk);
// Load the Reset Handler address of the application
application_code_entry = (void (*) (void)) (unsigned *) (* (unsigned *) (APP_START_ADDRESS + 4));
// Jump to user Reset Handler in the application
application_code_entry ();
}
unsigned int slow_crc16 (unsigned short sum, unsigned char * p, unsigned int len)
{
while (len--)
{
int i;
unsigned char byte = * (p ++);
for (i = 0; i <8; ++ i)
{
unsigned long osum = sum;
sum << = 1;
if (byte & 0x80)
sum | = 1;
if (osum & 0x8000)
sum ^ = 0x1021;
byte << = 1;
}
}
return sum;
}
void MC_reset (void)
{
tc_reset (& tc_instance_tc2);
i2c_master_reset (& i2c_master_instance);
spi_reset (& spi_master_instance);
}
// ------------------------ !!!!!! MAIN !!!!!! ----------- --------------------------
int main (void)
{
unsigned int temp;
unsigned int j;
unsigned char k;
init_variables ();
MC_init ();
for (i = 0; i <20; i ++)
{
LED_2_TOGGLE ();
delay_ms (100);
}
system_interrupt_enable_global ();
external_init ();
// ------------ check if there is a command to upgrade -------------------------
for (i = 0; i <8; i ++) twi_in_data [i] = 0;
// read the upgrade from the external EEPROM
twi_read_bytes (twi_in_data, 4, EEPROM_ADR, EEPROM_UPGRADE);
// team to upgrade
if (twi_in_data [0] == 0x55)
{
for (i = 0; i <20; i ++)
{
LED_1_TOGGLE ();
delay_ms (100);
}
// determine the length of the firmware and the position of crc in it
// since we are transmitting all the memory via the PLC, not just the code
// has not yet figured out how to transmit only the code
for (page_addr = 2; page_addr <194; page_addr ++)
{
// read the page
continuous_low_freq_read (spi_in_data, page_addr, 0, ext_flash_page_size);
temp = 0;
for (j = 0; j <ext_flash_page_size; j ++)
{
if (spi_in_data [j] == 0xff)
{
temp ++;
}
else temp = 0;
}
// first blank page
if (temp == ext_flash_page_size)
{
// read the previous page
page_addr--;
continuous_low_freq_read (spi_in_data, page_addr, 0, ext_flash_page_size);
last_page_number = page_addr;
// analyze the last page with the code from the end
// if the last byte is not ff, then this is a CRC
if (spi_in_data [ext_flash_page_size-1]! = 0xff)
{
CRC_read = spi_in_data [ext_flash_page_size-2];
CRC_read << = 8;
CRC_read | = spi_in_data [ext_flash_page_size-1];
}
// if the last bay ff, then look for a place crc
else
{
i = ext_flash_page_size-1;
while ((spi_in_data [i] == 0xff) && (i> 0))
{
i--;
}
CRC_read = spi_in_data [i];
CRC_read << = 8;
CRC_read | = spi_in_data [i-1];
// this is the last byte position of crc
last_page_length = i + 1;
break;
}
}
}
}
// -------------- check the current firmware CRC -----------------------------
else
{
k = 0;
CRC = 0;
CRC_read = 0xffff;
// if it doesn't fit, we will try 5 times
// if it doesn’t converge, then we blink red LED until blue)
while ((CRC! = CRC_read) && (k <5))
{
k ++;
CRC = 0;
CRC_read = 0;
// determine the length of the firmware and the position of crc in it
for (page_addr = 0x4000; page_addr <0x10000; page_addr + = NVMCTRL_PAGE_SIZE)
{
// read the page
nvm_read_buffer (page_addr, spi_in_data, NVMCTRL_PAGE_SIZE);
temp = 0;
for (j = 0; j <64; j ++)
{
if (spi_in_data [j] == 0xff)
{
temp ++;
}
else temp = 0;
}
// first blank page
if (temp == NVMCTRL_PAGE_SIZE)
{
// read the previous page
page_addr- = NVMCTRL_PAGE_SIZE;
nvm_read_buffer (page_addr, spi_in_data, NVMCTRL_PAGE_SIZE);
last_page_number = page_addr;
// analyze the last page with the code from the end
// if the last byte is not ff, then this is a CRC
if (spi_in_data [NVMCTRL_PAGE_SIZE-1]! = 0xff)
{
CRC_read = spi_in_data [NVMCTRL_PAGE_SIZE-2];
CRC_read << = 8;
CRC_read | = spi_in_data [NVMCTRL_PAGE_SIZE-1];
}
// if the last bay ff, then look for a place crc
else
{
i = NVMCTRL_PAGE_SIZE-1;
while ((spi_in_data [i] == 0xff) && (i> 0))
{
i--;
}
CRC_read = spi_in_data [i];
CRC_read << = 8;
CRC_read | = spi_in_data [i-1];
// this is the last byte position of crc
last_page_length = i + 1;
break;
}
}
}
// directly consider crc
for (page_addr = 0x4000; page_addr <last_page_number + 1; page_addr + = NVMCTRL_PAGE_SIZE)
{
// read the page
nvm_read_buffer (page_addr, spi_in_data, NVMCTRL_PAGE_SIZE);
temp = 0;
// swap adjacent bytes in the read
for (j = 0; j <NVMCTRL_PAGE_SIZE; j + = 2)
{
temp = spi_in_data [j];
spi_in_data [j] = spi_in_data [j + 1];
spi_in_data [j + 1] = temp;
}
if (page_addr == last_page_number)
{
// read separately crc parts of the page before the CRC linker
CRC = slow_crc16 (CRC, spi_in_data, last_page_length-2);
// after CRC linker
temp = NVMCTRL_PAGE_SIZE-last_page_length;
CRC = slow_crc16 (CRC, & (spi_in_data [last_page_length]), temp);
}
// if the entire page code
else
{
CRC = slow_crc16 (CRC, spi_in_data, NVMCTRL_PAGE_SIZE);
}
}
for (i = 0; i <NVMCTRL_PAGE_SIZE; i ++) spi_in_data [i] = 0xff;
while (page_addr <0x10000)
{
CRC = slow_crc16 (CRC, spi_in_data, NVMCTRL_PAGE_SIZE);
page_addr + = NVMCTRL_PAGE_SIZE;
}
CRC = slow_crc16 (CRC, zero, 2);
} // end o fwhile ((CRC! = CRC_read) && (k <5))
// if crc does not match
if (CRC! = CRC_read)
{
while (1)
{
LED_1_TOGGLE ();
delay_ms (500);
}
}
// if CRC converged
else
{
MC_reset ();
start_application ();
}
}
CRC = 0;
////////////////////////////////////////////////// //////////////////////////////
// ------------------------------ MAIN PROGRAM ---------------- -------------------
////////////////////////////////////////////////// //////////////////////////////
while (1)
{
// read the page to check the CRC
for (page_addr = 2; page_addr <last_page_number + 1; page_addr ++)
{
// read the page
continuous_low_freq_read (spi_in_data, page_addr, 0, ext_flash_page_size);
temp = 0;
// swap adjacent bytes in the read
for (j = 0; j <ext_flash_page_size; j + = 2)
{
temp = spi_in_data [j];
spi_in_data [j] = spi_in_data [j + 1];
spi_in_data [j + 1] = temp;
}
if (page_addr == last_page_number)
{
// read separately crc parts of the page before the CRC linker
CRC = slow_crc16 (CRC, spi_in_data, last_page_length-2);
// after CRC linker
temp = ext_flash_page_size-last_page_length;
CRC = slow_crc16 (CRC, & (spi_in_data [last_page_length]), temp);
}
// if the entire page code
else
{
CRC = slow_crc16 (CRC, spi_in_data, ext_flash_page_size);
}
}
// translate the address into bytes from the page numbers of the external flash
page_addr << = 8;
for (i = 0; i <NVMCTRL_PAGE_SIZE; i ++) spi_in_data [i] = 0xff;
while (page_addr <0xc200)
{
CRC = slow_crc16 (CRC, spi_in_data, NVMCTRL_PAGE_SIZE);
page_addr + = NVMCTRL_PAGE_SIZE;
}
CRC = slow_crc16 (CRC, zero, 2);
// if CRC matched
// read again and write to our flash
// otherwise go to application with some flag
// for rolling back the CRC firmware, we don’t count: we don’t know its size and CRC location
// but by default they are both 0
if (CRC == CRC_read)
{
page_addr = 0x4000;
for (i = 0; i <15; i ++)
{
// erase and write new firmware
continuous_low_freq_read (spi_in_data, i, 0, NVMCTRL_PAGE_SIZE);
program_memory (page_addr, spi_in_data, 256);
page_addr + = 256;
delay_ms (10);
}
twi_out_data [0] = EEPROM_FW_RESULT;
twi_out_data [1] = 0xda;
twi_write_bytes (twi_out_data, 2, EEPROM_ADR, EEPROM_FW_RESULT);
}
else
{
twi_out_data [0] = EEPROM_FW_RESULT;
twi_out_data [1] = 0xad;
twi_write_bytes (twi_out_data, 2, EEPROM_ADR, EEPROM_FW_RESULT);
}
// erase only the command for flashing
// parameters of the firmware do not erase
delay_ms (100);
twi_out_data [0] = EEPROM_UPGRADE;
twi_out_data [1] = 0xff;
twi_write_bytes (twi_out_data, 2, EEPROM_ADR, EEPROM_UPGRADE);
delay_ms (100);
for (j = 2; j <2048; j ++)
{
// erase the entire flash, except for the first two pages, in which the addresses of the bindings
erase_page (j);
temp = flash_wait (spi_delay);
temp = 0;
}
for (i = 0; i <20; i ++)
{
LED_1_TOGGLE ();
delay_ms (300);
}
MC_reset ();
start_application ();
} // end of while (1)
} // end of main