Хабрахабр

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

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

Будем запускаться на этой платформе. Итак, поехали!
Эмулятор QEMU поддерживает ядро Cortex-M3 и эмулирует на его базе платформу Stellaris LM3S811 от Texas Instruments. Далее нам потребуется написать основную логику нашей программы, стартовый код, который передаст управление в программу, и линкер скрипт. Нам понадобится тулчейн arm-none-eabi- (скачать можно здесь).

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

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

static volatile unsigned int * const UART_DR = (unsigned int *)0x4000c000; static void uart_print(const char *s)
} void c_entry(void) { uart_print("Hello, World!\n"); while (1) ;
}

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

Для этого создадим код следующего содержания (файл startup. Теперь, нам нужно как-то передать управление в нашу функцию с_entry в файле test.c. 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”.

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

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().

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

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

Работает.

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

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

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть