>Home>Rust STM32 Handling Static Variables

Rust STM32 Handling Static Variables

Published 9/11/2017

I've just pushed an example showing how to use code that contains statically initialized variables.

The general problem with static variables can be described as follows. Lets say we define a static variable INT_DATA and assign it some value as follows:

pub static mut INT_DATA : u32 = 5;

When we upload our Flash to the microcontroller now however, we are only overwriting the Flash partition. This means that the value 5 for our static variable needs to be stored in Flash. This would work fine, until somewhere in our application we have code which alters this variable such as:

unsafe { INT_DATA = 9 };

This is problematic because the variable is in Flash, where memory cannot be modified!

The solution to this problem is to store all variables with values that need to be initialized in our Flash, and to then copy them to RAM when the application is loaded. By default all variables with non zero values are stored in the .data section, while all zeroed static variables are stored in the .bss section, which must be set to zero during startup.

We start by specifying this in more detail in the linker script. A nice but cryptic resource on what is required is the following page: http://www.math.utah.edu/docs/info/ld_3.html#SEC18

We use the following updated linker script. We now define a section .etext which we use to get the location after everything else in the Flash memory (I did this because the example using SIZEOF didn't seem to quite work with our setup, but it might be possible). Using it we define the section .data, specifying AT to be the location of .etext, which makes the physical memory address of our data in the ROM be the location of .etext. Finally we also place .bss in RAM, without giving it an address in physical memory.

Within the linker script we also export all the addresses as symbol, so that we can use them within our code. The code _etext = . ; specifies that the current location should be stored under the _etext symbol, which we can use later in code. In this case _etext is the physical memory location of the variables requiring initialization, while _data and _edata mark the virtual addresses. The variables _bstart and _bend are the virtual addresses of the memory that needs to be cleared on startup.

MEMORY
{
  FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 64K
  RAM (rwx)  : ORIGIN = 0x20000000, LENGTH = 20K
}

SECTIONS
{
  /* Set stack top to end of RAM */
  __StackTop = ORIGIN(RAM) + LENGTH(RAM);

  /* first entry should be the stack pointer and then the interrupt table */
  .ivtable :
  {
    LONG(__StackTop);
    KEEP(*(.ivtable));
  } > FLASH

  /* after that the program data is copied */
  .text :
  {
    *(.text*)
 *(.rodata*)
  } > FLASH
 
  .etext :
  {
 _etext = . ;
  } > FLASH

  /* The data has a virtual address in RAM, but is copied just after the program data */
  .data : AT ( ADDR(.etext) )
  {
    _data = . ;
    *(.data*)
 _edata = . ;
  } > RAM

  /* The data that receives a virtual address in RAM, but needs to be initialized to zero */
  .bss : {
    _bstart = . ;
    *(.bss*) *(COMMON)
 _bend = . ;
  } > RAM
}

Now we need a way to be able to get these addresses within our code. This can be done be defining external variables as follows. To get the address of _etext we can then use the code addr_of!(_etext) using the macro we define below.

extern {
    pub static _etext : u8;
    pub static _data : u8;
    pub static _edata : u8;
    pub static _bstart : u8;
    pub static _bend : u8;
}

#[macro_export]
macro_rules! addr_of {
    ($id:expr) => (
        (&$id as *const u8) as *mut u8
    )
}

We now need to define both memclr and memcpy functions, which we will need to initialize the memory. This is fairly straightforward and we use the following:

#[no_mangle]
pub unsafe extern fn __aeabi_memcpy(dest: *mut u8, src: *mut u8, n: isize) -> *mut u8 {
    let mut i =  0;
    while i < n {
        *dest.offset(i) = *src.offset(i);
        i += 1;
    }
    return dest;
}

#[no_mangle]
pub unsafe extern fn __aeabi_memclr(dest: *mut u8, n: isize) -> *mut u8 {
    let mut i =  0;
    while i < n {
        *dest.offset(i) = 0u8;
        i += 1;
    }
    return dest;
}

Finally we can define an init_mem function, which we need to call from our main function. This simply clears the .bss section using the memclr function, and it then uses memcpy to copy our data from flash to RAM:

pub unsafe fn init_mem() {
    // zero .bss section
    __aeabi_memclr(addr_of!(_bstart), addr_of!(_bend) as isize - addr_of!(_bstart) as isize);
    // copy .data section
    __aeabi_memcpy(addr_of!(_data), addr_of!(_etext), addr_of!(_edata) as isize - addr_of!(_data) as isize);
}

This allows the following program to produce the correct output:


pub static mut INT_DATA: u32 = 5; // static value
pub static mut INT_BSS: u32 = 0; // default value

pub fn main() {
    unsafe { stm32::mem::init_mem() };

    // setup system
    init_clock();
    enable_led();
    enable_uart();

    println("STM32 Memory Test App");
    println("  - by Rudi Horn");

    unsafe {
        print("int_data: "); print_int(INT_DATA); println("");
        print("int_bss: "); print_int(INT_BSS); println("");

        INT_DATA = 9; INT_BSS = 99;

        print("int_data: "); print_int(INT_DATA); println("");
        print("int_bss: "); print_int(INT_BSS); println("");
    }

    loop {
    }
}

Output:

STM32 Memory Test App
  - by Rudi Horn
int_data: 5
int_bss: 0
int_data: 9
int_bss: 99

For the full source code see https://github.com/rudihorn/rust-stm32f103-examples/tree/master/stm32-mem.

Small note: This example does not compile with --release for some reason. Unfortunately I don't have a debugger (yet) and so it is a bit difficult to find out what the exact cause is, but it seems to be the memclr function and may be related to the actual function call to memclr. If anyone has any ideas let me know!

 

Previous EntryNext Entry