The goal of this blog article is to answer a support question that has been asked by several OpenBLT bootloader users: How can my own firmware read the version of the bootloader? The bootloader defines three macros that combined hold the bootloader version number. These macros are update each time a new version of the bootloader is released. You can find these macros in the “boot.h” header-file:
When you perform a text search for these macros through the bootloader’s sources, you’ll notice that the macros are not actually used. The idea is that they are available for you to use any way you prefer. You could for example customize the bootloader’s internally used communication protocol (XCP) such that the version number is read out by the PC tools MicroBoot and BootCommander. Although you are free to do so, most users prefer to not touch the bootloader’s core code and instead they want to access the bootloader’s version number from their own firmware. This article contains an example on how to do just that.
The example presented here is based on OpenBLT version 1.7.0 and uses the Nucleo-F303K8 demo programs that were created with the Atollic TrueStudio software development environment. Simply because the STM32 microcontroller, together with a GCC based compiler, is currently a popular combination. The approach presented in this article works for any type of microcontroller and software development environment though.
Before diving head-first into the details, lets first review the layout of the bootloader’s version number. The version number is comprised of three individual numbers, separated by a dot:
The first number is the major version. This number will only be incremented when a major overhaul of the bootloader code base occurred. This is to indicate that there will most likely be major compatibility issues when you attempt to upgrade from the previous major version. The second number is the minor version. This number is incremented each time a new stable release was published. OpenBLT currently follows a fixed release schedule where new stable releases are made twice during a year. One in January and one in July. The third number is the patch version. Whenever critical bugs are discovered in a stable release, the fix will be made into the development trunk so it will be resolved in future stable releases. Additionally, the fix is backported into the code of the most recent stable release. After which a new stable release is made by incrementing the patch number in the version number.
One way of making the bootloader’s version number available to your firmware, is by placing the version number in a constant variable, which the linker then always places at the same location in the memory map. This is a two step process.
Step one is adding the following code to the bootloader’s “main.c” file:
/** \brief Structure layout that contains the version number of a software * program. */ typedef struct { uint8_t major; /**< Major part of the version number. */ uint8_t minor; /**< Minor part of the version number. */ uint8_t patch; /**< Patch part of the version number. */ } tVersionInfo; /** \brief Constant variable that contains the bootloader version number. * The ".version" section attribute was added, allowing the linker * to find it and place it at a fixed address in the memory map. * Note that approach is GCC compiler/linker specific and will * need adjusting in case another toolchain is used. */ const tVersionInfo bootloaderVersion __attribute__ ((section (".version"))) = { .major = BOOT_VERSION_CORE_MAIN, .minor = BOOT_VERSION_CORE_MINOR, .patch = BOOT_VERSION_CORE_PATCH };
During compilation, the GCC compiler labels the variable’s section as “.version”. Step two is informing the linker where this section should be placed in the memory map. This is done by changing the following part in the “stm32f30_flash.ld” linker descriptor file:
/* The startup code goes first into FLASH */ .isr_vector : { . = ALIGN(4); KEEP(*(.isr_vector)) /* Startup code */ . = ALIGN(4); } >FLASH
To:
/* The startup code goes first into FLASH */ .isr_vector : { . = ALIGN(4); KEEP(*(.isr_vector)) /* Startup code */ KEEP(*(.version)) /* Bootloader ROM version table after the vectors */ . = ALIGN(4); } >FLASH
What this effectively does is inform the linker that all constant variables assigned to section “.version” should be placed right after the interrupt vector table. The interrupt vector table always has the same ROM size, meaning that the “bootloaderVersion” also gets a fixed memory address. To find out at what memory address the “bootloaderVersion” is placed, the linker generated MAP-file can be inspected:
This shows that the variable with bootloader information will always be located at memory address “0x08000188”. This concludes the changes that needed to be made in the bootloader.
To access the variable at the fixed memory address from your own firmware, the following code can be added. Either in a source-file such as “main.c” where you want to access the bootloader’s version number or perhaps even in a header-file such that it can be made accessible to all source-files:
/** \brief Structure layout that contains the version number of a software * program. */ typedef struct { uint8_t major; /**< Major part of the version number. */ uint8_t minor; /**< Minor part of the version number. */ uint8_t patch; /**< Patch part of the version number. */ } tVersionInfo; /** \brief Function-like macro for reading out the bootloader's version * number. */ #define BOOT_VERSION_GET() ((tVersionInfo const * const)0x08000188)
The following code snippet demonstrates how the bootloader’s version number can be read out in your firmware:
int main(void) { uint8_t bootVersionMajor; uint8_t bootVersionMinor; uint8_t bootVersionPatch; /* Read bootloader version. */ bootVersionMajor = BOOT_VERSION_GET()->major; bootVersionMinor = BOOT_VERSION_GET()->minor; bootVersionPatch = BOOT_VERSION_GET()->patch;
It can be tested by stepping through this newly added code during a debug session. This screenshot shows the values that were assigned to the three variables. As expected, it shows version 1.7.0 of the bootloader.
To summarize, the example presented in this article explains how the bootloader can be modified such that its version number is placed in a constant variable that is always linked to the same memory address. Once you know this memory address, you can use a pointer to it to read the bootloader’s version number directly in the code of your own firmware. Hopefully you found this article interesting and informative. Feel free to drop me a line if you have another topic idea that you would like to have covered on the blog.