разработка и программирование электронных устройств

Программирование AVR и ARM микроконтроллеров в Eclipse. Часть 2

В первой части статьи был рассмотрен процесс разработки программ для микроконтроллеров AVR в инструментальной среде Eclipse. В конце первой части представлена общая схема разработки программ для ARM в Eclipse с помощью GNU Tool chain.

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

Итак, процесс разработки программ в Eclipse для ARM микроконтроллеров с помощью GNU Tool chain изображен на следующей схеме.

Из схемы видно, что исходные файлы на языках С и Assembler компилируются с помощью arm-none-eabi-gcc. Компилятор arm-none-eabi-gcc может осуществлять вызов ассемблера arm-none-eabi-as, компоновщика arm-none-eabi-ld, поэтому в нашем Makefile вместо них будет указан arm-none-eabi-gcc.

В результате компиляции получается один или несколько объектных файлов. Далее компоновщик arm-none-eabi-ld выполняет компоновку объектных модулей в памяти микроконтроллера. Для микроконтроллеров с архитектурой ARM код программы может находиться в разных областях памяти( FLASH, SRAM и т.д. ).

На размещение кода и данных внутри микроконтроллера можно влиять путем создания файла сценария компоновки. Сценарий компоновки передает линкеру адрес и размер доступных в микроконтроллере объемов памяти, предписывает компоновщику размещение секций программы по конкретным адресам .

Чтобы написать скрипт компоновки необходимо иметь перед глазами карту памяти используемого микроконтроллера. Для большинства микроконтроллеров ARM можно найти готовый файл сценария.

После компоновки создается один файл исполняемого образа программы в формате elf .

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

Для этого необходимо выполнить преобразование утилитой arm-none-eabi-objcopy в один из доступных для прошивки форматов ( bin , intel hex, motorola S-records и т.д. ).

Из полученного после компоновки elf- файла можно получить листинг кода программы на языке ассемблера с помощью утилиты arm-none-eabi-objdump.

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

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

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

Однако мы рассмотрим более детально стартовый код для различных архитектур ядра ARM.

Далее изображен пример стартового файла crt0.S микроконтроллера компании NXP LPC2138 с архитектурой ядра ARM7. Будем рассматривать этот код частями сверху-вниз.

В этой части кода выполняются объявления. Директива ассемблера (GNU-ASM) .global делает указанный после директивы символ глобальным, это значит, что данный символ можно будет использовать в других модулях программы.

Директивы ассемблера .set и .equ аналогичны директиве #define в языке C и выполняют установку нового значения символу. При компиляции проекта везде в тексте программы символ будет заменен на его значение.

Рассмотрим следующий фрагмент кода:

Директива .text указывает на начало сегмента кода, все следующие за ней директивы будут размещены в секции «text».

Далее при помощи директивы .arm указывается, что будет использован набор команд ARM.
Для процессоров ARM7 всего существует два набора команд — ARM и Thumb. Второй режим включает набор 16-разрядных команд и предназначен для увеличения плотности кода программы.

Объявление .section .init, «ax» декларирует секцию внутри сегмента кода с названием init и с помощью флагов «ax» указывает на то , что раздел является перемещаемым и содержит исполняемый код.

Директива .code 32 указывает на 32-разрядный код , а директива .align указывает на выравнивание кода по 16-ти разрядам (двум байтам). Метка _boot является глобальной (.global) и указывает на начало функции (.func _boot ).

Далее расположена таблица векторов прерываний. Директива b _start выполняет переход на адрес _start , а все остальные директивы загружают в счетчик команд адреса обработчиков.

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

Теперь объявляем три глобальные метки, по которым можно перейти на следующий код. Код программы всегда начинается с функции _start.

Далее выполняем инициализацию указателей вершин стеков для всех режимов работы процессора ARM7.

Величина стека задана в скрипте компоновщика( далее будет описан ).

В расположенном выше фрагменте кода мы перешли в режим «Undefined Instruction», запретив прерывания установкой I_BIT и F_BIT. Потом загрузили в SP адрес вершины стека из R0. Вычли из текущего значения в R0 размер стека режима «Undefined Instruction» и получили в R0 адрес вершины стека для следующего режима.
Аналогичным образом выполняется инициализация для всех остальных режимов.

Следующая инициализация необходима, если код предназначен для загрузки в FLASH память микроконтроллера.

Заполняем секцию .bss нолями:

И , наконец, выполняем передачу управления функции main.

Подсчет размера функции _start и ее окончание.

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

Для процессорного ядра Cortex-M3 стартовый код вообще является необязательным. В «Кортексах» вся программа может быть написана на языке программирования С.

При компиляции описанного выше стартового кода необходимо указать компилятору использовать режим ARM, а точнее ничего не указывать, так как по-умолчанию код компилируется для режима ARM.
Раздельная компиляция ARM и Thumb исходных кодов, находящихся в разных файлах, является специфической особенностью компилятора GCC.

Для того, чтобы откомпилировать исходный текст программы в режиме Thumb необходимо указать компилятору ключ -mthumb. Если в проект входят файлы, откомпилированные в ARM и Thumb режимах и между ними выполняется вызов функций , то необходимо также указать компоновщику ключ -mthumb-interwork, который позволит осуществлять переход между режимами ARM и Thumb во время выполнения кода.

При сборке программы из объектных модулей компоновщик использует скрипт компоновки, в котором указывается размещение секций внутри памяти микроконтроллера. Если этот скрипт явно не указывать , то будет использоваться встроенный по-умолчанию в компоновщик скрипт.

Для подключения собственного файла скрипта необходимо указать компоновщику с помощью опции -T . название скрипта. Имя файла и расширение для компоновщика не имеют значения. Пример :

Компоновщик вырабатывает исполняемый файл с настраиваемыми адресами. Эти адреса устанавливаются каждый раз при загрузке модуля в память. Каждый раздел описывается двумя адресами — виртуальным адресом (VMA) и загружаемым в память адресом (LMA). Первый предназначен для внутреннего использования, а второй указывает на расположение памяти, куда будет загружен раздел. Процесс определения адреса модуля называется его размещением.

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

Рассмотрим пример сценария компоновщика для микроконтроллера LPC2138 ( ARM7 ).

Ключевое слово MEMORY используется для присвоения имен блокам адресного пространства. В данном примере указывается местоположение и размер памяти микроконтроллера. Внутренней FLASH памяти присваивается название rom, а внутреннее ОЗУ микроконтроллера поименовано как ram. Атрибуты памяти (rx) указывают на то, что содержимое памяти может читаться и выполняться, аналогичным образом атрибуты (rw) указывают на возможность чтения и записи в данной области памяти.

Ключевое слово SECTIONS указывает на то, что далее будет описана карта распределения памяти для скомпонованного объектного модуля. Точка указывает на текущий адрес, то есть строка
. = 0x00000000 устанавливает текущий адрес в абсолютный со значением 0x00000000. После установки текущего адреса он будет автоматически увеличиваться при добавлении элементов в выходной файл.

Оператор .text : { } помещает начало секции .text выходного файла по текущему адресу. В скобках указываются элементы, которые будут включены в раздел .text выходного файла. Можно указать конкретные имена входных файлов ( компонуемых объектных файлов), в данном примере указаны все ( оператор * ) имена входных файлов.

Строка >rom указывает на то, что данный раздел будет помещен в именованную область памяти rom (то есть в FLASH память ).

Наименования основных секций для компоновщика являются предопределеннымы. В секции .text находиться код программы, также там могут находиться константы (.rodata). Секция .data предназначена для размещения данных , а в секции .bss находятся инициализированные нулевым значением данные. Туда попадают,например, статические переменные в языке С, которые только объявлены, но должны содержать нулевое начальное значение. Поэтому заполнение секции .bss нулевыми значениями производиться в стартовом коде перед вызовом функции main.

Именованные разделы размещаются в том порядке, в котором они указаны. Раздел .bss в нашем примере будет находиться сразу после раздела .data, поскольку для него не указан начальный адрес.

Компоновщику, по большому счету, все-равно на каком языке программирования написан программный модуль, скомпилированный в объектный и используемый в качестве входного . Вы можете использовать при компоновке объектные модули, написанные на языках программирования С и Assembler, как это делается при подключении стартового кода. В большинстве случаев такая необходимость не возникает, поскольку объема памяти и производительности современных микроконтроллеров достаточно для применения языка С.

Все программы из пакета ARM Tool chain( в качестве ARM Tool chain может выступать пакет Sourcery G++, Yagarto и т.д ) имеют интерфейс командной строки. Можно последовательно вызывать из командной строки компилятор, компоновщик, утилиты для преобразования и прошивки исполняемого файла. А можно записать всю последовательность вызовов в Makefile, запустить утилиту make и наблюдать за результатами выполнения команд, перечисленных в Makefile.

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

Общая форма записи в Makefile на концептуальном уровне выглядит так :

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

Если, например, нам необходимо откомпилировать исходный файл main.c, потом скомпоновать объектный модуль main.o и стартовый код crt0.o в исполняемый образ main.out, далее преобразовать main.out в формат intel-hex , а также получить ассемблерный листинг программы, то Makefile будет содержать такие записи :

Цели all и clean называются псевдоцелями, это просто символьные метки,которые используются для запуска цепочки зависимостей из Makefile. Директива .PHONY: all clean перечисляет все псевдоцели, которые могут вызываться указанием названия цели в командной строке утилиты make.

Если мы запустим утилиту make с параметром all в командной строке, то активируется цель all. Утилита make проанализирует зависимость цели all — файлы main.hex и main.lst. Если эти файлы уже существуют, то на этом работа утилиты прекратится. При первом запуске у нас в наличии есть только исходные файлы crt0.s и main.c.

Содержимое main.c :

Далее анализируется файл main.hex, для его получения необходим main.out. В свою очередь для получения main.out необходимы объектные файлы crt0.o и main.o, которые можно получить из исходных файлов компиляцией. Все действия будут выполнены в обратном порядке для удовлетворения зависимостей. Сначала будут откомпилированы файлы crt0.s и main.c, потом скомпонованы в main.out, а уж после этого из main.out будут получены main.hex и main.lst.

Как видите, ничего сложного в составлении Makefile нет, необходимо научиться правильно создавать зависимости и знать опции используемых утилит( компилятор, компоновщик и т. д.).

Поэкспериментируйте с описанными выше файлами, поместите файлы Makefile, main.c, script.link, crt0.s в один каталог , запустите командную строку, перейдите с помощью команды cd в созданный каталог и наберите в командной строке make all. Потренироваться в написании скрипта компоновки можно путем его редактирования и просмотра результатов компоновки в файле листинга программы main.lst. Команда make clean, набранная из командной строки, удалит все созданные в результате выполнения make all файлы.

Настало время вернуться к Eclipse. Создадим новый проект с названием ARM7_project и добавим к нему все описанные выше файлы. Можете загрузить готовый проект.

Предоставленный код выполняет мерцание светодиодом, подключенным на первый вывод микроконтроллера LPC2138 ( PORT0.21 ), светодиод зажигается при высоком уровне на выводе PORT0.21 и гаснет при низком. Для понимания логики работы этого простого примера рекомендую почитать книгу Т. Мартин «Микроконтроллеры ARM7. Семейство LPC2000 компании Philips. Вводный курс».

Для отладки кода внутри кристалла микроконтроллера используем адаптер FTDI JTAG. Если Вы внимательно читали мои предыдущие статьи на этом сайте, то настраивать Eclipse для использования отладки Вы должны уметь.

В Fedora 13 Electronic Lab окно проекта, созданное в Eclipse Helios под Windows 7 , выглядело следующим образом.

В свежей версии FEL обновлять openocd до версии 0.4.0 не пришлось, так эта версия установлена изначально. Единственное, что пришлось установить, — это Sourcery G++. Путь к каталогу с утилитами из пакета Sourcery G++ в переменной PATH после перезагрузки потерялся, поэтому пришлось явно указать в Makefile расположение необходимых утилит.

Для отладки в Eclipse под FEL 13 вместо знакомого нам плагина ZylinCDT теперь используется GDB Hardware Debugging Plug-in.

На следующих рисунках изображены необходимые для запуска отладки в Eclipse под Fedora 13 (FEL) настройки.

Строка запуска openocd выглядела следующим образом :

Запускаем отладку. Процесс остановиться на входе в функцию main, поскольку мы установили точку прерывания в этом месте. Дальше запускаем пошаговое выполнение и наблюдаем включение-выключение светодиода.

Viewed 619 times by 180 viewers

24 Comments to Программирование AVR и ARM микроконтроллеров в Eclipse. Часть 2

  1. sasha's Gravatar sasha
    30 января 2011 at 23:23 | Permalink

    В инициализационном файле crt0, когда происходит переход к функции main, каким образом цпу поменяет режим??как он узнает,что нужно перходить именно в режим ARM?зачем = перед всеми глобальными переменными ( типа ldr r10,=main )?.
    .global main является глобальной.Те комманды
    ldr r10,=main
    mov lr,pc
    bx r10
    заставят компилятор перейти к функции main,что в файле main.c,Те int main(void) тоже является меткой??

  2. sasha's Gravatar sasha
    31 января 2011 at 22:27 | Permalink

    А как Вы знаете,какие адреса для памяти нужно задавать в скрипте для компоновищика

    MEMORY
    {
    rom(rx) : ORIGIN = 0x00000000, LENGTH = 512k
    ram(rw) : ORIGIN = 0x40000000, LENGTH = 96k
    }

    Я вот работаю с arm966e-s, и в результате
    monitor flash write_image erase unlock ./main.hex 0×08000000 ihex
    получаю,что gdb при работе с openocd пишет,что на данном адресе нет flash

  3. sasha's Gravatar sasha
    4 февраля 2011 at 22:44 | Permalink

    В скрипте линковщика Вы отводите паямять под flash и под sram. На команду

    monitor flash write_image ./main.out

    получаю примерно то,что писал выше.Сектор .text записывается нормально. Но дело в том что по адресу 0х0400,куда я записываю .data, .bss (т.е. в SRAM), получаю сообщение об ошибке. Есть ли какие нибудь способы указать openocd.что данный сектор не flash, a sram?или указать это в скрипте линковщика

    Карта памяти чипа

  4. sasha's Gravatar sasha
    4 февраля 2011 at 23:02 | Permalink

    Четче
    Получается,что используются одна команда openocd — flash, для загрузки в sram и flash

  5. sasha's Gravatar sasha
    5 февраля 2011 at 12:01 | Permalink

    CPU: STR912FW44X

    Запуск openocd

    подключение к openocd через gdb. Стирание и снятие блокировки провожу раздельно,иначе,если это делать через write_image,все виснет. (Вопрос:разве должно стирание происходить так долго(13.6 с)? )

    ENTRY(_boot)

    STACK_SIZE = 0x400;

    MEMORY

    {

    rom(rx) : ORIGIN = 0x00000000, LENGTH = 512k

    ram(rw) : ORIGIN = 0x04000000, LENGTH = 32k

    }

    SECTIONS

    {

    . = 0x00000000;

    .text : {

    KEEP(*(.init))

    *(.text)

    *(.rodata)

    }>rom

    _etext = .;

    . = 0x04000000;

    .data : {

    _data = .;

    *(.data)

    }>ram

    _edata = .;

    .bss : {

    __bss_start = .;

    *(.bss)

    }>ram

    __bss_end__ = .;

    .stack : {

    . += STACK_SIZE;

    PROVIDE (_stack = .);

    }>ram

    _end = .;

    }

  6. sasha's Gravatar sasha
    7 февраля 2011 at 21:59 | Permalink

    Я решил проблему,используя load_image. flash write_image отказывается прошивать sram.

    Хмм,у меня такой вопрос:как быстро у вас работает jtag? операцию записи он проводит c 0,2 кб\с. Стирание 512K происходит за 13 сек. В чем дело?

  7. sasha's Gravatar sasha
    13 февраля 2011 at 15:12 | Permalink

    Команда load в gdb загружает в отладчик(gdb) исполняемый файл чтобы тот мог с ним работать,устанавливать точки останова ,но не в процессор. Каким образом вы,уже работая в Eclipse, прошиваете чип?ведь судя по скринам с Eclipse,приведёнными вами выше, с конфигурацией openocd и gdb в среде, происходит только их настройка,а команд для прошивки (flash write_image или в моем случае это load_image) я что-то не увидел.

  8. sasha's Gravatar sasha
    13 февраля 2011 at 17:45 | Permalink

    Вот смотрите. Как вы считаете,если я сконфигурирую Run в Commands таким образом

    monitor soft_reset_halt
    monitor load_image ./main.out 0
    monitor soft_reset_halt
    monitor arm7_9 dbgrq enable
    monitor gdb_breakpoint_override hard
    tbreak main
    continue

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

  9. sasha's Gravatar sasha
    6 марта 2011 at 19:00 | Permalink

    Подскажите,правильно ли я делаю:
    мне нужно разместить одну сишную функцию в sram, и я делаю так

    _data:
    ldr pc,_FMI_Config
    _FMI_Config: .word FMI_Config

    Вроде как FMI_Config должен разместиться вначале sram.

Leave a Reply

You must be logged in to post a comment.