Hi all, I have a bootloader-fw setup on SAM3S4C. It is working fine except a certain conditions, then the global variables of the fw part are not initialized (are zeroed instead), like if the __main() was not called.
Bootloader occupies the first 16kB of the flash, fw the rest. The scatter files for both bootloader and fw are Keil-generated, from the memory layout dialog
LR_IROM1 0x00404000 0x0003C000 { ; load region size_region ER_IROM1 0x00404000 0x0003C000 { ; load address = execution address *.o (RESET, +First) *(InRoot$$Sections) .ANY (+RO) } RW_IRAM1 0x20000000 0x0000C000 { ; RW data .ANY (+RW +ZI) } }
LR_IROM1 0x00400000 0x00004000 { ; load region size_region ER_IROM1 0x00400000 0x00004000 { ; load address = execution address *.o (RESET, +First) *(InRoot$$Sections) .ANY (+RO) } RW_IRAM1 0x20000000 0x0000C000 { ; RW data .ANY (+RW +ZI) } }
Bootloader is loaded to the chip using JTAG, then it writes fw itself (fw is received from USART).
There is whole bunch of global variables, I am pasting just an example
typedef uint8_t (*TGetCurrentPower)(uint8_t); typedef void (*TGetItemName)(uint8_t,char*); typedef struct TDataLogItem { uint16_t mActSecOn; uint32_t mActPowerSecs; uint8_t mCbkParam; TGetCurrentPower fPower; TGetItemName fGetName; } TDataLogItem; TDataLogItem mDataLogger[8] = { {0,0,0,&GetLightIntensity,&GetLightName}, {0,0,1,&GetLightIntensity,&GetLightName}, {0,0,2,&GetLightIntensity,&GetLightName}, {0,0,3,&GetLightIntensity,&GetLightName}, {0,0,4,&GetLightIntensity,&GetLightName}, {0,0,5,&GetLightIntensity,&GetLightName}, {0,0,6,&GetLightIntensity,&GetLightName}, {0,0,7,&GetLightIntensity,&GetLightName}, };
The situation when the variables are not initialized is right after the firmware program is written and a jump is made to start its execution. Since the global variables are not initialized and contain zeroes, the first call of the function ptr above causes Hard Fault. The code execution in this case should be following
power on - run bootloader - write fw code to flash - jump to fw main - run fw main
When I turn the device off and on again using the main power button, not programming anything, just executing the previously written fw, everything suddenly work fine, the variables are initialized as they should be. The code execution in this case should be following
power on - run bootloader - jump to fw main - run fw main
The same jump to main function is used to pass the code execution to the fw. It is the following code
#define MAIN_ADDRESS 0x00404000 void _jumpToMain(void) { register func main_entry; int stackPointerAddress = MAIN_ADDRESS; int resetPointerAddress = MAIN_ADDRESS + 4; //zakazu interrupty, smazu pending //program si to musi povolit NVIC_DisableAllIRQ(); NVIC->ICPR[0] = 0xFFFFFFFF; NVIC->ICPR[1] = 0xFFFFFFFF; __disable_irq(); // Set stack pointer with the first word of the run mode program // just for remainder: Vector table's first entry is the stack pointer value __set_MSP((*(int*)stackPointerAddress)); //vector table offset *(int volatile*)0xE000ED08 = MAIN_ADDRESS; // Set the program counter to the application start address // just for remainder: Vector table's second entry is the system reset value main_entry = * ((func *)resetPointerAddress); main_entry(); }
The Reset Handler of the firmware program is generic
; Reset Handler Reset_Handler PROC EXPORT Reset_Handler [WEAK] IMPORT __main LDR R0, =__main BX R0 ENDP
I do not understand why the initialization of the variables is fine in the second case and fails in the first one. The control is passed the very same way in both cases.
To make it even more peculiar, total code size (?probably?) of the fw does somehow influence the variables initialization too. The compiled code size of the project is 17816 bytes. With this size, variables are not initialized. But when I remove some code to 17788 bytes (dummy code I added for testing purposes), initialization works just fine, even right after the fw upload. I was not able to trace any location dependency, I have tried to place/remove extra code to several different code units/objects, but it appears the only thing that matters is the total code size. But I am no expert at this, someone might see something from the .map files - I can send good one and bad one to email if necessary. Simple TotalCommander compare of the .map files shows just expected shift of the location of the influenced code.
I would be grateful for any comments. I know there is a workaround, to initialize the variables myself from the code, but I would like to understand the problem, not just to make the code work.
Thanks Petr
Should it call SystemInit prior to __main ?
The code in __main zeros memory, and copies statics, you should try off "run to main" and step through it. If copies the statics from the end of the image. Make sure you write the entire image properly and you haven't truncated it.
Review the statics in the .HEX or .AXF file. Confirm you can see the data you are expecting in there.
Thank you for the hints, but unfortunately none of that seems to be the case.
I am not sure whether it should call SystemInit. I was using Keil's default handler since day one, and it contains just __main() call. I have SystemInit function also default from Keil, and it sets up core frequency and some other stuff, and it is called from my code.
Jump to main function is pasted in the original post. It is just passing of the control to the Reset Handler of the uploaded image. The Reset Handler itself then calls __main(). I was not able to trace into __main, debugger just steps over it.
I checked the upload process and it works fine, whole image is loaded. I also browsed the compiled binary file and I found some string constants used in the code, so I guess this one is fine too.
All of that is supported by the fact that the global variables are zeroed only directly after the image upload. When the uP is reset, it works fine "till the end of time". So all the static variables must be included in the image, they are just not used the first time the image is executed.