The way we do it in one of our products is like this (code is based on IAR compiler, so pragmas might differ for you):
We defined a structure which contains important information of the application part (which is called mainpart in our projects):
#pragma pack(push,1)
struct softwareRevision_t {
uint8_t VersionSystem;
uint8_t VersionFunction;
uint8_t VersionError;
uint8_t VersionCustomer;
uint8_t BuildNumber;
};
#pragma pack(pop)
#pragma pack(push,1)
struct mainInfoStruct_t {
softwareRevision_t softwareRevision;
uint32_t startFunctionAddress;
uint8_t reserve[27];
uint32_t bootControl;
};
#pragma pack(pop)
This contains among other things the uint32_t startFunctionAddress
. Now for this structure to be useful, you have to do several things:
- Initialize it with meaningful values in the application
- Make it
const
so it can be placed in flash
- Tell the linker to place the structure always on the exact same location every time you compile your project
- share the location and structure between application and boot part
In the application part:
To share the location, we created a header-file which is included in both parts with a simple define:
#define MAIN_INFO_MEMORY_POSITION (0x0807FFD4)
In a .cpp
file of our application we create an instance of the structure which is placed accordingly:
#pragma location = MAIN_INFO_MEMORY_POSITION
extern __root const mainInfoStruct_t mainInfoStruct =
{
{
1U, // mainInfo.softwareRevision.VersionSystem
0U, // mainInfo.softwareRevision.VersionFunction
0U, // mainInfo.softwareRevision.VersionError
0U, // mainInfo.softwareRevision.VersionCustomer
0U // mainInfo.softwareRevision.BuildNumber
},
reinterpret_cast<uint32_t>((&_start)), // mainInfo.startFunctionAddress
{0U,0U,0U,0U,0U,0U,0U,0U,0U,0U,0U,0U,0U,0U,0U,0U,0U,0U,0U,0U,0U,0U,0U,0U,0U,0U,0U}, // reserve
0xF0F0FFFFU // mainInfo.bootControl
};
Several things happen here:
#pragma location = MAIN_INFO_MEMORY_POSITION
tells the linker where to place the next structure.
The __root
tells our linker that this symbol must be kept, even if it is not referenced by the application code (which it isn't). It has to be const
otherwise it would not get placed in flash (if it doesn't end up in flash try adding a static
).
So with this we have done everything in the application.
In the boot part:
The boot part needs access to the application part info structure. We wrapped a class around this whole thing, but basically a pointer will be enough:
volatile mainInfoStruct_t* pMainInfo = reinterpret_cast<mainInfoStruct_t*>(MAIN_INFO_MEMORY_POSITION);
We use volatile
here to make sure, that the compiler will not optimize the access to the flash away. With this you can now jump into the application code with an ugly cast:
(reinterpret_cast<void (*)(void)>(pMainInfo->startFunctionAddress))();
Notes:
In our application we do a CRC check over the whole application part which includes the mainInfoStruct, so we don't jump into nirvana is something went wrong with the update.
The first thing we do in our application is to set the stackpointer to the value it is expected to start with (you can get that from the linker configuration file or your interrupt vector table). Otherwise you might end up with a stack overflow and corrupting your other data.
This is not a minimal example, but might give some hint on what might be useful in a shared structure as well.
We use C++, sorry for the reinterpret_cast
s.
Best Answer
Your overall issue is in trying to branch to an application after a bootloader has heavily operated the system. This is strongly dis-recommended on the STM32, as to get it to work you have to get every aspect of the chip back to a state the application code assumes. Likely more obvious failures include things like having the clock PLL on and selected (as required for USB) and then the application code expected the PLL is not selected when it starts configuring it. But chasing down everything will be extremely frustrating, and you can be left with 90% functionality but 10% seemingly inexplicable weirdness in some peripheral.
Instead, the usual recommendation is to set a flag in memory or the RCC backup registers, reboot the system, and then detect the flag and branch early in startup before doing any configuration. When I needed to implement this I did it in assembly before even the C startup had run; that was probably unnecessary though if you chose to use a memory flag beware of the likelihood of startup code initializing RAM. If entering the bootloader requires explicit action, you wouldn't even need the flag, but only to check that the "stay in bootloader" GPIO or whatever condition is not met, and that there is an application present matching whatever validity check you use.
This is more easily explained. Your code attempts to utilize a local stack variable that was allocated before it changed the stack pointer, which is essentially a recipe for disaster. If you are going to do that, you don't want to make any use of the stack after you change it.