Заинтересовавшись микроконтроллерами семейства avr фирмы Atmel, я


с. 1
Дополнительные команды ассемблера
для микроконтроллеров семейства AVR

Заинтересовавшись микроконтроллерами семейства AVR фирмы Atmel, я стал искать компилятор языка Форт для них. В инете нашлось несколько таких программ, но в одних не нравился интерфейс, в других отсутствие документации или(и) ограничения на размер кода, отсутствие исходников и прочие болячки. Среди этих находок попался и продукт Брэда Экерта (Brad Eckert) Firmware Studio (http://www.tinyboot.com).

Отличный интерфейс, средства отладки, исходники, документация … всё как надо.

Автор поставил перед собой (и решил) сложную задачу: создать программную совместимость между микроконтроллерами различных фирм, архитектур и назначений - 8051, GoldFire и AVR. Он добился их совместимости на уровне Форта и теперь у разработчика развязаны руки в выборе целевого процессора для реализации своей задумки, можно не стесняясь использовать старые наработки.

Всё это хорошо, но мне пока туда не надо.

Взяв этот продукт на вооружение, я быстро убедился, что до полноценного использования Форта в микроконтроллерах я еще не дорос, и вернулся в привычный ассемблер фирменного пакета AVR Studio. Тоже хороший продукт, но:


  1. на дух не переносит кириллицу (допускает только в комментариях)

  2. столбовая запись программы, типичная для всех ассемблеров (очень длинные листинги получаются)

  3. бесконечные метки, метки и метки для ссылок (очень напрягает их выдумывать неповторяющимися)

Я отметил эти недостатки (нюансы?) только потому, что в ассемблере 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 следующая команда






с. 1

скачать файл