Дополнительные команды ассемблера
для микроконтроллеров семейства AVR
Заинтересовавшись микроконтроллерами семейства AVR фирмы Atmel, я стал искать компилятор языка Форт для них. В инете нашлось несколько таких программ, но в одних не нравился интерфейс, в других отсутствие документации или(и) ограничения на размер кода, отсутствие исходников и прочие болячки. Среди этих находок попался и продукт Брэда Экерта (Brad Eckert) Firmware Studio (http://www.tinyboot.com).
Отличный интерфейс, средства отладки, исходники, документация … всё как надо.
Автор поставил перед собой (и решил) сложную задачу: создать программную совместимость между микроконтроллерами различных фирм, архитектур и назначений - 8051, GoldFire и AVR. Он добился их совместимости на уровне Форта и теперь у разработчика развязаны руки в выборе целевого процессора для реализации своей задумки, можно не стесняясь использовать старые наработки.
Всё это хорошо, но мне пока туда не надо.
Взяв этот продукт на вооружение, я быстро убедился, что до полноценного использования Форта в микроконтроллерах я еще не дорос, и вернулся в привычный ассемблер фирменного пакета AVR Studio. Тоже хороший продукт, но:
-
на дух не переносит кириллицу (допускает только в комментариях)
-
столбовая запись программы, типичная для всех ассемблеров (очень длинные листинги получаются)
-
бесконечные метки, метки и метки для ссылок (очень напрягает их выдумывать неповторяющимися)
Я отметил эти недостатки (нюансы?) только потому, что в ассемблере Firmware Studio их нет (справедливости ради должен отметить, там есть свои нюансы, но не о них сейчас речь).
Ассемблер Firmware Studio лояльно относится к любым символам. В именах подпрограмм, регистров и пр. могут встречаться любые знаки, вплоть до препинания и стрелок. Команды можно записывать как в столбик, так и в строчку, а метки можно вообще не изобретать. Все это вкупе дало возможность писать программы на ассемблере, почти как на Си, «с чувством, с толком, с расстановкой».
Вот так:
code Деление ( делимое делитель -- остаток целое делитель) c( деление 8/8)
\ r1 r0 r2 r1 r0
\ вход: r1 - делимое; r0 - делитель
\ выход: r2 - остаток; r1 - целое; r0 - делитель
\ измена: Раб
sub r2,r2 \ очистить остаток и перенос
ldi Раб,9
begin
rol r1
dec Раб if_z ret then
rol r2
sub r2,r0 if_c add r2,r0 clc else sec then
again
c;
Вызывается эта подпрограмма так:
Rcall Деление
Или вот еще пример:
code Main \ основной цикл
begin
wdr
if_b КнопкиСчитаны
clr_b КнопкиСчитаны \ принято к исполнению
mov rab,knp
swap knp \ свежие кнопки kA' kB' kS' ( 0=нажатие)
eor rab,knp andi rab,7 \ выделение измененных кнопок
if_nz
if_b rabS
if_nb kS' rcall IndStatus else rcall IndPower then
else
mov rab,knp swap rab lsr rab \ раб=00SB A0S'B'
eor rab,knp andi rab,1 \ раб=0000 000(B' eor A) 1-если разный
if_b kS'
if_nz \ +
inc Мощность
else \ -
dec Мощность
then
rcall IndPower
\ расчет задержек
cli
rcall РасчетЗадержек
sei
ldi[ rab int1 ] out gimsk,rab \ разрешить прерывание1
else
if_nz
inc Режим
else
dec Режим
then
mov rab,Режим andi rab,3 mov Режим,rab
rcall IndStatus
then
then
then
then
sleep
again c;
Эти куски кода «выдраны» из рабочих проектов и приведены только для иллюстрации структурированности кода и его читабельности. Сразу видно, какие части кода работают в зависимости от различных условий. Возможность записать несколько команд в одной строке, позволяет выделять кванты действия, т.е. неделимую последовательность команд, преследующую единую цель. Всё это облегчает написание и отладку программ и это притом, что результирующий код остается столь же компактным, что и в классическом ассемблере.
Результат работы форт-ассемблера Брэда Экерта
|
Листинг классического ассемблера
|
0007E Деление: SUB R2,R2
|
div: sub drem8u,drem8u ;очистить остаток и перенос
|
00080 LDI R16,9
|
Ldi dcnt8u,9 ;инициализировать счетчик цикла
|
00082 ROL R1
|
D8u_1: rol dd8u ;делимое/результат сдвинуть влево
|
00084 DEC R16
|
dec dcnt8u ;умньшить на единицу счетчик цикла
|
00086 BRNE 0x8A
|
brne d8u_2 ;переход, если не ноль
|
00088 RET
|
ret ;выход из подпрограммы
|
0008A ROL R2
|
d8u_2: rol drem8u ;остаток сдвинуть влево
|
0008C SUB R2,R0
|
sub drem8u,dv8u ;остаток= остаток – делитель
|
0008E BRCC 0x96
|
brcc d8u_3 ;если результат < 0
|
00090 ADD R2,R0
|
add drem8u,dv8u ;восстановить остаток
|
00092 CLC
|
clc ;сбросить перенос
|
00094 RJMP 0x98
|
rjmp d8u_1 ;иначе
|
00096 SEC
|
d8u_3: sec ;установить перенос
|
00098 RJMP 0x82
|
rjmp d8u_1 ;вернуться назад
|
Близнецы, не правда ли?
Брэд Экерт в своем форт-ассемблере, разумеется, описал все команды процессоров семейства AVR, но, кроме того, за что ему отдельное спасибо, он написал еще целый ряд команд, облегчающий жизнь программиста.
Например, для меня всегда были проблемы с запоминанием маловразумительных мнемоник типа BRCC, BRCS, BRNE, SBRC и т.п., я в них путался (и путаюсь), приходилось постоянно обращаться к справочнику команд по ассемблеру. Брэд Экерт позволил мне писать вместо них команды IF_NC, IF_C, IF_NZ и плюс к этому любимое ELSE. Так же хорошим подспорьем явилось наличие циклов FOR … NEXT, BEGIN … AGAIN, MULTI ... REPEAT и оператора выбора CASE.
Автор понапридумывал еще много команд, с ними можно познакомиться в его документации.
Заразившись таким законотворчеством, я осмелился создать и свои команды, в основном для облегчения работы с битами, вот их списочек:
IF_B IF_NB SKIP_B SKIP_NB T>BIT BIT>T SBR[ CBR[ LDI[ ORI[ ANDI[ SET_B CLR_B WHILE_B WHILE_NB UNTIL_B UNTIL_NB
А теперь приступаю к тому ради чего, собственно, и пишется этот документ - к подробному описанию моих нововведений.
Особенности описания битов
Прежде чем приступить к работе с битами их нужно описать, т.е. к имени бита прицепить номер бита в байте и определить сам байт (это может быть регистр или порт).
Например:
20 register: Флаги \ теперь к 20 регистру можно обращаться по имени ФЛАГИ
/bit/ \ перейдем в режим описания битов
BitsIn Флаги \ укажем, что описываем биты в 20 регистре (Флаги)
asmbyte Бит0 \ теперь к нулевому биту 20 регистра можно обращаться по имени БИТ0
asmbyte Бит1
asmbyte Бит2
asmbyte Бит3
asmbyte +/- \ можно задать и такое имя
asmbyte Плюс
->>- \ это пропуск бита
asmbyte Бит7
/bit/ \ вернемся в обычный режим
В итоге с именем БИТ0 будет ассоциировано число 20.0 (здесь условно вставлена точка, чтобы визуально отделить адрес байта от номера бита), а с именем ПЛЮС – число 20.5.
Слово REGISTER: берет со стека число и связывает его именем, новое слово (константа) размещается в словаре REGISRERS.
Слово /BIT/ переводит компилятор в режим описания битов и обратно (смена словарей).
Слово BitsIn по имени определяет адрес байта и обнуляет счетчик.
Слово ASMBYTE занимается энумерацией, т.е. связывает значение счетчика с именем, что стоит после него и увеличивает на 1 счетчик.
Слово ->>- служит для пропуска неиспользуемых битов и просто инкрементирует счетчик.
Для описания битов в портах используется та же технология, только сами порты описываются через ASMLABEL, при этом новое слово размещается в словаре ASMLABELS.
1c asmlabel EECR
12 asmlabel PORTD
11 asmlabel DDRD
10 asmlabel PIND
/bit/
BitsIn EECR \ порт управления EPROM
asmbyte EERE \ разрешение чтения из EPROM
asmbyte EEWE \ разрешение записи в EPROM
asmbyte EEMWE \ подтверждение разрешения записи в EPROM
asmbyte EERIE \ разрешение прерывания от EPROM
BitsIn portD \ выходы
->>-
->>-
->>-
->>-
asmbyte Test \ Тестовый выход
asmbyte Light0
BitsIn ddrD \ управление направлением
asmbyte SD
asmbyte CD
->>-
->>-
->>-
asmbyte Light
BitsIn PinD \ входа
->>-
->>-
asmbyte EncA
asmbyte EncB
->>-
->>-
asmbyte Enter \ кнопка Enter -\_/-
/bit/
Таким образом, с каждым битом связано некоторое число, в котором присутствует адрес байта и номер бита. Адреса портов начинаются с $20 могут принимать значения до $3F. Регистры от $00 до $1F включительно.
Теперь, когда биты обозваны, можно попытаться их использовать.
Следует отметить, что некоторые битовые команда могут работать только с регистрами r16-r32, а порты вообще ограничены диапазоном $00($20)-$1F($3F) в смысле прямого доступа к битам (это ограничение инструкций процессора ( не виноватая я)).
IF_B
Команда IF_B используется в выражениях типа:
If_b бит0 \ если бит 0 в регистре 20 равен 1
… \ тогда выполнить этот код
…
then
или
if_b бит0 \ если бит 0 в регистре 20 равен 1
… \ тогда выполнить этот код
…
else \ в противном случае сделать это
…
…
then
То есть, обычная конструкция. Так как в имени бита заключен адрес байта, компилятор может определить, о чём идет речь, о регистре или о порте, и сгенерить правильную команду типа SBRC или SBIC. После этой команды компилируется безусловный переход на часть ELSE или на первую команду за словом THEN.
-
Форт-ассемблер
|
Скомпилированный код
|
If_b бит0
|
00016 SBRS R20,0
|
Nop
|
00018 RJMP 0x20
|
Nop
|
0001A NOP
|
Else
|
0001C NOP
|
Nop
|
0001E RJMP 0x24
|
Nop
|
00020 NOP
|
Then
|
00022 NOP
|
Следующая команда
|
00024 следующая команда
|
IF_NB
Эта команда антипод IF_B, то есть первая часть конструкции выполняется при равенстве бита 0, а вторая при 1.
SKIP_B
Эта команда заставляет микроконтроллер пропустить следующую команду, если бит (в порту или регистре)
равен 1. (Недоделанный IF_B, но иногда требуется).
Пример:
Skip_b тест
Rcall Расчет \ расчет выполнятся не будет, если установлен бит ТЕСТ
Удобно то, что не надо помнить, где этот чертов бит находится.
-
Форт-ассемблер
|
Скомпилированный код
|
skip_b test
|
0002A SBIS PORTD,4
|
rcall Расчет
|
0002C RCALL РАСЧЕТ
|
SKIP_NB
Эта команда заставляет микроконтроллер пропустить следующую команду, если бит (в порту или регистре)
равен 0. (Недоделанный IF_NB, но иногда требуется). Пример почти тот же, что и для SKIP_B.
T>BIT
Копирует бит Т из регистра состояния в указанный бит.
Пример:
t>bit Плюс \ бит Т переписывается в бит 5 регистра 20
t>bit Test \ бит Т переписывается в бит 4 порта portD
В случае если бит находиться в регистре то это - вариация команды BLD Rd,b только регистр указывать не надо, так как он определен при описании бита.
-
Форт-ассемблер
|
Скомпилированный код
|
t>bit плюс
|
0002E BLD R20,5
|
Если же речь идет о порте, то ситуация усложняется, так как у процессора нет команд подобных BLD, BST для работы с портами. В этом случае будут сгенерированы 3 команды (см. таблицу). Сначала бит сбрасывается, а потом, в зависимости от бита Т либо ставиться, либо нет.
-
Форт-ассемблер
|
Скомпилированный код
|
t>bit Test
|
00036 CBI PORTD,4
|
|
00038 BRTC 0x3C
|
|
0003A SBI PORTD,4
|
|
0003C следующая команда
|
BIT>T
Копирует указанный бит из регистра или порта в бит Т регистра состояния.
Пример:
bit>t Плюс \ бит 5 регистра 20 переписывается в бит Т
В данном случае - это вариация команды BSD Rd,b.
-
Форт-ассемблер
|
Скомпилированный код
|
bit>t плюс
|
00030 BST R20,5
|
Для портов имеем:
-
Форт-ассемблер
|
Скомпилированный код
|
bit>t Test
|
0003C CLT
|
|
0003E SBIC PORTD,4
|
|
00040 SET
|
|
0003C следующая команда
|
SBR[
Устанавливает поименованные биты в указанном регистре (другие биты не трогает).
Пример:
16 register: РАБ \ указываем, что 16 регистр у нас будет называться РАБ (рабочий)
sbr[ раб бит0 test плюс ] \ в регистре 16 будут установлены в 1 биты: 0, 5 и 6
Эта команда из полного адреса бита (адрес_байта.номер_бита) берет только номер бита, так, что ей без разницы, где конкретный бит находится. После sbr[ должно следовать имя регистра (16-32), биты которого вы хотите установить, а потом, через пробелы, в любом порядке имена битов. В конце надо поставить закрывающую квадратную скобку ].
-
Форт-ассемблер
|
Скомпилированный код
|
sbr[ раб бит0 test плюс ]
|
00032 ORI R16,49
|
CBR[
Эта команда того же плана, что и SBR[, только биты она не устанавливает, а сбрасывает.
-
Форт-ассемблер
|
Скомпилированный код
|
cbr[ раб бит0 test плюс ]
|
00034 ANDI R16,206
|
LDI[
Загрузка битов в регистр (16-32). Названные биты установит в 1, остальные сбросит в 0.
Работает аналогично SBR[.
Данная команда удобна для конфигурирования микроконтроллера, например:
ldi[ раб toie1 ticie toie0 ] out timsk,раб \ разрешить прерывания от таймеров
Обратите внимание, биты порта TIMSK напрямую не доступны (адрес $39), следовательно, и полный адрес им прописывать нет смысла (хотя и возможно), поэтому описываются они так:
7 asmlabel TOIE1
6 asmlabel OCIE1A
3 asmlabel TICIE
1 asmlabel TOIE0
Т.е. как константы, хранящие только номер бита.
-
Форт-ассемблер
|
Скомпилированный код
|
ldi[ раб toie1 ticie toie0 ]
|
00036 LDI R16,138
|
ORI[
Логическое групповое ИЛИ, синоним SBR[.
ANDI[
Логическое групповое И.
-
Форт-ассемблер
|
Скомпилированный код
|
andi[ раб toie1 ticie toie0 ]
|
00038 ANDI R16,138
|
Можно загрузить в регистр (16-32) какой-либо порт и с помощью этой команды выделить нужный бит или биты.
SET_B
Устанавливает бит в регистре (16-32) или порту ($00-$1F).
-
Форт-ассемблер
|
Скомпилированный код
|
set_b плюс
|
0003A ORI R20,32
|
set_b test
|
0003C SBI PORTD,4
| CLR_B
Сбрасывает бит в регистре (16-32) или порту ($00-$1F).
-
Форт-ассемблер
|
Скомпилированный код
|
clr_b плюс
|
0003E ANDI R20,223
|
clr_b test
|
00040 CBI PORTD,4
|
WHILE_B
Выполнять цикл пока бит установлен.
-
Форт-ассемблер
|
Скомпилированный код
|
begin
|
0000C NOP
|
nop
|
0000E NOP
|
nop
|
00010 SBRS R20,0
|
while_b бит0
|
00012 RJMP 0x1A
|
nop
|
00014 NOP
|
nop
|
00016 NOP
|
repeat
|
00018 RJMP 0xC
|
Следующая команда
|
0001A следующая команда
|
WHILE_NB
Выполнять цикл пока бит сброшен.
-
Форт-ассемблер
|
Скомпилированный код
|
begin
|
0000C NOP
|
nop
|
0000E NOP
|
nop
|
00010 SBRS R20,0
|
while_nb бит0
|
00012 RJMP 0x1A
|
nop
|
00014 NOP
|
nop
|
00016 NOP
|
repeat
|
00018 RJMP 0xC
|
Следующая команда
|
0001A следующая команда
|
UNTIL_B
Выполнять цикл до тех пор, пока бит не будет установлен (ждать установки бита).
-
Форт-ассемблер
|
Скомпилированный код
|
begin
|
0000C NOP
|
nop
|
0000E NOP
|
nop
|
00010 SBRS R20,0
|
until_b бит0
|
00012 RJMP 0xC
|
Следующая команда
|
00014 следующая команда
|
UNTIL_NB
Ждать очистки бита.
-
Форт-ассемблер
|
Скомпилированный код
|
begin
|
0000C NOP
|
nop
|
0000E NOP
|
nop
|
00010 SBRC R20,0
|
until_nb бит0
|
00012 RJMP 0xC
|
Следующая команда
|
00014 следующая команда
|
Достарыңызбен бөлісу: |