Пишем свою простейшую программу для ARM Cortex-M3

в 18:12, , рубрики: cortex-m, embox, open source, Блог компании Embox, микроконтроллеры, операционные системы, осрв, программирование микроконтроллеров, системное программирование

imageДобрый день! Сегодня я хочу рассказать вам как написать минимальную программу, которая запустится на ARM Cortex-M3 и при этом напечатает “Hello, World!”. Постараемся разобрать по шагам необходимый минимум, который нам для этого потребуется. Запускать будем на эмуляторе QEMU. Поэтому любой желающий может воспроизвести, даже если у него нет под рукой железки.

Итак, поехали!

Эмулятор QEMU поддерживает ядро Cortex-M3 и эмулирует на его базе платформу Stellaris LM3S811 от Texas Instruments. Будем запускаться на этой платформе. Нам понадобится тулчейн arm-none-eabi- (скачать можно здесь developer.arm.com/open-source/gnu-toolchain/gnu-rm/downloads). Далее нам потребуется написать основную логику нашей программы, стартовый код, который передаст управление в программу, и линкер скрипт.

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

Наш hello world в файле test.c:

volatile unsigned int * const UART_DR = (unsigned int *)0x4000c000;

static void uart_print(const char *s) {
	while (*s != '') {
		*UART_DR = *s;
		s++;
	}
}
 
void с_entry(void) {
	uart_print("Hello, World!n");
	while (1)
             ;
}

Вот этот самый адрес 0x4000c000 берется из документации, там лежит регистр DR нулевого уарта. Мы не будем заниматься настройкой, а попробуем сразу напрямую положить в него символы.

Теперь, нам нужно как-то передать управление в нашу функцию с_entry в файле test.c. Для этого создадим код следующего содержания (файл startup.S), и потом положим его в итоговый образ ELF в начало.

.type start, %function

.word stack_top /* Вот это вершина стека */
.word start         /* А здесь инициализируем PC */

.global start
start:
	ldr r1, =c_entry
	bx r1

Первым словом по адресу 0x0 должен лежать указатель на вершину стека (SP). По адресу 0x4 находится PC, который как и SP загружается в регистры. Отметим, что start объявлен именно как функция, а не как метка из-за того что код на Cortex-M исполняется в режиме Thumb (это такой упрощенный набор команд ARM), и требуется чтобы адреса функций в векторе прерываний были в виде (address | 0x1) — т.е. последний бит адреса должен быть равен 1.

Далее функция start просто загружает адрес нашей функции c_entry() из файла test.c и передает туда управление через “bx r1”.

Остается лишь успешно слинковать нашу программу. Для этого требуется задать карту памяти нашего микроконтроллера. В документации можно найти адреса и размеры флеш памяти (ROM) и ОЗУ (RAM). Приведу линкер скрипт:

ENTRY(start)
SECTIONS
{
	 . = 0x0; /* Это флэшка (ROM) */
	.text : {
		startup.o(.text)
		test.o(.text) 
	} 
	. = 0x20000000; /* С этого адреса начинается RAM */
	.data : { *(.data) }
	.bss : { *(.bss) }
	. = ALIGN(8);
	. = . + 0x1000; /* Отдаем под стек 4кБ */
	stack_top = .;
}

Здесь важно обратить внимание на адреса. “.” в линкер скрипте обозначает текущую позицию. Мы укладываем в начало ROM (адрес 0x0) секцию .text соблюдая очередность — первым идет startup.o(.text). Далее переходим к RAM (. = 0x20000000;) и укладываем туда data (инициализированные глобальные данные) и bss (неинициализированные глобальные данные). Ниже видим ALIGN(8) — ARM требует выравнивание SP (Stack Pointer) на 8. Так как стек растет вниз, то аллокация места под стек это всего лишь навсего прибавление ”. =. + 0x1000”. Нашу программу мы хорошо знаем, поэтому 4кБ стека хватит с большим запасом.

Вот и все, остается все это собрать вместе. Привожу build.sh:

#!/bin/sh

arm-none-eabi-as -c -mthumb -mlittle-endian -march=armv7-m -mcpu=cortex-m3 startup.S -o startup.o
arm-none-eabi-gcc -c -mthumb -ffreestanding -mlittle-endian -march=armv7-m -mcpu=cortex-m3 test.c -o test.o
arm-none-eabi-ld -T test.ld test.o startup.o -o test.elf

Тут все более-менее должно быть понятно, за исключением может быть флага -ffreestanding. В данном случае добавлять его необязательно (можете проверить), но так как мы готовим бареметальный образ с нуля, то лучше сказать компилятору, чтобы он не обращал внимания на такие функции как main().

В итоге у нас получился ELF файл test.elf. Запускаем его на QEMU:

$ qemu-system-arm -M lm3s811evb -kernel test.elf -nographic
Hello, World!

Работает.
Конечно, это учебный пример предназначенный для понимания происходящего. Если вам нужен более содержательный функционал, стоит воспользоваться готовыми вещами. Мы добавили поддержку данной платформы в Embox. Называется этот темплейт platform/stellaris/lm3s811evb. Поэтому если кто-то хочет попробовать запустить чуть более серьезную вещь (консоль, таймер, прерывания), то можете собрать и попробовать. При этом, повторюсь, вам не нужно иметь аппаратную плату.

А тех кому все-таки мало эмулятора, или кто хочет задать нам вопросы и поиграться с железками мы будем ждать в эту субботу и в воскресенье на IT фестивале techtrain.ru в Санкт-Петербурге. У нас на стенде будут различные железки, и на демо зоне мы постараемся рассказать как их программировать.

Автор: alexkalmuk

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js