3. Рантайм ZFP

Профиль ZFP - официально поддерживаемый AdaCore профиль компилятора для процессора SAM4S. Считаем, что он у нас собран шагом ранее.

3.1. Простейшее приложение

Попробуем написать простейшее приложение на Ada с использованием рантайм-библиотеки ZFP, и используя исключительно средства этой библиотеки, в том числе и для линковки (конфигурационный скрипт, скрипт линкёра и стартовый файл).

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

Таким образом оно должно продемонстрировать:

  • средства текстового обмена с консолью, в том числе туда будет выводится информация об исключениях, если таковое случится, и можно выполнять любую отладочную печать;
  • простейшую схему обработки прерываний;
  • настройку системного таймера;
  • сборку приложений для RAM и FLASH
  • загрузку и отладку приложения в RAM и FLASH.

Сначала реализуем версию для ОЗУ, обычно такая версия более удобна для отладки приложения прежде, чем оно будет прошиваться во флэш.

Структура приложения

"Полезная" часть программы начинается с процедуры Main в файле main.adb. Но прежде чем мы до неё доберёмся, должна быть выполнена инициализация "железа", библиотеки ZFP, и пакетов нашей программы. Посмотрим как это происходит.

После загрузки программы отладчиком в ОЗУ, отладчик устанавливает PC на точку входа, которая указана при линковке, в нашем случае _start_ram, которая находится в библиотечном файле start-ram.S.

В первую очередь, устанавливается указатель стека SP, значение адреса __stack_end определяется символом в скрипте линкёра.

Затем производится чистка секции .bss, это означает, что все не-инициализированные переменные программы будут заполнены нулями.

Далее вызывается процедура _ada_setup_pll (определена в файле setup_pll.adb), её задача установить значение умножителя тактовой частоты (PLL), такое чтобы процессор работал на максимальном быстродействии - 120МГц. При кварце 12 МГц - умножение производится на 10.

Установка PLL делается на самой ранней стадии, чтобы вся дальнейшая инициализации происходила быстрее. Поэтому процедура вызывается непосредственно из start-ram.S, а не из кода Ada.

После чего вызывается b__main.adb:main, которая запускает процедуры элаборации всех пакетов (инициализацию глобальных переменных) и, наконец, вызывает нашу главную функцию Main.

Функция Main в main.adb демонстрирует возможности консольного ввода-вывода.

Фактически, в качестве устройства стандартного ввода-вывода ZFP-runtime использует UART1, который на плате подключен к чипу EDBG и в нашей Linux-системе представляется устройством TTY-USB, в моём случае это /dev/ttyACM0.

Другая, более интересная, часть программы находится в пакете Interrupts interrupts.adb.

Процедура Init, которая вызывается при элаборации пакета, содержит минимальный код инициализации "железа", в частности установку регистра указателя таблицы векторов прерываний VTOR на действительный адрес таблицы векторов прерываний:

    System_Vectors : constant System.Address;
    pragma Import (Asm, System_Vectors, "__vectors0");
    ...
    SYS_CTRL.VTOR := System_Vectors'Address;
  

Настройку VTOR можно было бы сделать там же где сама таблица, т.е. в start-ram.S, однако в стандартном библиотечном start-ram.S этого нет, поэтому делаем здесь.

Кроме того, в Init настраиваем таймер, устанавливая желаемую частоту прерываний.

Перед инициализацией прерываний, мы запрещаем трапы (к которым относится и прерывание от системного таймера), а после - разрешаем их:

  Asm ("cpsid f", Volatile => True);
  ...
  Asm ("cpsie f", Volatile => True);

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

Вероятно правильнее было бы запретить трапы и прерывания непосредственно в начале старта в start-ram.S, а разрешить только после того как исключения и прерывания будут инициализированы полностью.

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

   procedure Timer_Intr_Handler;
   pragma Export (Asm, Timer_Intr_Handler, "SysTick_Handler");

Благодаря реализованной в процессоре системе обработки прерываний, обработчик написанный на Ada (или С) не требует какой-либо обёртки на ассемблере, что делает организацию таких обработчиков очень несложным делом.

Наш обработчик лишь периодически моргает лампочкой. В реальном приложении, он может обеспечивать системное время или планировать какие-либо события.

Сборка программы для ОЗУ

Для сборки используем скрипт build.sh в каталоге проекта, который настраивает окружение и вызывает gprbuild указывая ему файл проекта proj.gpr. Помимо других опций, gprbuild получает параметром -XLOADER=SAMBA, что заставляет gprbuild использовать в качестве скрипта линковки sam4s-samba.ld, где указана точка входа __start_ram. Чтобы разрешить этот символ линкёр подключает стартовый файл start-ram.S.

Файл проекта, в случае кросс-разработки должен содержать указание для какой платформы собирается приложение и какой рантайм нужно использовать:

   for Target use "arm-eabi";
   for Runtime ("Ada") use "zfp-sam4s";
Тоже самое можно сделать и опциями командной строки gprbuild.

Полезно проверить конфигурацию проекта, сгенерированного автоконфигуратором gprconfig, он находится в файле obj/auto.cgpr. А результаты сборки - по загрузочной карте obj/proj_ram.map, и листингу дизассемблера obj/proj_ram.lss.

Загрузка и отладка

Подключаем USB-кабель к разъёму платы с маркировкой DEBUG-USB. Должен загореться зелёный светодиод питания POWER и замигать жёлтый STATUS.

Запускаем openocd, указывая ему конфигурационный файл нашей платы board/atmel_sam4s_xplained_pro.cfg.

Для удобства запуска openocd в $PROJ/utils лежит стартовый openocd.sh и конфигурационный файлы openocd.cfg.

bash-4.3$ ./openocd.sh
Open On-Chip Debugger 0.8.0 (2014-06-07-21:57)
Licensed under GNU GPL v2
For bug reports, read
        http://openocd.sourceforge.net/doc/doxygen/bugs.html
Info : only one transport option; autoselect 'cmsis-dap'
Info : CMSIS-DAP: SWD  Supported
Info : CMSIS-DAP: Interface Initialised (SWD)
adapter speed: 500 kHz
adapter_nsrst_delay: 100
cortex_m reset_config sysresetreq
Info : CMSIS-DAP: FW Version = 01.0F.00DB
Info : SWCLK/TCK = 1 SWDIO/TMS = 1 TDI = 1 TDO = 1 nTRST = 0 nRESET = 1
Info : DAP_SWJ Sequence (reset: 50+ '1' followed by 0)
Info : CMSIS-DAP: Interface ready
Info : clock speed 500 kHz
Info : IDCODE 0x2ba01477
Info : ATSAM4SD32C.cpu: hardware has 6 breakpoints, 4 watchpoints

Openocd подключился к чипу EDBG на плате и создал три сокета в нашей Linux-системе. По умолчанию порт gdb_port=3333 предназначен для подключения отладчика GDB, telnet_port=4444 для команд через консоль telnet и tcl_port=6666 - это средство RPC для подключения других приложений.

Запускаем GDB, подключаемся к порту 3333, загружаем и запускаем программу на исполнение.

bash-4.3$ arm-eabi-gdb
(gdb) target remote localhost:3333
Remote debugging using localhost:3333
0x00400634 in ?? ()
(gdb) file obj/proj_ram.elf
A program is being debugged already.
Are you sure you want to change the file? (y or n) y
Reading symbols from obj/proj_ram.elf...done.
(gdb) load
Loading section .text, size 0xd64 lma 0x20000800
Loading section .rodata, size 0x16c lma 0x20001564
Loading section .data, size 0x1c lma 0x200016d0
Start address 0x20000e34, load size 3820
Transfer rate: 3 KB/sec, 1273 bytes/write.
(gdb) c

Далее работаем в GDB как обычно.

Прерываем исполнение программы по Cntrl-C.

Перед следующей загрузкой необходимо выполнить сброс периферии, иначе процессор, скорее всего, окажется в режиме обработки аппаратного исключения и программа не сможет нормально запуститься.
(Внимание! Присваивание в синтаксисе Ada)

(gdb) set *0x400e1400 := 0xA5000004

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

(gdb) info all-reg
r0             0x0      0
r1             0x20001598       536876440
r2             0x400e0800       1074661376
r3             0x0      0
r4             0x0      0
r5             0x1ddf07f9       501155833
r6             0xa97ef9bf       -1451296321
r7             0x200026bc       536880828
r8             0xcdf8f3e0       -839322656
r9             0x28c93131       684273969
r10            0xcedaf647       -824510905
r11            0x2c9cffae       748486574
r12            0x20000f74       536874868
sp             0x200026bc       0x200026bc
lr             0x20000e73       536874611
pc             0x20000e34       0x20000e34 <_start_ram>
xPSR           0x61000000       1627389952
msp            0x200026bc       0x200026bc
psp            0xf8ea9550       0xf8ea9550
primask        0x0      0
basepri        0x0      0
faultmask      0x0      0
control        0x0      0

xPSR в последних разрядах содержит 0, это значит, что процессор в Thread-mode, а не в обработке исключения;
pc содержит адрес точки входа;
primask = 0 значит, что прерывания разрешены;
faultmask = 0 значит, что исключения также разрешены;
control имеет нулевые младшие два бита, процессор находится в привилегированном режиме и будет использовать указатель стека msp;

Работа в GDB становится удобнее при использовании скриптов, поэтому организуем скрипт $PROJ/utils/gdb.ram с обще-полезными функциями

define reset
  monitor reset init
  # Reset peripheral  (RSTC_CR)
  set *0x400e1400 := 0xA5000004
  #info all-reg
end

define connect
  target remote localhost:3333
end

и скрипт gdb.ram в каталоге проекта, для решения текущих задач отладки

file obj/proj_ram.elf

source ../utils/ram.gdb

#set disassemble-next-line on
set remotecache off
set stack-cache off

b __gnat_last_chance_handler
b main
#b _start_ram

connect
reset

Теперь будем запускать GDB таким образом:

bash$ arm-eabi-gdb -x ram.gdb

Для сброса перифирии и загрузки теперь можем использовать команды

(gdb) reset
(gdb) load
  

точка останова на __gnat_last_chance_handler позволит посмотреть стек исключения, если таковое случится.

Стандартный ввод-вывод

Запускаем minicom следующим образом

$bash minicom -o -8 -b 115200 -D /dev/ttyACM0

Для упрощения запуска терминала в дальнейшем, сделаем скрипт такого же содержания в каталоге $PROJ/utils.
/dev/ttyACM0 - это терминальное устройство, создаваемое в Linux при подключении EDBG.

Нажимая клавиши в терминале, видим эхо-вывод нашего приложения.

Сборка программы для флэш

К сожалению, для сборки проекта во FLASH, мы не можем использовать библиотечный файл start-rom.S (start-rom.o) в чистом виде, поскольку он не экспортирует таблицу векторов прерываний __vectors0), которую использует наше приложение.

Поэтому, чтобы наш проект годился и для загрузки во флэш, что обычно и является конечной целью, и без существенных модификаций, проще всего заменить start-rom.S, что и сделаем - назовём его start-rom-my.S.

По-существу, содержимое файла то же, что и в библиотечном start-rom.S, но добавлена таблица векторов прерываний, такая же как в start-ram.S.

И эта таблица экспортируется как внешний символ (чтобы сделать видимой линковщику) посредством макродирективы

.globl  __vectors0

Небольшое отличие ROM-варианта от RAM-варианта в том, что на нулевой позиции здесь записан адрес верхушки стека, который процессор автоматически загрузит в SP по сбросу. Таким образом, код явной установки SP, в этом случае не нужен. Также по сбросу, процессор загрузит в PC значение следующего вектора (__vectors0 + 4) - адрес точки входа - в этом случае _start_rom.

По-умолчанию, таблица векторов размещается с адреса 0, при этом в зависимости от специальных битов настройки отображения GPNVM1 и GPNVM2 (см. в руководстве Enhanced Embedded Flash Controller (EEFC)) область внутренней флэш-памяти (16#400_0000# - 16#800_0000#) может отображаться в эти нижние адреса, как это и есть на нашей плате (иначе туда отображается область ROM-загрузчика SAM-BA).

В соответствии со скриптом линковки библиотеки ZFP - sam4s-rom.ld, наше приложение линкуется в адреса внутренней флэш-памяти, причём первой секцией будет .vectors, с таблицей векторов прерываний. Таким образом процессор найдёт её через отображение с адреса 0, загрузит SP, и перейдёт на точку входа _start_rom.

Фактически, в этом случае установка VTOR уже не требуется, но и никак не мешает.

Далее производится чистка .bss, и, в отличие от RAM-версии - секция данных (т.е. все инициализированные переменные) копируется из флэш в ОЗУ.

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

Прошить программу во флэш микроконтроллера можно с помощью openocd:

  bash$ openocd -f ../utils/openocd.cfg \
         -c "program obj/proj_rom.elf verify reset"

Для удобства поместим эту команду в файл upload.sh в каталоге проекта.

Отладка в целом аналогична отладке программы скомпилированной для RAM: запускаем openocd, подключаемся к нему отладчиком, отладчик сбрасывает процессор и останавливается на точке входа. Загружать программу в этом случае, естественно, не нужно. Можем устанавливать точки останова и продолжать исполнение.

Реализованная в этом проекте схема обработки прерываний, очевидна и традиционна, она практически таким же образом может быть написана и на языке Си.

Профиль компилятора и рантайм ZFP, к сожалению, не позволяет использовать традиционные для языка Ada мощные средства обработки прерываний, как то: динамическое назначение обработчиков прерываний посредством функции Attach_Handler и использование protected objects в качестве обработчиков прерываний.

В случае более развитого рантайма, начиная с ravenscar-sfp нам вообще не пришлось бы заботиться об организации подсистемы прерываний.

Прочитать о способах организации обработки прерываний на Ada можно здесь:
Gem #13: Interrupt Handling Idioms (Part 1)
Gem #14: Interrupt Handling Idioms (Part 2)


© 2013-2015 Евгений Турышев. Материалы данного сайта нельзя использовать без предварительного согласия автора