Panel For Example Panel For Example Panel For Example

An OTA Upgrade Method for LoRa Devices

Author : Adrian November 17, 2025

An OTA Upgrade Method for LoRa Devices

1. Overview

This article describes adding OTA firmware upgrade to a LoRa-based wireless product. Because LoRa has low data rates, reducing the APP-region firmware size to speed up upgrades is important. The firmware uses STM32_Cryptographic_Library, STM32_Std_Library, and a LoRa driver library, whose compiled sizes are large. The idea explored here was to place those libraries inside the bootloader, expose well-defined interfaces for APP use, and keep APP code size minimal.

2. Debugging and Implementation

2.1 Concept

Typical firmware upgrade partitions on-chip Flash into a bootloader region and an APP region. The APP region receives the new firmware into on-chip or external Flash, sets an upgrade flag, then jumps to the bootloader, which performs the actual update. Because APP contains many libraries, its binary grows large and LoRa transfer time becomes long.

Flash bootloader and app regions

To reduce APP size, put the commonly used libraries into the bootloader and add a third Flash region for shared functions. The shared function region stores the bootloader-exposed interfaces. When APP calls a shared function, it finds the function address in the shared region, jumps to the corresponding bootloader code, executes it, and returns results to APP. 

2.2 Placing Functions and Variables at Absolute Addresses

To implement this, functions and variables must be placed at fixed Flash addresses so APP can reliably locate bootloader-fixed interfaces. The IAR toolchain provides mechanisms for this, summarized below.

2.2.1 IAR extended keywords

  • @ is used to place functions or variables at an absolute address or section.
  • __no_init prevents a variable from being initialized at system startup.
  • __root ensures symbols that appear unused are still included in the final binary.

2.2.2 Absolute placement of a function

Define a function and append the section name with @, for example:

void fun1(int a, int b) @".MY_SECTION"{... // function body}

Then add a placement in the linker file (.icf). For example, 0x08010000 is a Flash address and .MY_SECTION must match the section name used after @:

place at address mem:0x08010000 { readonly section .MY_SECTION};

2.2.3 Absolute placement of a variable

Example of placing a variable at an absolute RAM address:

__no_init char array1[100]@0x2000B000;

2.2.4 Absolute placement of a constant

Example for a constant placed in a named section:

__root const int str1[4]@".MYSEG" = {1, 2, 3, 4};

Placing a constant in Flash requires updating the .icf file, for example:

place at address mem:0x08018500 { readonly section .MYSEG};

2.2.5 Placing a .c file at an absolute address

To place the entire test.c output at a given Flash address, add the following to the .icf file:

place at address mem:0x08018000 { section .text object test.o };

After building, all functions from test.c will be located after 0x08018000.

2.3 Implementing Bootloader Shared Functions

Because shared functions may change during development, it is inconvenient to place each function at a fixed address individually. A convenient approach is to place a table of function pointers in a fixed section, for example:

__root const uint32_t func_table[]@".COMMON_FUNC_SEG" = {(uint32_t)&fun1, /** 00 _/__(uint32_t)&fun2, /_ \* 01 _/__(uint32_t)&fun3, /_ \* 02 */}

Then place that array at a fixed address via the .icf file. The shared function table can be located starting at 0x08010000, so each entry (a 4-byte function pointer) is at a known offset.

/* Place the array at a fixed address */place at address mem:0x08010000 { readonly section .COMMON_FUNC_SEG};

2.4 Using Shared Functions in the APP

With libraries and shared interfaces frozen in the bootloader and the function table at a fixed address, APP can access bootloader functions via function pointers. Example:

/* 1. Declaration */typedef int (*app_fun1)(int a, int b);typedef void (*app_fun2)(void);typedef char (*app_fun3)(char p);/* 2. Define variables of function pointer type */app_fun1 fun1;app_fun2 fun2;app_fun1 fun3;/* 3. Redefinition of common functions */#define FUNC_TABLE_ADDR (0x08010000) /* common functions base address */void redefine_common_function(void){uint32_t *func_table_addr = (uint32_t *)FUNC_TABLE_ADDR;_fun1 = (app_fun1)func_table_addr[0]; /* 00 _/_ */_fun2 = (app_fun2)func_table_addr[1]; /* 01 _/_ */_fun3 = (app_fun3)func_table_addr[2]; /* 02 */}

This allows APP to call bootloader-fixed functions. Note that this approach makes debugging harder, so ensure bootloader interfaces are validated early.

3. Considerations

  1. Functions located in the update area and placed at absolute addresses must not call arbitrary other library functions; any called functions must also be placed at absolute addresses.
  2. If an absolute-address function uses constants, those constants must also be placed at absolute addresses.
  3. If an absolute-address function uses global variables, those globals must be placed at absolute addresses; local variables are not subject to this restriction.

4. Troubleshooting Example

The initial implementation experienced crashes when APP called shared functions. Analysis found the STM32 standard library configures the clock using two global arrays defined as:

/* stm32f10x_rcc.c */static __I uint8_t APBAHBPrescTable[16] = {0, 0, 0, 0, 1, 2, 3, 4, 1, 2, 3, 4, 6, 7, 8, 9};static __I uint8_t ADCPrescTable[4] = {2, 4, 6, 8};

Because these two global arrays were originally in the bootloader region, reset or reinitialization after jumping to APP could overwrite their expected locations, causing clock configuration errors. The solution was to place these arrays in fixed sections inside the bootloader so they are not modified. For example:

__root const uint8_t APBAHBPrescTable[16]@".AHBAPB_PRESC_TABLE"={0, 0, 0, 0, 1, 2, 3, 4, 1, 2, 3, 4, 6, 7, 8, 9};__root const uint8_t ADCPrescTable[4]@".ADC_PRESC_TABLE"={2, 4, 6, 8};/* Corresponding .icf file modifications */place at address mem:0x08010000 { readonly section .AHBAPB_PRESC_TABLE};place at address mem:0x08010010 { readonly section .ADC_PRESC_TABLE};

5. Verification

To verify symbols are placed at absolute addresses, examine the generated linker map file. The defined sections and their addresses will be visible in the map file, confirming placement.

Linker map file showing section addresses