Конфигурация платформы потеряна между _start и main

Я использую https://github.com/ARM-software/CMSIS_5/blob/develop/Device/ARM/ARMCM33/Source/startup_ARMCM33.c со следующими изменениями:

_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) */
}

__PROGRAM_START(); перейдет к _start, который выполнит все настройки времени выполнения, упомянутые в crt0.o, а затем перейдет к main (см. https://embeddedartistry.com/blog/2019/04/08/a-general-обзор-о-происходит-до-основного/ для более подробной информации).

В приведенном выше фрагменте я выполняю инициализацию LPUART до _start. После отладки .elf я узнал, что эта инициализация LPUART теряется, когда программа достигает main. Удивительно, но эта же программа работает, если я делаю инициализацию LPUART внутри main:

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

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

Кажется, что crt0.o делает что-то, что приведет к потере конфигурации LPUART (или платформы). Я не могу понять причину. Любая помощь?

Редактирует:

void lpuart_init(lpuart_info_t* p_info, void* base_addr, uint32_t version)
{
    p_info->base_addr = base_addr;
    p_info->version = version;
}

person Niraj    schedule 25.08.2020    source источник
comment
Инициализация статических переменных выполняется после вашей функции, которая эффективно обнуляет все ваши изменения. Что ты делаешь в lpuart_init() ? Вы устанавливаете какие-либо глобальные переменные? I got to know that this LPUART initialization gets lost Как именно вы определяете, что эта инициализация LPUART теряется? Что значит, что именно потерялся?   -  person KamilCuk    schedule 25.08.2020
comment
lpuart_init устанавливает базовый адрес и версию терминала LPUART. Короче говоря, какой терминал будет использоваться для printf. В драйвере LPUART есть переменная base_addr. Его значение должно быть 0x12345678, но становится равным 0, когда достигает main. Из-за этого LPUART не будет настроен, и выполнение будет остановлено на while цикле внутри драйвера.   -  person Niraj    schedule 25.08.2020
comment
sets base address and version код говорит 1000 слов. Пожалуйста, предпочитайте показывать код, а не объяснять его. There is a base_addr Определена ли эта переменная в области файла? Как было задано, вы изменяете глобальные переменные из своей функции?   -  person KamilCuk    schedule 25.08.2020
comment
Вот почему он работает под main. В любом случае добавил код в описание.   -  person Niraj    schedule 25.08.2020
comment
вы не можете запускать код C, пока не загрузите C, это не так.   -  person old_timer    schedule 26.08.2020
comment
вы можете сделать это таким образом, если вы напишете его на asm и обработаете базовые вещи начальной загрузки, которые вам нужны для этого asm (возможно, указатель стека, но, вероятно, нет).   -  person old_timer    schedule 26.08.2020
comment
неудивительно, что он работает в main().   -  person old_timer    schedule 26.08.2020


Ответы (2)


Глобальные переменные инициализируются после выполнения вашей функции. Таким образом, любые изменения переменных в сегментах .data и .bss будут потеряны и перезаписаны статическая процедура инициализации.

Отбросьте метод, который вы делаете, и используйте непереносимое расширение gcc __attribute__((__constructor__)) для выполнения функции перед основным, но после статической инициализации или, альтернативно, добавьте адрес функции в раздел .init. Ссылки newlib/arm /crt0.S функция gcc атрибуты newlib/init.c инициализация gcc

Пример псевдокода, иллюстрирующий происходящее:

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
}

Под статическими переменными в своих комментариях я подразумевал переменные с статическим сроком хранения ( не с внутренней связью, это то, что делает ключевое слово static, извините за путаницу если есть). Обратите внимание на инициализацию переменных со статической продолжительностью хранения - они инициализированы нулем.

person KamilCuk    schedule 25.08.2020
comment
Спасибо! Я поместил lpuart_init в раздел .init. И это работает. Существует postprocess, который должен выполняться после main и до _exit. Я помещу postprocess в раздел .fini. - person Niraj; 26.08.2020
comment
inside .fini section Сначала убедитесь, что ваша стандартная библиотека действительно выполняет функции из раздела fini. Я не думаю, что newlib-nano это делает, но, возможно, это так, не знаю - большинство проектов, с которыми я работал, удалили выход. Используйте atexit. newlib exit.c и комментарий newlib в __atexit.c - person KamilCuk; 26.08.2020
comment
Я использую GNU Arm Embedded Toolchain Version 8-2019-q3-update и могу выполнить функцию из раздела .fini. - person Niraj; 27.08.2020

Не всегда безопасно вызывать функции C из вектора сброса, если вектор сброса также устанавливает SP. К счастью, это не проблема с ARM, но вы все равно должны убедиться, что все настройки памяти выполнены, прежде чем писать в переменные RAM (некоторые советуют, как развернуть все это вручную здесь). Как упоминалось другими, наиболее вероятная проблема здесь заключается в том, что вы записываете переменные области статического файла до того, как будет выполнена инициализация .bss, поэтому их значения будут стерты.

(Если вы понятия не имеете, что означают .bss и .data, то пока не стоит возиться с ЭЛТ. Вот объяснение: Что находится в разных типах памяти микроконтроллера?)

Есть два возможных решения:

  • Отключите инициализацию .bss и .data как нестандартную настройку проекта. Это очень распространено во встроенных системах, но означает, что вы больше не можете полагаться на стандартную инициализацию переменной C для static или переменных области видимости файла.
  • Или поместите свои внутренние переменные драйвера UART в выделенный сегмент ОЗУ, который не инициализирован по умолчанию.

Настоящий вопрос заключается в том, почему вам нужно настроить UART так рано. Регистры UART не могут быть такими критичными по времени, так как сам UART медленный. Должно быть достаточно, если вы установите направление данных и вытащите резисторы из вектора сброса, а затем выполните остальную часть некритической инициализации в main().

Кроме того, нет особого смысла инициализировать UART до того, как вы установите системные часы, иначе ваша скорость передачи данных может оказаться неправильной. Что касается этой темы, также вполне возможно, что библиотеки CRT/CMSIS были написаны обезьянами, которые серьезно намеревались запустить вашу инициализацию .bss/.data с настройкой внутреннего RC-генератора по умолчанию до того, как отработают какие-либо системные часы настройки PLL. Поскольку большинство плат используют внешние кварты, вы не хотите, чтобы ваш запуск был невероятно медленным и беспричинно потреблял ток — чтобы предотвратить такой ужасный дизайн, вам нужно либо настроить часы самостоятельно из вектора сброса, либо отключить .bss/.data инициализация.

person Lundin    schedule 26.08.2020
comment
Спасибо @Lundin за подробный ответ. Почему я так рано устанавливаю UART? По сути, существуют тысячи общих тестов для эмулятора и кремния. И я выполняю инициализацию UART только в том случае, если эмулятор является моей целевой платформой. Таким образом, не рекомендуется добавлять функцию инициализации UART в каждый тесткейс (main()). Моя цель - инициализировать UART (и любые другие периферийные устройства) непосредственно перед main(). Я не знал о функциональном использовании раздела .ini. Ответ @KamilCuk помог мне в этом. - person Niraj; 27.08.2020
comment
@Niraj Тогда, возможно, лучшее решение — настроить CRT так, чтобы в конце он не вызывал main(), а вызывал пользовательскую функцию periph_init(), где вы инициализируете все аппаратные периферийные устройства, а затем вызываете main() оттуда . - person Lundin; 27.08.2020
comment
Но сохранение periph_init() внутри раздела .init работает. Разве это не хорошее решение? - person Niraj; 27.08.2020