-1

I am using https://github.com/ARM-software/CMSIS_5/blob/develop/Device/ARM/ARMCM33/Source/startup_ARMCM33.c file with following modifications:

_NO_RETURN void Reset_Handler(void)
{
  __set_MSPLIM((uint32_t)(&__STACK_LIMIT));

  SystemInit();                             /* CMSIS System Initialization */
  lpuart_init(&m33_uart, (void*)LPUART0_BASE, LPUART); // Initialize LPUART
  __PROGRAM_START();                        /* Enter PreMain (C library entry point) */
}

The __PROGRAM_START(); will jump to _start which will do all the runtime configurations mentioned in the crt0.o and then it will jump to main (See https://embeddedartistry.com/blog/2019/04/08/a-general-overview-of-what-happens-before-main/ for more details).

In above snippet, I am doing LPUART initialization before _start. After debugging the .elf, I got to know that this LPUART initialization gets lost when program reaches to main. Surprisingly, the same program works if I do LPUART initialization inside main:

void main() {
lpuart_init(&m33_uart, (void*)LPUART0_BASE, LPUART);

/* some more code 
...... */
}

It seems crt0.o is doing something that will cause loss of the LPUART (or platform) configuration. I am not able to figure out the reason. Any help?

Edits:

void lpuart_init(lpuart_info_t* p_info, void* base_addr, uint32_t version)
{
    p_info->base_addr = base_addr;
    p_info->version = version;
}
Nee
  • 159
  • 10
  • Static variables initialization is executed after your function which is effectively zeroing all your changes. What are you doing in `lpuart_init()` ? Are you setting any global variables? `I got to know that this LPUART initialization gets lost` How exactly do you detect that "this LPUART initialization gets lost"? What does it mean that it "got lost" exactly? – KamilCuk Aug 25 '20 at 19:19
  • The `lpuart_init` sets base address and version of LPUART terminal. In short, which terminal will be used for `printf`. There is a `base_addr` variable under LPUART driver. It's value should be 0x12345678 but becomes 0 when reaches to `main`. Due to this, there will be no LPUART configured and execution will be stuck at `while` loop inside the driver. – Nee Aug 25 '20 at 19:34
  • `sets base address and version` code speaks 1000 words. Please prefer _showing_ the code, instead of explaining it. `There is a base_addr` Is this variiable defined at file scope? As asked, do you modify global variables from your function? – KamilCuk Aug 25 '20 at 19:46
  • That's why it's working under `main`. Anyway added the code in description. – Nee Aug 25 '20 at 19:49
  • you cant run C code until you bootstrap C, that is not how things work. – old_timer Aug 25 '20 at 22:20
  • you can do it this way if you write it in asm and handle the basic bootstrap stuff you need for that asm (stack pointer perhaps, but probably not). – old_timer Aug 25 '20 at 22:20
  • no surprise whatsoever that it works within main(). – old_timer Aug 25 '20 at 22:21

2 Answers2

1

Global variables are initialized after your function is executed. So any modification to variables that are in .data and .bss segments will be lost and overwritten by the static initialization routine.

Drop the method you are doing and use the gcc non-portable extension __attribute__((__constructor__)) to execute a function before main but after static initialization or alternatively add the address of the function to the .init section. Links newlib/arm/crt0.S gcc function attributes newlib/init.c gcc initialization

A pseudocode example to illustrate what is going on:

int some_global_explicitly_initialized_var = 1; // in .data section
static int some_global_var = 2; // in .data section
int global_vars_without_initialization_are_default_initialized_to_zero; // in .bss section
static int m33_uart; // in .bss section

int your_func() {
   // you set your variables, but it will be overwritten in _init
   m33_uart = 12354;
}

// this function is called first, ie. entrypoint before main
void Reset_Handler(void) {
    your_func();
    _init();
}

void _init(void) {
    // variables in .data section are initialized explicitly
    // ie. some_global_explicitly_initialized_var is set to 1 and 
    // and some_global_var is set to 2
    // this is done by copying a section from flash memory into ram into .data section
    // linker takes care of properly placing the variables
    memcpy(&_data_section, &_data_initialization_from_flash, sizeof(_data_initialization_from_flash));

    // variables in .bss section are initialized to 0
    // and uninitialized pointers are set to NULL
    // so all global variables are cleared
    // so global_vars_without_initialization_are_default_initialized_to_zero is set to 0
    // and m33_uart is also set to 0
    memset(&_bss_section, 0, sizeof(_bss_section));

    // after that main is called
    main();
}

int main() {
    // m33_uart will be set to 0
    // because m33_uart is inside .bss section
    // and will be cleared to 0 by the memset in _init
}

By the term "static variables" in my comments I meant variables with static storage duration (not with internal linkage that is what static keyword does, sorry for confusion if any). Note about initialization of variables with static storage duration - they are zero initialized.

KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • Thanks! I have put `lpuart_init` inside `.init` section. And it works. There is a `postprocess` which should execute after `main` and before `_exit`. I will put `postprocess` inside `.fini` section. – Nee Aug 26 '20 at 10:51
  • `inside .fini section` First confirm that your standard library actually executes functions from fini section. I do not think newlib-nano does, but maybe it does, not idea - most projects I worked with removed exit. Use `atexit`. [newlib exit.c](https://chromium.googlesource.com/native_client/nacl-newlib/+/master/newlib/libc/stdlib/exit.c#62) and [newlib comment in __atexit.c](https://chromium.googlesource.com/native_client/nacl-newlib/+/master/newlib/libc/stdlib/__atexit.c#4) – KamilCuk Aug 26 '20 at 10:53
  • 1
    I am using GNU Arm Embedded Toolchain Version 8-2019-q3-update and able to execute the function from `.fini` section. – Nee Aug 27 '20 at 10:18
1

It isn't always safe to call C functions from the reset vector, in case the reset vector also sets the SP. Fortunately this is not an issue with ARM, but you must still ensure that all memory setups are done before you write to RAM variables (some advise for how to roll out all of this manually here). As mentioned by others, the most likely issue here is that you write to static file scope variables before .bss initialization has been done, so their values will get erased.

(If you have no clue what .bss and .data means, then you shouldn't be meddling with the CRT for now. Here's an explanation: What resides in the different memory types of a microcontroller?)

There are two feasible solutions:

  • Disable .bss and .data initialization as a non-standard project setup. This is very common in embedded systems, but means that you can no longer rely on standard C variable initialization of static or file scope variables.
  • Or place your internal UART driver variables in a dedicated RAM segment, which is not default initialized.

The real question is why you need to setup UART so early though. UART registers cannot be that time critical, since UART itself is slow. It should be sufficient if you set data direction and pull resistors from the reset vector, then do the rest of the non-critical initialization in main().

Also, it makes little sense to initialize UART before you have set the system clock, or your baudrate might end up wrong. On that topic, it is also quite possible that the CRT/CMSIS libs were written by monkeys who seriously intend to run your .bss/.data initialization on the default internal RC oscillator setting, before any system clock of PLL setup has executed. Since most boards use external quarts, you don't want your startup to be incredibly slow and needlessly current consuming for no reason - to prevent such horrid design, you need to either setup the clock yourself from the reset vector, or disable .bss/.data initialization.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • Thanks @Lundin for your detailed answer. "Why am I setting UART so early?" Basically, there are thousands of common testcases for Emulator and Silicon. And I am doing UART initialization only if Emulator is my target platform. So, it's not good idea to add UART init function inside each testcase (main()). My purpose is to initialize UART (and any other peripherals) just before main(). I was not aware of functional use of `.ini` section. @KamilCuk 's answer helped me with that. – Nee Aug 27 '20 at 10:35
  • @Niraj Then maybe the best solution is to tweak the CRT so that at the end, it doesn't call main(), but call a custom function periph_init(), where you initialize all hardware peripherals and then call main() from there. – Lundin Aug 27 '20 at 13:42
  • But keeping `periph_init()` inside `.init` section works. Isn't it a good solution? – Nee Aug 27 '20 at 18:35