Нельзя доверять коду, который вы не написали полностью сами. — Кен ТомпсонПожалуй, моя самая любимая цитата. Именно она и стала причиной по которой я решил нырнуть в самую глубь кроличьей норы. Свой путь в мир программирования я начал совсем недавно, прошло всего около месяца и я решил писать статьи для закрепления материала. Все началось с простой задачи, синхронизировать лампы в своей фото студии с помощью Ардуины. Задача была решена, но в фото студию я больше не заходил, времени нет. С того момента я решил основательно заняться программированием микроконтроллеров. Ардуина, хоть и привлекательна в своей простоте, как платформа мне не понравилась. Выбор пал на компанию ST и их популярную продукцию. В тот момент я еще не представлял в чем особо разница, но как типичный потребитель я сравнил скорость «процессора» и количество памяти, купил себе внушительную плату с дисплеем STM32F746NG — Discovery. Я пропущу моменты отчаяния и сразу перейду к делу.
Погрузившись в образ программиста я очень много читал, изучал, экспериментировал. И как я уже описал выше я хотел изучить ну вот прям все. И для этого я поставил цель, ни каких готовых решений, только свое. И если получилось все у меня то и у вас получиться.
Список всего того что понадобиться:
- Виртуальная машина Ubuntu 16+ ну или что под рукой есть
- arm компилятор — скачать по ссылке developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads
- openocd отладчик и программатор — скачивать по ссылке не получится, собираем из исходников, инструкция прилагается
git clone https://git.code.sf.net/p/openocd/code openocd
- Текстовый редактор любой по вкусу
После того как все установили и собрали, можем приступать к первому проекту! И нет, это даже не мигание лампочкой. Для начала мы должны вникнуть в процесс инициализации самого мк.
Что нам понадобится:
- Makefile
- Linker.ld
- 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 и что даже если мы не используем его в коде, то он все равно должен быть обязательно включен в программу. И первым его элементом будет тот самый указатель.
Теперь давайте разберемся, зачем этот массив и с чем его съест мк. В отличии от обычного процессора, мк на архитектуре арм начинает исполнение когда не с нулевого адреса, а с адреса на который указывает указатель в этом массиве, все сложно. Так же первый указатель в этом массиве всегда указывает на начало стэка, а вот второй уже указывает на начало нашего кода.
Приведу пример. дано что стэк начинается с 0x20010000 а код программы находиться 0x0800008. то массив можно написать как
void *vectors[] __attribute__((section(".isr_vector"), used)) = {
0x20010000,
0x08000008
}
То есть, первым делом контроллер инициализирует стэк, а потом считает адрес первой инструкции и загрузит ее в регистр программного счетчика. Теперь самое главное, в зависимости от модели эти цифры могу быть разные, но на примере stm32f7 я могу уверенно сказать что этот массив должен быть в памяти по адресу 0x08000000. Именно с этого адреса мк начнет свою работу после включения или ресета.
Теперь прервемся и обратим внимание на то как именно положить этот массив в нужную нам секцию. Этим занимается «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</code>
}
Разберемся, что тут происходит. MEMORY определяет секции памяти а SECTIONS определяет секции. тут же мы видим и наш _estack и то что он равен сумме начала памяти и ее длины, то есть конец нашей памяти. Также определена секция .isr_vector в которую мы положили наш массив.
>ROM_AXIM
в конце нашей секции означает что эта секция должна быть помещена в секцию памяти которая начинается с 0x08000000 как того требовал наш мк. Мы поместили массив куда надо теперь надо какую нибудь инструкцию нашему мк для работы. Вот дополнены 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
И тут мы видим много непонятного. по порядку:
- -mthumb компилируем для armv7, который использует только thumb инструкции
- -TLinker.ld указываем скрипт линкера. по дефолту он скомпилирует для исполнения в среде линукс
- -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» что будет символизировать нам об успехе. Это моя первая статья и я не вполне смог раскрыть весь материал и суть, поэтому в следующей я более подробно опишу механизмы и мы друзья сможем перейти к программе которая не будет мигать лампочкой, но выставит скорость процессора.