Ассемблер С чего начать
🕛 17.09.2009, 12:22
Язык программирования Ассемблер- один из самых старых языков программирования. Если рассматривать его применительно к микроконтроллерам, то он является, по мнению достаточно большого количества роботолюбителей, самым приемлемым решением. Кратко его преимущества:1. Код сгенерированной программы (HEX или ему подобный файл) не больше и не меньше того, что вы запланировали. Это значит, что если вы написали 12 инструкций, то в памяти МК программа займет именно 12 слов (плюс небольшое место для таблицы прерываний, если они используются) или же 24 байта.
2. Полный контроль над <железом>. Вы всегда точно знаете, что делает программа, а в итоге, и микроконтроллер.
3. Инструкций не очень много, во всяком случае, часто используемых. Их можно запомнить, использовать шпаргалку или готовые шаблоны.
4. Имея небольшой опыт, писать программы на нем не сложнее, чем на других языках программирования.
5. Вы будете считаться в кругу знакомых <крутым хакером>.
Недостатки тоже есть, но пусть их перечисляют оппоненты.
С чего начать? Наверное с переменных и констант. МК фирмы ATMEL семейства AVR обычно имеют 32 регистра общего назначения. Это 8-битные регистры, поэтому в них можно хранить числа от 0 до 255. Если вы начали программировать на ассемблере, то должны привыкать к двоичным и шестнадцатеричным числам. Что это значит? Очень просто. У нас 8 разрядов (бит). Каждый может быть либо 0, либо 1. число 0 записывается как 00000000, 1- 0000001, 2- 0000010, и так далее до 256. Чтобы сократить количество цифр в записи, используется 16-ричная система счисления. 0= $00, 1=$01 2=$02 3=$03, а вот числа после 9 записываются по-другому: 10=$0A, 11=$0B, 12=$0C, 13=$0D, 14=$0E, 15=$0F. Теперь ещё раз- 10=00001010=$0A. Каждые четыре разряда шифруются одним символом от 0 до F, поэтому $FF=11111111, $F0=11110000, $0F=00001111. Да, ассемблер допускает использование десятичных цифр в программе, компилятор Ассемблера сам переведет десятичные цифры в двоичные, но лучше всё-таки вести запись программе на <машинном> языке, например, в шестнадцатеричном формате. В 8-битных микроконтроллерах регистры и ячейки памяти имеют максимум 8 разрядов (бывает меньше), поэтому каждому разряду соответствует свой бит. Например, если мы подали 1 на ножки микроконтроллера PB0 и PB3, а на остальные - 0, то при чтении информации из порта мы получим число 00001001=$09. Двоичные и шестнадцатеричные числа записывают так же, как и десятичные, старшие цифры пишут слева, младшие- справа. В десятичном формате каждый бит означает следующее: 128, 64, 32, 16, 8, 4, 2, 1, итого- 8 цифр, если их сложить, то получится именно 255.
Ну вот сейчас будет про переменные.
У микроконтроллера есть регистры ввода-вывода, к которым мы можем обращаться как R0 : R31. Это не совсем удобно как для начинающего программиста, так и для профессионала. Удобнее использовать мнемонические названия, или подстановки (определения). Например:
.DEF symbol=R27
.DEF led=R26
.DEF sost=R24
.DEF temp=R30
.DEF count1=R23
LDI temp,$FF
DEC temp
Эти определения обычно пишут в начале программы, хотя можно сделать это в любом месте, главное, не запутаться. После этого можно использовать в командах <переменные> вместо номеров регистров.
Как и зачем нужны константы.
Например, для управления роботом у вас есть плата с МК, и вы будете включать и выключать двигатели, подавая 1 и 0 на схему управления. Вы использовали 4 выхода одного регистра. Для того, чтобы уменьшить головную боль и заняться более насущными проблемами, вам проще давать команду на двигатели, просто записывая в регистр определенный байт. Например:
.EQU go_forward=$12 ; оба вперед 0001 0010
.EQU go_left=$08 ; левый назад 0000 1000
.EQU go_right=$04 ; правый назад 0000 0100
.EQU go_back=$0С ; оба назад 0000 1100
Мы при помощи макрокомады .EQU определили, что число $08 теперь можно писать go_left. Так это выглядит в программе:
; поворот влево
ldi temp, go_left ;загрузить в регистр константу
out PORTD, temp ;передать значение регистра в порт
Робот при этом должен начать двигаться так- левый двигатель будет крутить колесо назад, при этом правый останется на месте, и робот сделает поворот влево. Константы вы можете использовать сколько угодно и как угодно. Это достаточно удобная вещь. Константой может также быть определенный бит. Например:
.EQU bit0=3
.EQU bit1=2
.EQU bit2=6
sbi DDRA, bit0 ; установить бит№3 в порту А
sbi DDRA, bit1 ; установить бит№2 в порту А
сbi DDRA, bit2 ; сбросить бит№6 в порту А
Зачем все-таки, использовать подстановки? Можно ведь и запомнить! Да, в программе на 20 строк- можно. Но через пару дней программа вырастет до 60-и команд, а потом до 160. Вы будете больше времени тратить на разбор своего- же старого кода, чем на написание нового. А это уже никуда не годится:
Так вот, для начала необходимо изучить всего несколько команд, из которых вполне можно построить свою первую программу.
1. Команда загрузки константы в регистр. Это как операция присвоения go_left=8. В разных языках она пишется по-разному, на ассемблере так:
ldi R28, $08 ; загрузить константу $08 в регистр R28
Если мы будем использовать определения, то это может выглядеть так:
.DEF current=R28
.EQU basic-current=$08
ldi current, basic_current ; загрузить константу $08 в регистр R28
Вы можете определять константы каким-то определенным образом, например, c_basic_current, а регистры - r_current. Тогда будет всем понятно, где регистр, а где константа.
2. Команда сравнения значения регистра с константой.
cpi R30, $FF
или
cpi r_current, c_one_current
При этом процессор вычитает значение константы из содержимого регистра. Формируются различные флаги, но не о них сейчас речь. Для начала можно использовать флаги <равно> и <знак>. То есть, вы будете знать равны числа или нет, а если не равны, то какое из них больше (меньше).
3. Команды условного перехода. Они принадлежат к группе команд передачи управления.
cpi R30, $FF
breq label1
Пока что в статье не упоминались метки. Это адреса в памяти программ, по которым находятся какие-либо операции. В современных языках программирования высокого (или очень высокого) уровня метки практически не используются, хотя они есть. В Ассемблере эта система пока ещё сохранилась, поэтому привыкайте вместе с двоичным счислением к использованию меток. Пример:
ldi R26,$FF
loop73: dec R26
nop
nop
nop
cpi R26,$00
brne loop73
Это цикл. Переменной цикла является регистр R26. Вначале мы заносим в него число 255 ( $FF), потом уменьшаем на 1 и делаем 3 пустых операции. После сравниваем значение регистра с 0, если нет, переходим по метке loop73. Если взять два регистра, и <вложить> циклы один в другой, получится намного более длинный цикл.
Есть также команда безусловного перехода - rjmp метка. Используйте её, если хотите перейти по определенной метке вне зависимости от значений регистров.
4. Вывод в порт. Командами CBI и SBI можно сбросить или установить определённый бит в порту ввода-вывода, но для начала попробуйте использовать более простой метод- пересылку из регистра общего назначения в порт (правильно- регистр ввода-вывода). Пример был немного выше:
ldi temp, go_left ; загрузить в регистр константу
out PORTD, temp ; передать значение регистра в порт D
Для того, чтобы компилятор ассемблера правильно вас <понял>, необходимо подключать файл определений такой командой:
.include “2313def.inc”
где 2313def.inc- обычный текстовый файл, который поставляется в комплекте с ассемблерами и AVRStudio. Перепишите файл 2313def.inc (или другой, в зависимости от типа МК) в каталог с вашим исходным .ASM- файлом. Да, но формат команды такой, как в примере, не забудьте точку в начале макрокоманды и кавычки.