📜 ⬆️ ⬇️

Remote firmware update microcontroller

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:



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 // 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(); } 

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:

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}; /* Run the checksum algorithm */ calc = slow_crc16(0, (unsigned char *) ChecksumStart,(ChecksumEnd - ChecksumStart+1)); /* Rotate out the answer */ calc = slow_crc16(calc, zeros, 2); /* Test the checksum */ if (calc != checksum) { abort(); /* Failure */ } } 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:

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

Source: https://habr.com/ru/post/275381/


All Articles