Программирование ARM-контроллеров STM32 на ядре Cortex-M3. Часть 2. Основы ассемблера, структура и синтаксис программы. Простейшая программа

  1. Часть 1. Установка MDK, создание проекта, основы Keil uVision
  2. Часть 2. Основы ассемблера, структура и синтаксис программы. Простейшая программа.
  3. Часть 3. Карта памяти контроллеров STM32, доступ к отдельным битам памяти.
  4. Часть 4. Регистры, старт и режимы работы контроллеров STM32.
  5. Часть 5. Как залить прошивку в контроллер.
  6. Часть 6. Настройка системы тактирования.
  7. Часть 7. Работа с портами ввода-вывода.

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

Что дальше? Дальше, собственно говоря, можно писать программу, используя набор команд thumb-2, поддерживаемый ядром Cortex-M3. Список и описание поддерживаемых команд можно посмотреть в документе под названием Cortex-M3 Generic User Guide (глава The Cortex-M3 Instruction Set), который можно найти на вкладке Books в менеджере проекта, в Keil uVision 5. Подробно о командах thumb-2 будет написано в одной из следующих частей этой статьи, а пока поговорим о программах для STM32 в общем.

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

Например, разбить программу на отдельные секции позволяет специальная директива — AREA. Она имеет следующий синтаксис: AREA Section_Name {,type} {, attr} …, где:

  1. Section_name — имя секции.
  2. type — тип секции. Для секции, содержащей данные нужно указывать тип DATA, а для секции, содержащей команды — тип CODE.
  3. attr — дополнительные атрибуты. Например, атрибуты readonly или readwrite указывают в какой памяти должна размещаться секция, атрибут align=0..31 указывает каким образом секция должна быть выровнена в памяти, атрибут noinit используется для выделения областей памяти, которые не нужно инициализировать или инициализирующиеся нулями (при использовании этого атрибута можно не указывать тип секции, поскольку он может использоваться только для секций данных).

Директива EQU наверняка всем хорошо знакома, поскольку встречается в любом ассемблере и предназначена для присвоения символьных имён различным константам, ячейкам памяти и т.д. Она имеет следующий синтаксис: Name EQU number и сообщает компилятору, что все встречающиеся символьные обозначения Name нужно заменять на число number. Скажем, если в качестве number использовать адрес ячейки памяти, то в дальнейшем к этой ячейке можно будет обращаться не по адресу, а используя эквивалентное символьное обозначение (Name).

Директива GET filename вставляет в программу текст из файла с именем filename. Это аналог директивы include в ассемблере для AVR. Её можно использовать, например, для того, чтобы вынести в отдельный файл директивы присвоения символьных имён различным регистрам. То есть мы выносим все присвоения имён в отдельный файл, а потом, чтобы в программе можно было пользоваться этими символьными именами, просто включаем этот файл в нашу программу директивой GET.

Разумеется, кроме перечисленных выше есть ещё куча всяких разных директив, полный список которых можно найти в главе Directives Reference документа Assembler User Guide, который можно найти в Keil uVision 5 по следующему пути: вкладка Books менеджера проектов -> Tools User’s Guide -> Complete User’s Guide Selection -> Assembler User Guide.

Большинство команд, псевдокоманд и директив в программе имеют следующий синтаксис:

{label} SYMBOL {expr} {,expr} {,expr} {; комментарий}

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

SYMBOL — команда, псевдокоманда или директива. Команда, в отличии от метки, наоборот, должна иметь некоторый отступ от начала строки даже если перед ней нет метки.

{expr} {,expr} {,expr} — операнды (регистры, константы…)

; — разделитель. Весь текст в строке после этого разделителя воспринимается как комментарий.

Ну а теперь, как и обещал, простейшая программа:

	AREA START, CODE, READONLY
	dcd 0x20000400
	dcd Program_start
	ENTRY
Program_start
	b Program_start
	END

В этой программе у нас всего одна секция, которая называется START. Эта секция размещается во flash-памяти (поскольку для неё использован атрибут readonly).

Первые 4 байта этой секции содержат адрес вершины стека (в нашем случае 0x20000400), а вторые 4 байта — адрес точки входа (начало исполняемого кода). Далее следует сам код. В нашем простейшем примере исполняемый код состоит из одной единственной команды безусловного перехода на метку Program_start, то есть снова на выполнение этой же команды.

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

В данном случае у нас перемешаны данные и команды. Адрес вершины стека и адрес точки входа (данные) записаны с помощью директив dcd прямо в секции кода. Так писать конечно можно, но не очень красиво. Особенно, если мы будем описывать всю таблицу прерываний и исключений (которая получится достаточно длинной), а не только вектор сброса. Гораздо красивее не загромождать код лишними данными, а поместить таблицу векторов прерываний в отдельную секцию, а ещё лучше — в отдельный файл. Аналогично, в отдельной секции или даже файле можно разместить и инициализацию стека. Мы, для примера, разместим всё в отдельных секциях:

	AREA STACK, NOINIT, READWRITE
	SPACE 0x400       ; пропускаем 400 байт
Stack_top                 ; и ставим метку
 
	AREA RESET, DATA, READONLY
	dcd Stack_top     ; адрес метки Stack_top
	dcd Program_start ; адрес метки Program_start
 
	AREA PROGRAM, CODE, READONLY
	ENTRY             ; точка входа (начало исполняемого кода)
Program_start             ; метка начала программы
	b Program_start
 
	END

Ну вот, та же самая программа (которая по прежнему не делает нифига полезного), но теперь выглядит намного нагляднее. В scatter-файле для этой программы нужно указать в качестве First_Section_Name имя RESET, чтобы эта секция располагалась во flash-памяти первой.

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

  1. Часть 1. Установка MDK, создание проекта, основы Keil uVision
  2. Часть 2. Основы ассемблера, структура и синтаксис программы. Простейшая программа.
  3. Часть 3. Карта памяти контроллеров STM32, доступ к отдельным битам памяти.
  4. Часть 4. Регистры, старт и режимы работы контроллеров STM32.
  5. Часть 5. Как залить прошивку в контроллер.
  6. Часть 6. Настройка системы тактирования.
  7. Часть 7. Работа с портами ввода-вывода.

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