Using arm-gcc to develop software for stm32l-discovery board

Инфо:

STM32L152RBT6 microcontroller, featuring 128 KB of Flash memory, 16 KB of RAM

Программы пишут на ассемблере или на C. Существует несколько поставок компиляторов, они
все включают и ассемблер, и C. Собственно, сейчас будет список компиляторов.

Не основанные на GCC, коммерческие:
———————————-
ARM DS-5 toolchain
Keil
IAR

Основанные на GCC:
——————
GNUARM
CodeSoucery
Yagarto
arm-gcc
Atollic TrueSTUDIO
gcc_ride7

в результате компиляции должен получиться бинарник, который прошивается во flash-память
контроллера.

Начнём рассмотрение компиляции с тех поставок, которые основаны на GCC.

Хорошим стилем считается разделить программу на две части. В одной части будет
собственно наша специфическая логика. А в другой части будет некоторая программная обвеска,
которая выполняет всякие инициализирующие функции. Например, выбрать источник системной
частоты для процессора: внешний кварц или внутренний. Такая универсальная инициализирующая
функция, пригодная для любых программ, уже написана. Она называется SystemInit() и объявлена
в system_stm32l1xx.h (а реализована, соответственно, в файле system_stm32l1xx.c).
В тех же файлах есть ещё несколько функций для работы с системной частотой. Такие файлы
есть для всех моделей микроконтроллеров STM32, поскольку системная частота – самая базовая
функция. Итак, перед началом логики удобно вызвать эту функцию, но можно и не вызывать, или
вообще вызвать свою.

Язык программирования C предоставляет несколько гарантий. Например, что все глобальные
переменные будут проинициализированы перед началом выполнения программы. Значения, которыми
надо проинициализовать эти глобальные переменные, хранятся в бинарном образе программы во
флеш-памяти, а сами глобальные переменные, поскольку их можно изменять, будут расположены
в оперативной памяти. Поэтому нужен стартовый код, который будет копировать значения из
флеш-памяти в ОЗУ. Этот код расположен в ассемблерном файле startup_stm32lxx_md.s
Собственно, выполнение бинарного образа начинается как раз с этого кода, а уж этот код,
выполнив все инициализации, вызывает main(). Кстати, хитрые авторы кода сделали так, что
они сами вызывают SystemInit() из стартового кода, а уже потом вызывают main(), так что
нам этого делать не надо. Но всегда можно поменять стартовый код.

В поставку входит несколько стартовых кодов, под разные ассемблеры. Для сред, основанных
на GCC, можно взять gcc_ride7 или TrueSTUDIO. Они отличаются только одной строчкой кода.
TrueSTUDIO ещё вызывает инициализатор своей стандартной библиотеки. Надо вам оно или нет –
решать вам.

Ещё в стартовом коде находится таблица прерываний по-умолчанию.
И один обработчик по-умолчанию для этих прерываний, который впадает в бесконечный цикл.
Собственно, таблица прерываний для GCC-компиляторов работает так: объявляется набор
“слабых алиасов” (weak alias, http://en.wikipedia.org/wiki/Weak_symbol),
то есть если присутствует функция с таким именем, то будет ипользоваться она,
а если нет, то этот алиас. А алиас устанавливает прерывание на обработку по-умолчанию.
Так что если программа на C хочет использовать прерывания, то ей надо только
объявить функцию с правильным именем (тем, которое используется в стартовом коде!).

Итак, чтобы собрать пустую программу на C, понадобится стартовый код,
библиотечный файл system_ и файл с функцией main().

Вызовем мы компилятор, и превратим два сишных файла и один ассемблерный файл в три
объектных файла, затем слинкуем их. Но как задать такой бинарник, чтобы он знал про
абсолютное расположение флеш-памяти в адресном пространстве процессора? И как указать,
что стартовая точка – вот она, в стартовом коде? Для этого применяется скрипт линковщика.
Хорошая документация по скриптам есть тут:
http://sourceware.org/binutils/docs-2.21/ld/Scripts.html
Подробности про скрипт линковщика – внизу.

Если программировать на ассемблере, то всё немного попроще, но скрипт линковщика
всё равно нужен.

Зато на ассемблере плохо с библиотеками. Хотя я вру! Ведь библиотеки, написанные
на языке C, вполне можно использовать из ассемблерного кода. Но не так удобно.

Собственно, одну библиотеку мы уже использовали, библиотеку системной частоты.
Ещё есть библиотека периферийных регистров в файле stm32l1xx.h, но её трудно назвать
библиотекой, потому что там нет исполняемого кода, а только удобные мнемонические
имена для адресов периферийных регистров. Ну не использовать же, в самом деле, числовые
константы в коде, это непонятно. Есть ещё библиотека стандартной периферии,
то есть тех устройств, которые расположены на самом кристалле. там для каждого
устройства есть заголовочный файл и файл с реализацией. И в четвёртых, есть библиотка
для устройств, расположенных на плате Discovery: touch sensor, temperature sensor,
lcd panel.

библиотеки поставляются в исходном виде. Я думаю, что для этого есть несколько причин.
Первая – пришлось бы компилировать библиотеки подо все линковщики.
И ещё писать документацию. А так есть исходники, и документация не нужна.

Все туториалы рекомендуют скопировать себе исходники библиотек в проект и компилировать
их в каждом проекте. Но это же маразм! Надо откомпилировать их один раз, и потом
линковать ко всем проектам. Вот!

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

А ещё там определяется константа STM32L1XX_MD, и непонятно, зачем это сделано.
Потому что нигде эта константа не используется!
Для STM32F в туториалах рекомендуют откомментировать строчку, которая соответствует
конкретному контроллеру, но тут он один! Как я понял, получилось так: для F в коде
system_stm32 при установке системной частоты ориентировались на модель контроллера. А
для STM32L в этом коде уже оставили только один вариант, пригодный для L, и поэтому
объявлять модель не нужно.

Ещё там проверяется, определено ли USE_STDPERIPH_DRIVER, и если да,
то инклудится ещё и stm32l1xx_conf.h . Ну, это уже элемент стиля программирования. В тех
примерах проектов, которые поставляются, в этот conf.h прописывают заголовочные файлы для
той периферии, с которой программа будет работать. То есть conf- этакое единое место
для подключения-отключения используемой периферии, а USE_STDPERIPH_DRIVER – это
такой глобальный отключатель.

Ещё как элемент стиля – объявлять обработчики прерывания в файле …_it (interrupt table)

Сишный код – вроде общий для всех компиляторов, но это только кажется. Внутри повсеместно
встречаются вещи вроде
#if defined ( __CC_ARM )

#elif defined ( __ICCARM__ )

#elif defined ( __GNUC__ )

#elif defined ( __TASKING__ )

#endif

Ещё небольшая неэлегантность. В библиотеке стандартной периферии stm32l1xx_flash_ramfunc.c
находятся функции, которые должны находиться в оперативной памяти. Для
этого в файле фstm32l1xx.h определён макрос __RAM_FUNC, использующий особенности
компиляторов. Однако помимо управления размещением кода функции, этот макрос
заодно говорит, что функция возвращает FLASH_Status, который определён в драйвере
флеш-памяти stm32l1xx_flash.с из библиотеки стандартной периферии.

From Programmer’s manual
————————

On reset, the processor loads the MSP with the value from address 0x00000000.

On reset, the processor loads the PC with the value of the reset vector,
which is at address 0x00000004

2.3.4 Vector table

The vector table contains the reset value of the stack pointer,
and the start addresses, also called exception vectors, for all exception handlers.
On system reset, the vector table is fixed at address 0x00000000.
Privileged software can write to the VTOR to relocate the vector table start address
to a different memory location.

From Reference manual:
———————-

The STM32L15xxx features 48 Kbytes of SRAM. It can be accessed as bytes, half-words
(16 bits) or full words (32 bits). The SRAM start address is 0x2000 0000.

Program memory (flash) starts at 0x0800 0000

2.4 Boot configuration

ножки BOOT1 и BOOT0 выбирают, какой участок памяти отображается на адресное пространство
0x0000 0000 – 0x0005 FFFF
Это может быть flash (0x0800 0000), это может быть System memory (0x1FF0 0000),
это может быть SRAM (0x2000 0000). См. таблицу 10.

Точнее говоря, эти ножки при сбросе записывают значения в биты регистра SYSCFG_MEMRMP,
а уже этот регистр управляет отображением памяти.

Even when aliased in the boot memory space, the related memory is still accessible at its
original memory space.

The embedded boot loader is used to reprogram the Flash memory through one of the
following interfaces: USART1, USART2 or USB
This program is located in the system memory and is programmed by ST during production.

Обещаные подробности про скрипт линковщика:
——————————————-

хорошая статья:
http://www.embedds.com/programming-stm32-discovery-using-gnu-tools-linker-script/

ENTRY указывать бессмысленно. Надо проверить, можно ли не указывать. Все равно
процессор выполняет ResetHandler, потому что на него ссылается выполнение из
таблицы прерываний. Но, может, эта директива помогает отладчику? Нет, проблема в другом.
Линковщик требует знать точку начала, и по-умолчанию это будет символ _start. Поскольку
такого символа у нас нет, то нужно указать как точку старта любой из имеющихся символов.
Хотя бы и main.

Чтобы посмотреть, какие секции генерируются в объектных файлах, нужно
objdump -h

Зачем-то явно указывают значение _estack. Я считаю, что оно должно вычисляться.
Хотя, возможно, его указывают, чтобы не заполз выше, чем SRAM. Надо проверить способ
задать ограничение на _estack, а не само значение. Ну, или хотя бы сделать через выражение,
чтобы он указывал на конец RAM.

Дальше идут два региона памяти: rom и ram.

Потом идут секции в таком порядке:
.isr_vector с атрибутом KEEP, потому что на него другие секции не ссылаются
*(.text) – это код
*(.text*) – тоже код
*(.rodata) – константы
*(.rodata*) – тоже константы
ну и другая gcc-шная туфта.

это всё в rom.

Потом идёт секция инициализированных глобальных переменных
.data
Чтобы стартовый код мог её скопировать в SRAM, там вычисляются два символа: начало и конец
Загружается оно во flash, но во время выполнения будет в SRAM, и это указывается линковщику.
Система хитрая: program counter вычисляется на основе того, где секция будет во время
исполнения, то есть в RAM. А в бинарном образе она будет идти вместе с ROM-секциями.
Эта секция должна быть объявлена первой из тех, кто будет в RAM загружаться

потом идёт секция неициализированных данных. Тоже вычисляются два символа, которые
используются стартовым кодом, чтобы почистить участок памяти.

Потом – секция для стека. Она там просто чтобы проверить, что от данных
до окончания ram-памяти остаётся некоторое пространство.
Подробности про опции командной строки линковщика:
——————————————-

Документация по линковщику есть только общая, и в ней не указываются всякие
значения по-умолчанию, специфичные для конкретной сборки. Чтобы их узнать,
наберём

ld –verbose

опция -m управляет какой-то эмуляцией. Эмуляция – это набор значений по-умолчанию.
Например, конкретный режим эмуляции содержит скрипт по-умолчанию. Разумеется,
есть и режим эмуляции, включённый по-умолчанию. Его нам должен показать
вышеупомянутый ключ –verbose .

Ключ -M (он же –print-map) выводит расположение символов и секций в получившемся
файле. Дополнительный ключ -Map=mapfile задаёт вывод в файл, а не в стандартный
вывод. Удобно.

Указать линковщику объектный файл или библиотеку можно двумя способами: или
непосредственно по имени в командной строке (тогда он ищет файл в зависимости
от пути. Если путь относительный, то относительно текущего каталога, а если
путь абсолютный, то он и указывает), или через ключ -l, который говорит, что
искать надо в “Library path”, который конфигурится отдельно.

Если указать линковщику библиотеку (он её называет “архив”), то из библиотеки будут
извлечены только требуемые символы. У линковщика есть 2 режима. Если просто указать
библиотеку в командной строке (напрямую или через ключ -l), то извлекутся
те символы, которые были неопределёнными в предыдущих файлах.
Поэтому библиотеки указывают в самом конце командной строки.
А второй режим состоит в том, что собирается “группа” библиотек, поиск в которых
будет проводиться всё время как только обнаружится ненайденный символ.
Такую группу можно задать, сначала введя ключ -(, потом через пробел перечислить библиотеки,
а потом закрыть ключом -). Я так понимаю, что первый способ гораздо быстрее,
а второй – удобнее.

Теперь про “Library path”. Он указывается через опции -L , таких опций может быть
несколько.

Вышеперечисленные опции командной строки можно передавать и не через командную строку,
а через скрипт! Файлы указываются через INPUT или GROUP. Путь для поиска библиотек
можно создавать через команду SEARCH_DIR. Ключ -T можно применять несколько
раз, при этом скрипты конкатенируются.

Есть такой ключ у линковщика -nostdlib. Этот ключ говорит о том, что
пути для поиска библиотек следует брать только из командной строки, а те пути,
что в скрипте (даже в скрипте по-умолчанию!) брать не нужно.

Если работать не с линковщиком напрямую, а вызывать его неявно из gcc, то опции
ему нужно передавать через -Wl, (и после запятой сразу опцию). Но некоторые опции
типа -l, -L, -nostdlib используются GCC напрямую. Для -nostdlib это означает дополнительно,
что стандартный стартовый код не будет прилинкован, что нам и нужно!

gcc -print-search-dirs

Для решения некоторых проблем, возникающих при работе с линковщиком, поможет опция
-y symbol (она же –trace-symbol=symbol), которая выводит файлы, в которых встречается
указанный символ. Помогает вычислить, кто использует этот символ.

–target-help
Print a summary of all target specific options on the standard output and exit
Обрезание ELF-а до BIN-а:
——————————————-
arm-none-eabi-objcopy -S -O binary “${ProjName}” “${ProjName}.bin”

  1. No comments yet.
  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: