Data exchange between OpenBLT and your firmware

Several OpenBLT bootloader users asked, if it is possible two exchange data between the bootloader and their firmware. Even though the bootloader and your firmware are two completely separate programs in flash memory, which never execute at the same time, such a data exchange is definitely possible. This article presents a free-to-use software module that makes parameter exchange possible, by means of a shared section in RAM.

This data exchange is especially useful in a situation where you want to change the configuration and behavior of the bootloader in a way that was not yet known at the time that the bootloader was programmed into flash memory. For example to change the CAN identifiers the bootloader uses during a firmware update, based on a node-address that was dynamically assigned in your user program. Another example is to pass the IP-address on to the bootloader, which was assigned in your user program via DHCP.

The first part of the article delves into the design of this shared parameter module, which is followed by a step-by-step demonstration on how to integrate and use the shared parameter module. The demonstration includes an example on how to perform the actual data exchange between the bootloader and user program. The bootloader and user program in this demonstration are prepared for a ST Nucleo-F091RC board with Atollic TrueStudio as the development environment. Note though that the general concept works for any type of microcontroller and development environment combination.

Design

The foundation of the shared parameter module is a section in RAM that is accessible by both the bootloader and the user program. Parameters can be read and written to this RAM section, for the purpose of data exchange between the bootloader and the user program. For the parameter sharing via this RAM section to work properly, the shared parameter module must meet the following requirements:

  1. The bootloader and user program agree on the size and location of the RAM section.
  2. The RAM section must not be initialized by the C-startup routine.
  3. A validation method is available to determine if the RAM section contents are valid.

Requirement (1) is straightforward. For both programs to share parameters via this RAM section, they both need to know where it is located. Requirement (2) is needed because the C-startup routine (the code that runs before function main() is called) initializes RAM with zero values. If the bootloader writes parameters to the RAM section that are to be read by the user program, then the C-startup should not overwrite these parameters values with zeroes. This leads to requirement (3), because if the RAM section is not initialized by the C-startup routine, the bootloader and the user program need another way to determine if its contents are valid. Think for example of a common scenario where the bootloader is started after the system was completely powered down. In this case the contents of the RAM section are undefined. The bootloader must be able to detect this situation and then correctly prepare the RAM section contents.

The compiler and linker of your integrated development environment can handle requirements (1) and (2). By using a special #pragmaor __attribute__when declaring the variable that represents the RAM section, you can instruct the linker to place the variable in a specific location in RAM. You can specify this location in the linker script. Bypassing the initialization of the variable that represents the RAM section, is typically achieved by specifying noinitor noloadfor the variable. How this variable placement and initialization bypass is achieved, greatly depends on the compiler and linker that you build your software with. The demo programs presented in this article are configured for Atollic TrueStudio. Consult the documentation of your compiler and linker in case you build your software with a different integrated development environment.

To meet requirement (3), the shared parameter module follows this predefined layout for the RAM section contents:

The identifier is a 32-bit constant value, which informs the bootloader and user program that the RAM contents holds data that follows the before mentioned layout. To further validate the RAM contents, a 16-bit checksum is calculated over its contents (identifier and data-array). This checksum is updated each time a parameter value is written to the data-array. If the identifier and checksum are checked each time the RAM section is accessed, you can be sure that its contents are valid.

The implementation of the shared parameter module offers the bootloader and user program an easy-to-use application programming interface (API):

void SharedParamsInit(void)

Function SharedParamsInit()initializes the software module. It only needs to be called once during the initialization of the software program. Typically at the start of function main().

bool SharedParamsReadByIndex(uint32_t idx, uint8_t * value)

Function SharedParamsReadByIndex()obtains a byte-value from the data-array inside the shared parameter buffer. The idxparameter specifies the zero-based index into the array. Parameter valueis a byte-pointer where the read data value is written to. The function returns trueif the read operation was successful, i.e. the shared parameter buffer was valid. Otherwise falseis returned.

bool SharedParamsWriteByIndex(uint32_t idx, uint8_t value)

Function SharedParamsWriteByIndex()writes a byte-value to the data-array inside the shared parameter buffer. The idxparameter specifies the zero-based index into the array. Parameter valueholds the value that should be written to the data-array at the specified index. The function returns trueif the write operation was successful, i.e. the shared parameter buffer was valid. Otherwise falseis returned.

Demonstration

Integration

Now that the idea behind the shared parameter module is presented, it is time to see it in action. This section contains step-by-step instructions on how to integrate and use the shared parameter module. In the demonstration, we are going to use the shared parameter module in a bi-directional way: The bootloader makes its version number available to the user program and the user program informs the bootloader of the UART communication speed that should be used during firmware updates. OpenBLT version 1.6.0 was used as a foundation for this demonstration.

Start by downloading the shared parameter module: shared_params.zip. After extracting the downloaded zip-archive, copy the files “shared_params.c” and “shared_params.h” to both the bootloader and user program software projects. The “shared_params.c” source-file should be compiled and linked when building the bootloader and the user program. Additionally, include the “shared_params.h” header-file in all source files that plan on calling API function of the shared parameter module. For this example that means that the following line should be added to the files “main.c” and “hooks.c” in the bootloader, and to the file “header.h” in the user program:

#include "shared_params.h"

The source-file of the shared parameter module contains an internal variable called sharedParamsBuffer. It is of custom type tSharedParamsBuffer. If you look at how this type is defined, you’ll see that it resembles the previously presented RAM section layout. The source-file also contains internal functions for managing the checksum inside the buffer and for validating the buffer contents. These parts together satisfy requirement (3).

If you look closely at the sharedParamsBuffervariable declaration, you’ll see that the following was added at the end of the declaration:

__attribute__ ((section (".shared")))

This instructs the compiler and linker to place variable sharedParamsBufferinto a custom section called “.shared”. For the linker to understand what to do with data in this section, the linker script files need to be updated. For the Nucleo-F091RC/TrueStudio demo programs in the OpenBLT software package, the linker script file is called “STM32F091RC_FLASH.ld” in both the bootloader and user program. Note that the changes need to be made in the linker script file of both the bootloader and user program.

Add the following entry at the start of the SECTIONSarea:

.shared (NOLOAD) :
{
  . = ALIGN(4);
  _sshared = .;
  __shared_start__ = _sshared;
  *(.shared)
  *(.shared.*)
  KEEP(*(.shared)) 
  . = ALIGN(4);
  _eshared = .;
  __shared_end__ = _eshared;
} >SHARED

This identifies the “.shared” section to the linker and informs it about what should go into this custom section. This part results in the sharedParamsBuffervariable to be placed in this section, because of the attribute that was added at the end of its declaration. Note that the first line of the section definition contains NOLOAD. This is to prevent the C-startup code from initializing the sharedParamsBuffervariable, which satisfies requirement (2).

The last line of the newly added “.shared” section informs the linker that the section contents should be placed in memory area SHARED. The next step is to make this memory area known to the linker. Change the following line in the MEMORYarea:

RAM (xrw) : ORIGIN = 0x200000C0, LENGTH = 32K – 192

to:

SHARED (xrw) : ORIGIN = 0x200000C0, LENGTH = 64
RAM (xrw) : ORIGIN = 0x20000100, LENGTH = 32K - 192 - 64

This basically takes 64 bytes away of the RAM available to the remainder of the software program, and assigns it to the new SHAREDarea. Note that the size of the sharedParamsBuffervariable is also 64 bytes. This results in the linker always placing the sharedParamsBufferat the fixed memory address 0x200000C0, which satisfies requirement (1).

Initialization

The shared parameter module is now fully integrated and satisfies all requirements. The bootloader and user program can now call its API functions. The first thing that needs to be done in both the bootloader and user program, is initializing the shared parameters module. This is achieved by simply placing a call to function SharedParamsInit()at the start of function main():

int main(void)
{
  /* Initialize the microcontroller. */
  Init();
  /* Initialize the shared parameters module. */
  SharedParamsInit();

Passing parameters to the user program

In this demonstration the first 3 bytes (index 0, 1, 2) of the data-array inside the shared parameters RAM section are reserved for storing the bootloader version number. The bootloader writes these values and the user program can then read them. To write the bootloader version number, at the following lines to the bootloader’s function main(), right before entering the super loop:

/* Share the bootloader version in the first 3 bytes. */
SharedParamsWriteByIndex(0, BOOT_VERSION_CORE_MAIN);
SharedParamsWriteByIndex(1, BOOT_VERSION_CORE_MINOR);
SharedParamsWriteByIndex(2, BOOT_VERSION_CORE_PATCH);

/* Start the infinite program loop. */
while (1)
{

Whenever you want to access the bootloader’s version number from the user program, you can use the following piece of code in the user program to retrieve the values that were stored by the bootloader:

static unsigned char bootVersionMajor, bootVersionMinor, bootVersionPatch;

/* Read the bootloader version information from shared RAM. */
SharedParamsReadByIndex(0, &bootVersionMajor);
SharedParamsReadByIndex(1, &bootVersionMinor);
SharedParamsReadByIndex(2, &bootVersionPatch);

Passing parameters to the bootloader

Now its time to look at how parameter values can be passed the other way around. It follows the same approach as in the previous section. By default the bootloader configures a fixed UART communication speed for firmware update with UART. In the Nucleo-F091RC demo bootloader it is set to 57600 bits/sec via macro BOOT_COM_UART_BAUDRATEin the “blt_conf.h” configuration header-file. In this demonstration the UART baudrate that the user program configures, for detecting firmware update requests, is changed to 19200 bits/sec and it will request the bootloader to use this baudrate as well via the shared parameters approach.

To configure the 19200 bits/sec baudrate in the user program, go to function BootComUartInit()in source-file boot.c and change this line:

uartHandle.Init.BaudRate = BOOT_COM_UART_BAUDRATE;

to:

uartHandle.Init.BaudRate = 19200;

Next, this desired baudrate needs to be stored in the shared parameters RAM section. This is achieved by simply adding the following lines at the end of function BootComUartInit():

/* Write the desired 32-bit UART communication speed 
 * for firmware updates 
 */
SharedParamsWriteByIndex(3, (unsigned char)(19200));
SharedParamsWriteByIndex(4, (unsigned char)(19200 >> 8));
SharedParamsWriteByIndex(5, (unsigned char)(19200 >> 16));
SharedParamsWriteByIndex(6, (unsigned char)(19200 >> 24));

Note that the 32-bit baudrate value is stored at index 3, 4, 5, and 6. This is because index 0, 1, and 2 were already reserved for storing the bootloader version number. These are all the changes needed in the user program.

In the bootloader, the UART baudrate for firmware updates is configured via macro BOOT_COM_UART_BAUDRATEin the “blt_conf.h” configuration header-file. This needs to be updated such that by default it still uses the 57600 bits/sec value, unless otherwise specified by the user program. In the bootloader’s “blt_conf.h” header-file, change the following line:

#define BOOT_COM_UART_BAUDRATE (57600)

to:

extern unsigned long CfgGetUartBaudrateHook(void);
#define BOOT_COM_UART_BAUDRATE CfgGetUartBaudrateHook()

This links the UART baudrate configuration to a function instead of just a fixed value. This function is not yet available, so add it to the bootloader’s “hooks.c” source-file:

unsigned long CfgGetUartBaudrateHook(void)
{
  /* Initialize to the default fallback value in case no 
   * valid baudrate is present in the shared parameter RAM.
   */
  blt_int32u result = 57600;
  blt_int8u  sharedParamValue;
  blt_int32u baudrate = 0;

  /* Attempt to read the baudrate from shared parameter 
   * RAM byte-by-byte. 
   */
  SharedParamsReadByIndex(3, &sharedParamValue);
  baudrate |= sharedParamValue;
  SharedParamsReadByIndex(4, &sharedParamValue);
  baudrate |= ((blt_int32u)sharedParamValue << 8);
  SharedParamsReadByIndex(5, &sharedParamValue);
  baudrate |= ((blt_int32u)sharedParamValue << 16);
  SharedParamsReadByIndex(6, &sharedParamValue);
  baudrate |= ((blt_int32u)sharedParamValue << 24);
  /* Validate the baudrate value before using it. */
  if ( (baudrate >= 1200) && (baudrate <= 256000) )
  {
    /* Valid baudrate specified by the user program 
     * via shared parameter RAM. Use this value for  
     * firmware updates, instead of the default value.
     */
    result = baudrate;
  }
  /* Give the result back to the caller. */
  return result;
}

This is basically all that needs to be done, except for one little snag: If you rebuild the bootloader software program, you will run into a compile error. This is because the bootloader contains a header file called “plausibility.h”, which job it is to catch problems with the bootloader configuration. One of the checks it performs is to see if the configured value for macro BOOT_COM_UART_BAUDRATEis a positive value. This macro is now linked to a function instead of a constant value and the compiler’s preprocessor can therefore not perform this plausibility check. The solution is to simply remove the following lines from the “plausibility.h” header-file:

#if (BOOT_COM_UART_BAUDRATE <= 0)
#error "BOOT_COM_UART_BAUDRATE must be > 0"
#endif

Testing

Make sure the modified bootloader and user programs are rebuilt. Then start by erasing the entire flash contents of the STM32F091RC that is present on the Nucleo-F091RC board. You can use a tool such as ST’s STM32CubeProgrammer for this. Next, program just the bootloader and start it. Refer to the demo program description on the OpenBLT wiki for detailed instructions.

Since no user program has yet been programmed, no new baudrate is passed on via the shared parameters module. This means that firmware updates via UART should be possible via the default baudrate of 57600 bits/sec. Configure MicroBoot or BootCommander for this baudrate and start a firmware update to program the user program from the demonstration:

If you now attempt the exact same firmware update again, you notice that it won’t work anymore. This is expected because the user program is now running, which configured the UART baudrate for 19200 bits/sec. If you reconfigure MicroBoot or BootCommander for a baudrate of 19200, you’ll notice that it works:

This proves that the user program correctly passed on the desired baudrate of 19200 bits/sec to the bootloader by means of the shared parameter module. After detection of the firmware update request, the user program activated the bootloader. The bootloader then detected the shared parameters with the requested baudrate of 19200 bits/sec and configured its UART interface for this baudrate, instead of the default 57600 bits/sec.

Conclusion

This article presented the requirements, design and usage of the shared parameters module. This software module enables you to exchange data between the OpenBLT bootloader and your firmware in a bidirectional way via a reserved section in RAM.

The shared parameters module can be downloaded and used for free, under the same license terms as the OpenBLT bootloader itself. In case you don’t have the time to manually re-create the examples described in the demonstration section, you can download the software package with the demonstration results here.

Feel free to contact us with further assistance getting the shared parameters module configured and working for you.

This entry was posted in OpenBLT and tagged , , . Bookmark the permalink.