Performance Zone is brought to you in partnership with:

Erich is Professor at Lucerne University of Applied Sciences and Arts and Distinguished Member of Technical Staff at Freescale Semiconductor. Erich has a MsCS degree and 18+ years of experience in the embedded software and tools world. He created many embedded cross C/C++ compilers and debuggers. Additionally he is researching in the domain of programming languages, real time and mechatronic systems. Erich is a DZone MVB and is not an employee of DZone and has posted 88 posts at DZone. You can read more from them at their website. View Full User Profile

Serial Bootloader for the Freedom Board with Processor Expert

05.01.2013
| 1513 views |
  • submit to reddit

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-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?

Serial Bootloader made with Processor Expert

Serial Bootloader made with Processor Expert

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.

Bootloader and Application Memory Map

Bootloader and Application Memory Map

:idea: 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

  1. 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 :-) .
  2. 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.
  3. The bootloader needs to use a communication channel to read that file. That can be RS-232, USB or an SD card file system (e.g. FatFS).
  4. 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.

:idea: 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:

Bootloader System Block Diagram

Bootloader System Block Diagram

  • 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:

Bootloader FLASH Area

Bootloader FLASH Area

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:

Protecting Regions in the CPU Component

Protecting Regions in the CPU Component

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/

Terminal Program

Terminal Program

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.

:idea: 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:

Loading S19 File

Loading S19 File

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:

S19 Parser

S19 Parser

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:

Processor Expert Flash Programming Component

Processor Expert Flash Programming Component

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:

BitIOLdd Component

BitIOLdd Component

:idea: 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):

FRDM-KL25Z with Bootloader Switch Board

FRDM-KL25Z with Bootloader Switch Board

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:

User Code before PE initialization

User Code before PE initialization

To make sure it has the proper declaration, I add the header file too:

User data declarations

User data declarations

These code snippets get added to the __init_hardware() function which is called from the bootloader startup code:

Custom startup code with Bootloader code inserted

Custom startup code with Bootloader code inserted

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:

Bootloader Memory Map

Bootloader Memory Map

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:

Application Memory Map

Application Memory Map

To make this happen, I need to change the addresses for m_interrupts and m_text in the CPU build options:

Application Memory Area Settings

Application Memory Area Settings

That’s it :-)

:idea: 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 are available on GitHub here.

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.

Happy Bootloading :-)



Published at DZone with permission of Erich Styger, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)