STM32 с чистого листа

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.
Нельзя доверять коду, который вы не написали полностью сами. — Кен Томпсон
Пожалуй, моя самая любимая цитата. Именно она и стала причиной по которой я решил нырнуть в самую глубь кроличьей норы. Свой путь в мир программирования я начал совсем недавно, прошло всего около месяца и я решил писать статьи для закрепления материала. Все началось с простой задачи, синхронизировать лампы в своей фото студии с помощью Ардуины. Задача была решена, но в фото студию я больше не заходил, времени нет. С того момента я решил основательно заняться программированием микроконтроллеров. Ардуина, хоть и привлекательна в своей простоте, как платформа мне не понравилась. Выбор пал на компанию 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 и что даже если мы не используем его в коде, то он все равно должен быть обязательно включен в программу. И первым его элементом будет тот самый указатель.

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

Приведу пример. дано что стэк начинается с 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

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

  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» что будет символизировать нам об успехе. Это моя первая статья и я не вполне смог раскрыть весь материал и суть, поэтому в следующей я более подробно опишу механизмы и мы друзья сможем перейти к программе которая не будет мигать лампочкой, но выставит скорость процессора.
Источник: https://habr.com/ru/post/490474/


Интересные статьи

Интересные статьи

Год назад на завод, на котором я тогда работал, обратились с просьбой сделать устройство, которое считывает длину листового рулонного железа пройденную через станок по изготовления профли...
После того, как некий продукт становится популярным, у компаний, которые могли бы его создать, но не сделали этого, неизбежно возникает желание прокатиться на волне его популярности. Это ...
Прошли те времена, когда фронтендеру достаточно было открыть «Блокнот», написать несколько строк кода, проверить его в браузере и загрузить на сервер через FTP. Современная разработка пользов...
У некоторых бизнес-тренеров в области е-коммерса и консультантов по увеличению интернет-продаж на многие вопросы часто можно слышать универсальную отмазку — «надо тестировать» или другую (чтобы не...
Здесь я расскажу как создать минимальный проект на CMSIS с использованием «родной» IDE для микроконтроллеров STM – STM32CubeIDE. Возможно STM32CubeIDE и обладает рядом недостатков, но у нее, ...