Cortex-M0 Boot

stm32f0_img

Still alive and kicking. And with another year in my back in Embedded Development.

My recent tamperings have been about creating a bootloader for a Cortex-M0 µ-processor that performs firmware update either from UART or SPI.

There was a interesting bit on how to set-up the system to have two Firmwares running (Boot Mode and application mode). And that is what I’ll explain in this post. How to set up a project to build a boot and successfully run your main application.

Before Starting

This post and code is prepared for a specific Cortex-M0. That is an STM32F071 µ-controller from ST. Since ARM cores are manufactured in different ways and flavours this procedure may differ. I actually doubt that this will change, but I have to cover by back 😛 It’s a second nature

STM32F0X – Memory Mapping

The guts of this problem is about the memory mapping of the whole situation.

The Cortex-M0 has aliasing memory areas. It will “always” start execution at address 0x0000_0000. But what the address 0x0000_0000 represents can be configured.

So, we can have that address linking FLASH memory, SRAM memory, or SYSTEM memory. Our applications live in FLASH so that’s what we are going to execute by default.

STM32F0 memory map

Section 2.2 of STM32f071 doc

This information is important to understand some changes that we will need to set in our application.

Building the Bootloader

What we’ll build is a Second stage bootloader. That means, we are not going to erase the SYSTEM bootloader (provided by ST) we will simply shift our real application somewhere else, and put the Bootloader as the first thing to run.

To build this you just need a default “project” for your ARM core, and when I say project I mean: some code, an appropriate linker file, and toolchain to build. You can use whatever you please, in this case I started the project using STCube. At the end of the day, the idea is that your binary has to live in position 0x0000_0000. That is Flash address 0x0080_0000.

Your linker file has to show this. Additionally, since this application is actually the BOOT you may want to limit its size.

/* Specify the memory areas */
MEMORY
{
FLASH (rx)  : ORIGIN = 0x08000000, LENGTH = 0x2000
...
}

Building the Application

The application build has to be a little bit different. To do so, we’ll simply change its starting memory address that gets assigned on linking. Again, this is a task for the linker script file:

/* Specify the memory areas */
MEMORY
{
FLASH (rx)  : ORIGIN = 0x08002000, LENGTH = 128K - 0x2000
VTRAM (xrw) : ORIGIN = 0x20000000, LENGTH = 0xC0
...
}

You can see from this example that I increased the ORIGIN value of FLASH, so the application gets linked starting from 0x0800_2000. Then we can actually have the BOOTLOADER and the Application living in the same FLASH but not overlapping.

Start End Comment
0x08000000 0x08002000 Boot application
0x08002000 0x........ Firmware

Boot memory Diagram

Boot memory Diagram



Side Note: It is interesting (if not compulsory), to match the size of your BOOT and starting application address with the size of Flash Pages. If later on you want to delete flash pages (As I will be doing for a firmware update), you don’t want a page to have half boot and half application.

Jump to application

So, we built an application that starts at 0x0800_0000 and we call it BOOTLOADER. Its only mission right now is to jump wherever the other application lives (0x0800_2000).

The Cortex-M0 needs two simple things to control the flow of the program:

  • the $pc (program counter). To know which assembler line to execute.
  • the $sp (stack pointer). To know where the stack is.

That’s easy right? The only thing that the BOOTLOADER needs to know is where this $pc and $sp should point in memory.

To do this correctly we need to understand that when we linked the binary, the address 0x0800_2000 does not actually contain assembler instructions, but the vector table.

The vector table structure for a Cortex-M0 is defined in the Cortex-M0 devices Generic user guide. You can consult the whole document in ARM infocenter, but I copied the relevant stack diagram here:

Cortex-M0 vector table

Cortex-M0 vector table

As I highlighted, the first two values of this vector table have the Initial SP value and the Reset value. Reset value being the address of the first instruction to execute.

Once we know this, writing C code to jump to the application is fairly trivial. I do a couple of C tricks though.

First, let’s define a type pFunction. This represents a function with no return value and no parameter. So if we assign the address as a type pfunction, when calling it the $pc register will change as expected.

Before doing so, we have to set a correct $sp. Libraries like STCube have specific functions to do so (__set_MSP). To change the value directly we’ll use the register construct forcing a variable to map to the MSP register. This way when a value is assigned to that variable it is actually assigned to the register.


typedef void (*pFunction)(void);

int boot_main(void){
    uint32_t startAddress, applicationStack;
    register uint32_t __regMSP __ASM("msp");
    pFunction applicationEntry;
    
    //The address where the application is written
    startAddress     = 0x08002000
    
    //Retrieve values
    applicationStack = (uint32_t)  *(volatile unsigned int*) (startAddress);
    applicationEntry = (pFunction) *(volatile unsigned int*) (startAddress + 4);
    
    /*Set a valid stack pointer for the application */
    __regMSP = applicationStack;
    
    /*Start the application */
    applicationEntry();
}

The Application Vector table

Since we are working on a cortex-M0 we don’t have the luxury of relocating the vector table wherever we want. But there’s a simple trick that will suffice.

Cortex-M0 expects the vector table to be at address 0x0000_0000, and it can only be there. As discussed early, this address can represent different physical memory addresses. Since Flash memory has the BOOTLOADER, and we don’t want to modify SYSTEM memory, we’ll have to put the application vector table in SRAM memory 0x2000_0000.

The first thing our application has to do is copy its Vector table to SRAM memory and remap the Cortex-M0 memory, so 0x0000_0000 is actually 0x2000_0000.

In summary, we have to:

  • Disable all interrupts
  • Copy the application ISR vector map, to SRAM
  • Modify the SYSCONFIG register to map SRAM to 0x0000_0000
  • Continue

I’ll dump here the code to perform this task:

    /**Firmware starts at address different than 0*/
    #define FW_START_ADDR 0x0800000

    /**Force VectorTable to specific memory position defined in linker*/
    volatile uint32_t VectorTable[48] __attribute__((section(".RAMVectorTable")));

    //copy the vector table to SRAM
    void remapMemToSRAM( void )
    {
        uint32_t vecIndex = 0;
        __disable_irq();

        for(vecIndex = 0; vecIndex < 48; vecIndex++){
            VectorTable[vecIndex] = *(volatile uint32_t*)(FW_START_ADDR + (vecIndex << 2));
        }

        __HAL_SYSCFG_REMAPMEMORY_SRAM();

        __enable_irq();
    }

Hell, I’ve been lazy and used a macro provided by ST. You can try to decipher it
by yourself. :-D.

I’ll just comment that, the register we have to modify is the SYSCFG, living in
memory address 0x4001_0000. And set its MEM_MODE[1:0]. And I know,
this is a mouthful.

syscfg_cfgr1

SYSCFG_CFGR1 on an STM32F071

#define __HAL_SYSCFG_REMAPMEMORY_SRAM() \
 do {SYSCFG->CFGR1 &= ~(SYSCFG_CFGR1_MEM_MODE); \
     SYSCFG->CFGR1 |= (SYSCFG_CFGR1_MEM_MODE_0 | SYSCFG_CFGR1_MEM_MODE_1); \
 }while(0) 

For the function remapMemToSRAM code to work, we must ensure that the SRAM has some reserved space for our vector table. We do so in the linker script sections

SECTIONS {
  /*Other sections not related with VTRAM may go here before the RAMVectorTable definition*/

  .RAMVectorTable(NOLOAD):
  {
    KEEP(*(.RAMVectorTable))
  } > VTRAM
}

Compile, link, create .hex (or whatever binary file your program uses) and GO!

Conclusions

It’s been a while since I wrote anything remotely long. I forgot how much time it takes.

Writing about low level C embedded software is also a little bit more tricky than writing stuff about higher level languages. I want to be precise and concise, but the more concise I get the more confusing the writing sounds. I hope that over time I can achieve better writing skills

Hope that this post is useful for somebody, and if not, it has been a fun topic for me to write about.

Anyway, this is quite a niche topic.

References

ARM infocenter ⇒GO
STM32F071 reference manual ⇒GO

Advertisements

3 Comments

Filed under code, electronic

3 responses to “Cortex-M0 Boot

  1. Jeff

    Hi Jordi,

    I really appreciated this post of yours, thank you very much! I do have a small question for you. I do not want to reset my STM32F071 to preserve my IO states, and I am trying to jump “back” to my bootloader. Am I right to say that all I need to do for the vector table to work again is to call the __HAL_SYSCFG_REMAPMEMORY_FLASH() function at the start of my main(). Everything seems to work for me, except that my systick does not want to run when I jump back to the bootloader from my main application.

    Any help on this topic would be greatly appreciated.

    Jeff

    • kxtells

      Jeff! Thanks for your comment.

      Yes, you are quite right. The only thing you should do here is remap to FLASH, either after jumping to your bootloader, or even before, if you don’t need to service any other ISRs. I was doing something similar in my project.

      Systick wise, there should be no problem, it is simply another interrupt. Do not forget to enable/disable interrupts when performing jumps between boot and firmware. Additionally, note that if you are using a global counter or similar it will have a different value when in bootloader mode and application mode as long as you don’t specify a “shared memory” region.

      I’m not sure if my response helped you at all :-p

      • Jeff

        Jordi,

        Thank you very much for your quick response and info. After emitting the DMA used by my I2C the problem went away, which kind of make sense if there is a problem with it. What the DMA issue is, I do not know, but that is a problem for another day. At least my “jumping” up and down is working flawlessly and I can remote update my firmware.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s