This tutorial was extracted from Erich Styger blog http://mcuoneclipse.wordpress.com with his agreement.
Bootloaders are a very useful thing: it allows programming an application file without a debugger. This makes it ideal for upgrading a system in the field.
Usually, there are application notes and examples from silicon vendors available. But typically they are for a certain microcontroller, and hard to change it to another system without a lot knowledge about its implementation. What I need for a project based on the FRDM-KL25ZFRDM-KL25Z is a bootloader which shall be small and portable. As I’m using Processor Expert to keep my applications portable across different microcontroller families: why not create a bootloader with Processor Expert components? With the Processor Expert drivers available, things can get a lot simpler compared to the ‘traditional’ approach. With less than 10 KByte footprint?
A bootloader is a program which is able to load another program (the application program). Typically the bootloader program is not changed, and is kept in the microcontroller. That way the bootloader can load again and again a different program.
Architecturally there can be a ‘mini’ or ‘micro’ bootloader which can load the ‘real’ bootloader. E.g. the OpenSDA bootloader on the Freedom boards have this capability.
The Bootloader Code and the Bootloader Vectors are programmed into a new part (e.g. with a debugger or a standalone flash programmer (e.g. with USBDM). Then the Bootloader can be used to load or change the Application Code and Application Vectors. With this, the Bootloader remains the same, while the Application can be updated.
Bootloader Sequence
A typical bootloader is doing something like this
- The bootloader decides at startup if it should enter bootloader mode or if it shall run the application. Typically this is decided with a button or jumper set (or removed). If it shall run the application, the bootloader calls the application and we are done .
- Otherwise, the bootloader will reprogram the application with a new file. S19 (S-Record) files are often used for this, as they are easy to parse and every tool chain can produce them.
- The bootloader needs to use a communication channel to read that file. That can be RS-232RS-232, USB or an SD card file system (e.g. FatFS).
- Using that file, the bootloader programs the flash memory. Special consideration has to be taken into account for the application vector table. As the bootloader runs out of reset, it is using its own (default) vector table, and needs to relocate the vector table if running the application.
It would be possible to use the reset button on the FRDM-KL25Z board as a user button (see this post). To keep things simple, I’m using a dedicated bootloader push button on PTB8.
So writing a bootloader requires the following parts:
- Communication Channel: File I/O or any other means to read the Application File.
- File Reader: A reader which reads the Application File.
- Flash Programming: to program the Application.
- Vector Redirection: To switch between the Bootloader and Application Vector Table.
- User Interface: Showing status and information to the user, and to switch between application and bootloader mode at system startup.
Processor Expert comes with Flash Programming and Communication components (USB, SCI, I2C, …) installed. I have a Shell user interface already, plus an S19 file reader component created. Combining this with my other components should enable me to make a bootloader .
Flash Memory of the Bootloader
To make sure the bootloader gets linked only into its space, I reduce the FLASH memory for it. With the settings below I limit the FLASH memory from 0×0000 (vector table) up to 0x3FFF. That means my application memory area starts at 0×4000.
So I change the available flash for the bootloader in the CPU properties, and cut the available FLASH size on the KL25Z128 from 0x1FBF0 (~128 KByte) in the Build Options tab to 0x3FB0:
With this, the bootloader occupies the space from address 0×0000 (vector table) up to 0x3FFF.
Flash Protection
My bootloader resides in the first lower flash pages. To avoid that it might get destroyed and overwritten by the application, I protect the bootloader flash blocks. There is a setting in the CPU component properties where I can protect 4 KByte regions:
Terminal Program
For my bootloader I need a way to send a file with a terminal program. As my serial connection has only Tx and Rx, but no RTS/CTC lines for flow control, it is useful if the terminal program either implements software flow control (XON/XOFF), or a delay value for sending a file.
After some searching the internet, I have found an open source terminal program which exactly can do this: https://sites.google.com/site/terminalbpp/
It supports sending a file with a delay (shown above with 1 ms delay), and supports XON and XOFF. I used it successfully with my bootloader.
Using a zero delay did not work in all cases. Not yet sure why. What worked was sending a file with a 1 ms delay setting.
Bootloader Shell
The bootloader features a shell with following commands:
-------------------------------------------------------------- FRDM Shell Bootloader -------------------------------------------------------------- CLS1 ; Group of CLS1 commands help|status ; Print help or status information BL ; Group of Bootloader commands help|status ; Print help or status information erase ; Erase application flash blocks restart ; Restart application with jump to reset vector load s19 ; Load S19 file
The ‘BL status’ command shows the application flash range, and the content of the application vector table (more about this later):
App Flash : 0x00004000..0x0001FFFF @0x00004000: 0xFFFFFFFF 0xFFFFFFFF
With ‘BL restart’ it starts the user application (if any), and with ‘BL erase’ the application flash can be erased:
CMD> Erasing application flash blocks...done!
Bootloading an Application
With ‘BL load s19′ a new application file can be loaded. It will first erase the application flash blocks, and then waits for the S19. To send the file, I use the ‘Send File’ button:
It writes then the address of each S19 line programmed to the shell console:
CMD> Erasing application flash blocks...done! Waiting for the S19 file... S0 address 0x00000000 S1 address 0x00008000 S1 address 0x00008010 ... S1 address 0x00009420 S1 address 0x00009430 S1 address 0x00009440 S9 address 0x00009025 done! CMD>
Bootloader Details
If I enter ‘BL Load S19′, it executes the function BL_LoadS19() in Bootloader.c:
static
uint8_t BL_LoadS19(CLS1_ConstStdIOType *io) {
unsigned
char
buf[16];
uint8_t res = ERR_OK;
/* first, erase flash */
if
(BL_EraseAppFlash(io)!=ERR_OK) {
return
ERR_FAILED;
}
/* load S19 file */
CLS1_SendStr((unsigned
char
*)
"Waiting for the S19 file..."
, io->stdOut);
parserInfo.GetCharIterator = GetChar;
parserInfo.voidP = (
void
*)io;
parserInfo.S19Flash = BL_onS19Flash;
parserInfo.status = S19_FILE_STATUS_NOT_STARTED;
parserInfo.currType = 0;
parserInfo.currAddress = 0;
parserInfo.codeSize = 0;
parserInfo.codeBuf = codeBuf;
parserInfo.codeBufSize =
sizeof
(codeBuf);
while
(AS1_GetCharsInRxBuf()>0) {
/* clear any pending characters in rx buffer */
AS1_ClearRxBuf();
WAIT1_Waitms(100);
}
do
{
if
(S19_ParseLine(&parserInfo)!=ERR_OK) {
CLS1_SendStr((unsigned
char
*)
"ERROR!\r\nFailed at address 0x"
, io->stdErr);
buf[0] =
'\0'
;
UTIL1_strcatNum32Hex(buf,
sizeof
(buf), parserInfo.currAddress);
CLS1_SendStr(buf, io->stdErr);
CLS1_SendStr((unsigned
char
*)
"\r\n"
, io->stdErr);
res = ERR_FAILED;
break
;
}
else
{
CLS1_SendStr((unsigned
char
*)
"\r\nS"
, io->stdOut);
buf[0] = parserInfo.currType;
buf[1] =
'\0'
;
CLS1_SendStr(buf, io->stdOut);
CLS1_SendStr((unsigned
char
*)
" address 0x"
, io->stdOut);
buf[0] =
'\0'
;
UTIL1_strcatNum32Hex(buf,
sizeof
(buf), parserInfo.currAddress);
CLS1_SendStr(buf, io->stdOut);
}
if
(parserInfo.currType==
'7'
|| parserInfo.currType==
'8'
|| parserInfo.currType==
'9'
) {
/* end of records */
break
;
}
}
while
(1);
if
(res==ERR_OK) {
CLS1_SendStr((unsigned
char
*)
"\r\ndone!\r\n"
, io->stdOut);
}
else
{
while
(AS1_GetCharsInRxBuf()>0) {
/* clear buffer */
AS1_ClearRxBuf();
WAIT1_Waitms(100);
}
CLS1_SendStr((unsigned
char
*)
"\r\nfailed!\r\n"
, io->stdOut);
/* erase flash again to be sure we do not have illegal application image */
if
(BL_EraseAppFlash(io)!=ERR_OK) {
res = ERR_FAILED;
}
}
return
res;
}
It first fills a callback structure of type S19_ParserStruct:
typedef
struct
S19_ParserStruct {
uint8_t (*GetCharIterator)(uint8_t*,
void
*);
/* character stream iterator */
void
*voidP;
/* void pointer passed to iterator function */
uint8_t (*S19Flash)(
struct
S19_ParserStruct*);
/* called for each S19 line to be flashed */
/* the following fields will be used by the iterator */
S19_FileStatus status;
/* current status of the parser */
uint8_t currType;
/* current S19 record, e.g. 1 for S1 */
uint32_t currAddress;
/* current code address of S19 record */
uint16_t codeSize;
/* size of code in bytes in code buffer */
uint8_t *codeBuf;
/* code bufffer */
uint16_t codeBufSize;
/* total size of code buffer, in bytes */
} S19_ParserStruct;
That structure contains a callbacks to read from the input stream:
static
uint8_t GetChar(uint8_t *data,
void
*q) {
CLS1_ConstStdIOType *io;
io = (CLS1_ConstStdIOType*)q;
if
(!io->keyPressed()) {
#if USE_XON_XOFF
SendXONOFF(io, XON);
#endif
while
(!io->keyPressed()) {
/* wait until there is something in the input buffer */
}
#if USE_XON_XOFF
SendXONOFF(io, XOFF);
#endif
}
io->stdIn(data);
/* read character */
if
(*data==
'\0'
) {
/* end of input? */
return
ERR_RXEMPTY;
}
return
ERR_OK;
}
Parsing of the S19 file is done in S19_ParesLine() which is implemented in a Processor Expert component which I already used for another bootloader project:
This parser is calling my callback BL_onS19Flash()
for every S19 line:
static
uint8_t BL_onS19Flash(S19_ParserStruct *info) {
uint8_t res = ERR_OK;
switch
(info->currType) {
case
'1'
:
case
'2'
:
case
'3'
:
if
(!BL_ValidAppAddress(info->currAddress)) {
info->status = S19_FILE_INVALID_ADDRESS;
res = ERR_FAILED;
}
else
{
/* Write buffered data to Flash */
if
(BL_Flash_Prog(info->currAddress, info->codeBuf, info->codeSize) != ERR_OK) {
info->status = S19_FILE_FLASH_FAILED;
res = ERR_FAILED;
}
}
break
;
case
'7'
:
case
'8'
:
case
'9'
:
/* S7, S8 or S9 mark the end of the block/s-record file */
break
;
case
'0'
:
case
'4'
:
case
'5'
:
case
'6'
:
default
:
break
;
}
/* switch */
return
res;
}
Of interest are the S1, S2 and S3 records as they contain the code. With BL_ValidAppAddress()
it checks if the address is within the application FLASH memory range:
/*!
* \brief Determines if the address is a valid address for the application (outside the bootloader)
* \param addr Address to check
* \return TRUE if an application memory address, FALSE otherwise
*/
static
bool
BL_ValidAppAddress(dword addr) {
return
((addr>=MIN_APP_FLASH_ADDRESS) && (addr<=MAX_APP_FLASH_ADDRESS));
/* must be in application space */
}
If things are ok, it flashes the memory block:
/*!
* \brief Performs flash programming
* \param flash_addr Destination address for programming.
* \param data_addr Pointer to data.
* \param nofDataBytes Number of data bytes.
* \return ERR_OK if everything was ok, ERR_FAILED otherwise.
*/
static
byte BL_Flash_Prog(dword flash_addr, uint8_t *data_addr, uint16_t nofDataBytes) {
/* only flash into application space. Everything else will be ignored */
if
(BL_ValidAppAddress(flash_addr)) {
if
(IFsh1_SetBlockFlash((IFsh1_TDataAddress)data_addr, flash_addr, nofDataBytes) != ERR_OK) {
return
ERR_FAILED;
/* flash programming failed */
}
}
return
ERR_OK;
}
The Flash Programming itself is performed by the IntFLASH Processor Expert components:
This component is used for erasing too:
/*!
* \brief Erases all unprotected pages of flash
* \return ERR_OK if everything is ok; ERR_FAILED otherwise
*/
static
byte BL_EraseApplicationFlash(
void
) {
dword addr;
/* erase application flash pages */
for
(addr=MIN_APP_FLASH_ADDRESS;addr<=MAX_APP_FLASH_ADDRESS;addr+=FLASH_PAGE_SIZE) {
if
(IFsh1_EraseSector(addr) != ERR_OK) {
/* Error Erasing Flash */
return
ERR_FAILED;
}
}
return
ERR_OK;
}
Bootloader or Not, that’s the Question
One important piece is still missing: the bootloader needs to decide at startup if it shall run the Bootloader or the application. For this we need to have a decision criteria, which is typically a jumper or a push button to be pressed at power up to enter bootloader mode.
In this bootloader this is performed by BL_CheckForUserApp()
:
/*!
* \brief This method is called during startup! It decides if we enter bootloader mode or if we run the application.
*/
void
BL_CheckForUserApp(
void
) {
uint32_t startup;
/* assuming 32bit function pointers */
startup = ((uint32_t*)APP_FLASH_VECTOR_START)[1];
/* this is the reset vector (__startup function) */
if
(startup!=-1 && !BL_CheckBootloaderMode()) {
/* we do have a valid application vector? -1/0xfffffff would mean flash erased */
((
void
(*)(
void
))startup)();
/* Jump to application startup code */
}
}
The function checks if the ‘startup’ function in the vector table (index 1) is valid or not. If the application flash has been erased, it will read -1 (or 0xffffffff). So if we have an application present and the user does not want to run the bootloader, we jump to the application startup.
Below is the code to decide if the user is pressing the button to enter the startup code:
static
bool
BL_CheckBootloaderMode(
void
) {
/* let's check if the user presses the BTLD switch. Need to configure the pin first */
/* PTB8 as input */
/* clock all port pins */
SIM_SCGC5 |= SIM_SCGC5_PORTA_MASK |
SIM_SCGC5_PORTB_MASK |
SIM_SCGC5_PORTC_MASK |
SIM_SCGC5_PORTD_MASK |
SIM_SCGC5_PORTE_MASK;
/* Configure pin as input */
(
void
)BitIoLdd3_Init(NULL);
/* initialize the port pin */
if
(!BL_SW_GetVal()) {
/* button pressed (has pull-up!) */
WAIT1_Waitms(50);
/* wait to debounce */
if
(!BL_SW_GetVal()) {
/* still pressed */
return
TRUE;
/* go into bootloader mode */
}
}
/* BTLD switch not pressed, and we have a valid user application vector */
return
FALSE;
/* do not enter bootloader mode */
}
I’m using
BitIOLdd3_Init()
to initialize my port pin, which is part of the BitIO component for the push button:
When creating a BitIO component for Kinetis, Processor Expert automatically creates a BitIO_LDD component for it. As I do not have control over the name of that BitIO_LDD, I need to use in my bootloader whatever Processor Expert has assigned as name.
I’m using PTB8 of the Freedom board, and have it connected to a break-out board (pull-up to 3.3V if button is not pressed, GND if button is pressed):
You might wonder why I have to initialize it, as this is usually done automatically by PE_low_level_init()
in main()
? The reasons is: I need to do this *before* main()
gets called, very early in the startup()
code. And that’s the reason as well why I need to set the SIM_SCGC5
register on Kinetis to clock the peripheral.
Inside the CPU component properties, there is a Build option setting where I can add my own code to be inserted as part of the system startup:
To make sure it has the proper declaration, I add the header file too:
These code snippets get added to the __init_hardware()
function which is called from the bootloader startup code:
This completes the Bootloader itself. Next topic: what to do in the application to be loaded…
Application Memory Map
As shown above: the bootloader is sitting in a part of the memory which is not available by the application. So I need to make sure that application does not overlap with the FLASH area of the bootloader. My bootloader starts at address 0×0000 and ends at 0x3FFF:
While the application can be above 0×4000. These numbers are used in Bootloader.c:
/* application flash area */
#define MIN_APP_FLASH_ADDRESS 0x4000 /* start of application flash area */
#define MAX_APP_FLASH_ADDRESS 0x1FFFF /* end of application flash */
#define APP_FLASH_VECTOR_START 0x4000 /* application vector table in flash */
#define APP_FLASH_VECTOR_SIZE 0xc0 /* size of vector table */
The application just needs to stay outside the FLASH used by the bootloader:
To make this happen, I need to change the addresses for m_interrupts
and m_text
in the CPU build options:
As for the ARM Cortex-M4/0+ do not need to copy the vector table in the bootloader to a different location, I can debug the application easily without the bootloader.
S-Record (S19) Application File Generation
The bootloader loads S19 or S-Records. This post explains how to create S19 files for Kinetis and GNU gcc.
Code Size
The bootloader is compiled with gcc for the FRDM-KL25Z board. Without optimization (-O0), it needs 13 KByte of FLASH. But optimized for size, it needs only 8 KByte :
text data bss dec hex filename 8024 24 2396 10444 28cc FRDM_Bootloader.elf
Summary
With this, I have a simple serial bootloader for only 8 KByte of code. The bootloader project and sources are attached to this message.
And I have several ideas for extensions:
- Using a memory stick to load the appliation file (USB MSD Host).
- Using a SD-Card interface with FatFS.
- Using a USB MSD device to load the file.
- Performing vector auto-relocation: the bootloader should detect the vector table at address 0×00 of the application file and automatically relocate it to another location in FLASH. That way I can debug the Application without change of the vector table.
- Making sure it runs on other boards and microcontroller families.
- Creating a special component for the bootloader.
While the bootloader only needs 8 KByte for now, I keep the reserved range at 16 KByte, just to have room for future extensions.