8-900-374-94-44
[email protected]
Slide Image
Меню

Ассемблер команды avr – avr 8- RISC AVR

AVR. Учебный курс. Макроассемблер | Электроника для всех

Перед изучением системы команд микроконтроллера надо бы разобраться в инструментарии. Плох тот плотник который не знает свой топор. Основным инструментом у нас будет компилятор. У компилятора есть свой язык — макроассемблер, с помощью которого жизнь программиста упрощается в разы. Ведь гораздо проще писать и оперировать в голове командами типа MOV Counter,Default_Count вместо MOV R17,R16 и помнить что у нас R17 значит Counter, а R16 это Default_Count. Все подстановки с человеческого языка на машинный, а также многое другое делается средствами препроцессора компилятора. Его мы сейчас и рассмотрим.

Комментарии в тексте программы начинаются либо знаком «;«, либо двойными слешами «//«, а еще AVR Studio поддерживает Cишную нотацию комментариев, где коменты ограничены «колючей проволокой» /* коммент */.

Оператор .include позволяет подключать в тело твоей программы кусок кода из другого текстового файла. Что позволяет разбить большую исходник на кучу мелких, чтобы не загромождать и не мотать туда сюда огромную портянку кода. Считай куда ты воткнул

.include туда и вставился кусок кода из другого файла. Если надо подключать не весь файл, а только его часть, то тебе поможет директива .exit дойдя до которой компилятор выйдет из файла.

Оператор .def позволяет привязать к любому слову любое значение из ресурсов контроллера — порт или регистр. Например сделал я счетчик, а считаемое значение находится в регистре R0, а в качестве регистра-помойки для промежуточных данных я заюзал R16. Чтобы не запутаться и помнить, что в каком регистре у меня задумано я присваиваю им через .def символические имена.

1
2
.def 	schetchik = R0
.def 	pomoika = R16

.def schetchik = R0 .def pomoika = R16

И теперь в коде могу смело использовать вместо официального имени R0 неофицальную кличку schetchik
Одному и тому же регистру можно давать кучу имен одновременно и на все он будет честно откликаться.

Также есть оператор .undef после которого компилятор напрочь забывает, что данной переменной что либо соответствовало. Иногда бывает удобно. Когда одно и то же символическое имя хочется присвоить разным ресурсам.

Оператор .equ это присвоение выражения или константы какой либо символической метке.
Например, у меня есть константа которая часто используется. Можно, конечно, каждый раз писать ее в коде, но вдруг окажется, что константа выбрана неверно, а значит придется весь код шерстить и везде править, а если где-нибудь забудешь, то получишь такую махровую багу, что задолбаешься потом ее вылавливать. Так что нафиг, все константы писать надо через
.equ! Кроме того, можно же присвоить не константу, а целое выражение. Которое при компиляции посчитается препроцессором, а в код пойдет уже исходное значение. Надо только учитывать, что деление тут исключительно целочисленное. С отбрасыванием дробной части, без какого-либо округления, а значит 1/2 = 0, а 5/2 = 2

1
2
3
.equ 	Time = 5
.equ 	Acсelerate = 4
.equ 	Half_Speed = (Accelerate*Time)/2

.equ Time = 5 .equ Acсelerate = 4 .equ Half_Speed = (Accelerate*Time)/2

Директивы сегментации. Как я уже рассказывал в посте про архитектуру контроллера AVR память контроллера разбита на независимые сегменты — данные (ОЗУ), код (FLASH), EEPROM

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

.CSEG сегмент кода, он же флеш. После этой директивы идет тело программы, комманды процессора. Тут же можно засунуть какие нибудь данные которые не меняются, например таблицу с заранее посчитаными значениями, статичный текст или таблицу символов для знакогенератора.

В сегменте кода уместны директивы:
Адресная метка. Любое слово, не содержащее пробелов и не начинающееся с цифры, главное, чтобы после него стояло двоеточие.

1
2
3
.CSEG
label:   LDI      R16,'A'
         RJMP   label

.CSEG label: LDI R16,'A' RJMP label

В итоге, после компиляции вместо label в код подставится адрес команды перед которой стоит эта самая метка, в данном случае адрес команды LDI R16,’A’

Адресными метками можно адресовать не только код, но и данные, записанные в любом сегменте памяти. Об этом чуть ниже.

.ORG address означет примерно следующее «копать отсюда и до обеда», т.е. до конца памяти. Данный оператор указывает с какого адреса пойдет собственно программа. Обычно используется для создания таблицы прерываний.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
	.CSEG
	.ORG 0x0000		
	RJMP Start  ;перепрыгиваем таблицу векторов.
 
	.ORG INT0addr 	; External Interrupt0 Vector Address
	RJMP INT0_expection
 
	.ORG INT1addr 	; External Interrupt1 Vector Address
	RETI
 
	.ORG OC2addr 	; Output Compare2 Interrupt Vector Address
	RJMP PWM_1
 
	.ORG OVF2addr 	; Overflow2 Interrupt Vector Address
	RETI
 
	.ORG ICP1addr 	;Input Capture1 Interrupt Vector Address
	RETI
 
	.ORG 0х0032 		; Начало основной программы
 
Start: 	LDI R16,0x54 	; и понеслась

.CSEG .ORG 0x0000 RJMP Start ;перепрыгиваем таблицу векторов. .ORG INT0addr ; External Interrupt0 Vector Address RJMP INT0_expection .ORG INT1addr ; External Interrupt1 Vector Address RETI .ORG OC2addr ; Output Compare2 Interrupt Vector Address RJMP PWM_1 .ORG OVF2addr ; Overflow2 Interrupt Vector Address RETI .ORG ICP1addr ;Input Capture1 Interrupt Vector Address RETI .ORG 0х0032 ; Начало основной программы Start: LDI R16,0x54 ; и понеслась

Статичные данные пихаются в флеш посредством операторов

.db массив байтов.
.dw массив слов — два байта.
.dd массив двойных слов — четыре байта
.dq массив четверных слов — восем байт.

1
2
3
Constant:	.db  	10     ; или 0хAh в шестнадцатеричном коде 
Message:	.db  	"Привет лунатикам"
Words: 		.dw	10, 11, 12

Constant: .db 10 ; или 0хAh в шестнадцатеричном коде Message: .db "Привет лунатикам" Words: .dw 10, 11, 12

В итоге, во флеше вначале будет лежать число 0А, затем побайтно будут хекскоды символов фразы «привет лунатикам», а дальше

000A, 000B, 000С.
Последнии числа, хоть сами и невелики, но занимают по два байта каждое, так как обьявлены как .dw.

.DSEG сегмент данных, оперативка. Те самые жалкие считанные байты. Сюда не зазорно пихать перменные, делать тут буффера, тут же находится стек.

Тут действует оператор .BYTE позволяющий указать на расположение данных в памяти.

1
2
var1: 		.BYTE 1
table: 		.BYTE 10

var1: .BYTE 1 table: .BYTE 10

В первом случае мы указали переменную var1 состоящую из одного байта.
Во втором случае у нас есть цепочка из 10 байт и переменная table указывающая на первый байт из цепочки. Адрес остальных вычисляется смещением.
Указывать размеры перменных нужно для того, чтобы компилятор их правильно адресовал и они не налезали друг на друга.

.EESEG сегмент EEPROM, энергонезависимая память. Можно писать, можно считывать, а при пропаже питания данные не повреждаются.
Тут действуют те же директивы что и в flash — db, dw, dd, dq

.

MACRO — оператор макроподстановки. Вот уж реально чумовая вещь. Позволяет присваивать имена целым кускам кода, мало того, еще параметры задавать можно.

1
2
3
4
.MACRO SUBI16		; Start macro definition 
        subi @1,low(@0)	; Subtract low byte 
        sbci @2,high(@0)	; Subtract high byte 
.ENDM                       	; End macro definition

.MACRO SUBI16 ; Start macro definition subi @1,low(@0) ; Subtract low byte sbci @2,high(@0) ; Subtract high byte .ENDM ; End macro definition

@0, @1, @2 это параметры макроса, они нумеруются тупо по порядку. А при вызове подставляются в код.

Вызов выглядит как обычная команда:

После имени через запятую передаются параметры, которые подставятся в код.
Макросы позволяют насоздавать себе удобных команд на все случаи жизни, по сути создать свой язык. Но надо помнить, что каждый макрос это тупо кусок кода, поэтому если макрос получается большой, то его лучше оформить в виде процедуры или функции — будет резкая экономия места в памяти, но выполняться будет чуток медленней.

Макроассемблер это мощнейшая штука. По ходу пьесы я буду вводить разные макросы и показывать примеры работы макроопределений.

easyelectronics.ru

Основные ассемблерные команды микроконтроллеров AVR

Для изучения азов программирования микроконтроллеров AVR на ассемблере AVR Studio необходимо понимать значения ассемблерных мнемоник. В новейших микроконтроллерах AVR семейства MEGA доступно около двух сотен операций, и почти каждая из команд выполняется микроконтроллером за один такт, за исключение команд ветвления и аппаратного умножения.

Наиболее часто употребимые ассемблерные команды, которые необходимо знать для понимания чужого кода и тем более для написания своего.

 

Регистры ввода/вывода:

CBI - сброс бита порта

SBI - установка лог. "1" бита порта

IN -загрузка значения из порта в регистр

OUT - загрузка значения в регистр

SBIC - пропуск следующей команды, если бит порта сброшен

SBIS -  пропуск следующей команды, если бит порта установлен

 

Ветвление:

CALL -абсолютный вызов

RCALL - относительный вызов

RET - возврат из подпрограммы

RETI - возврат из прерывания, флаг разрешения прерываний I "жестко" устанавливается

JMP - абсолютный переход

RJMP - относительный переход

BRBC - переход, если бит регистра SREG сброшен

BRBS - переход, если бит регистра SREG установлен

SBRC - пропустить следующую команду, если бит регистра сброшен

SBRS - пропустить следующую команду, если бит регистра установлен

 

Работа со стеком:

PUSH - сохранить регистр в стеке

POP - извлечение регистра из стека

 

Важные команды:

NOP - команда, которая ничего не делает

CLI - запретить прерывания

SEI - разрешить прерывания

CP - сравнить значения двух регистров общего назначения

CPI - сравнить значение регистра с константой

LDI - загрузить константу в регистр общего назначения

CLR - очистить регистр общего назначения

 

Примеры применения данных команд:


ldi      R16, 0b00001001 ;   загрузка константы в регистр общего назначения (R16 - R32)
out    PORTD, R16         ;   запись значения регистра в порт D

in       R25, PORTB          ; считать значения Port B в регистр R25
cpi     R25, 4                  ; сравнить считанное значение с константой =4
breq   exit                      ; переход на метку если было равно
...
exit:                              ; метка
nop                               ; пустой такт

sbi        PORTD, PD4       ; записать в 4-й бит порта D лог. "1"
rcall      my_delay            ; вызов подпрограммы задержки
cbi        PORTD, PD4       ; сброс 4-го бита порта D
...
my_delay:                      ; подпрограмма задержки (4 такта)
nop                            ; холостой такт
nop
nop
nop
nop
ret                               ; возвращение из подпрограммы (3 такта)
; вызов и выполнение подпрограммы my_delay займёт 4+ 5+3 = 12 тактов

 

В великолепной книге Джона Мортона по программированию микроконтроллеров AVR на ассемблере можно найти описание всех команд и множество примеров.

 

 

radioded.ru

Как понять ассемблер для AVR / Habr

Всем добрый вечер! Веду свою трансляцию из уютного мира, который называется «ассемблер». Сразу поясню что тема касается микроконтроллеров AVR — и я пока ещё не знаю, пригодится ли этот пост тем, кто хочет использовать ассемблер для любой другой задачи. Дело в том, что я буквально несколько дней назад начал учить ассемблер с нуля — нужно сделать одно устройство — и я решил сделать в нём всё самостоятельно. Так вот — в один прекрасный день понял, что учить ассемблер абсолютно бесполезно! Ассемблер можно только понять! То есть всем тем, кто хочет программировать на ассемблере я настоятельно рекомендую детально вникнуть в то, каким образом ФИЗИЧЕСКИ работает микроконтроллер, а затем уже изучать тонкости команд.
Так вот, я пожалуй начну небольшой цикл статей, в которых буду с самого начала рассказывать как именно я понял те или иные вещи в программировании на ассемблере — думаю для тех, кто вообще не понимает что такое асм я буду как раз таким «переводчиком» с языка тех, кто в этом деле очень хорошо шарит.

Сразу скажу, что я более-менее вкурил эту тему с подачи DIHALT — поэтому эти статейки будут являться неким переводом с супер-пупер-ассемблерно-микроконтроллерного языка на язык понятный большинству людей. Ну а гуру надеюсь будут меня поправлять по ходу пьесы и если вдруг я что то объясню неправильно — то они поправят меня.
Итак первые выводы об ассемблере, которые я сделал пару дней назад, меня потрясли до глубины души — и я просидел за статьями DI HALT'а с 11 вечера до 5 утра — после чего лёг спать довольным и счастливым. Я понял суть программирования на ассемблере для микроконтроллеров.
Как же это объяснить ещё проще? Думаю нужно начать с самой сути.
***
Изначально не будем вдаваться в технические подробности (о них мы поговорим в следующей статье) — просто представьте, что есть 3 персонажа:
1. Микроконтроллер - это англичанин Стив, который приехал к русскому. Он идеально знает английский язык, но по-русски он вообще не понимает — ни единого слова. Только английский. Он проиграл в споре и обязался делать бесприкословно всё то, о чём его попросит русский.
2. Ассемблер - это переводчик Вася у которого мама англичанка а папа русский. Он знает идеально и английский и русский язык.
3.Мы -это русский, к которому приехал англичанин. Ну то есть мы это мы=) При этом мы идеально знаем русский язык и (!!!) чуть-чуть английский — самую малость, со словариком.
***
Представьте такую ситуацию — англичанин сидит у Вас в комнате на стуле. А Вы сидите себе за компом и читаете этот пост, как вдруг у Вас внезапно открылась форточка! Вот ведь незадача! Ветер дует, занавеска превратилась в парус… Было бы неплохо закрыть! Но вот ведь как лень вставать со стула, снимать ноги с системника, запихивать их в тапочки, отставлять кружку с кофе(пивом) и идти бороться со стихией. И тут Вы внезапно осознаёте, что у нас то в комнате есть проспоривший англичанин, которого самое время погонять! И вы ему так мило говорите «Дружище! Закрой форточку пожалуйста, а потом можешь опять присесть на стул!» а он сидит, смотрит на вас с недоумением и ничего не делает! Можно конечно по щам надавать — но он же тогда всё равно вас не поймёт! Тогда Вы звоните своему другу-переводчику Василию — он приходит, и садится рядом с англичанином на стул. И вы говорите — Переведи: «Стив, пойди и закрой форточку, а потом обратно сядь на стул!» Переводчик переводит на английский — англичанин понимает и идёт закрывает форточку, а затем приходит и садится на стул.
В этом моменте нужно просто понять роль ассемблера в этой цепочке «Мы-Ассемблер-Контроллер»
То есть как бы что такое ассемблер все поняли? Тогда читаем дальше.
***

Так вот, представляем такую ситуацию. Васе говоришь — «Слушай, ну короче такое дело — я калькулятор дома забыл, раздели 56983 на 2 и скажи Стиву, чтобы он столько раз отжался на кулаках» и Вася на калькуляторе считает и говорит Стиву по-английски " Отожмись на кулаках 28491 раз" Это называется «ДИРЕКТИВА» — другими словами директива это задание для Васи, результат выполнения которой это действие Стива.

Есть другая ситуация — Вы говорите Васе «Скажи Стиву, чтобы он отжался 28491 раз» и Вася просто переводит Ваши слова на английский. Это называется ОПЕРАТОР

Всё просто — есть директива и есть оператор. Оператор — это Ваше прямое указание что делать Стиву — Вася тут только переводит Ваше требование на инглиш. А Директива — это задание для самого Васи — и Вася сначала делает то, что Вы ему сказали, а потом уже в зависимости от результата говорит Стиву что-либо.

Теперь мы будем мучать англичанина регулярно! Но предварительно нужно получше познакомиться с нашим переводчиком Васей. Нужно знать следующее — Вася всегда Вас слушается беспрекословно — что ему сказали, то он и делает. Васин калькулятор не имеет десятичных знаков — если вы глянете пример с отжиманиями то 56983 \ 2 = 28491.5 — но у Васи всё после запятой обрубается — и он видит только целое число — причём неважно там будет 28491.000001 или там будет 28491.9999999 — для Васи это один фиг будет 28491 в обоих случаях. Ничего не округляется. Ещё важная информация про Васю. Вася жесток — ему пофиг на то, что Стив затрахается отжиматься двадцать восемь тысяч раз. Ему сказали — Вася перевёл. Причём не только перевёл — но и заставил сделать то, что Вы попросили. Так что если Стив помрёт на двадцать три тысячи пятьсот тринадцатом отжимании — то это будет исключительно Ваша вина.

Собственно это пока что всё. В следующем посте будем копать глубже — пока же просто достаточно понять это. Просто представить эту ситуацию и понять что к чему, кто исполняет какую роль и чем директива отличается от оператора.
А дальше мы постараемся называть всё своими именами и примерно прикинуть как же ассемблер работает с микроконтроллером по взрослому.

habr.com

Расширенный ассемблер (C--) для AVR

Никакой язык программирования не сравнится а ассемблером по возможности писать самый компактный и быстрый код. На сегодняшний день, каким бы продвинутым не был компилятор, и какие бы хитрые оптимизации он не творил с кодом, результат всё равно не будет идеален. А иногда он будет совсем сильно не идеален. По крайней мере, это точно свойственно компилятору AVR GCC.

Почему ассемблер для AVR - это хорошо

Не смотря на все утверждения о том, что современные компиляторы научились отлично оптимизировать код лучше человека, это не так. По кр.мере, применительно к AVR GCC. Практика показывает, что создаваемый им код может быть ощутимо улучшен по размеру (и, соответственно, скорости выполнения). Взять, например, обработчики прерываний, которые должны сохранять все используемые регистры на старте и восстанавливать их при завершении. Так вот тут AVR GCC часто использует в обработчике регистров больше, чем надо (т.е., использует несколько разных регистров там, где можно было бы использовать один и тот же регистр повторно). И, соответственно, имеем лишнюю работу по сохранению/восстановлению из стека (это при том, что операции со стеком занимают по два машинных цикла). Оптимизация обработчиков прерываний особенно важна если они вызываются десятки тысяч раз в секунду, тогда даже устранение одной лишней пары PUSH/POP даст ощутимую экономию ресурсов CPU.

Другой пример - функции, работающие с десятком (примерно) переменных и аргументов. GCC может свободно использовать большую часть верхних регистров (r18 - r31, подробнее см. тут) в коде Си-функции не заботясь о их сохранении. Если же этих регистров ему немного не хватило, компилятор вызывает довольно “жирные” подпрограммы __prologue_saves__ и __epilogue_restores__ при входе в функцию и выходе из неё. Функции эти сохраняют на входе стеке и восстанавливают на выходе все нижние регистры (все, без разбора). Это также даёт ощутимый оверхед. При том, что часто этой же функции, переписанной руками, будет достаточно доступных регистров, и сохранять вообще ничего не придётся.

Третий пример - не все команды ассемблера имеют свои аналоги в Си. Например, проход по битам байтовой переменно на Си выльется в монструозный цикл, внутри которого вычисляется битовая маска и делается логическое И по этой маске. Тогда как на ассемблере такой код может быть гораздо проще сделан с использованием команды ROR. Аналогичные моменты возникают при работе с массивами и строками, когда ассемблерный код может быть более лаконичен.

Итого, на ассемблере имеет смысл почти всегда писать обработчики прерываний и наиболее требовательные к производительности функции. Также на нём можно писать совсем простые прошивки. И, да, если писать код целиком на ассемблере, то можно свободно использовать в нём дополнительные регистры r0-r17, которые GCC использует по своему усмотрению (и при написании ассемблерных процедур, вызываемых из Си-кода, программист должен заботиться о сохранении и восстановлении этих регистров).

Один наглядный пример

В качестве примера рассмотрим простейший обработчик прерывания, который инкрементирует uint16-переменную при переполнении таймера. С-код тривиален:


ISR(TIMER1_OVF_vect) {
	counter16++;
}

Сгерерированный компилятором ассемблерный код будет сложнее:


  ISR(TIMER1_OVF_vect) {
    3a0c:	1f 92       	push	r1
    3a0e:	0f 92       	push	r0
    3a10:	0f b6       	in	r0, 0x3f	; 63
    3a12:	0f 92       	push	r0
    3a14:	11 24       	eor	r1, r1
    3a16:	8f 93       	push	r24
    3a18:	9f 93       	push	r25
	counter16++;
    3a1a:	80 91 3e 04 	lds	r24, 0x043E	; 0x80043e 
    3a1e:	90 91 3f 04 	lds	r25, 0x043F	; 0x80043f 
    3a22:	01 96       	adiw	r24, 0x01	; 1
    3a24:	90 93 3f 04 	sts	0x043F, r25	; 0x80043f 
    3a28:	80 93 3e 04 	sts	0x043E, r24	; 0x80043e 
}
    3a2c:	9f 91       	pop	r25
    3a2e:	8f 91       	pop	r24
    3a30:	0f 90       	pop	r0
    3a32:	0f be       	out	0x3f, r0	; 63
    3a34:	0f 90       	pop	r0
    3a36:	1f 90       	pop	r1
    3a38:	18 95       	reti

Что тут происходит:

  • Зачем-то сохраняется регистр r1, который в обработчике не используется. Далее r1 зануляется
  • Зачем-то сохраняется регистр r0, а затем SREG
  • Выполняется инкремент счётчика
  • Сохранённые регистры восстанавливаются из стека, делается возврат из прерывания

Итого 19 команд и 46 байт. При том, что сохранять r0 и r1 тут нет никакой необходимости, обработчик можно было бы переписать так:


  push	r24
  push	r25
  in    r24, SREG
  push  r24
  lds   r24, counter16
  lds   r25, counter16+1
  adiw	r24, 1
  sts   counter16+1, r25
  sts   counter16, r24
  pop   r24
  out   SREG, r24
  pop   r25
  pop   r24
  reti

Итого получается 14 команд (при том, что мы избавились от 4х лишних push/pop команд, выполнение которых занимает по 2 такта). Если бы в обработчике не было команды adiw, и SREG бы не модифицировался, то можно было бы убрать ещё 4 инструкции. GCC же тут использует стандартный шаблон для обработчика прерываний, в котором сохраняет всё, что может быть изменено в Си-коде.

Немного фактов и цифр

В качестве примера того, почему ассемблер - это хорошо, могу привести Дисплейный модуль 128х128, на микроконтроллере ATMega328P, работающий на частоте 20 МГц. Модуль, вообщем, работает и не тормозит, но захотелось мне его переделать под ATMega8A на 16 МГц. Преимущества последней очевидны - она штатно умеет работать на максимальной частоте при питании от 3.3В (ATMega328P по даташиту при таком напряжении гарантированно будет работать только на 10 МГц), стоит в раза в 3-4 дешевле, да и достать её легче. Вообщем, захотелось мне сделать для ZX-магнитофона дисплейный модуль с питанием от 3.3В на ATMega8A, возможно, с немного урезанным функционалом, но с максимально компактной и быстрой прошивкой. Прошивка модуля на ATMega328P на тот момент имела размер в почти 20Кб (из которых ровно 7Кб занимали шрифты, 5х7 и 13х15). Шрифты были ужаты до 3.5 КБ путём оптимизации их формата. В результате под код осталось чуть более 4кб флеша (с учётом того, что надо ещё оставить место для bootloader-а). В итоге, мне удалось впихнуть всю прошивку в примерно 3Кб, сохранив основной функционал, а именно рисование точек и линий, рисование и заливка прямоугольников, окружностей и текста (шрифтами 5х7 и 13х15, латиница + кириллица + основные символы). При этом был удалён код работы с клавиатурой, пищалкой и подсветкой (только потому, что в случае ZX-магнитофона он не нужен), но оставшихся свободных полутора килобайт без проблем хватит на эти вещи. При этом производительность новой прошивки должна быть ощутимо выше, т.к., чем меньше кода, тем быстрее он будет выполняться.

Почему ассемблер для AVR ужасен

Цена эффективности ассемблера - его высокая сложность. Хотя, сама по себе система машинных команд МК достаточно проста. Но ассемблерные компиляторы до безобразия примитивные (по кр.мере, AVRA и GCC). Программа состоит из функций, а функции используют регистры для обработки данных. Всего доступно 32 8-битных регистра, которых обычно бывает более, чем достаточно, чтобы держать основные данные внутри функции. Но, при этом, нет никакой возможности присвоить этим регистрам какие-то осмысленные имена внутри функции. А человеческий мозг, как известно, может более-менее эффективно работать одновременно с 7±2 объектами. И, когда этих объектов (т.е., регистров) раза в два больше, то мозг взрывается, и читать код, пытаясь держать в уме таблицу соответствия между регистрами и их назначением, становится категорически невозможно. Приходится писать комментарии. А ещё сложнее такой код изменять.

Второе невыносимое неудобство - это передача аргументов функциям. Тут, аналогично, приходится держать в уме (или копипастить комментарии), что в каком регистре передаётся. Что так же убивает всякую читаемость кода.

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

Препроцессор для ассемблера

Осознав, что на чистом ассемблере писать что-то более-менее большое решительно невозможно, я решил сделать свой препроцессор, который будет расширять синтаксис языка и сохранять файл для компиляции. Препроцессор написан на яве и дружит с Atmel assembler/AVRA и AVR GCC.

Сначала я добавил возможность понятие процедуры, имеющей аргументы, локальные псевдонимы для регистров и локальные метки. Затем, сделал циклы и условные операции. Далее - возможность работать с группами регистров как единым целым (для организации переменных длиной более, чем 8 бит). По мере развития проекта, а исходном, когда-то ассемблерном коде, становилось все меньше и меньше ассемблера..

Препроцессор позволяет как писать код на чистом ассемблере, деля минимальные вставки, так и почти полностью перейти на Си-подобный синтаксис.

Синтаксис

Процедуры

Чтобы быстро продемонстрировать возможности языка, рассмотрим пример функции. Это функция рисования круга, которая использует всего 10 регистров - три - в качестве аргументов и семь - в качестве локальных переменных. Именно эта функция, будучи написанной на ассемблере, заставила меня начать писать препроцессор - её код был абсолютно нечитаем.


.proc drawCircle
   .args circle_x(r24), circle_y(r22), radius(r20)
   .use r19 as x0, r16 as y0
   .use r26 as ddF_x, r27 as ddF_y
   .use r30 as x, r31 as y, r23 as f

   x0 = circle_x
   y0 = circle_y
   f = 1 - radius
   ddF_x = 1
   ddF_y = radius
   x = 0
   y = radius
   ddF_y = -ddF_y
   ddF_y += ddF_y

   rcall drawPixel (x: circle_x,   y: circle_y+radius)
   rcall drawPixel (x: x0,   y: y0-y)
   rcall drawPixel (x: x0+y, y: y0)
   rcall drawPixel (x: x0-y, y: y0)

   loop {
      if s(x >= y) {
        ret
      }
      if (!f[7]) {
        y -= 1
        ddF_y += 2
        f += ddF_y
      }
      x++
      ddF_x += 2
      f += ddF_x

      rcall drawPixel(x: x0 + x, y: y0 + y)
      rcall drawPixel(x: x0 + x, y: y0 - y)
      rcall drawPixel(x: x0 - x, y: y0 - y)
      rcall drawPixel(x: x0 - x, y: y0 + y)

      rcall drawPixel(x: x0 + y, y: y0 + x)
      rcall drawPixel(x: x0 + y, y: y0 - x)
      rcall drawPixel(x: x0 - y, y: y0 + x)
      rcall drawPixel(x: x0 - y, y: y0 - x)
   }
.endproc

Сначала мы объявляем процедуру директивой .proc. Имя продедуры станет меткой в ассемблерном коде. Затем идёт объявление трёх аргументов процедуры директивой .args- координат и радиуса окружности. Далее, директивы .use определяют локальные для процедуры псевдонимы для регистров, весь дальнейший код бедет оперировать ими. Вместо ассемблерных команд mov, ldi и разной арифметики можно использовать простые математические выражения. Так же процедура может иметь свои локальные метки, начинающиеся с символа @, эти метки видны только внутри процедуры.

Процедура drawCircle вызывает другую процедуру drawPixel, принимающую два аргумента - координаты точки. Аргументы передаются через регистры. При вызове процедуры (например, командой rcall) или переходе на неё (например, командой rjmp) можно указать в скобках значения переменных в формате имя : значение. Переменные будут проинициализированы в том порядке, в котором они объявлены. При этом можно указать не все аргументы, а только нужные (если мы знаем, что другие регистры аргументов уже инициализированы правильно). Если аргумент у процедуры только один, его имя (вместе с двоеточием) можно опустить.

Если надо сослаться на функцию, которая не объявлена в текущем файле (актуально в AVR GCC), это можно сделать директивной .extern так:


.extern drawPixel (x: r24, y: r22)

Аналогичным образом можно объявить фцнкцию одной строкой (без директивы .args):


.proc drawCircle (circle_x: r24, circle_y: r22, radius: r20)
...

Циклы

Часто нужно организовать цикл со счётчиком от некоторой величины до нуля. Для этого служат директива loop. Выражение, выполняющиеся в цикле, заключается в фигурные скобки. Пример цикла:


delay_10ms:
  loop (r18 = 100) {
    rcall delay_100us
  }

В круглых скобках можно указать регистр, который бедет служить счётчиком цикла и (опционально) его стартовое значение (в данном примере это 100, именно столько раз будет выполнено содержимое блока). Если регистр не указан, то цикл будет выполняться бесконечно. Также в циклах могут использоваться команды break (передаёт управление на первую инструкцию, следующую за циклом) и continue (передаёт управление на начало цикла не уменьшая счётчика).

Условные операторы

Чтобы не держать в уме более 20 ассемблерных команд условных переходов, добавлена команды if и else. Её условием может быть

  • арифметическое сравнение (==, !=, <, >, <=, >=)
  • проверка одиночного бита регистра
  • проверка флага регистра SREG
  • проверка бита порта ввода-вывода

Условие должно быть простым (&& и || не поддерживаются). Операции могут быть одиночными и блочными. Следующие примеры демонстрируют возможности if:


if (!io[UCSRA].UDRE) goto UartSendChar		; wait for empty buffer

if (!r11[0]) ret

if (!F_ZERO) goto @return

if (r21 == 0xFE) goto send_command

if (r21.r22 < ZH.ZL) break

if (ZH.ZL.XH.XL >= r4.r3.r2.r1) {
  ZH.ZL.XH.XL -= r4.r3.r2.r1
  F_CARRY = 1
} else {
  F_CARRY = 0
}

Группы регистров

Кроме 32 8-битных регистров доступны регистровые пары X, Y и Z (их имена всегда пишутся заглавными буквами). Т.к. 8-битных регистров AVR не всегда достаточно, их можно объединять в группы точкой. Например так:


loop {
    if (r11.r12 < ZH.ZL) break
    r11.r12 -= ZH.ZL
  }

Группы могут использваться как в математических выражениях, так и в качестве аргументов процедур.

Переменные и константы

Регистры - это хорошо, но их на всё не хватит. Кроме регистров нужны переменные в памяти. Переменная может иметь один из следующих типов:

Имя Размер (байт) Описание
byte 1 Одно-байтовая переменная в ОЗУ
word 2 Двух-байтовая переменная в ОЗУ
dword 4 Четырёх-байтовая переменная в ОЗУ
pointer 2 Указатель на область ОЗУ, массив
ptr 2 Указатель на константный массив (строку) во флеш-памяти

Примеры объявления переменных:


.extern s_video_mem : ptr
.extern var_b : byte
.extern var_w : word
.extern var_dw : dword

txtErr:
	.DB "Error in parameter!"
	
Z = txtUartErr    ; поместить в регистровую пару Z смещение строки-константы

Массивы PRG, RAM и IO

Для доступа к ОЗУ, ПЗУ и портам ввода-вывода можно использовать квази-массивы с именами ram[], prg[] и io[] соответственно. Например команда


   r0 = io[PINA]

прочтёт и сохранит содержимое порта PINA в регистре r0

Доступ к портам возможен только по их именам, доступ к памяти возможен только по адресам-регистровым парам. Т.е., тут всё как в ассемблере, декрементировать и инкрементировать адрес до или после выполнения операции:


  r0 = ram[Z++] ; прочитать байт из ОЗУ с пост-декрементом указателя Z
  r1 = prg[--X] ; прочитать байт из флеш-памяти с пред-декрементом указателя X

Чтение/запсись из портов и памяти возможна только через регистр. Следующая конструкция прочтёт байт из памяти, адресованной регистровой парой Z во временный регистр r0, а затем передаст его в порт UART-а UDR:


  io[UDR] = r0 = ram[Z]

А вот пример кода, передающего 16 байт из буфера в памяти в порт UART:


  loop (r1 = 16) {
    if (!io[UCSRA].UDRE) continue	; ждём, пока буфер пуст
    io[UDR] = r0 = ram[Z++]
  }

Тут ещё раз стоит заметить, что continue передаст управление на начало блока цикла и счётчик r1 при этом не будет декрементирован.

А этот код прочтёт строку из памяти программ и передаст её побайтово функции рисования на дисплее:


loop (len) {
  rcall	writeToLCD (prg[Z++])
  rcall	delay40us
}

Флаги

Для доступа к флагам регистра SREG введены следующие квази-переменные: F_GLOBAL_INT, F_BIT_COPY, F_HALF_CARRY, F_SIGN, F_TCO, F_NEG, F_ZERO, F_CARRY Их можно использовать в качестве условий операций if - goto и присвоения.

Как этим пользоваться

Это описание очень поверхностное и навряд ли может дать полное понимание. Лучше ознакомиться с языком можно посмотрев исходники реальных проектов:

Первый проект: Частотомер-тестер кварцев
Исходник, переписанный на расширенный синтаксис
Оригинальный ассемблерный исходник

Тут я старался особо не трогать процедуры обработки прерываний, и оставил их на ассемблере, чтобы было проще считать такты. Остальной код переписан.

Второй пример: Контроллер клавиатуры 86РК Камиля Каримова
Исходники проекта закрыты, и прошивку пришлось (да простит меня автор, но по-другому никак не заточить прошивку под себя) декомпилировать.
Исходник, переписанный на расширенный синтаксис
Декомпилированный ассемблерный исходник

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


asm_ext = True

Тогда при сборке все ассемблерный файлы будут прогоняться через препроцессор avr-asm-ext. Для работы препроцессора в системе должна быть установлена Java.

Исходный код препроцессора можно найти на гитхабе. В данный момент проект полностью переписывается с нуля с целью получить вместо препроцессора полноценный компилятор со своей средой разработки (в данный момент это плагины для IDE NetBeans). Кроме AVR планируется добавить поддержку других архитектур, в частности, Z80.

Исходники препроцессора на гитхабе

trolsoft.ru

Лекция 3 Основы программирования на ассемблере avr

Компилятор транслирует исходные коды с языка ассемблера в объектный код. Полученный объектный код можно использовать в симуляторе ATMEL AVR Studio, либо в эмуляторе ATMEL AVR In-Circuit Emulator. Компилятор также генерирует код, который может быть непосредственно запрограммирован в микроконтроллеры AVR.

Компилятор генерирует код, который не требует линковки.

Компилятор работает под Microsoft Windows 3.11, Microsoft Windows95 и Microsoft Windows NT. Кроме этого есть консольная версия для MS-DOS.

Набор инструкций семейства микроконтроллеров AVR описан в данном документе кратко, для более полной информации по инструкциям обращайтесь к полному описанию инструкций и документации по конкретному микроконтроллеру.

Исходные коды

Компилятор работает с исходными файлами, содержащими инструкции, метки и директивы. Инструкции и директивы, как правило, имеют один или несколько операндов.

Строка кода не должна быть длиннее 120 символов.

Любая строка может начинаться с метки, которая является набором символов заканчивающимся двоеточием. Метки используются для указания места, в которое передаётся управление при переходах, а также для задания имён переменных.

Входная строка может иметь одну из четырёх форм:

[метка:] директива [операнды] [Комментарий][метка:] инструкция [операнды] [Комментарий]КомментарийПустая строка

Комментарий имеет следующую форму:

; [Текст]

Позиции в квадратных скобках необязательны. Текст после точки с запятой (;) и до конца строки игнорируется компилятором. Метки, инструкции и директивы более детально описываются ниже.

Примеры:

label:     .EQU var1=100 ; Устанавливает var1 равным 100 (Это директива)           .EQU var2=200 ; Устанавливает var2 равным 200

test:      rjmp test     ; Бесконечный цикл (Это инструкция)                         ; Строка с одним только комментарием

                         ; Ещё одна строка с комментарием

Компилятор не требует чтобы метки, директивы, комментарии или инструкции находились в определённой колонке строки.

Инструкции процессоров avr

Ниже приведен набор команд процессоров AVR, более детальное описание их можно найти в AVR Data Book.  

Арифметические и логические инструкции

Мнемоника

Операнды

Описание

Операция

Флаги

Циклы

ADD 

Rd,Rr 

Суммирование без переноса

Rd = Rd + Rr 

Z,C,N,V,H,S 

1

ADC

Rd,Rr

Суммирование с переносом

Rd = Rd + Rr + C

Z,C,N,V,H,S

1

SUB

Rd,Rr

Вычитание без переноса

Rd = Rd - Rr

Z,C,N,V,H,S

1

SUBI

Rd,K8

Вычитание константы

Rd = Rd - K8

Z,C,N,V,H,S

1

SBC

Rd,Rr

Вычитание с переносом

Rd = Rd - Rr - C

Z,C,N,V,H,S

1

SBCI

Rd,K8

Вычитание константы с переносом

Rd = Rd - K8 - C

Z,C,N,V,H,S

1

AND

Rd,Rr

Логическое И

Rd = Rd · Rr

Z,N,V,S 

1

ANDI

Rd,K8

Логическое И с константой

Rd = Rd · K8

Z,N,V,S

1

OR

Rd,Rr

Логическое ИЛИ

Rd = Rd V Rr

Z,N,V,S

1

ORI

Rd,K8

Логическое ИЛИ с константой

Rd = Rd V K8

Z,N,V,S

1

EOR

Rd,Rr

Логическое исключающее ИЛИ

Rd = Rd EOR Rr

Z,N,V,S

1

COM

Rd

Побитная Инверсия

Rd = $FF - Rd

Z,C,N,V,S

1

NEG

Rd

Изменение знака (Доп. код)

Rd = $00 - Rd

Z,C,N,V,H,S

1

SBR

Rd,K8

Установить бит (биты) в регистре

Rd = Rd V K8

Z,C,N,V,S

1

CBR

Rd,K8

Сбросить бит (биты) в регистре

Rd = Rd · ($FF - K8)

Z,C,N,V,S

1

INC

Rd

Инкрементировать значение регистра

Rd = Rd + 1

Z,N,V,S

1

DEC

Rd

Декрементировать значение регистра

Rd = Rd -1

Z,N,V,S

1

TST

Rd

Проверка на ноль либо отрицательность

Rd = Rd · Rd

Z,C,N,V,S

1

CLR

Rd

Очистить регистр

Rd = 0

Z,C,N,V,S

1

SER

Rd

Установить регистр

Rd = $FF

None

1

ADIW

Rdl,K6

Сложить константу и слово

Rdh:Rdl = Rdh:Rdl + K6 

Z,C,N,V,S

2

SBIW

Rdl,K6

Вычесть константу из слова

Rdh:Rdl = Rdh:Rdl - K 6

Z,C,N,V,S

2

MUL

Rd,Rr

Умножение чисел без знака

R1:R0 = Rd * Rr

Z,C

2

MULS

Rd,Rr

Умножение чисел со знаком

R1:R0 = Rd * Rr

Z,C

2

MULSU

Rd,Rr

Умножение числа со знаком с числом без знака

R1:R0 = Rd * Rr

Z,C

2

FMUL

Rd,Rr

Умножение дробных чисел без знака

R1:R0 = (Rd * Rr) << 1

Z,C

2

FMULS

Rd,Rr

Умножение дробных чисел со знаком

R1:R0 = (Rd *Rr) << 1

Z,C

2

FMULSU

Rd,Rr

Умножение дробного числа со знаком с числом без знака

R1:R0 = (Rd * Rr) << 1

Z,C

2

Инструкции ветвления

Мнемоника

Операнды

Описание

Операция

Флаги

Циклы

RJMP

k

Относительный переход

PC = PC + k +1

None

2

IJMP

Нет

Косвенный переход на (Z)

PC = Z

None

2

EIJMP

Нет

Расширенный косвенный переход на (Z)

STACK = PC+1, PC(15:0) = Z, PC(21:16) = EIND

None

2

JMP

k

Переход

PC = k

None

3

RCALL

k

Относительный вызов подпрограммы

STACK = PC+1, PC = PC + k + 1

None

3/4*

ICALL

Нет

Косвенный вызов (Z)

STACK = PC+1, PC = Z 

None

3/4*

EICALL

Нет

Расширенный косвенный вызов (Z)

STACK = PC+1, PC(15:0) = Z, PC(21:16) =EIND

None

4*

CALL

k

Вызов подпрограммы

STACK = PC+2, PC = k

None

4/5*

RET

Нет

Возврат из подпрограммы

PC = STACK

None

4/5*

RETI

Нет

Возврат из прерывания

PC = STACK

I

4/5*

CPSE

Rd,Rr

Сравнить, пропустить если равны 

if (Rd ==Rr) PC = PC 2 or 3

None

1/2/3

CP

Rd,Rr

Сравнить

Rd -Rr

Z,C,N,V,H,S

1

CPC

Rd,Rr

Сравнить с переносом

Rd - Rr - C

Z,C,N,V,H,S

1

CPI

Rd,K8

Сравнить с константой

Rd - K

Z,C,N,V,H,S

1

SBRC

Rr,b

Пропустить если бит в регистре очищен

if(Rr(b)==0) PC = PC + 2 or 3

None

1/2/3

SBRS

Rr,b

Пропустить если бит в регистре установлен

if(Rr(b)==1) PC = PC + 2 or 3

None

1/2/3

SBIC

P,b

Пропустить если бит в порту очищен

if(I/O(P,b)==0) PC = PC + 2 or 3

None

1/2/3

SBIS

P,b

Пропустить если бит в порту установлен

if(I/O(P,b)==1) PC = PC + 2 or 3

None

1/2/3

BRBC

s,k

Перейти если флаг в SREG очищен

if(SREG(s)==0) PC = PC + k + 1

None

1/2

BRBS

s,k

Перейти если флаг в SREG установлен

if(SREG(s)==1) PC = PC + k + 1

None

1/2

BREQ

k

Перейти если равно

if(Z==1) PC = PC + k + 1

None

1/2

BRNE

k

Перейти если не равно

if(Z==0) PC = PC + k + 1

None

1/2

BRCS

k

Перейти если перенос установлен

if(C==1) PC = PC + k + 1

None

1/2

BRCC

k

Перейти если перенос очищен

if(C==0) PC = PC + k + 1

None

1/2

BRSH

k

Перейти если равно или больше

if(C==0) PC = PC + k + 1

None

1/2

BRLO

k

Перейти если меньше

if(C==1) PC = PC + k + 1

None

1/2

BRMI

k

Перейти если минус

if(N==1) PC = PC + k + 1

None

1/2

BRPL

k

Перейти если плюс

if(N==0) PC = PC + k + 1

None

1/2

BRGE

k

Перейти если больше или равно (со знаком)

if(S==0) PC = PC + k + 1

None

1/2

BRLT

k

Перейти если меньше (со знаком)

if(S==1) PC = PC + k + 1

None

1/2

BRHS

k

Перейти если флаг внутреннего переноса установлен

if(H==1) PC = PC + k + 1

None

1/2

BRHC

k

Перейти если флаг внутреннего переноса очищен

if(H==0) PC = PC + k + 1

None

1/2

BRTS

k

Перейти если флаг T установлен

if(T==1) PC = PC + k + 1

None

1/2

BRTC

k

Перейти если флаг T очищен

if(T==0) PC = PC + k + 1

None

1/2

BRVS

k

Перейти если флаг переполнения установлен

if(V==1) PC = PC + k + 1

None

1/2

BRVC

k

Перейти если флаг переполнения очищен

if(V==0) PC = PC + k + 1

None

1/2

BRIE

k

Перейти если прерывания разрешены

if(I==1) PC = PC + k + 1

None

1/2

BRID

k

Перейти если прерывания запрещены

if(I==0) PC = PC + k + 1

None

1/2

* Для операций доступа к данным количество циклов указано при условии доступа к внутренней памяти данных, и не корректно при работе с внешним ОЗУ. Для инструкций CALL, ICALL, EICALL, RCALL, RET и RETI, необходимо добавить три цикла плюс по два цикла для каждого ожидания в контроллерах с PC меньшим 16 бит (128KB памяти программ). Для устройств с памятью программ свыше 128KB , добавьте пять циклов плюс по три цикла на каждое ожидание.

Инструкции передачи данных

Мнемоника

Операнды

Описание

Операция

Флаги

Циклы

MOV

Rd,Rr

Скопировать регистр

Rd = Rr

None

1

MOVW

Rd,Rr

Скопировать пару регистров

Rd+1:Rd = Rr+1:Rr, r,d even

None

1

LDI

Rd,K8

Загрузить константу

Rd = K

None

1

LDS

Rd,k

Прямая загрузка

Rd = (k)

None

2*

LD

Rd,X

Косвенная загрузка

Rd = (X)

None

2*

LD

Rd,X+

Косвенная загрузка с пост-инкрементом

Rd = (X), X=X+1

None

2*

LD

Rd,-X

Косвенная загрузка с пре-декрементом

X=X-1, Rd = (X)

None

2*

LD

Rd,Y

Косвенная загрузка

Rd = (Y)

None

2*

LD

Rd,Y+

Косвенная загрузка с пост-инкрементом

Rd = (Y), Y=Y+1

None

2*

LD

Rd,-Y

Косвенная загрузка с пре-декрементом

Y=Y-1, Rd = (Y)

None

2*

LDD

Rd,Y+q

Косвенная загрузка с замещением

Rd = (Y+q)

None

2*

LD

Rd,Z

Косвенная загрузка

Rd = (Z)

None

2*

LD

Rd,Z+

Косвенная загрузка с пост-инкрементом

Rd = (Z), Z=Z+1

None

2*

LD

Rd,-Z

Косвенная загрузка с пре-декрементом

Z=Z-1, Rd = (Z)

None

2*

LDD

Rd,Z+q

Косвенная загрузка с замещением

Rd = (Z+q)

None

2*

STS

k,Rr

Прямое сохранение

(k) = Rr

None

2*

ST

X,Rr

Косвенное сохранение

(X) = Rr

None

2*

ST

X+,Rr

Косвенное сохранение с пост-инкрементом

(X) = Rr, X=X+1

None

2*

ST

-X,Rr

Косвенное сохранение с пре-декрементом

X=X-1, (X)=Rr

None

2*

ST

Y,Rr

Косвенное сохранение

(Y) = Rr

None

2*

ST

Y+,Rr

Косвенное сохранение с пост-инкрементом

(Y) = Rr, Y=Y+1

None

2

ST

-Y,Rr

Косвенное сохранение с пре-декрементом

Y=Y-1, (Y) = Rr

None

2

ST

Y+q,Rr

Косвенное сохранение с замещением

(Y+q) = Rr

None

2

ST

Z,Rr

Косвенное сохранение

(Z) = Rr

None

2

ST

Z+,Rr

Косвенное сохранение с пост-инкрементом

(Z) = Rr, Z=Z+1

None

2

ST

-Z,Rr

Косвенное сохранение с пре-декрементом

Z=Z-1, (Z) = Rr

None

2

ST

Z+q,Rr

Косвенное сохранение с замещением

(Z+q) = Rr

None

2

LPM

Нет

Загрузка из программной памяти

R0 = (Z)

None

3

LPM

Rd,Z

Загрузка из программной памяти

Rd = (Z)

None

3

LPM

Rd,Z+

Загрузка из программной памяти с пост-инкрементом

Rd = (Z), Z=Z+1

None

3

ELPM

Нет

Расширенная загрузка из программной памяти

R0 = (RAMPZ:Z)

None

3

ELPM

Rd,Z

Расширенная загрузка из программной памяти

Rd = (RAMPZ:Z)

None

3

ELPM

Rd,Z+

Расширенная загрузка из программной памяти с пост-инкрементом

Rd = (RAMPZ:Z), Z = Z+1

None

3

SPM

Нет

Сохранение в программной памяти

(Z) = R1:R0

None

-

ESPM

Нет

Расширенное сохранение в программной памяти

(RAMPZ:Z) = R1:R0

None

-

IN

Rd,P

Чтение порта

Rd = P

None

1

OUT

P,Rr

Запись в порт

P = Rr

None

1

PUSH

Rr

Занесение регистра в стек

STACK = Rr

None

2

POP

Rd

Извлечение регистра из стека

Rd = STACK

None

2

* Для операций доступа к данным количество циклов указано при условии доступа к внутренней памяти данных, и не корректно при работе с внешним ОЗУ. Для инструкций LD, ST, LDD, STD, LDS, STS, PUSH и POP, необходимо добавить один цикл плюс по одному циклу для каждого ожидания.

Инструкции работы с битами

Мнемоника

Операнды

Описание

Операция

Флаги

Циклы

LSL

Rd

Логический сдвиг влево

Rd(n+1)=Rd(n), Rd(0)=0, C=Rd(7)

Z,C,N,V,H,S

1

LSR

Rd

Логический сдвиг вправо

Rd(n)=Rd(n+1), Rd(7)=0, C=Rd(0)

Z,C,N,V,S

1

ROL

Rd

Циклический сдвиг влево через C

Rd(0)=C, Rd(n+1)=Rd(n), C=Rd(7)

Z,C,N,V,H,S

1

ROR

Rd

Циклический сдвиг вправо через C

Rd(7)=C, Rd(n)=Rd(n+1), C=Rd(0)

Z,C,N,V,S

1

ASR

Rd

Арифметический сдвиг вправо

Rd(n)=Rd(n+1), n=0,...,6

Z,C,N,V,S

1

SWAP

Rd

Перестановка тетрад

Rd(3..0) = Rd(7..4), Rd(7..4) = Rd(3..0)

None

1

BSET 

s

Установка флага

SREG(s) = 1

SREG(s)

1

BCLR

s

Очистка флага

SREG(s) = 0

SREG(s)

1

SBI

P,b

Установить бит в порту

I/O(P,b) = 1

None

2

CBI

P,b

Очистить бит в порту

I/O(P,b) = 0

None

2

BST

Rr,b

Сохранить бит из регистра в T

T = Rr(b)

T

1

BLD

Rd,b

Загрузить бит из T в регистр

Rd(b) = T

None

1

SEC

Нет

Установить флаг переноса

C =1

C

1

CLC

Нет

Очистить флаг переноса

C = 0

C

1

SEN

Нет

Установить флаг отрицательного числа

N = 1

N

1

CLN

Нет

Очистить флаг отрицательного числа

N = 0

N

1

SEZ

Нет

Установить флаг нуля

Z = 1

Z

1

CLZ

Нет

Очистить флаг нуля

Z = 0

Z

1

SEI

Нет

Установить флаг прерываний

I = 1

I

1

CLI

Нет

Очистить флаг прерываний

I = 0

I

1

SES

Нет

Установить флаг числа со знаком

S = 1

S

1

CLN

Нет

Очистить флаг числа со знаком

S = 0

S

1

SEV

Нет

Установить флаг переполнения

V = 1

V

1

CLV

Нет

Очистить флаг переполнения

V = 0

V

1

SET

Нет

Установить флаг T

T = 1

T

1

CLT

Нет

Очистить флаг T

T = 0

T

1

SEH

Нет

Установить флаг внутреннего переноса

H = 1

H

1

CLH

Нет

Очистить флаг внутреннего переноса

H = 0

H

1

NOP

Нет

Нет операции

Нет

None

1

SLEEP

Нет

Спать (уменьшить энергопотребление)

Смотрите описание инструкции

None

1

WDR

Нет

Сброс сторожевого таймера

Смотрите описание инструкции

None

1

Ассемблер не различает регистр символов.

Операнды могут быть таких видов:

Rd: Результирующий (и исходный) регистр в регистровом файле Rr: Исходный регистр в регистровом файле b: Константа (3 бита), может быть константное выражение s: Константа (3 бита), может быть константное выражение P: Константа (5-6 бит), может быть константное выражение K6; Константа (6 бит), может быть константное выражение K8: Константа (8 бит), может быть константное выражение k: Константа (размер зависит от инструкции), может быть константное выражение q: Константа (6 бит), может быть константное выражение Rdl:  R24, R26, R28, R30. Для инструкций ADIW и SBIW X,Y,Z: Регистры косвенной адресации (X=R27:R26, Y=R29:R28, Z=R31:R30)

studfiles.net

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *