[Из песочницы] STM32 с чистого листа

?v=1
Нельзя доверять коду, который вы не написали полностью сами. — Кен Томпсон

Пожалуй, моя самая любимая цитата. Именно она и стала причиной по которой я решил нырнуть в самую глубь кроличьей норы. Свой путь в мир программирования я начал совсем недавно, прошло всего около месяца и я решил писать статьи для закрепления материала. Все началось с простой задачи, синхронизировать лампы в своей фото студии с помощью Ардуины. Задача была решена, но в фото студию я больше не заходил, времени нет. С того момента я решил основательно заняться программированием микроконтроллеров. Ардуина, хоть и привлекательна в своей простоте, как платформа мне не понравилась. Выбор пал на компанию ST и их популярную продукцию. В тот момент я еще не представлял в чем особо разница, но как типичный потребитель я сравнил скорость «процессора» и количество памяти, купил себе внушительную плату с дисплеем STM32F746NG — Discovery. Я пропущу моменты отчаяния и сразу перейду к делу.
Погрузившись в образ программиста я очень много читал, изучал, экспериментировал. И как я уже описал выше я хотел изучить ну вот прям все. И для этого я поставил цель, ни каких готовых решений, только свое. И если получилось все у меня то и у вас получиться.

Список всего того что понадобиться:

  1. Виртуальная машина Ubuntu 16+ ну или что под рукой есть
  2. arm компилятор — скачать по ссылке developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads
  3. openocd отладчик и программатор — скачивать по ссылке не получится, собираем из исходников, инструкция прилагается
    git clone https://git.code.sf.net/p/openocd/code openocd
  4. Текстовый редактор любой по вкусу


После того как все установили и собрали, можем приступать к первому проекту! И нет, это даже не мигание лампочкой. Для начала мы должны вникнуть в процесс инициализации самого мк.

Что нам понадобится:

  1. Makefile
  2. Linker.ld
  3. Init.c


Начнем с последнего пункта Init.c. Первым делом наш мк должен загрузить адрес «stack pointer» это указатель на адрес памяти который используется для инструкций PSUH и POP. Настоятельно рекомендую досконально изучить эти две инструкции, так как я не буду в деталях объяснять все инструкции. Как это реализовать смотрим ниже

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


Теперь разберем данный пример. Extern означает, что символ внешний, и этот символ мы объявили в файле Linker.ld, к нему мы еще вернемся чуть позже.

 __attribute__((section(".isr_vector"), used))


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

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

Приведу пример. дано что стэк начинается с 0×20010000, а код программы находиться 0×0800008. то массив можно написать как 

void *vectors[] __attribute__((section(".isr_vector"), used)) = {
    0x20010000,
    0x08000008
}


То есть, первым делом контроллер инициализирует стэк, а потом считает адрес первой инструкции и загрузит ее в регистр программного счетчика. Теперь самое главное, в зависимости от модели эти цифры могу быть разные, но на примере stm32f7 я могу уверенно сказать что этот массив должен быть в памяти по адресу 0×08000000. Именно с этого адреса мк начнет свою работу после включения или ресета.

Теперь прервемся и обратим внимание на то как именно положить этот массив в нужную нам секцию. Этим занимается «ld» или линкер. Это программа собирает всю нашу программу воедино и для этого используется скрипт Linker.ld. Привожу пример и дальше разберем его.

MEMORY{
	ROM_AXIM (rx) : ORIGIN = 0x08000000, LENGTH = 1M
	RAM_DTCM (rwx): ORIGIN = 0x20000000, LENGTH = 64K
}

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

SECTIONS{
	.isr_vector : {
	KEEP(*(.isr_vector))
	} >ROM_AXIM
}


Разберемся, что тут происходит. MEMORY определяет секции памяти, а SECTIONS определяет секции. тут же мы видим и наш _estack и то что он равен сумме начала памяти и ее длины, то есть конец нашей памяти. Также определена секция .isr_vector в которую мы положили наш массив. >ROM_AXIM в конце нашей секции означает что эта секция должна быть помещена в секцию памяти которая начинается с 0×08000000 как того требовал наш мк.

Мы поместили массив куда надо теперь надо какую нибудь инструкцию нашему мк для работы. Вот дополнены Init.c

extern void *_estack;

void Reset_Handler();

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

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


Как я уже упомянул ранее, вторым адресом должен быть указатель на первую функцию или инструкцию. И опять атрибуты, но тут все просто, функция не возвращает ни каких значений и следует ни каким ABI при входе, то есть голая «naked». В такие функции обычный пихают ассемблер.

Теперь самое время скомпилировать наш код, и посмотреть что же там под капотом. Makefile пока трогать не будем.

arm-none-eabi-gcc -c init.c -o init.o -mthumb
arm-none-eabi-gcc -TLinker.ld -o prog.elf init.o -Wl,--gc-sections -nostartfiles -nodefaultlibs -nostdlib


И тут мы видим много непонятного. по порядку:

  1. -mthumb компилируем для armv7, который использует только thumb инструкции
  2. -TLinker.ld указываем скрипт линкера. по дефолту он скомпилирует для исполнения в среде линукс
  3. -Wl,--gc-sections -nostartfiles -nodefaultlibs -nostdlib убираем все стандартные библиотеки, файлы инициализации среды С и все другие вспомогательные библиотеки типа математики.


Загружать такое в мк конечно толку нет. Зато есть толк в просмотре и изучении бинарника.

objcopy -O ihex prog.elf prog.bin
hexdump prog.bin


И тут мы увидим вывод.»00000000: 00 01 00 02 09 00 00 08» что будет символизировать нам об успехе. Это моя первая статья и я не вполне смог раскрыть весь материал и суть, поэтому в следующей я более подробно опишу механизмы и мы друзья сможем перейти к программе которая не будет мигать лампочкой, но выставит скорость процессора.

© Habrahabr.ru