Avr опрос кнопок ассемблер: 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 это параметры макроса, они нумеруются тупо по порядку. А при вызове подставляются в код.

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

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

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

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

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

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

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

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

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

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

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

AVR. Учебный курс. Скелет программы

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

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

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

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

Структура же программы при этом следующая:

  • Макросы и макроопредения
  • Сегмент ОЗУ
  • Точка входа — ORG 0000
  • Таблица векторов — и вектора, ведущие в секцию обработчиков прерываний
  • Обработчики прерываний — тела обработчиков, возврат отсюда только по RETI
  • Инициализация памяти — а вот уже отсюда начинается активная часть программы
  • Инициализация стека
  • Инициализация внутренней периферии — программирование и запуск в работу всяких таймеров, интерфейсов, выставление портов ввода-вывода в нужные уровни. Разрешение прерываний.
  • Инициализация внешней периферии — инициализация дисплеев, внешней памяти, разных аппаратных примочек, что подключены к микроконтроллеру извне.
  • Запуск фоновых процессов — процессы работающие непрерывно, вне зависимости от условий. Такие как сканирование клавиатуры, обновление экрана и так далее.
  • Главный цикл — тут уже идет вся управляющая логика программы.
  • Сегмент ЕЕПРОМ


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
	.include "m16def.inc"   ; Используем ATMega16
 
;= Start macro.inc ========================================
   	.macro    OUTI          	
      	LDI    R16,@1
   	.if @0 < 0x40
      	OUT    @0,R16       
   	.else
      	STS      @0,R16
   	.endif
   	.endm
 
   	.macro    UOUT        
   	.if	@0 < 0x40
      	OUT	@0,@1         
	.else
      	STS	@0,@1
   	.endif
   	.endm
;= End 	macro.inc =======================================

.include «m16def.inc» ; Используем ATMega16 ;= Start macro.inc ======================================== .macro OUTI LDI R16,@1 .if @0 < 0x40 OUT @0,R16 .else STS @0,R16 .endif .endm .macro UOUT .if @0 < 0x40 OUT @0,@1 .else STS @0,@1 .endif .endm ;= End macro.inc =======================================

В оперативке пока ничего не размечаем. Нечего.

1
2
3
; RAM ===================================================
		.DSEG
; END RAM ===============================================

; RAM =================================================== .DSEG ; END RAM ===============================================

С точкой входа и таблицей векторов все понятно, следуя нашему давнему шаблону, берем его оттуда:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
; FLASH ======================================================
         .CSEG
         .ORG $000      ; (RESET) 
         RJMP   Reset
         .ORG $002
         RETI             ; (INT0) External Interrupt Request 0
         .ORG $004
         RETI             ; (INT1) External Interrupt Request 1
         .ORG $006
         RETI	      ; (TIMER2 COMP) Timer/Counter2 Compare Match
         .ORG $008
         RETI             ; (TIMER2 OVF) Timer/Counter2 Overflow
         .ORG $00A
         RETI	     ; (TIMER1 CAPT) Timer/Counter1 Capture Event
         .ORG $00C 
         RETI             ; (TIMER1 COMPA) Timer/Counter1 Compare Match A
         .ORG $00E
         RETI             ; (TIMER1 COMPB) Timer/Counter1 Compare Match B
         .ORG $010
         RETI             ; (TIMER1 OVF) Timer/Counter1 Overflow
         .ORG $012
         RETI             ; (TIMER0 OVF) Timer/Counter0 Overflow
         .ORG $014
         RETI             ; (SPI,STC) Serial Transfer Complete
         .ORG $016
         RETI    	     ; (USART,RXC) USART, Rx Complete
         .ORG $018
         RETI             ; (USART,UDRE) USART Data Register Empty
         .ORG $01A
         RETI             ; (USART,TXC) USART, Tx Complete
         .ORG $01C
         RETI	     ; (ADC) ADC Conversion Complete
         .ORG $01E
         RETI             ; (EE_RDY) EEPROM Ready
         .ORG $020
         RETI             ; (ANA_COMP) Analog Comparator
         .ORG $022
         RETI             ; (TWI) 2-wire Serial Interface
         .ORG $024
         RETI             ; (INT2) External Interrupt Request 2
         .ORG $026
         RETI             ; (TIMER0 COMP) Timer/Counter0 Compare Match
         .ORG $028
         RETI             ; (SPM_RDY) Store Program Memory Ready
 
	 .ORG   INT_VECTORS_SIZE      	; Конец таблицы прерываний

; FLASH ====================================================== .CSEG .ORG $000 ; (RESET) RJMP Reset .ORG $002 RETI ; (INT0) External Interrupt Request 0 .ORG $004 RETI ; (INT1) External Interrupt Request 1 .ORG $006 RETI ; (TIMER2 COMP) Timer/Counter2 Compare Match .ORG $008 RETI ; (TIMER2 OVF) Timer/Counter2 Overflow .ORG $00A RETI ; (TIMER1 CAPT) Timer/Counter1 Capture Event .ORG $00C RETI ; (TIMER1 COMPA) Timer/Counter1 Compare Match A .ORG $00E RETI ; (TIMER1 COMPB) Timer/Counter1 Compare Match B .ORG $010 RETI ; (TIMER1 OVF) Timer/Counter1 Overflow .ORG $012 RETI ; (TIMER0 OVF) Timer/Counter0 Overflow .ORG $014 RETI ; (SPI,STC) Serial Transfer Complete .ORG $016 RETI ; (USART,RXC) USART, Rx Complete .ORG $018 RETI ; (USART,UDRE) USART Data Register Empty .ORG $01A RETI ; (USART,TXC) USART, Tx Complete .ORG $01C RETI ; (ADC) ADC Conversion Complete .ORG $01E RETI ; (EE_RDY) EEPROM Ready .ORG $020 RETI ; (ANA_COMP) Analog Comparator .ORG $022 RETI ; (TWI) 2-wire Serial Interface .ORG $024 RETI ; (INT2) External Interrupt Request 2 .ORG $026 RETI ; (TIMER0 COMP) Timer/Counter0 Compare Match .ORG $028 RETI ; (SPM_RDY) Store Program Memory Ready .ORG INT_VECTORS_SIZE ; Конец таблицы прерываний

Обработчики пока тоже пусты, но потом добавим

1
2
; Interrupts ==============================================
; End Interrupts ==========================================

; Interrupts ============================================== ; End Interrupts ==========================================

Инициализация ядра. Память, стек, регистры:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
Reset:   	LDI R16,Low(RAMEND)		; Инициализация стека
	  	OUT SPL,R16			; Обязательно!!!
 
	  	LDI R16,High(RAMEND)
	  	OUT SPH,R16
 
; Start coreinit.inc
RAM_Flush:	LDI	ZL,Low(SRAM_START)	; Адрес начала ОЗУ в индекс
		LDI	ZH,High(SRAM_START)
		CLR	R16			; Очищаем R16
Flush:		ST 	Z+,R16			; Сохраняем 0 в ячейку памяти
		CPI	ZH,High(RAMEND)		; Достигли конца оперативки?
		BRNE	Flush			; Нет? Крутимся дальше!
 
		CPI	ZL,Low(RAMEND)		; А младший байт достиг конца?
		BRNE	Flush
 
		CLR	ZL			; Очищаем индекс
		CLR	ZH
		CLR	R0
		CLR	R1
		CLR	R2
		CLR	R3
		CLR	R4
		CLR	R5
		CLR	R6
		CLR	R7
		CLR	R8
		CLR	R9
		CLR	R10
		CLR	R11
		CLR	R12
		CLR	R13
		CLR	R14
		CLR	R15
		CLR	R16
		CLR	R17
		CLR	R18
		CLR	R19
		CLR	R20
		CLR	R21
		CLR	R22
		CLR	R23
		CLR	R24
		CLR	R25
		CLR	R26
		CLR	R27
		CLR	R28
		CLR	R29
; End coreinit.inc

Reset: LDI R16,Low(RAMEND) ; Инициализация стека OUT SPL,R16 ; Обязательно!!! LDI R16,High(RAMEND) OUT SPH,R16 ; Start coreinit.inc RAM_Flush: LDI ZL,Low(SRAM_START) ; Адрес начала ОЗУ в индекс LDI ZH,High(SRAM_START) CLR R16 ; Очищаем R16 Flush: ST Z+,R16 ; Сохраняем 0 в ячейку памяти CPI ZH,High(RAMEND) ; Достигли конца оперативки? BRNE Flush ; Нет? Крутимся дальше! CPI ZL,Low(RAMEND) ; А младший байт достиг конца? BRNE Flush CLR ZL ; Очищаем индекс CLR ZH CLR R0 CLR R1 CLR R2 CLR R3 CLR R4 CLR R5 CLR R6 CLR R7 CLR R8 CLR R9 CLR R10 CLR R11 CLR R12 CLR R13 CLR R14 CLR R15 CLR R16 CLR R17 CLR R18 CLR R19 CLR R20 CLR R21 CLR R22 CLR R23 CLR R24 CLR R25 CLR R26 CLR R27 CLR R28 CLR R29 ; End coreinit.inc

Всю эту портянку можно и нужно спрятать в inc файл и больше не трогать.

Секции внешней и внутренней инициализации переферии пока пусты, но ненадолго. Равно как и запуск фоновых программ. Потом я просто буду говорить, что мол добавьте эту ботву в секцию Internal Hardware Init и все 🙂

1
2
3
4
5
6
7
8
9
10
11
; Internal Hardware Init  ======================================
 
; End Internal Hardware Init ===================================
 
; External Hardware Init  ======================================
 
; End Internal Hardware Init ===================================
 
; Run ==========================================================
 
; End Run ======================================================

; Internal Hardware Init ====================================== ; End Internal Hardware Init =================================== ; External Hardware Init ====================================== ; End Internal Hardware Init =================================== ; Run ========================================================== ; End Run ======================================================

А теперь, собственно, сам главный цикл.

1
2
3
4
5
; Main =========================================================
Main:
 
		JMP	Main
; End Main =====================================================

; Main ========================================================= Main: JMP Main ; End Main =====================================================

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

1
2
3
; Procedure ====================================================
 
; End Procedure ================================================

; Procedure ==================================================== ; End Procedure ================================================

Ну и вот тебе файлик с уже готовым проектом под этот шаблон

Волшебная сила макросов, или как облегчить жизнь ассемблерного программиста AVR

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

Компания ATMEL в свое время постаралась и разработала линейку восьмиразрядных микроконтроллеров с очень качественной архитектурой и простой, но при этом очень мощной системой команд. Но, как известно, нет предела совершенству, и некоторых часто употребляемых инструкций не хватает. К счастью макроассемблер, любезно и абсолютно бесплатно предоставленный производителем, позволяет существенно упростить код за счет использования директив. Перед тем, как перейти непосредственно к макросам, выполним некоторые предварительные действия


Определение констант

.EQU    FOSC = 16000000
.EQU CLK8 = 0

Эти два определения позволяют избавиться от «магических чисел» в макросах, где значения регистров рассчитываются, исходя из частоты процессора и состояния фьюза делителя периферии. Превое определение — частота кристалла процессора в герцах, второе — состояние делителя частоты периферии.


Именование регистров

.DEF TempL = r16
.DEF TempH = r17
.DEF TempQL = r18
.DEF TempQH = r19
.DEF AL = r0
.DEF AH = r1
.DEF AQL = r2
.DEF AQH = r3

Несколько избыточное на первый взгляд именование регистров, которые могут быть использованы в макросах. Сразу четыре регистра для Temp нужны, если мы будем иметь дело с 32-х разрядными значениями (например, в операциях перемножения двух 16-и разрядных чисел). Если мы уверены, что двух регистров временного хранения для использования в макросах нам достаточно, то TempQL и TempQH можно не определять. Определения для A нужны для макросов, использующих операции умножения. Необходимость в AQ отпадает, если с наших макросах мы не используем с 32-х разрядную арифметику.


Макросы для реализации простых команд

Теперь, когда мы разобрались с именованием регистров, приступим к реализации команд, которых не хватает и начнем с того, что попытаемся упростить существующие. Ассемблер AVR имеет одну неудобную особенность. Для ввода и вывода первые 64 порта используются команды in/out, а для остальных lds/sts. Для того, чтобы каждый раз не смотреть в документацию в поисках нужной команды для конкретного порта, создадим набор универсальных команд, которые самостоятельно будет подставлять нужные значения.

.MACRO XOUT
.IF @0<64
out      @0,@1
.ELSE
sts     @0,@1
.ENDIF
.ENDMACRO

.MACRO XIN
.IF @1<64
in      @0,@1
.ELSE
lds     @0,@1
.ENDIF
.ENDMACRO

Для того, чтобы подстановка работала правильно, в макросе используется условная компиляция. В случае, когда адрес порта менее 64, выполняется первая условная секция, в противном случае — вторая. Наши макросы полностью повторяют функционал стандартных команд работы с портами ввода-вывода, поэтому для обозначения того, что наша команда обладает расширенными возможностями добавим стандартному именованию префикс X.
Одной из самых распространенных команд, которые отсутствуют в ассемблере, но постоянно требуются, является команда записи констант в регистры ввода вывода. Реализация макроса для этой команды будет выглядеть следующим образом

.MACRO OUTI
ldi      TempL,@1    
.IF @0<64
out      @0, TempL                   
.ELSE
sts @0, TempL
.ENDIF
.ENDMACRO

В данном случае название в макроса, чтобы не нарушать логику именования команд, добавим к стандартному наименованию постфикс I, используемый разработчиком для обозначения команд работы с константами. В этом макросе мы используем для работы ранее определенный регистр временного хранения TempL.
В ряде случаев требуется запись не одного регистра, а целой пары, хранящей 16-битное значение. Создадим по новый макрос для записи 16-битного значения в пару регистров ввода-вывода

.MACRO OUTIW
ldi      TempL,HIGH(@1)    
.IF @0<64
out      @0H, TempL                   
.ELSE
sts @0H, TempL
.ENDIF
ldi      TempL,LOW(@1)    
.IF @0<64
out      @0L, TempL                   
.ELSE
sts @0L, TempL
.ENDIF
.ENDMACRO

В этом макросе мы используем встроенные функции LOW и HIGH для выделения младшего и старшего байта из 16-и битного значения. В название макроса к команде добавим постфиксы I и W для обозначения того, что в данном случае команда работает с 16 битным значением (словом).
Не менее часто в программах встречается загрузка регистровых пар, например для установки указателей на память. Создадим и такой макрос

.MACRO ldiw
ldi    @0L, LOW(@1)
ldi    @0H, HIGH(@1)
.ENDMACRO

В этом макросе мы используем тот факт, что стандартное именование регистров и портов у производителя подразумевает постфикс L для младшей и постфикс H для старшей части двухбайтного значения. Если при именовании собственных переменных придерживаться этого правила, то макрос будет правильно работать в том числе и с ними. Прелесть макросов еще и в том, что они обеспечивают простую подстановку, поэтому и в случае, когда второй операнд число, и в случае, когда это название метки, макрос отработает правильно.


Макросы для реализации сложных команд.

Когда речь идет о более сложных операциях, макросы, как правило, не используют, предпочитая подпрограммы. Однако и в этих случаях макросы могут облегчить жизнь и сделать код более читабельным. На помощь нам и в этом случае приходит условная компиляция. Подход к программированию может выглядеть следующим образом:
Все наши подпрограммы мы размещаем в отдельном файле, который назовем, например, Library.inc. Каждая подпрограмма в этом файле будет иметь следующий вид

_sub0:
.IFDEF __sub0
; ----- код нашей подпрограммы -----
ret     
.ENDIF

В данном случае наличие определения __sub0 означает, что подпрограмма должна быть включена в результирующий код. В противном случае она игнорируется.
Далее в отдельном файле Macro.inc определяем макросы вида

.MACRO SUB0
.IFNDEF __sub0
.DEF __sub0
.ENDIF
; --- здесь мы располагаем команды инициализации регистров перед вызовом подпрограммы
call _sub0
.ENDMACRO

При использовании данного макроса мы проверяем наличие определения __sub0 и, в случае его отсутствия, выполняем определение. В результате, использование макроса разблокирует включение кода подпрограммы в выходной файл. В случае использования подпрограмм в макросах код основной программы приобретет следующий вид

.INCLUDE “Macro.inc”
;---- код основной программы ----
.INCLUDE “Library.inc”

В качестве примера, приведем реализацию макроса для деления 8-и разрядных целых беззнаковых чисел. Сохраняем логику производителя и результат размещаем в AL (r0), а остаток от деления в AH(r1). Подпрограмма будет выглядеть следующим образом

_div8u:
.IFDEF __ div8u
;AH - остаток
;AL результат
;TempL - делимое
;TempH - делитель
;TempQL -счетчик цикла 
clr AL;
clr AH; 
ldi TempQL,9 
d8u_1:  rol TempL 
dec TempQL
brne d8u_2
ret
d8u_2: rol A 
sub  AH, TempH 
brcc  d8u_3
add  AH,TempH
clc 
rjmp d8u_1 
d8u_3: sec
rjmp d8u_1 
.ENDIF

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

.MACRO DIV8U
.IFNDEF __div8u
.DEF __div8u
.ENDIF
mov TempL, @0
mov     TempH, @1
call    _div8u
.ENDMACRO

При желании можно добавить и версию для работы с константой

.MACRO DIV8UI
.IFNDEF __div8u
.DEF __div8u
.ENDIF
mov TempL, @0
ldi     TempH, @1
call    _div8u
.ENDMACRO

В результате использование в тексте программы операции деления получается тривиальным

DIV8U r10, r11 ; r0 = r10/r11 r1 = r10 % r11
DIV8UI r10, 35 ; r0 = r10/35  r1 = r10 % 35 

Используя условную компиляцию мы можем разместить в Library.inc все подпрограммы, которые могли бы нам пригодиться. При этом в выходном коде окажутся только те из них, которые хотя бы раз вызывались. Обратите внимание на позицию метки входа. Вывод метки за границы условия обусловлен особенностями компилятора. Если разместить метку в тело условного блока, то компилятор может выдать ошибку. Наличие в коде неиспользуемых меток не страшно, так как наличие любого количества меток никак не влияет на результат.


Макросы для работы с периферией

Одной из операций, где трудно обойтись без использования документации производителя, является инициализация периферийных устройств. Даже с использованием мнемонических обозначений регистров и разрядов из кода бывает трудно понять, в каком режиме настроено то или иное устройство, тем более, что иногда режим настраивается комбинацией значений бит разных регистров. Посмотрим как можно использовать макросы на примере USART.
Начнем с макроса инициализации асинхронного режима.

.MACRO USART_INIT ; speed, bytes, parity, stop-bits 
  .IF CLK8 == 0
 .SET DIVIDER = FOSC/16/@0-1
  .ELSE
  .SET DIVIDER = FOSC/128/@0-1
  .ENDIF
  ; Set baud rate to UBRR0   
  outi    UBRR0H, HIGH(DIVIDER)   
  outi    UBRR0L, LOW(DIVIDER)

  ; Enable receiver and transmitter   
  .SET UCSR0B_ = (1<<RXEN0)|(1<<TXEN0) 
  outi    UCSR0B, UCSR0B_

  .SET UCSR0C_  = 0
  .IF @2 == 'E'
  .SET UCSR0C_ |= (1<<UPM01)
  .ENDIF
 .IF @2 == 'O'
  .SET UCSR0C_ |= (1<<UPM00)
  .ENDIF
  .IF @3== 2
  .SET UCSR0C_ |= (1<<USBS0)
  .ENDIF
  .IF @1== 6
  .SET UCSR0C_ |= (1<<UCSZ00)
  .ENDIF
  .IF @1== 7
  .SET UCSR0C_ |= (1<<UCSZ01)
  .ENDIF
  .IF @1== 8
  .SET UCSR0C_ = UCSR0C_ |(1<<UCSZ01)|(1<<UCSZ00)
  .ENDIF
  .IF @1== 9
  .SET UCSR0C_ |= (1<<UCSZ02)|(1<<UCSZ01)|(1<<UCSZ00)
  .ENDIF
  ; Set frame format
  outi    UCSR0C,UCSR0C_
 .ENDMACRO

Использование макроса нам позволило заменить инициализацию регистров настройки USART непонятными без чтения документации значениями на строчку, с который способен справится даже тот, кто впервые столкнулся с этим контроллером. В этом макросе так же наконец стало понятно для чего мы определяли константы частоты и делителя. Ну и следует отметить, что несмотря на внушительный код самого макроса, результирующий будет иметь тот же вид, как если бы мы писали инициализацию обычным образом.
Чтобы закончить с USART приведем еще несколько небольших макросов

 .MACRO USART_SEND_ASYNC
  outi  UDR0, @0
 .ENDMACRO

Здесь всего одна строчка, но использование этого макроса позволит лучше видеть, где в программе идет вывод данных в USART. Если мы предполагаем работу в синхронном режиме без использования прерываний, то вместо USART_SEND_ASYNC лучше использовать макрос, приведенный ниже

  .MACRO USART_SEND
USART_Transmit:
xin TempL, UCSR0A 
sbrs TempL, UDRE0 
rjmp USART_Transmit 
outi    UDR0, @0
 .ENDMACRO

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


Сравнение программ без и с использованием макросов.

Посмотрим на небольшой пример и сравним код, написанный без использования макросов с кодом, где они используются. Для примера возьмем программу выводящую классический «Hello world!» в терминал через аппаратный UART.

 RESET: ldi r16, high(RAMEND) 
out SPH,r16 
ldi r16, low(RAMEND)
out SPL,r16 
USART_Init: 
out UBRR0H, r17 
out UBRR0L, r16 
ldi r16, (1<<RXEN0)|(1<<TXEN0) 
out UCSRnB,r16
ldi r16, (1<<USBS0)|(3<<UCSZ00) 
out UCSR0C,r16 
ldi ZL, LOW(STR<<1)
ldi ZH, HIGH(STR<<1)
LOOP: lpm   r16, Z+
or  r16,r16
breq    END
USART_Transmit: 
in r17, UCSR0A 
sbrs r17, UDRE0 
rjmp USART_Transmit 
out UDR0,r16 
rjmp    LOOP
END: rjmp END
STR: .DB  “Hello world!”,0

А вот как выглядит та же программа, но написанная с использованием макросов

.INCLUDE “macro.inc”
.EQU    FOSC = 16000000
.EQU   CLK8 = 0
RESET: ldiw SP, RAMEND; 
USART_INIT 19200, 8, "N", 1
ldiw Z, STR<<1
LOOP: lpm TempL, Z+
test    TempL
breq    END
USART_SEND TempL 
rjmp    LOOP
END: rjmp END
STR: .DB  “Hello world!”,0

В этом примере мы использовали описанные выше макросы, что позволило существенно упростить код программы и сделать его более понимаемым. Бинарный код в обоих программах при этом будет абсолютно идентичным.


Вывод

Использование макросов позволяет значительно сократить ассемблерный код программы, сделать его более понятным и читабельным. Условная компиляция позволяет создавать универсальные команды и библиотеки процедур без создания избыточного выходного кода. В качестве недостатка можно указать весьма скромный по меркам высокоуровневых языков набор допустимых операций и ограничения при объявлении данных «вперед». Это ограничение не позволяет, к примеру, написать средствами макросов полноценную универсальную команду для переходов jmp/rjmp и существенно раздувает код самого макроса при реализации сложной логики.

Макрос в Assembler на примере AVR

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

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

Запись Макроса

Макрос на языке программирования Assembler состоит из следующих частей:

  1. .macro — директива начала макроса, после объявления макроса в той же строке объявляется имя этого макроса;
  2. .endm — директива окончания макроса;
  3. @0 — запись переменной;
  4. Тело макроса .

Запись макроса для микроконтроллера AVR может выглядеть следующим образом:


 ; Макрос вывода в порт или регистр порта
 .macro outi                ; Директива макроса с объявлением имени макроса 
    	ldi r16, @1         ; Установка значения в регистр
    	out @0, R16         ; Вывод данных в порт или регистр порта
 .endm                      ; Окончание макроса

Применение данного Макроса будет выглядеть следующим образом:


 outi DDRB,  0b01111111    ; Инициализация вывода столбцов развертки индикации
 outi DDRC,  0b00111111    ; Инициализация вывода строк развертки индикации
 outi DDRD,  0b00000001    ; Инициализация вывода для активации сигнала будильника
 outi PORTD, 0b11111111    ; Инициализация PD1 - PD7 в режиме PullUp

В данном случае Макрос вызывается по своему имени outi. На первом месте @0 находится регистр DDRB, а на втором @1 находится  переменная 0b01111111 , которая записывается в данный регистр. То есть фактически применение макроса выглядит следующим образом:


outi @0, @1, @2 ... @N

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

Таблицы команд ассемблера AVR — Микроконтроллеры для всех

В этой статье я хочу представить еще один вариант таблиц команд ассемблера для микроконтроллеров AVR.

Из дополнительных материалов у приобретателей курса уже есть pdf документ с набором таблиц команд. Так в чем же основное отличие набора команд, который представлен ниже?

В первую очередь, тем что в колонке «Описание» дается описание команд на английском. И как не трудно заметить, многие мнемоники команд образованы как раз от этих сокращений. Поэтому, тем кто знает английский язык, тем будет проще запомнить написание команд. И так же присутствует колонка «Код операции», где можно посмотреть каким образом та или иная команда выглядит в двоичном виде. Это на тот случай, если вам вдруг захочется по-программировать в машинных кодах.

Сразу стоит отметить, что здесь представлены в основном только команды семейства tiny. Я намерено убрал команды семейства mega, что бы лишний раз не вносить путаницу.

Арифметические и логические команды

Команда Описание Действие Циклы Код операции Флаги
add Rd,Rr Add two Registers Rd←Rd+Rr 1 0000 11rd dddd rrrr Z,C,S,N,V,H
adc Rd,Rr Add with Carry two Registers Rd←Rd+Rr+C 1 0001 11rd dddd rrrr Z,C,S,N,V,H
adiw Rdl,K Add Immediate to Word Rdh:Rdl←Rdh:Rdl+K 2 1001 0110 KKdd KKKK Z,C,S,N,V
sub Rd,Rr Subtract two Registers Rd←Rd-Rr 1 0001 10rd dddd rrrr Z,C,S,N,V,H
sbc Rd,Rr Subtract with Carry two Registers Rd←Rd-Rr-C 1 0000 10rd dddd rrrr Z,C,S,N,V,H
subi Rd,K Subtract Constant from Register Rd←Rd-K 1 1010 KKKK dddd KKKK Z,C,S,N,V,H
sbci Rd,K Subtract with Carry Constant from Register Rd←Rd-K-C 1 0100 KKKK dddd KKKK Z,C,S,N,V,H
sbiw Rdl,K Subtract Immediate from Word Rdh:Rdl←Rdh:Rdl-K 2 1001 0111 KKdd KKKK Z,C,S,N,V
and Rd,Rr Logical AND Registers Rd←Rd AND Rr 1 0010 00rd dddd rrrr Z,S,N
andi Rd,K Logical AND Register and Constant Rd←Rd AND K 1 0111 KKKK dddd KKKK Z,S,N
or Rd,Rr Logical OR Registers Rd←Rd OR Rr 1 0010 10rd dddd rrrr Z,S,N
ori Rd,K Logical OR Register and Constant Rd←Rd OR K 1 0110 KKKK dddd KKKK Z,S,N
eor Rd,Rr Exclusive OR Registers Rd←Rd EOR Rr 1 0010 01rd dddd rrrr Z,S,N
com Rd One’s complement Rd←0xFF-Rd 1 1001 010d dddd 0000 Z,S,N
neg Rd Two’s complement Rd←0x00-Rd 1 1001 010d dddd 0001 Z,C,S,N,V,H
sbr Rd,K Set Bit (s) in Register Rd←Rd OR K 1 0110 KKKK dddd KKKK Z,S,N
cbr Rd,K Clear Bit (s) in Register Rd←Rd AND (0xFF- K) 1 0111 KKKK dddd KKKK Z,S,N
inc Rd Increment Rd←Rd+1 1 1001 010d dddd 0011 Z,S,N,V
dec Rd Decrement Rd←Rd-1 1 1001 010d dddd 1010 Z,S,N,V
tst Rd Test for Zero or Minus Rd←Rd AND Rd 1 0010 00dd dddd dddd Z,S,N
clr Rd Clear Register Rd←Rd EOR Rd 1 0010 01dd dddd dddd Z,S,N
ser Rd Set Register Rd←0xFF 1 1110 1111 dddd 1111 None

Команды пересылки данных

Команда Описание Действие Циклы Код операции Флаги
mov Rd,Rr Move Between Registers Rd←Rr 1 0010 11rd dddd rrrr None
movw Rd,Rr Copy Register Word Rd+1:Rd←Rr+1:Rr 1 0000 0001 dddd rrrr None
ldi Rd,K Load Immediate Rd←K 1 1110 KKKK dddd KKKK None
ld Rd,X Load Indirect Rd← (X) 2 1001 000d dddd 1100 None
ld Rd,X+ Load Indirect and
Post-Inc.
Rd← (X), X←X+1 2 1001 000d dddd 1101 None
ld Rd, -X Load Indirect and
Pre-Dec.
X←X-1, Rd← (X) 2 1001 000d dddd 1110 None
ld Rd,Y Load Indirect Rd← (Y) 2 1000 000d dddd 1000 None
ld Rd,Y+ Load Indirect and
Post-Inc.
Rd← (Y), Y←Y+1 2 1001 000d dddd 1001 None
ld Rd, -Y Load Indirect and
Pre-Dec.
Y←Y-1, Rd← (Y) 2 1001 000d dddd 1010 None
ldd Rd,Y+q Load Indirect with Displacement Rd← (Y+q) 2 10q0 qq0d dddd 1qqq None
ld Rd,Z Load Indirect Rd← (Z) 2 1000 000d dddd 0000 None
ld Rd,Z+ Load Indirect and
Post-Inc.
Rd← (Z), Z←Z+1 2 1001 000d dddd 0001 None
ld Rd, -Z Load Indirect and
Pre-Dec.
Z←Z-1, Rd← (Z) 2 1001 000d dddd 0010 None
ldd Rd,Z+q Load Indirect with Displacement Rd← (Z+q) 2 10q0 qq0d dddd 0qqq None
lds Rd,k Load Direct from SRAM Rd← (k) 2 1001 000d dddd 0000kkkk kkkk kkkk kkkk None
st X,Rr Store Indirect (X) ←Rr 2 1001 001r rrrr 1100 None
st X+,Rr Store Indirect and
Post-Inc.
(X) ←Rr, X←X+1 2 1001 001r rrrr 1101 None
st -X,Rr Store Indirect and
Pre-Dec.
X←X-1, (X) ←Rr 2 1001 001r rrrr 1110 None
st Y,Rr Store Indirect (Y) ←Rr 2 1000 001r rrrr 1000 None
st Y+,Rr Store Indirect and
Post-Inc.
(Y) ←Rr, Y←Y+1 2 1001 001r rrrr 1001 None
st -Y,Rr Store Indirect and
Pre-Dec.
Y←Y-1, (Y) ←Rr 2 1001 001r rrrr 1010 None
std Y+q,Rr Store Indirect with Displacement (Y+q) ← Rr 2 10q0 qq1r rrrr 1qqq None
st Z,Rr Store Indirect (Z) ←Rr 2 1000 001r rrrr 0000 None
st Z+,Rr Store Indirect and
Post-Inc.
(Z) ←Rr, Z←Z+1 2 1001 001r rrrr 0001 None
st -Z,Rr Store Indirect and
Pre-Dec.
Z←Z-1, (Z) ←Rr 2 1001 001r rrrr 0010 None
std Z+q,Rr Store Indirect with Displacement (Z+q) ← Rr 2 10q0 qq1r rrrr 0qqq None
sts k,Rr Store Direct to SRAM (k) ←Rr 2 1001 001r rrrr 0000kkkk kkkk kkkk kkkk None
lpm Load Program Memory R0← (Z) 3 1001 0101 1100 1000 None
lpm Rd,Z Load Program Memory Rd← (Z) 3 1001 000d dddd 0100 None
lpm Rd,Z+ Load Program Memory
and Post-Inc.
Rd← (Z), Z←Z+1 3 1001 000d dddd 0101 None
spm Store Program Memory (Z) ←R1:R0 1001 0101 1110 1000 None
in Rd,P In Port Rd←P 1 1011 0PPd dddd PPPP None
out P,Rr Out Port P←Rr 1 1011 1PPr rrrr PPPP None
push Rr Push Register in Stack STACK←Rr, SP←SP-1 2 1001 001r rrrr 1111 None
pop Rd Pop Register from Stack SP←SP+1, Rd←STACK 2 1001 000d dddd 1111 None

Команды передачи управления

Команда Описание Действие Циклы Код операции Флаги
rjmp k Relative Jump PC←PC+k+1 2 1100 kkkk kkkk kkkk None
ijmp Indirect Jump to (Z) PC← (Z) 2 1001 0100 0000 1001 None
*jmp k Direct Jump PC←k 3 1001 010k kkkk 110kkkkk kkkk kkkk kkkk None
rcall k Relative Subroutine Call STACK←PC+1,PC←PC+k+1,SP←SP-2 or 3 ¾ 1101 kkkk kkkk kkkk None
icall Indirect Call to (Z) STACK←PC+1, PC← (Z),SP←SP-2 or 3 ¾ 1001 0101 0000 1001 None
*call k Direct Subroutine Call STACK←PC+1, PC←k,SP←SP-2 or 3 4/5 1001 010k kkkk 111kkkkk kkkk kkkk kkkk None
ret Subroutine Return PC←STACK,
SP←SP+2 or 3
4/5 1001 0101 0000 1000 None
reti Interrupt Return PC←STACK,
SP←SP+2 or 3
4/5 1001 0101 0001 1000 I
cpse Rd,Rr Compare, Skip if Equal if (Rd=Rr)
PC←PC+2 or 3
½/3 0001 00rd dddd rrrr None
cp Rd,Rr Compare Rd-Rr 1 0001 01rd dddd rrrr Z,C,S,
N,V,H
cpc Rd,Rr Compare with Carry Rd-Rr-C 1 0000 01rd dddd rrrr Z,C,S,
N,V,H
cpi Rd,K Compare Register with Immediate Rd-Rr-K 1 0011 KKKK dddd KKKK Z,C,S,
N,V,H
sbrc Rr,b Skip if Bit in
Register is Cleared
if (Rr (b)=0)
PC←PC+2 or 3
½/3 1111 110r rrrr obbb None
sbrs Rr,b Skip if Bit in
Register is Set
if (Rr (b)=1)
PC←PC+2 or 3
½/3 1111 111r rrrr obbb None
sbic P,b Skip if Bit in IO
Register is Cleared
if (P (b)=0)
PC←PC+2 or 3
½/3 1001 1001 PPPP Pbbb None
sbis P,b Skip if Bit in IO
Register is Set
if (P (b)=1)
PC←PC+2 or 3
½/3 1001 1011 PPPP Pbbb None
brbc s,k Branch if Status
Flag is Cleared
if (SREG (s)=0)
PC←PC+k+1
½ 1111 01kk kkkk ksss None
brbs s,k Branch if Status
Flag is Set
if (SREG (s)=1)
PC←PC+k+1
½ 1111 00kk kkkk ksss None
brcc k Branch if Carry
Flag is Clearsd
if (C=0) PC←PC+k+1 ½ 1111 01kk kkkk k000 None
brcs k Branch if Carry
Flag is Set
if (C=1) PC←PC+k+1 ½ 1111 00kk kkkk k000 None
brsh k Branch if Same
or Higher
if (C=0) PC←PC+k+1 ½ 1111 01kk kkkk k000 None
brlo k Branch if Lower if (C=1) PC←PC+k+1 ½ 1111 00kk kkkk k000 None
brne k Branch if Not Equal if (Z=0) PC←PC+k+1 ½ 1111 01kk kkkk k001 None
breq k Branch if Equal if (Z=1) PC←PC+k+1 ½ 1111 00kk kkkk k001 None
brpl k Branch if Plus if (N=0) PC←PC+k+1 ½ 1111 01kk kkkk k010 None
brmi k Branch if Minus if (N=1) PC←PC+k+1 ½ 1111 00kk kkkk k010 None
brvc k Bruach if Overflow
Flag is Cleared
if (V=0) PC←PC+k+1 ½ 1111 01kk kkkk k011 None
brvs k Branch if Overflow
Flag is Set
if (V=1) PC←PC+k+1 ½ 1111 00kk kkkk k011 None
brge k Branch if Greate or
Equal, Signed
if (S=0) PC←PC+k+1 ½ 1111 01kk kkkk k100 None
brlt k Branch if Less than
Zero, Signed
if (S=1) PC←PC+k+1 ½ 1111 00kk kkkk k100 None
brhc k Branch if Half Carry
Flag is Cleared
if (H=0) PC←PC+k+1 ½ 1111 01kk kkkk k101 None
brhs k Branch if Half Carry
Flag is Set
if (H=1) PC←PC+k+1 ½ 1111 00kk kkkk k101 None
brtc k Branch if Transfer
Flag is Cleared
if (T=0) PC←PC+k+1 ½ 1111 01kk kkkk k110 None
brts k Branch if Transfer
Flag is Set
if (T=1) PC←PC+k+1 ½ 1111 00kk kkkk k110 None
brid k Branch if Interrupt
Disable
if (T=0) PC←PC+k+1 ½ 1111 01kk kkkk k111 None
brie k Branch if Interrupt
Enable
if (T=1) PC←PC+k+1 ½ 1111 00kk kkkk k111 None

*Обратите внимание! Команды jmp и call не поддерживаются микроконтроллерами семейства tiny, но так как они часто используются при программировании семейства mega, то я решил их так же внести в таблицу, что бы вы не забывали о их существовании.

 

Команды условных переходов по состоянию флагов SREG

Проверкафлага Команда условногоперехода АльтернативнаяФорма написания Условие перехода
C brbc 0,k brcc k Переход если флаг переноса установлен
brsh k Переход если больше или равно
brbs 0,k brcs k Переход если флаг переноса сброшен
brlo k Переход если меньше
Z brbc 1,k breq k Переход если равно
brbs 1,k brne k Переход если не равно
N brbc 2,k brpl k Переход если плюс
brbs 2,k brmi k Переход если минус
V brbc 3,k brvc k Переход если флаг дополнительного кода сброшен
brbs 3,k brvs k Переход если флаг дополнительного кода установлен
S brbc 4,k brge k Переход если больше или равно нулю (знаковое)
brbs 4,k brlt k Переход если меньше нуля (знаковое)
H brbc 5,k brhc k Переход если флаг половинного переноса сброшен
brbs 5,k brhs k Переход если флаг половинного переноса установлен
T brbc 6,k brtc k Переход если флаг хранения бита сброшен
brbs 6,k brts k Переход если флаг хранения бита установлен
I brbc 7,k brid k Переход если прерывания запрещены
brbs 7,k brie k Переход если прерывания разрешены

Команд битовых операций

Команда Описание Действие Циклы Код операции Флаги
sbi P,b Set Bit in I/O Rerister I/O (P,b) ←1 2 1001 1010 PPPP Pbbb None
cbi P,b Clear Bit in I/ORerister I/O (P,b) ←0 2 1001 1000 PPPP Pbbb None
lsl Rd Logical Shift Left Rd (n+1) ←Rd (n), Rd (0) ←0 1 0000 11dd dddd dddd Z,C,N,V
lsr Rd Logical Shift Right Rd (n) ←Rd (n+1), Rd (7) ←0 1 1001 010d dddd 0110 Z,C,N,V
rol Rd Rotate Left through Carry Rd (0) ←C, Rd (n+1) ←Rd (n), C←Rd (7) 1 0001 11dd dddd dddd Z,C,N,V
ror Rd Rotate Right through Carry Rd (7) ←C, Rd (n) ←Rd (n+1), C←Rd (0) 1 1001 010d dddd 0111 Z,C,N,V
asr Rd Arithmetic Shift Right Rd (n) ←Rd (n+1),
n=0…6
1 1001 010d dddd 0101 Z,C,N,V
swap Rd Swap Nibbles Rd (3…0) ←Rd (7…4),Rd (7…4) ←Rd (3…0) 1 1001 010d dddd 0010 None
bst Rr,b Bit Store from
Rerister to T
T←Rr (b) 1 1111 101b bbbb 0bbb T
bld Rd,b Bit Load from T
to Rerister
Rd (b) ←T 1 1111 100b bbbb 0bbb None
bset s Flag Set SREG (s) ←1 1 1001 0100 0sss 1000 SREG (s)
bclr s Flag Clear SREG (s) ←0 1 1001 0100 1sss 1000 SREG (s)
sec Set Carry C←1 1 1001 0100 0000 1000 C
clc Clear Carry C←0 1 1001 0100 1000 1000 C
sez Set Zero Flag Z←1 1 1001 0100 0001 1000 Z
clz Clear Zero Flag Z←0 1 1001 0100 1001 1000 Z
sen Set Negative Flag N←1 1 1001 0100 0010 1000 N
cln Clear Negative Flag N←0 1 1001 0100 1010 1000 N
sev Set Twos Complement Overflow V←1 1 1001 0100 0011 1000 V
clv Clear Twos Complement Overflow V←0 1 1001 0100 1011 1000 V
ses Set Signed Test Flag S←1 1 1001 0100 0100 1000 S
cls Clear Signed Test Flag S←0 1 1001 0100 1100 1000 S
seh Set Half Carry Flag H←1 1 1001 0100 0101 1000 H
clh Clear Half Carry Flag H←0 1 1001 0100 1101 1000 H
set Set Transfer bit T←1 1 1001 0100 0110 1000 T
clt Clear Transfer bit T←0 1 1001 0100 1110 1000 T
sei Global Interrupt Enable I←1 1 1001 0100 0111 1000 I
cli Global Interrupt Disable I←0 1 1001 0100 1111 1000 I

Команды управления процессором

Команда Описание Действие Циклы Код операции Флаги
nop No operation 1 0000 0000 0000 0000 None
sleep Sleep 1 1001 0101 1000 1000 None
wdr Watchdog Reset 1 1001 0101 1010 1000 None

 

 

Понравилась статья? Поделиться с друзьями:

Справочник по команде ассемблера AVR

рублей руб. руб.

Все команды этой группы выполнить переход (PC ← PC + A + 1) при разных условиях.

Мнемоника Описание Операция Флаги
ADD Rd, Rr Сложение двух регистров Rd ← Rd + Rr Z, C, N, V, H
АЦП Rd, Rr Сложение двух регистров с переносом Rd ← Rd + Rr + С Z, C, N, V, H
SUB Rd, Rr Вычитание двух регистров Rd ← Rd — Rr Z, C, N, V, H
SBC Rd, Rr Вычитание двух регистров с заёмом Rd ← Rd — Rr — С Z, C, N, V, H
ADIW Rd, К Сложение регистровой пары с константой R (d + 1): Rd ← R (d + 1): Rd + K Z, C, N, V, S
SBIW Rd, K Вычитание константы из регистровой пары R (d + 1): Rdl ← R (d + 1): Rd — K Z, C, N, V, S
SUBI Rd, К Вычитание константы из регистра Rd ← Rd — K Z, C, N, V, H
SBCI Rd, K Вычитание константы из регистра с заёмом Кд ← Кд — К — С Z, C, N, V, H
INC Rd Инкремент регистратор кр ← кр + 1 Z, N, V ​​
DEC Rd Декремент регистратор кр ← кр — 1 Z, N, V ​​
MUL Rd, Rr Умножение чисел без знака R1: R0 ← Rd * Rr Z, C
MULS Rd, Rr Умножение чисел со знаком R1: R0 ← Rd * Rr Z, C
MULSU Rd, Rr Умножение числа со знаком с номером без знака R1: R0 ← Rd * Rr Z, C
FMUL Rd, Rr Умножение дробных чисел без знака R1: R0 ← (Rd * Rr) << 1 Z, C
FMULS Rd, Rr Умножение дробных чисел со знаком R1: R0 ← (Rd * Rr) << 1 Z, C
FMULSU Rd, Rr Умножение дробного числа со знаком с номером без знака R1: R0 ← (Rd * Rr) << 1 Z, C
Мнемоника Описание Операция Флаги
CBR Rd, K Очистка разрядов регистратора Rd ← Rd и (0FFH — K) Z, N, V ​​
SBR Rd, K Установка разрядов регистра Rd ← Rd или K Z, N, V ​​
CBI P, b Сброс разряда I / O-регистратор с.б ← 0
ГБО П, б Установка разряда I / O-регистратор P.b ← 1
BCLR s Сброс флага SREG SREG.s ← 0 SREG.s
BSET с Установка флага SREG SREG.s ← 1 SREG.s
BLD Rd, b Загрузка разряда регистра из флага T Rd.б ← Т
БСТ рр, б Запись разряда регистра во флаг T T ← Rd.b т
CLC Сброс флага переноса К ← 0 С
СЕК Установка флага переноса К ← 1 С
CLN Сброс флага отрицательного числа N ← 0 N
SEN Установка флага отрицательного числа N ← 1 N
CLZ Сброс флага нуля Z ← 0 Z
ОЭЗ Установка флага нуля Z ← 1 Z
CLI Общий прерываний I ← 0 I
SEI Общее разрешение прерываний I ← 1 I
CLS Сброс флага знака S ← 0 S
СЭС Установка флага знака S ← 1 S
CLV Сброс флага переполнения дополнительного кода В ← 0 В
SEV Установка флага переполнения дополнительного кода В ← 1 В
CLT Сброс пользовательского флага T Т ← 0 т
НАБОР Установка пользовательского флага T Т ← 1 т
CLH Сброс флага половинного переноса H ← 0 H
SEH Установка флага половинного переноса H ← 1 H
Мнемоника Описание Операция Флаги
ASR Rd Арифметический сдвиг вправо Rd (i) ← Rd (i + 1) (n = 0..6), C ← Rd (0) Z, C, N, V ​​
LSL Rd Логический сдвиг влево Rd (i + 1) ← Rd (i), Rd (0) ← 0, C ← Rd (7) Z, C, N, V ​​
ЛСР Руд Логический сдвиг вправо Rd (i) ← Rd (i + 1), Rd (7) ← 0, C ← Rd (0) Z, C, N, V ​​
ROL Rd Сдвиг влево через перенос Rd (i + 1) ← Rd (i), Rd (0) ← C, C ← Rd (7) Z, C, N, V ​​
ROR Rd Сдвиг вправо через перенос Rd (i) ← Rd (i + 1), Rd (7) ← C, C ← Rd (0) Z, C, N, V ​​
SWAP Rd Обмен местами тетрад Рд (3..0) ↔ Rd (7..4)
Мнемоника Описание Операция Флаги
MOV Rd, Rr Пересылка между регистрами Rd ← Rr
MOVW Rd, Rr Пересылка между парами регистров R (d +1): Rd ← R (r + 1):
LDI Rd, K Загрузка константы в регистр Rd ← K
LD Rd, X Косвенное чтение Кр ← [X]
LD Rd, X + Косвенное чтение с пост-инкрементом Rd ← [X], X ← X + 1
LD Rd, -X Косвенное чтение с пред-декрементом X ← X — 1, Rd ← [X]
LD Rd, Y Косвенное чтение Rd ← [Y]
LD Rd, Y + Косвенное чтение с пост-инкрементом Rd ← [Y], Y ← Y + 1
LD Rd, -Y Косвенное чтение с пред-декрементом Y ← Y — 1, Rd ← [Y]
LDD Rd, Y + Q Косвенное чтение со смещением Rd ← [Y + q]
LD Rd, Z Косвенное чтение Rd ← [Z]
LD Rd, Z + Косвенное чтение с пост-инкрементом Rd ← [Z], Z ← Z + 1
LD Rd, -Z Косвенное чтение с пред-декрементом Z ← Z — 1, Rd ← [Z]
LDD Rd, Z + q Косвенное чтение со смещением Rd ← [Z + q]
LDS Rd, A Непосредственное чтение из ОЗУ Rd ← [A]
ST X, Rr Косвенная запись [X] ← Rr
ST X +, Rr Косвенная запись с пост-инкрементом [X] ← Rr, X ← X + 1
ST -X, Rr Косвенная запись с пред-декрементом X ← X — 1, [X] ← Rr
ST Y, Rr Косвенная запись [Y] ← Rr
ST Y +, Rr Косвенная запись с пост-инкрементом [Y] ← Rr, Y ← Y + 1
ST -Y, Rr Косвенная запись с пред-декрементом Y ← Y — 1, [Y] ← Rr
СТД Y + q, Rr Косвенная запись со смещением [Y + q] ←
ST Z, Rr Косвенная запись [Z] ← Rr
ST Z +, Rr Косвенная запись с пост-инкрементом [Z] ← Rr, Z ← Z + 1
ST -Z, Rr Косвенная запись с пред-декрементом Z ← Z — 1, [Z] ← Rr
STD Z + q, Rr Косвенная запись со смещением [Z + q] ←
СТС А, рр Непосредственная запись в ОЗУ [A] ← Rr
л / мин Загрузка данных из памяти программы R0 ← {Z}
л / мин Rd, Z Загрузка данных из памяти программы в регистр Rd ← {Z}
л / мин Rd, Z + Загрузка данных из памяти программы с пост-инкрементом Z Rd ← {Z}, Z ← Z + 1
SPM Запись в программную память {Z} ← R1: R0
IN Rd, P Пересылка из I / O-регистратора в регистр Кряд ← П
ВЫХ P, Rr Пересылка из регистра в I / O-регистр P ← руб
PUSH Rr Сохранение регистра в стеке СТЕК ← Rr
POP Rd Извлечение регистратора из стека Rd ← СТЕК
Мнемоника Описание Условие Флаги
BRBC s, A Переход если флаг S сброшен Если SREG (S) = 0
BRBS s, A Переход если флаг S установлен Если SREG (S) = 1
BRCS A Переход по переносу Если C = 1
BRCC A Переход если нет переноса Если C = 0
BREQ A Переход если равно Если Z = 1
BRNE A Переход если не равно Если Z = 0
БРШ А Переход если больше или равно Если C = 0
BRLO A Переход если меньше Если C = 1
BRMI А Переход если отрицательное значение Если N = 1
BRPL A Переход если положительное значение Если N = 0
BRGE A Переход если больше или равно (со знаком) Если (N и V) = 0
BRLT A Переход если меньше (со знаком) Если (N или V) = 1
BRHS A Переход по половинному переносу Если H = 1
BRHC A Переход если нет половинного переноса Если H = 0
БРТС А Переход если флаг T установлен Если T = 1
BRTC A Переход если флаг T сброшен Если T = 0
BRVS A Переход по переполнению дополнительного кода Если V = 1
BRVC A Переход если нет переполнения дополнительного кода Если V = 0
BRID A Переход если прерывания запрещены Если I = 0
BRIE A Переход если прерывания разрешены Если I = 1
SBRC Rd, K Пропустить другую команду, если бит в регистре очищен Если Rd [K] = 0
SBRS Rd, K Пропустить другую команду, если бит в регистре установлен Если Rd [K] = 1
SBIC A, b Пропустить регистровую команду если бит в ввод / вывод очищен Если ввод / вывод (A, b) = 0
СБИС А, б Пропустить другую команду, если бит в регистре ввода / вывода установлен Если ввод / вывод (A, b) = 1
.

Макрос в Ассемблер на примере AVR

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

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

Запись Макроса

Макрос на языке программирования Assembler состоит из следующих частей:

  1. .macro — директива начала макроса, после объявления макроса в той же строке объявляется имя этого макроса;
  2. .конец — директива окончания макроса;
  3. @ 0 — запись вариантов;
  4. Тело макроса .

Запись макроса для микроконтроллера AVR может выглядеть следующим образом:

 ; Макрос вывода в порт или регистр порта
 .macro outi; Директива макроса с объявлением макроса
    ldi r16, @ 1; Установка значений в регистр
    out @ 0, R16; Вывод данных в порт или регистр порта
 .endm; Окончание макроса 

Применение данного Макроса будет выглядеть следующим образом:

 outi DDRB, 0b01111111; Инициализация вывода столбцов развертки индикации
 outi DDRC, 0b00111111; Инициализация вывода строк развертки индикации
 outi DDRD, 0b00000001; Инициализация вывода для активации сигнала будильника
 outi PORTD, 0b11111111; Инициализация PD1 - PD7 в режиме PullUp 

В данном случае Макрос вызывается по своему имени outi. На первом месте @ 0 находится регистр DDRB, а на втором @ 1 находится переменная 0b01111111 , которая записывается в данный регистр. То есть фактически применение макроса выглядит следующим образом:

outi @ 0, @ 1, @ 2 ... @N 

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

.

Оставить комментарий

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