📜 ⬆️ ⬇️

USB bootloader on the microcontroller: firmware update from a flash drive

The ability to update the firmware on commercially available products, or on individual products that are in operation at the customer, is difficult to overestimate. This not only makes it possible to subsequently eliminate bugs and expand functionality, but also allows a developer with a lighter heart to release a “still damp” product on the market, if the management requires it.

Therefore, the importance of having a bootloader in newly developed devices in most cases is beyond doubt. This article will discuss the development of a bootloader via a USB interface on an Atmel SAM D21 microcontroller with a Cortex M0 + core. Specifically, on SAMD21J18A . SAM D20 / 21 microcontrollers do not have a pre-written bootloader, so you will have to deal with its software implementation. On the Atmel website, you can find Application notes , how to make it using standard interfaces (UART, I2C, SPI, USB). Under the cat description of the process of creating a USB-bootloader.

Formulation of the problem



A bit of theory and preparation


Memory

The address space in the memory of the SAMD20 / 21 series microcontrollers is simple:
Memory organization samd20
Non-volatile memory is organized in rows, each row contains 4 pages. The size of 1 page is 64 bytes. Non-volatile memory is erased in rows, and is written page by page. It is important to remember.
The lower (lower) rows in the main address space of non-volatile memory can be used for the bootloader (configured using BOOTPROT fyuz), and the upper rows for EEPROM emulation.
The bootloader section is protected by the BOOTPROT lock bits and fuses corresponding to this address space.
BOOTPROT fuses simultaneously determine the size of the bootloader section and protect the allocated memory area from reading.
EEPROM can be recorded despite the protection of the corresponding memory area.

What will be required to organize a bootloader?


  1. Work with the controller's memory - the non-volatile memory controller (NVM) is responsible for this;
  2. Working with USB - the USB controller is responsible for this;
  3. Working with the file system is the power of FATFS.
  4. And the little things: working with I / O ports, clocking.


Note: Atmel Studio version 6.2 (inherited from AVR Studio) and the ASF framework (Atmel Software Framework) are used as the development environment.
')
USB subtleties

In accordance with the USB standard, very precise clocking is required to implement the bus. We will use external 32 kHz quartz as a support for the DFLL (Digital Frequency Locked Loop). The DFLL output will be used both for clocking the USB module and the entire controller. For the USB module to work, you must configure the DFLL so that the output is exactly 48 MHz. For stability and accuracy of the DFFL output frequency, it must be configured in closed loop mode.
Clocking

We collect the project


Using the ASF wizard, we connect all the modules we need listed above.

USB host

Add USB Host service in mass storage mode.

After the driver is added to the project, several header and executive files appear. We are interested in 2 of them:

For the operation of the USB host stack, we register two definitions in the project properties:
USB_MASS_STORAGE_ENABLE=true ACCESS_MEM_TO_RAM_ENABLED=true 

To do this, right-click on the project, select Properties -> Toolchain -> ARM / GNU C Compiler -> Symbols .
We comment on the line "#define Lun_usb_unload - NULL" in USB LUNs Definitions in the conf_access.h file to prevent errors during compilation.
To track connected devices on the USB bus, an interrupt handler (callback) on the Start of Frame event is introduced. This interrupt occurs only once each time a SOF is sent, and since SOF is sent every 1 ms when the device is connected to the bus, this event can be used as a timer.
We register the interrupt handler in the conf_usb_host.h file.
To do this, we add the prototype of the main_usb_sof_event () function at the beginning of the conf_usb_host.h file after all #include.
 void main_usb_sof_event(void); 

We also add a line to this file:
 # define UHC_SOF_EVENT() main_usb_sof_event() 

Now we need to globally define a counter variable in the main.c file, we will increase it each time we call the corresponding handler:
 volatile static uint16_t main_usb_sof_counter = 0; 

Add the interrupt handler itself (callback):
 void main_usb_sof_event(void) { main_usb_sof_counter++; } 

File system

Add FAT FS file system service (with the help of the ASF wizard). We open the module and select the calendar_polled driver's RTC mode.
For the full functioning of the file system module, add at the beginning of main.c :
 #include "string.h" #define MAX_DRIVE _VOLUMES #define FIRMWARE_FILE "firmware.bin" const char firmware_filename[] = {FIRMWARE_FILE}; /* FATFS variables */ static FATFS fs; static FILE file_object; 

The file name (#define FIRMWARE_FILE "firmware.bin") must match the name of the firmware file on the connected USB flash drive.

Work with non-volatile memory

Add NVM-Non-volatile memory (driver). In addition, we define the necessary constants and variables in the main.c file:
 #define APP_START_ADDRESS (NVMCTRL_ROW_SIZE * 200) uint8_t page_buffer[NVMCTRL_PAGE_SIZE]; 

Still need to configure the controller non-volatile memory. To do this, add the configuration structure (globally), read the default settings, change the necessary and set (make out a separate function):
 struct nvm_config nvm_cfg; void nvm_init(void) { nvm_get_config_defaults(&nvm_cfg); nvm_cfg.manual_page_write=false; nvm_set_config(&nvm_cfg); } 

All the necessary modules are added, you can write code.

Code


It is worth noting that if the bootloader uses the same peripherals as the application, then it must be reset before switching to the application. A reset is performed by special functions in the ASF.
Also note that you can access the USB device only after 1-2 seconds from the moment it is connected to the bus, since before this the device is initialized.
A brief operation algorithm (bootloader only) is shown in the figure below:
Algorithm
Main code
 #include <asf.h> #include <exp_io.h> #include <led.h> #include "string.h" //------------------------------------------------------------------------------------------------------------------------------ #define MAX_DRIVE _VOLUMES #define FIRMWARE_FILE "Modbus_RTU_TCP.bin" #define APP_START_ADDRESS (NVMCTRL_ROW_SIZE * 200) //------------------------------------------------------------------------------------------------------------------------------ const char firmware_filename[] = {FIRMWARE_FILE}; // FATFS variables static FATFS fs; static FIL file_object; // NVM uint8_t page_buffer[NVMCTRL_PAGE_SIZE]; struct nvm_config nvm_cfg; //USB volatile static uint16_t main_usb_sof_counter = 0; //------------------------------------------------------------------------------------------------------------------------------ void main_usb_sof_event(void) { main_usb_sof_counter++; } static void check_boot_mode(void) { uint32_t app_check_address; uint32_t *app_check_address_ptr; // Check if WDT is locked if (!(WDT->CTRL.reg & WDT_CTRL_ALWAYSON)) { //Disable the Watchdog module WDT->CTRL.reg &= ~WDT_CTRL_ENABLE; } app_check_address = APP_START_ADDRESS; app_check_address_ptr = (uint32_t *)app_check_address; if (*app_check_address_ptr == 0xFFFFFFFF) { // No application; run bootloader return; } // 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 TODO: use RAM 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(); } void delay_ms(uint32_t ms) { volatile int a=0; for(uint32_t i=0; i<ms; i++) { for( int j=0; j<2000; j++) a++; } } void nvm_init(void) { nvm_get_config_defaults(&nvm_cfg); nvm_cfg.manual_page_write=false; nvm_set_config(&nvm_cfg); } void init_IO(void) { ExpIO_Init(); LED_Init(); } int main (void) { volatile uint16_t z=0; uint32_t fw_size ; UINT bytes_read = 0; enum status_code error_code; uint32_t current_page; uint32_t curr_address = 0; // Erase flash rows to fit new firmware uint16_t rows_clear; uint16_t i; check_boot_mode(); system_init(); init_IO(); nvm_init(); uhc_start(); while (1) { if(65000==z) { LED(GREEN,0); delay_ms(1000); LED(GREEN,1); z = 0; } z++; // Wait 2 seconds before trying to access the USB drive if (main_usb_sof_counter > 2000) { main_usb_sof_counter = 0; volatile uint8_t lun = LUN_ID_USB; // Mount drive memset(&fs, 0, sizeof(FATFS)); FRESULT res = f_mount(lun, &fs); if (FR_INVALID_DRIVE == res) { continue; } res = f_open(&file_object,firmware_filename, FA_READ); if (res == FR_NOT_READY) { // LUN not ready f_close(&file_object); continue; } if (res != FR_OK) { // LUN test error f_close(&file_object); continue; } // Get size of file fw_size = f_size(&file_object); bytes_read = 0; if (fw_size != 0) { current_page = APP_START_ADDRESS /NVMCTRL_PAGE_SIZE; curr_address = 0; // Erase flash rows to fit new firmware rows_clear = fw_size / NVMCTRL_ROW_SIZE; for (i = 0; i < rows_clear; i++) { do { error_code = nvm_erase_row( (APP_START_ADDRESS) +(NVMCTRL_ROW_SIZE * i)); } while (error_code == STATUS_BUSY); } do { //Read data from USB stick to the page buffer f_read(&file_object,page_buffer,NVMCTRL_PAGE_SIZE,&bytes_read ); bytes_read=64; curr_address += bytes_read; // Write page buffer to flash do { error_code = nvm_write_buffer(current_page * NVMCTRL_PAGE_SIZE, page_buffer, bytes_read); } while (error_code == STATUS_BUSY); current_page++; } while (curr_address < fw_size); } f_close(&file_object); system_interrupt_disable_global(); uhc_stop(1); NVIC_SystemReset(); } } } 


Preparing the firmware file


In the SAMD21J18A (as in the other controllers of the SAMD20 / 21 series), each NVM row consists of 4 pages, each of which is 64 bytes. Thus, 200 rows (which we allocate for the bootloader) are (200 * 4 * 64) bytes = 51200 (0xC800) bytes of memory. And the application part should start after 51200 bytes of flash memory.
Splitting flash memory:
Bootloader section:

Application section:

In order to generate the firmware starting from the address we need, and not from the beginning of the flash memory, as it happens by default, you need to change the linker file.
The file itself can be found in the solution explorer. In our case, it is called samd21j18a_flash.ld :
Path: src-asf-sam0-utils-linker scripts-samd21-gcc
It is necessary to make changes to the definitions of memory areas:
Default configuration:
rom (rx) : ORIGIN = 0x00000000,
LENGTH = 0x00040000

should be replaced by
rom (rx) : ORIGIN = 0x0000C800,
LENGTH = 0x00033800

Now the compiled binary can be uploaded through the bootloader.

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


All Articles