The idea is simple. If you need to update the program, plug in the EEPROM(with update program) and turn on the board. The bootloader will update the program from the EEPROM. After that, turn off board, and unplug EEPROM.
This project demostrate the bootloader for STM32F0 using STM32F072RB Discovery board.
When program booting, the bootloader will check if there is EEPROM connected to the specified I2C interface.
If EEPROM found, bootloader will load the new application.
Even this bootloader use I2C EEPROM to load new application, it can be easily converted to other booting device, UART, SPI devices, for example.
Microchip EEPROM 24C512 used for this project. The following are the pin definition,
EEPROM 24C512 | STM32F072RB PINS |
---|---|
1 A0 | GND |
2 A1 | GND |
3 A2 | GND |
4 GND | GND |
5 SDA | PB9, PULL-UP 4.7K to 3V |
6 SCL | PB8 PULL-UP 4.7K to 3V |
7 WP | GND |
8 VCC | 3V |
It is possible to configure bootloader size, application address, boot device hardware I2C ID, and even different kind of format. The folloing is the configuration file 'bootconfig.h'
// bootloader size and application location address
#define APPLICATION_ADDRESS (uint32_t)0x08003000
#define FLASH_USER_START_ADDR ADDR_FLASH_PAGE_6 /* Start @ of user Flash area */
#define ADDR_FLASH_PAGE_6 ((uint32_t)0x08003000) /* Base @ of Page 6, 2 Kbytes */
// intel hex format bootloader
extern int8_t Intel_BootLoader(void);
#define BootLoader() Intel_BootLoader()
// I2C boot device
extern I2C_HandleTypeDef hi2c1;
#define I2C_EE_ADDR 0xA0
#define I2C_EE2_ADDR 0xA2
#define EE_I2C hi2c1
// dummy main
#define Uses_DUMMY_MAIN
-
Bootloader Size and Application Address
Depend on different optimization option, the bootloader size is different. The boundary can be only on the multiply of flash size(2048 bytes). The default size for bootloader is 12K, so that the application address is from 0x8003000(page 6). If C/C++ compiler optimize option is -O3, is possible to change to bootloader size to 8K, with the following changes in 'bootconfig.h'
#define APPLICATION_ADDRESS (uint32_t)0x08002000 #define FLASH_USER_START_ADDR ADDR_FLASH_PAGE_4 /* Start @ of user Flash area */ #define ADDR_FLASH_PAGE_0 ((uint32_t)0x08000000) /* Base @ of Page 0, 2 Kbytes */ #define ADDR_FLASH_PAGE_4 ((uint32_t)0x08002000) /* Base @ of Page 4, 2 Kbytes */
-
I2C EEPROM (or other boot device)
The EEPROM I2C ID is defined
#define I2C_EE_ADDR 0xA0
You can change to different one if circuit is different. The bootloader use the following function to read data for updated application, defined in 'i2c_ee.c'
// // the Generic Stream Reader using I2C // HAL_StatusTypeDef StreamRead( uint16_t ReadAddr, uint8_t *pBuffer, uint16_t numByteToRead ) { return EE_ReadBuffer( I2C_EE_ADDR, ReadAddr, pBuffer, numByteToRead ); }
It is possible to use different kind of method(UART, SPI) just to rewrite the 'StreamRead' function.
-
Intel Hex Format (or different format)
The updated program is the hex file generated by the Keil compiler. When you load hex file to EEPROM, make sure to use binary format (instead of INTEL Hex format). Only binary format will retain the hex file data.
-
Dummy main program
Before the application loaded, bootloader will run a dummy main program before new application loaded.
It is very convenient for debugging.
This is defined at bootconfig.h.#define Uses_DUMMY_MAIN
If Uses_DUMMY_MAIN is not defined, the linker optimization will delete the dummy main vector table(at the application area) so that we can concatenate bootloader(without the end of file record) and application program to form a complete program.
-
Application Load Address
The application load address are defined in the scatter file app3000.sct.
; For direct debugging, use the following two lines ;LR_IROM1 0x08000000 0x00020000 { ; load region size_region for debugging ; ER_IROM1 0x08000000 0x00020000 { ; load address = execution address ; For bootload application, use the following two lines LR_IROM1 0x08003000 0x0001D000 { ; load region size_region ER_IROM1 0x08003000 0x0001D000 { ; load address = execution address *.o (RESET, +First) *(InRoot$$Sections) .ANY (+RO) } RW_IRAM1 0x200000C0 0x00003F40 { ; RW data .ANY (+RW +ZI) } }
If bootloader change to size to 8K and application address to 0x8002000, change to scatter file to
LR_IROM1 0x08002000 0x0001E000 { ; load region size_region ER_IROM1 0x08002000 0x0001E000 { ; load address = execution address *.o (RESET, +First) *(InRoot$$Sections) .ANY (+RO) } RW_IRAM1 0x200000C0 0x00003F40 { ; RW data .ANY (+RW +ZI) } }
For debugging only, just change back to regular load address and you can use debugger.
LR_IROM1 0x08000000 0x00020000 { ; load region size_region ER_IROM1 0x08000000 0x00020000 { ; load address = execution address *.o (RESET, +First) *(InRoot$$Sections) .ANY (+RO) } RW_IRAM1 0x200000C0 0x00003F40 { ; RW data .ANY (+RW +ZI) } }
We reserve 0x20000000~0x200000BF(excluded in RW_RAM1) for vector table for application launched from bootloader.
-
Remap Vector Table at launched application
At first line in main() function, the following code is used to remap the vector table for launched application.
/* USER CODE BEGIN 1 */ #define LOAD_ADDRESS Image$$ER_IROM1$$Base volatile uint32_t *VectorTable = (volatile uint32_t *) 0x20000000; extern unsigned int LOAD_ADDRESS; if ((uint32_t)&LOAD_ADDRESS != 0x8000000 ) { // load vector table uint32_t ui32_VectorIndex; for ( ui32_VectorIndex = 0; ui32_VectorIndex < 48; ui32_VectorIndex++ ) { VectorTable[ui32_VectorIndex] = *(__IO uint32_t *) ((uint32_t) &LOAD_ADDRESS + (ui32_VectorIndex << 2)); } __HAL_RCC_AHB_FORCE_RESET(); __HAL_RCC_SYSCFG_CLK_ENABLE(); __HAL_RCC_AHB_RELEASE_RESET(); __HAL_SYSCFG_REMAPMEMORY_SRAM(); } /* USER CODE END 1 */