on 2024-10-21 05:19 AM
This article provides a step-by-step guide on how to create a memory section in the linker script. Additionally, how to allocate a variable in that specific memory address region using two different IDEs: STM32CubeIDE and IAR EWARM.
Being able to specify an address where a variable is being placed in memory can be useful for a large array of applications, such as:
This article uses the NUCLEO-G070RB as its hardware base, but all these steps can be easily replicated with any other STM32 series.
Since the article covers two different IDEs, STM32CubeIDE, and IAR EWARM, we will rely on the STM32CubeMX for the project generation and select the different IDEs at the end.
Start by creating a new project using the STM32CubeMX for the selected board. This can be quickly accomplished by using the main menu and selecting the [Start My project from ST Board]:
Once the new window opens, locate the empty field of [Commercial Part Number]. Type in the board [NUCLEO-G070RB] (adjust to your board if needed), select the board and click [Start]. Another window pops up asking [Initialize all peripherals with their default Mode?], click [Yes].
In the [Project Manager] tab, we can specify which IDE we want to use for the project and code generation. This is also the place where we can specify the project location and the project name. Make sure you fill the information aligned with your preferences. Keep in mind that for the STM32CubeIDE, you need to make sure your project does not have [Generate Under Root] checkbox selected, as it can corrupt other generated projects.
Click on the [Generate Code] button on the upper right corner once for each IDE. You should have this folder structure:
Now that we have both projects ready, we focus on the linker script editing. This is different between the two IDEs.
A small reminder on the memory sizes and their addresses: the STM32 we are currently using contains 128 KB of flash, starting at address 0x8000000 and 36 KB of RAM, that starts at address 0x20000000.
For the next steps, it is assumed that the respective project is already opened in the selected IDE.
In the STM32CubeMX, locate and open the STM32G070RBTX_FLASH.ld file. This is the linker script responsible for organizing the memory allocation inside the microcontroller. To create a new memory section, we can add the code below to the [SECTIONS] division.
/* My RAM section */
.myRAMBlock 0x20001000 :
{
. = ALIGN(4);
KEEP(*(.myRAMsection))
. = ALIGN(4);
} > RAM
As we can observe, the address 0x20001000 represents the position this section resides and the >RAM indicates that it is inside the RAM region. So, in simple terms, this reserves the memory address 0x20001000 in RAM for this section. The keyword KEEP prevents the compiler from optimizing this section out if not in use. We can and should consider ensuring the alignment in this memory region and this is done using the [. = ALIGN(4);].
The same process can be achieved if you want to allocate a section from flash. The modifications needed are quite simple. Make sure that the address and the name of the memory are adjusted. There is one additional parameter that can be added to prevent a warning when building, which is the (READONLY). This tells the compiler that this section is meant to be read only without any write capabilities, which is the usual scenario for flash memory.
/* My FLASH section */
.myFLASHBlock 0x8004000 (READONLY) :
{
. = ALIGN(4);
KEEP(*(.myFLASHsection))
. = ALIGN(4);
} > FLASH
Add the two sections, as the application code uses it in the demonstration.
Locate and open the[stm32g070xx_flash.icf. This file can be more easily found in the EWARM folder, but is not visible from the EWARM workspace by default. You can drag and drop the stm32g070xx_flash.icf in the EWARM workspace and open it to edit.
To create the sections in existing regions, we can use the command [place at address mem:]. Therefore, we create two sections, one for the RAM and another one for the flash. To establish the starting point of the section, two symbols definition are created, leading to this:
define symbol __ICFEDIT_myRAMsection_offset__ = 0x1000;
define symbol __ICFEDIT_myFLASHsection_offset__ = 0x4000;
And the sections can be added like this:
place at address mem:(start(ROM_region) + __ICFEDIT_myFLASHsection_offset__) {section myFLASHsection};
place at address mem:(start(RAM_region) + __ICFEDIT_myRAMsection_offset__) {section myRAMsection};
Here is a visual representation of the places to add the mentioned edit commands:
Let us use the newly created sections. The code has slight differences based on the IDE selected, so the code relies on #if defined(__GNUC__) to determine the IDE being used. You can locate the portion of the code in the main.c file by checking the USER CODE BEGIN xyz.
/* USER CODE BEGIN PV */
#if defined(__GNUC__)
uint8_t __attribute__((section(".myRAMsection"))) u8RamVariable = 10;
uint32_t __attribute__((section(".myFLASHsection"))) u32FlashVariable = 0xAABBCCDD;
#else
/* Place following data in section myRAMsection */
#pragma default_variable_attributes = @ "myRAMsection"
uint8_t u8RamVariable = 10;
#pragma default_variable_attributes =
/* Place following data in section myFLASHsection */
#pragma default_variable_attributes = @ "myFLASHsection"
const uint32_t u32FlashVariable = 0xAABBCCDD;
/* Stop placing data in section MY_DATA */
#pragma default_variable_attributes =
#endif
/* USER CODE END PV */
To avoid having the compiler optimizing out, let us add a small amount of code using these contents.
/* USER CODE BEGIN 2 */
LocalVar = u32FlashVariable;
LocalVar ++;
u8RamVariable = LocalVar;
/* USER CODE END 2 */
Also, on IAR EWARM only, make sure to change the optimization level from its default HIGH to NONE. This allows the debugging experience to be better for this simple example code. You can change the optimizations by going into the [Project] → [Options] → [C/C++ Compiler] → [Optimization] tab.
This way we have a variable called u8RamVariable in a section .myRAMsection that is located in the address 0x20001000 in RAM for both IDEs and in a similar fashion, we have an initialized variable that resides inside a flash memory section myFLASHsection, called u32FlashVariable. Of course, you can read this variable normally, but if it is needed to change its value during code execution. The same procedure of a regular flash programming needs to be used, including unlocking the flash, erasing the page and only then proceeding with the programming. The variable in flash was just to showcase the usage of the sections.
Press [Ctrl + B] on STM32CubeIDE or [F7] on EWARM IAR to build the project, there should be no errors. Now, connect the NUCLEO-G070RB to the computer using a USB/Micro USB connector and enter in debug mode. The process for the IDEs is rather similar, as both allow live view of the variables and memory as well.
In the STM32CubeIDE, it is possible to add the variables in the live watch:
And IAR EWARM, it is possible to add them in the live watch as well.
Here is a small tutorial on how to locate additional documentation regarding the GCC/STM32CubeIDE. Within STM32CubeIDE, locate and click on the [Information Center] button.
In the home page, click on [STM32CubeIDE manuals].
This page offers multiple useful links for tools of the STM32CubeIDE. Scrolling down, you are able to find the document about the linker script and GCC below the [TOOLCHAIN MANUALS] section.
This article shows how to create a variable and place it into a specific address in any memory available in the STM32 microcontroller.
Here are some useful links that can help you learn more about the linker script on both the IAR, and the GCC compiler.