STM32F030. Минимальная программа

Будем считать, что  binutils установлен. Теперь не мешало бы убедиться, что даже с таким минимальным набором инструментов мы можем что-то творить.

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

Сейчас, для нашей не столь великой цели, нам потребуется Только один дополнительный файл — файл для Линковщика.

Система Cortex действительно сложная. Собирать проекты по умолчанию (как это делается, например, для AVR) тут в принципе тоже можно. Но когда сам управляешь процессом сборки, то получаешь совершенно «другой коленкор»!

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

Давайте теперь прикоснемся к великому и ужасному Cortex-у и научимся направлять его силу (чуть не сказал — дурь!) в нужном нам направлении. Давайте создадим для наших упражнений директорий и поместим в него два файла.

Первый файл — это исходник, файл нашей программы на языке ассемблера. Я его назвал start-0.S. Второй файл linker.ld — срипт для Линковщика.

Исходник start-0.S:

/* Самый простой файл для старта Cortex-M0 */

/* Инструкции Thumb-2 поддерживаются только в режиме синтаксиса unified */
.syntax unified

/* Таблица векторов обработчиков прерываний */
.section ".vector_table"
.long  __Initial_Stack_Value       /* Вершина стека */
.long  Reset_Handler               /* Обработчик Reset */
.long  NMI_Handler                 /* Обработчик NMI */
.long  HardFault_Handler           /* Обработчик Hard Fault */
.long  MemManage_Handler           /* Обработчик MPU Fault */
.long  BusFault_Handler            /* Обработчик Bus Fault */
.long  UsageFault_Handler          /* Обработчик Usage Fault */
.long  0                           /* Не используется */
.long  0                           /* Не используется */
.long  0                           /* Не используется */
.long  0                           /* Не используется */
.long  SVC_Handler                 /* Обработчик SVCall Handler               */
.long  DebugMon_Handler            /* Обработчик Debug Monitor Handler        */
.long  0                           /* Не используется */
.long  PendSV_Handler              /* Обработчик PendSV*/
.long  SysTick_Handler             /* Обработчик SysTick */

/* Секция программного кода */
.section ".text"

@-----------------------------------------------------------------------------
/* Отсюда начнет выполняться наша программа после сброса */
/* Объявим функцию как thumb_func. Иначе линковка не будет успешной */
.thumb_func
/* Чтобы Линковщик увидел символ Reset_Handler мы должны объявить его как global */
.global Reset_Handler

Reset_Handler:
B       .    /* Тупо зациклимся на месте */

@-----------------------------------------------------------------------------
/* Обработчики прерываний */
/* Сделаем как делают ленивые парни -- направим все прерывания на один обаботчик */
/* Объявляем все функции как thumb_func */
.thumb_func
NMI_Handler:

.thumb_func
HardFault_Handler:

.thumb_func
MemManage_Handler:

.thumb_func
BusFault_Handler:

.thumb_func
UsageFault_Handler:

.thumb_func
SVC_Handler:

.thumb_func
DebugMon_Handler:

.thumb_func
PendSV_Handler:

.thumb_func
SysTick_Handler:
B    .        /* и тоже тупо замкнем его в бесконечном цикле */

Скрипт для Линковщика linker.ld:

/* Зададим выходной формат поумолчанию elf32-littlearm */
OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm")

/* Определим целевую архитектуру */
OUTPUT_ARCH(arm)

/* Зададим точку входа */
ENTRY(Reset_Handler)

/* Теперь займемся распределением памяти */
SECTIONS
{
  /* Флешь в STM32F030 начинается с адреса 0x08000000 */
  .flash 0x08000000 :
  {
    /* Скажем Линковщику собрать таблицу обработчиков векторов прерываний на этот адрес */
    KEEP(*(.vector_table));

    /* Затем укажем Линковщику собрать все секции исполняемого кода в одну кучку */
    *(.text .text.*);

    /* Затем -- собрать все константы в другую кучку */
    *(.rodata .rodata.*);
  }

  /* Оперативная память в STM32F030 начинается с адреса 0x20000000. */
  /* Отсюда и начнем размечать память для размещения переменных */

  /* Сначала должна разместиться секция инициализированных данных. */
  /****************************************************************************************
  Понятно, что начальные значения (значения для инициализации) должны где-то
  храниться, когда питание микроконтроллера выключено. Они и храняться во флешь-пямяти.
  Но флешь-память -- постоянная память, а нам для размещения переменых нужна опреативная
  память. Вот, в оперативной память мы и разместим переменные, а проинициализируем их
  значениями из флешь-памяти. Хитрожопо, но по другому никак!
  *****************************************************************************************/
  .data_at_ram 0x20000000: AT (LOADADDR(.flash) + SIZEOF(.flash))
  {
    *(.data .data.*);
  }

  .ram :
  {
    /* Теперь скажем Линковщику собрать в этом месте все неинициализированные глобальные и
    статические переменные */
    *(.bss .bss.*);
  }

  /*
  Теперь установим указатель стека на конец оперативной памяти.
  TOS = top of stack -- вершина стека.
  BOS = bottom of stack -- дно стека.
  В момент инициализации вершина стека совпадает с его дном.
  Конец оперативной памяти зависит от конкретного типа микроконтроллера.
  Например, у меня SRM32F030F4, который имеет 4 кБ оперативы.
  */
  __Initial_Stack_Value = 0x20000000 + (4 * 1024); /* Нехитрые вычисления дают верный результат*/
}

Можете их тупо закопипастить отсюда.

Процесс сборки нашей программы будет состоять из двух шагов. Сначала мы отассемблируем исходный текст программы, в результате чего получим объектный модуль. Затем мы произведем линковку этого модуля с… ничем. Проект наш крошечный, состоит из одного файла, библиотек не использует — поэтому линковать его не с чем. Линковку производим только потому, чтобы получить из объектника файл формата .elf. У объектника адреса переменных, адреса функций, адреса переходов — непределены, а в .elf-е — все уже намертво привязано к своим адресам.

Ассемблирование производим командой

$ arm-none-eabi-as -mcpu=cortex-m0 -o start-0.o start-0.S

Специфика Линукса заключается в том, чтобы не орать (не засорять эфир) лишний раз, когда в этом нет необходимости. А необходимости нет только в одном случае — когда все получилось. Вот когда что-то не срослось, то нужно поднимать шум. И это правильно! А если все хорошо и получилось  именно то, что планировали — в чем прикол?

— Папа, я покакала! (с) из к/ф «О чем говорят мужчины» и из мой жизни тоже.

Поэтому, если после ассемблирования будет тишина, то паниковать не надо. Все под контролем. Наслаждайтесь умиротворяющей тишиной и спокойствием.

После ассемблирования давайте произведем линковку:

$ arm-none-eabi-ld -T linked.ld -o start-0.elf start-0.o

И в этом случае, если все прошло  так, как задумывалось, тоже будет тишина. Но если посмотреть в директорий, то можно заметить, что появились два файла — с суфиксами .o и .elf.

Теперь давайте посмотрим на размеры программы:

$ arm-none-eabi-size start-0.elf
text       data        bss        dec        hex    filename
68          0          0         68         44    start-0.elf

Целых 68 байт для программы, которая ничего не делает. Но зато откомпилировалась без ошибок. Круто! Я надеюсь у вас тоже, как и у меня, — без ошибок. Круто три раза!

Через какое-то время мы начнем наращивать мясо. Но сейчас немного поиграемся с инструментами binutils.

Давайте дизассемблируем нашу прогу обратно, и сравним с исходником:

$ arm-none-eabi-objdump -S start-0.elf

У меня получилась следующая хрень:

start-0.elf:     формат файла elf32-littlearm

Дизассемблирование раздела .flash:

08000000 :
 8000000:	20001000 	.word	0x20001000
 8000004:	08000041 	.word	0x08000041
 8000008:	08000043 	.word	0x08000043
 800000c:	08000043 	.word	0x08000043
 8000010:	08000043 	.word	0x08000043
 8000014:	08000043 	.word	0x08000043
 8000018:	08000043 	.word	0x08000043
	...
 800002c:	08000043 	.word	0x08000043
 8000030:	08000043 	.word	0x08000043
 8000034:	00000000 	.word	0x00000000
 8000038:	08000043 	.word	0x08000043
 800003c:	08000043 	.word	0x08000043

08000040 :
 8000040:	e7fe      	b.n	8000040

08000042 :
 8000042:	e7fe      	b.n	8000042

В начале листинга идет таблица векторов. Обратите внимание на нечетный адрес переходов. Например, обработчик располагается по адресу 8000042, а в таблице указан адрес перехода на него как 08000043.

Это не глюк. Это так и должно быть. Дело в том, что в Cortex-ах принято соглашение, что если адрес перехода нечетный, то это будут команды из набора Thumb-2.

После таблицы мы видим две подпрограммы — два одинаковых обработчика, которые размещаются на адресах 08000040 и 08000042. Смешная команда (имя команды состоит из одной буквы — b) — команда перехода. Это так незатейливо организуется бесконечный цикл.

И еще прошу обратить внимание на размер команды перехода — команда двухбайтовая. А это дает посулы, что размеры программ для Cortex-ов не будут значительно больше аналогичных программ для AVR и MSP430. А теперь сравните у них цены и размеры флешь памяти, и станет ясно направление — куда катится мир.

Теперь давайте попробуем вытащить на свет божий адреса функций и переменных. (Адреса, куда они отлинковались)

$ arm-none-eabi-nm start-0.elf

У меня получился вот такой расклад:

08000042 t BusFault_Handler
08000042 t DebugMon_Handler
08000042 t HardFault_Handler
20001000 A __Initial_Stack_Value
08000042 t MemManage_Handler
08000042 t NMI_Handler
08000042 t PendSV_Handler
08000040 T Reset_Handler
08000042 t SVC_Handler
08000042 t SysTick_Handler
08000042 t UsageFault_Handler

Сейчас, разумеется, смотреть особо не на что — программа-то никакая! Задача этого этапа (этой программы) состоит в том, чтобы проверить работоспособность нашего инструментария и немного потренироваться на сборке программ.

Следующим этапом нам предстоит получить файлы описаний регистров и битов для микроконтроллера (STM32F030) и попытаться написать программу моргания светодиодом.

Реклама

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

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s