...

пятница, 6 марта 2020 г.

STM32 Чать 2: Инициализация

Программирование — это разбиение чего-то большого и невозможного на что-то маленькое и вполне реальное.

Всем привет, для начала я хотел бы поблагодарить модераторов за то что пропустили мой первый (отвратительный) пост, и передать привет маме! А также я хотел бы поблагодарить всех читателей и людей которые указали на мои ошибки и помогли их исправить. Я сразу оговорюсь что по русски я не писал с 6 класса, в общем не серчайте.

Итак, давайте приступим. В прошлой статье я бегло пробежался по самым первым пунктам. Это был наш startup.c файл, который отвечал за 2 вектора (stack, Reset_Handler) и немного про линкер скрипт. Сегодня мы дополним наш код инициализации, разберем линкер на запчасти и узнаем как все устроено.

extern void *_estack;

void Reset_Handler();

void *vectors[] __attribute__((section(".isr_vector"), used)) = {
    &_estack,
    &Reset_Handler
};

void __attribute__((naked, noreturn)) Reset_Handler()
{
    while(1);
}

Если кому то этот код не понятен, то прочитать о нем можно вот здесь (статья не фонтан, но попытался объяснить).

Теперь давайте разберемся чего тут не хватает и как это добавить. Очевидно, что у мк есть и другие вектора. К примеру вектор Hard_Fault, это вектор который вызывается при неправильных действияx с мк, допустим если он попытается исполнить инструкцию которую не понимает процессор перепрыгнет по адресу вектора Hard_Fault. Таких векторов очень много и не факт что в нашей программе мы будем использовать все. Также вектора прерываний находятся в этой же таблице.

(Для тех кто до сих пор не знает что такое вектор, это указатель на адрес, aka «pointer»).

Дополним наш код, и потом я поясню что я натворил.

extern void *_estack;

void Reset_Handler();
void Default_Handler();

void NMI_Handler()   __attribute__((weak, alias("Default_Handler")));
void HardFault_Handler()   __attribute__((weak, alias("Default_Handler")));
//И так далее со всеми векторами

void *vectors[] __attribute__((section(".isr_vector"), used)) = {
    &_estack,
    &Reset_Handler
    &NMI_Handler,
    &HardFault_Handler
};

void __attribute__((naked, noreturn)) Reset_Handler()
{
    while(1);
}

void __attribute__((naked, noreturn)) Default_Handler(){
    while(1);
}

Тут я добавил новую функцию Default_Handler(); которая будет обрабатывать все прерывания, абсолютно. Но также с помощью __attribute__((weak, alias("Default_Handler"))); я пометил что если будет объявлена функция с таким же именем то именно она и будет обработчиком этого прерывания. Другими словами сейчас это просто прозвище для Default_Handler. Так мы можем добавить все вектора по списку из вашего Reference manual. Делается это для того, что если вдруг вы решили использовать прерывание, но забыли создать функцию обработчик, то вызовется обычный Default_Handler.

Думаю на этом моменте с векторами можно закончить и перейти к инициализации секторов и к линкеру. Для начала давайте опять обновим наш startup.c добавив в тело Reset_Handler инициализацию секторов data и bss а также несколько внешних переменых.

extern void *_sidata, *_sdata, *_edata, *_sbss, *_ebss;

void __attribute__((naked, noreturn)) Reset_Handler()
{


        void **pSource, **pDest;
        for (pSource = &_sidata, pDest = &_sdata; pDest != &_edata; pSource++, pDest++)
                *pDest = *pSource;

        for (pDest = &_sbss; pDest != &_ebss; pDest++)
                *pDest = 0;

        while(1);
}

Итак, новые переменные, и опять они были объявлены в нашем линкер срипте. Вспомним предыдущий скрипт. А также сравним его с новым.

MEMORY{
        ROM(rx) : ORIGIN = 0x08000000, LENGTH = 1M
        SRAM (rwx):     ORIGIN = 0x20010000, LENGTH = 240K
}

_estack = LENGTH(SRAM) + ORIGIN(SRAM);

SECTIONS{
        .isr_vector : {
        KEEP(*(.isr_vector))
        } >ROM
        
        .text : {
        . = ALIGN(4);
        *(.text)
    } >ROM
        
        _sidata = LOADADDR(.data);
        .data : {
                . = ALIGN(4);
                _sdata = .;
                *(.data)
                . = ALIGN(4);
                _edata = .;
        } >SRAM AT>ROM
        
        .bss : {
                . = ALIGN(4);
                _sbss = .;
                *(.bss)
                . = ALIGN(4);
                _ebss = .;
        } >SRAM
}

Добавилось две новые секции, это .data и .bss. Зачем они нам? ну в .data уйдут глобальные переменные а в bss пойдут статические. Обе эти секции находятся в оперативной памяти, и если с bss секцией все просто (это просто нули), то с data секцией есть небольшие трудности. Во-первых, data это уже инициализированные данные которые известны во время компиляции. Таким образом, изначально data секция будет лежать где то во флэш памяти. Во-вторых, нам надо как то указать линкер скрипту что лежит она во флэш памяти, но будет перенесена в оперативную память во время исполнения программы. Давайте взглянем на кусок скрипта где мы описываем data секцию.


        _sidata = LOADADDR(.data);
        .data : {
                . = ALIGN(4);
                _sdata = .;
                *(.data)
                . = ALIGN(4);
                _edata = .;
        } >SRAM AT>ROM

Начинается он с объявления _sidata и присвоением, на данный момент, не понятного нам значения. Далее начинается описание самой секции. Про функцию ALIGN советую прочитать тут, но в двух словах, мы присваиваем нашей точке (текущему адресу) значение которое легче воспримется нашим процессором, когда он попытается загрузить оттуда данные или инструкцию.

Также идут объявления _sdata, _edata и конец секции. Но вот в конце секции есть как раз то что нам и надо. SRAM AT>ROM. Это строка говорит нашему линкеру что секция находиться в SRAM но загружена эта секция в ROM или флэш память. Теперь вернемся к нашей переменной _sidata и функции LOADADDR. Эта функция вернет нам значение которое будет равно адресу загрузке секции. То есть мы указали что загружена секция во флэш памяти, но используем ее мы в оперативной памяти, и для того что бы перенести эту секцию, нам надо знать ее адрес во флэш памяти, что и возвращает функция LOADADDR. Тем временем, переменные _sdata и _edata указывают на расположение секции в оперативной памяти, тем самым давая нам возможность загрузить data секцию в правильный кусок памяти. Напомню что именно этим и занимается нам код инициализации который мы дополнили выше.

Для чего вообще нужен этот линкер и такие трудности с распределением секций нашего приложения? Data секция это лишь малый пример того зачем нам надо уметь размещать и смещать секции в памяти. ниже приведу пример text секции который я использую.

.text (ORIGIN(ROM_ITCM) + SIZEOF(.isr_vector)): {
        . = ALIGN(4);
        *(.text)
    } AT>ROM_AXIM

Суть этого блока в том что я загружаю секцию text также во флэш память но использую другую шину для доступа к инструкциям и ART ускоритель (вроде как обычный кэш). И для того что бы все работало я указал адрес загрузки и адрес во время исполнения кода, но это все бы не заработало если бы я не указал, что секция, должна быть смещена. Таких изощрений еще много, все их показывать не буду.

На данном этапе, секции загружены куда надо и инициализированы. у нас есть рабочая база для дальнейшего написания кода. В следующей статье мы напишем наш первый драйвер и помигаем светодиодом.

Всем спасибо за внимание, и успехов в ваших начинаниях.

Let's block ads! (Why?)

Комментариев нет:

Отправить комментарий