learn_arm_cortex_m0_asm
Программирование микроконтроллера на языке ассемблера
Регистры процессора Arm Cortex M0 (M0+)
Регистр - это ячейка памяти находящаяся прямо внутри процессора. Это не оперативная память!
Регистр не имеет адреса, но у каждого регистра есть порядковый номер. В инструкциях для процессора содержатся закодированные номера регистров, к которым должен обратиться процессор для выполнения очередной инструкции.
![]() |
| Рис. 1 Регистры процессора Arm Cortex M0 (M0+). |
Процессор Arm Cortex M0 (M0+) имеет основной банк из 16 регистров. Регистры размером 32 бита каждый. Кроме того есть несколько специальных регистров.
Сейчас нас интересует основной банк регистров. Из 16 регистров основного банка 12 регистров являются регистрами общего назначения (регистры R0 - R12), которые программист может использовать по своему усмотрению. Регистры R13 - R15 имеют специальное фиксированное назначение.
Рассмотрим подробнее регистры основного банка.
Большинство инструкций процессора могут работать только с регистрами R0 - R7. Это связано с тем номер регистра в этих инструкциях кодируется 3мя битами, в 3 бита момещается максимальный номер 7.
Регистр R13 - это указатель стека. Что такое стек мы рассмотрим в следующем уроке.
Регистр R14 - это Link регистр, который хранит в себе адрес возврата из функции. Зачем это нужно мы тоже узнаем на следующем уроке.
Регистр R15 - это регистр счетчика инструкций. Он хранит в себе адрес следующей инструкции, которая будет исполнена процессором.
Программирование на языке ассемблера
Любая программа для микроконтроллера в конечном итоге превращается в список инструкций для процессора.
Такой список инструкций можно получить скомпилировав программу, написанную на языке Си.
В результате компиляции получается бинарный файл, содержащий машинный код, понятный процессору. В машинном коде закодированы те самые инструкции для процессора.
Программу в виде инструкций для процессора можно написать и вручную. Это делается на языке ассемблера. Этот язык еще вполне читается человеком, но при этом из него легко получить машинный код, понятный процессору.
Для каждой инструкции на языке ассемблера есть соотвествующия инструкция в виде машинного кода.
После написания программы на языке ассемблера нужно запустить специальную утилиту - ассемблер, которая из текстового файла с программой на языке ассемблера создаст бинарный файл с машинными кодом.
Утилита ассемблер намного проще компилятора, она по сути заменяет каждую инструцию на языке ассемблера соотвествующим машинным кодом.
Процессор Arm Cortex M0 (M0+) имеет архитектуру ARMv6-M и работает с набором инструкций, который называется Thumb. В этом наборе есть как 16-и битные инструкции так и 32х битные инструкции.
![]() |
| Рис. 2 Набор инструкций процессора Arm Cortex M0 (M0+). |
В общем виде инструкции на языке ассемблера записываются так:
|
Где mnemonic - это сокращение обозначающее инструкцию, operand1 и operand2 - это операнды, которых может быть один, два или больше.
В программе много инструкций, они записываются друг за другом. Процессор также их будет выполнять друг за другом.
|
Кроме того в программе могут присутствовать метки (labels), которые указывают на адрес инструкции после метки. Они нужны для записи циклов, переходов и другого.
Например, в данной записи
|
метка label указывает на инструкцию следующую сразу за ней. Эти метки можно использовать вместо операндов, например может быть так:
|
В данном случае в качестве операнда для второй инструкции подставляется метка label, содержащая адрес первой инструкции.
Простейшая программа на языке ассемблера для нашего микроконтроллера выглядит примерно так:
|
Пока не будем вникать во все строки этой программы. Сейчас нас интересуют только две последние строчки.
В самой последней строке программы записана инструкция b loop.
Если сравнить последнюю строчку с общим видом записи инструкций,
|
то становится понятно, что b - это mnemonic, а loop это operand1.
Инструкция b - это инструкция безусловного перехода (b - мнемоника для англ. слова branch). Это значит что процессор должен перейти на указанный адрес в первом операнде и выполнить инструкцию, лежащую по этому адресу.
![]() |
| Общий вид инструкции b. |
В данном случае метка loop указывает на адрес той же инструкции b loop.
Это означает, что процессор будет циклически выполнять одну и ту же инструкцию. То есть процессор будет работать в бесконечном пустом цикле.
По сути эта программа состоит из одной ассемблерной инструкции.
Рассмотрим программу, состоящую из нескольких ассемблерных инструкций. При этом опустим первые строки программы, потому что они будут точно такими же.
|
Эта программа складывает два числа: 5 + 7. После этого попадает в бесконечный цикл.
Здесь применены новые для нас инструкции movs, adds.
![]() |
| Общий вид инструкции movs с кодированием числа внутри инструкции. |
![]() |
| Общий вид инструкции adds. |
Инструкция movs r0, #5 записывает в регистр r0 число 5. При этом стоит отметить, что число 5 закодировано в самой инструкции.
Инструкция movs r1, #7 аналогично предыдущей инструкции записывает в регистр r1 число 7.
Инструкция adds r0, r0, r1 Складывает R0 + R1 и записывает результат в R0.
Рассмотрим еще несколько простых программ на языке ассемблера.
|
Эта программа выполняет вычитание чисел: 20 - 3.
Новая инструкция subs, вот её описание:
![]() |
| Общий вид инструкции subs. |
Рассмотрим программу чуть посложнее.
|
Эта программа решает такое задание: если r0 == r1, записать в r2 число 1, иначе 0.
Новые инструкции: cmp, beq.
Вот их описание:
![]() |
| Общий вид инструкции cmp. |
|
|
|
|
| Общий вид инструкции beq. |
Инструкция cmp сравнивает два операнда и если они равны, то в специальном регистре выставляется флаг z.
Инструкция beq проверяет, есть ли в специальном регистре флаг z. Если флаг z есть, то процессор переходит на инструкцию указанную в операнде. В данном случае процессор переходит на адрес инструкции, записанный в метке equal.
Еще похожая программа, но с инструкцией bne, a не beq.
|
Эта программа увеличивает r0 от 0 до 10 в цикле.
Инструкция bne смотрит на флаг z, и если он не выставлен, то заставляет процессор перейти по адресу инструкции, указанному в операнде.
![]() |
| Суффикс для инструкции b. b + ne = bne |
Получается, что пока число в регистре R0 не достигло 10 процессор возвращается на адрес инструкции, записанному в метке count_loop.
Задание 1
Напишите программу на языке ассемблера, которая будет проверять равно ли число, записанное в регистр R0, числу 10 и результат записывать в регистр R1.
Задание 2
Напишите программу на языке ассемблера, которая будет считать от 5 до 0 в цикле.
Задание 3
Посчитать сумму чисел от 1 до 5, используя цикл.
Задание 4
Напишите программу, которая вычисляет 3 * 4 через сложение.
Задание 5
В r0 и r1 записать числа. Найти максимальное из этих чисел с помощью инструкций и записать результат в r2.









