Avr работа с портами ассемблер – AVR для начинающих.Порты микроконтроллера | MKPROG.RU

AVR. Учебный курс. Работа с портами ввода-вывода. Практика

Вот ты читаешь сейчас это и думаешь — память, регистры, стек и прочее это хорошо. Но ведь это не пощупать, не увидеть. Разве что в симуляторе, но я и на дельфи с тем же условием могу накодить. Где мясо!!!
 
В других курсах там, чуть ли не с первых строк, делают что то существенное — диодиком мигают и говорят, что это наш Hello World. А тут? Гыде???
 

Да-да-да, я тебя понимаю. Более того, наверняка ты уже сбегал к конкурентам и помигал у них диодиком ;)))) Ничего, простительно.
 

Я просто не хотел на этом же мигании дидодиков и остановиться, а для прогресса нужно четкое понимание основ и принципов — мощная теоретическая база. Но вот пришла очередь практики.
 

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

Инструментарий
Работа с портами, обычно, подразумевает работу с битами. Это поставить бит, сбросить бит, инвертировать бит. Да, конечно, в ассемблере есть удобные команды
 

cbi/sbi, но работают они исключительно в малом адресном диапазоне (от 0 до 1F, поэтому давайте сначала напишем универсальные макросы, чтобы в будущем применять их и не парить мозг насчет адресного пространства.
 

Макросы будут зваться:
 

  • SETB byte,bit,temp
  • CLRB byte,bit,temp
  • INVB byte,bit,temp,temp2

 

Причем при работе с битами младших РВВ (0-1F адрес) то значение параметра TEMP можно и не указывать — он все равно подставляться не будет. За исключением команд инверсии — там промежуточные регистры полюбому нужны будут.
 

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

  • SETBM byte,bit
  • CLRBM byte,bit
  • INVBM byte,bit

 

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

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
;= Start macro.inc ========================================
;SET BIT with stack
	.MACRO	SETBM 
	.if @0 < 0x20
	SBI	@0,@1
	.else
		.if @0<0x40
	PUSH	R17
	IN	R17,@0
	ORI	R17,1<<@1
	OUT	@0,R17
	POP	R17
		.else
	PUSH	R17
	LDS	R17,@0
	ORI	R17,1<<@1
	STS	@0,R17
	POP	R17
		.endif
	.endif
	.ENDM
 
;SET BIT with REG
	.MACRO	SETB
	.if @0 < 0x20			; Low IO
	SBI	@0,@1
	.else
		.if @0<0x40		; High IO
	IN	@2,@0
	ORI	@2,1<<@1
	OUT	@0,@2
		.else			; Memory
	LDS	@2,@0
	ORI	@2,1<<@1
	STS	@0,@2
		.endif
	.endif
	.ENDM
;.............................................................
;Clear BIT with REG
	.MACRO	CLRB
	.if @0 < 0x20			; Low IO
	CBI	@0,@1
	.else
		.if @0<0x40		; High IO
	IN	@2,@0
	ANDI	@2,~(1<<@1)
	OUT	@0,@2
		.else			; Memory
	LDS	@2,@0
	ANDI	@2,~(1<<@1)
	STS	@0,@2
		.endif
	.endif
	.ENDM
 
;Clear BIT with STACK
	.MACRO	CLRBM 
	.if @0 < 0x20
	CBI	@0,@1
	.else
		.if @0<0x40
	PUSH	R17
	IN	R17,@0
	ANDI	R17,~(1<<@1)
	OUT	@0,R17
	POP	R17
		.else
	PUSH	R17
	LDS	R17,@0
	ANDI	R17,~(1<<@1)
	STS	@0,R17
	POP	R17
		.endif
	.endif
	.ENDM
;.............................................................
 
	.MACRO	INVB
	.if	@0 < 0x40
	IN	@2,@0
	LDI	@3,1<<@1
	EOR	@3,@2
	OUT	@0,@3
	.else
	LDS	@2,@0
	LDI	@3,1<<@1
	EOR	@2,@3
	STS	@0,@2
	.endif
	.ENDM
 
	.MACRO	INVBM
	.if	@0 < 0x40
	PUSH	R16
	PUSH	R17
	IN	R16,@0
	LDI	R17,1<<@1
	EOR	R17,R16
	OUT	@0,R17
	POP	R17
	POP	R16
	.else
	PUSH	R16
	PUSH	R17
	LDS	R16,@0
	LDI	R17,1<<@1
	EOR	R17,R16
	STS	@0,R17
	POP	R17
	POP	R16
	.endif
	.ENDM
 
;= End macro.inc ========================================

;= Start macro.inc ========================================
;SET BIT with stack
.MACRO SETBM
.if @0 < 0x20
SBI @0,@1
.else
.if @0<0x40
PUSH R17
IN R17,@0
ORI R17,1<<@1
OUT @0,R17
POP R17
.else
PUSH R17
LDS R17,@0
ORI R17,1<<@1
STS @0,R17
POP R17
.endif
.endif
.ENDM

;SET BIT with REG
.MACRO SETB
.if @0 < 0x20 ; Low IO
SBI @0,@1
.else
.if @0<0x40 ; High IO
IN @2,@0
ORI @2,1<<@1
OUT @0,@2
.else ; Memory
LDS @2,@0
ORI @2,1<<@1
STS @0,@2
.endif
.endif
.ENDM
;…………………………………………………….
;Clear BIT with REG
.MACRO CLRB
.if @0 < 0x20 ; Low IO
CBI @0,@1
.else
.if @0<0x40 ; High IO
IN @2,@0
ANDI @2,~(1<<@1)
OUT @0,@2
.else ; Memory
LDS @2,@0
ANDI @2,~(1<<@1)
STS @0,@2
.endif
.endif
.ENDM

;Clear BIT with STACK
.MACRO CLRBM
.if @0 < 0x20
CBI @0,@1
.else
.if @0<0x40
PUSH R17
IN R17,@0
ANDI R17,~(1<<@1)
OUT @0,R17
POP R17
.else
PUSH R17
LDS R17,@0
ANDI R17,~(1<<@1)
STS @0,R17
POP R17
.endif
.endif
.ENDM
;…………………………………………………….

.MACRO INVB
.if @0 < 0x40
IN @2,@0
LDI @3,1<<@1
EOR @3,@2
OUT @0,@3
.else
LDS @2,@0
LDI @3,1<<@1
EOR @2,@3
STS @0,@2
.endif
.ENDM

.MACRO INVBM
.if @0 < 0x40
PUSH R16
PUSH R17
IN R16,@0
LDI R17,1<<@1
EOR R17,R16
OUT @0,R17
POP R17
POP R16
.else
PUSH R16
PUSH R17
LDS R16,@0
LDI R17,1<<@1
EOR R17,R16
STS @0,R17
POP R17
POP R16
.endif
.ENDM

;= End macro.inc ========================================

 

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

Но вернемся к коду,
Мигнем уж светодиодиком то, наконец?
 

Да не вопрос. На демоплате уже смонтированы светодиоды, почему бы их не заюзать? Они висят на выводах порта PD4,PD5, PD7. Надо только одеть джамперы.
 

Для Pinboard II с модулем AVR джамперы находятся на самом модуле. Вот тут:

 

Сперва настроим эти порты на выход, для этого надо записать в регистр DDR единичку (вспоминаем статью про порты ввода вывода)
 

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

1
2
3
4
5
6
7
; Internal Hardware Init  ======================================
 
	SETB	DDRD,4,R16	; DDRD.4 = 1
	SETB	DDRD,5,R16	; DDRD.5 = 1
	SETB	DDRD,7,R16	; DDRD.7 = 1
 
; End Internal Hardware Init ===================================

; Internal Hardware Init ======================================

SETB DDRD,4,R16 ; DDRD.4 = 1
SETB DDRD,5,R16 ; DDRD.5 = 1
SETB DDRD,7,R16 ; DDRD.7 = 1

; End Internal Hardware Init ===================================

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

1
2
3
4
5
; Main =========================================================
Main:	SETB	PORTD,4,R16	; Зажгли LED1
	SETB	PORTD,7,R16	; Зажгли LED3
	JMP	Main
; End Main =====================================================

; Main =========================================================
Main: SETB PORTD,4,R16 ; Зажгли LED1
SETB PORTD,7,R16 ; Зажгли LED3
JMP Main
; End Main =====================================================

 

Компилиуем, можешь в трассировщике прогнать, сразу увидишь как меняются биты. Прошиваем… и после нажатия на RESET и выгрузки bootloader (если конечно у тебя Pinboard) увидишь такую картину:
 


 

И для версии II
 


 

Во! Тока это же скучно. Давай ка ими помигаем.
 

Заменим всего лишь наши макрокоманды.
 

1
2
3
4
5
; Main =========================================================
Main:	SETB	PORTD,4,R16		; Зажгли LED1
	INVB	PORTD,7,R16,R17		; Инвертировали LED3
	JMP	Main
; End Main =====================================================

; Main =========================================================
Main: SETB PORTD,4,R16 ; Зажгли LED1
INVB PORTD,7,R16,R17 ; Инвертировали LED3
JMP Main
; End Main =====================================================

Зажгли, прошили…

А вот фиг — горят оба, но один чуть-чуть тусклей. На самом деле он мерцает, но очень очень быстро. Если ткнуть осциллографом в вывод PD7, то будет видно, что уровень меняется там с бешеной частотой:
 

 

Что делать? Очевидно замедлить. Как? Самый простой способ, который практикуют в подавляющем большинстве обучалок и быстрых стартов — тупой задержкой. Т.е. получают код вида:

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
; Main =========================================================
Main:	SETB	PORTD,4,R16		; Зажгли LED1
	INVB	PORTD,7,R16,R17		; Инвертировали LED3
 
	RCALL 	Delay	
 
	JMP	Main
; End Main =====================================================
 
; Procedure ====================================================
 
.equ 	LowByte  = 255
.equ	MedByte  = 255
.equ	HighByte = 255
 
Delay:	LDI	R16,LowByte	; Грузим три байта
	LDI	R17,MedByte	; Нашей выдержки
	LDI	R18,HighByte
 
loop:	SUBI	R16,1		; Вычитаем 1
	SBCI	R17,0		; Вычитаем только С
	SBCI	R18,0		; Вычитаем только С
 
	BRCC	Loop 		; Если нет переноса - переход
	RET
; End Procedure ================================================

; Main =========================================================
Main: SETB PORTD,4,R16 ; Зажгли LED1
INVB PORTD,7,R16,R17 ; Инвертировали LED3

RCALL Delay

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

; Procedure ====================================================

.equ LowByte = 255
.equ MedByte = 255
.equ HighByte = 255

Delay: LDI R16,LowByte ; Грузим три байта
LDI R17,MedByte ; Нашей выдержки
LDI R18,HighByte

loop: SUBI R16,1 ; Вычитаем 1
SBCI R17,0 ; Вычитаем только С
SBCI R18,0 ; Вычитаем только С

BRCC Loop ; Если нет переноса — переход
RET
; End Procedure ================================================

Прошили, запустили… О да, теперь мигание будет заметно.
 

При параметрах 255.255.255 длительность выдержки на 8Мгц будет около 2.1 секунды. Можно увеличить разрядность задержки еще на несколько байт. Тогда можно хоть час зарядить.
 

Но этот метод ущербен, сейчас покажу почему.
 

Давай добавим кнопку. LED3 пусть мигает в инверсии. А мы сделаем так, что когда кнопка нажата у нас горит LED1, а когда отпущена горит LED2.
 

В качестве кнопки возьмем тактовую A, подключим ее к порту PD6.
 

Для версии II аналогично. Только кнопку возьмем с группы кнопок и соединим вывод PD6 контроллера с штырем COL1 на кнопочной панели.


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

Проверка кнопки делается командой SBIC, но вначале ее надо инициализировать. Сделать DDR=0, PORT=1 — вход с подтяжкой.
 

Добавляем в секцию инициализации (Internal Hardware Init) эти строчки:
 

1
2
	SETB	PORTD,6,R16
	CLRB	DDRD,6,R16

SETB PORTD,6,R16
CLRB DDRD,6,R16

Усе, все настроено как надо 🙂
 

Осталось теперь проверить нажата ли кнопка или нет. Если нажата, то в бите PIND.6 будет 0.
 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
; Main =========================================================
Main:		SBIS	PIND,6			; Если кнопка нажата - переход
		RJMP	BT_Push
 
		SETB	PORTD,5			; Зажгем LED2
		CLRB	PORTD,4			; Погасим LED1
 
Next:		INVB	PORTD,7,R16,R17		; Инвертировали LED3
 
		RCALL 	Delay	
 
		JMP	Main
 
 
BT_Push:	SETB	PORTD,4			; Зажгем LED1
		CLRB	PORTD,5			; Погасим LED2
 
		RJMP	Next
 
; End Main =====================================================

; Main =========================================================
Main: SBIS PIND,6 ; Если кнопка нажата — переход
RJMP BT_Push

SETB PORTD,5 ; Зажгем LED2
CLRB PORTD,4 ; Погасим LED1

Next: INVB PORTD,7,R16,R17 ; Инвертировали LED3

RCALL Delay

JMP Main


BT_Push: SETB PORTD,4 ; Зажгем LED1
CLRB PORTD,5 ; Погасим LED2

RJMP Next

; End Main =====================================================

Ну чо, работает. Кнопочка жмется — диодики меняются. Третий же бодро подмигивает. Но есть западло:
 


 

Торомозит программка то! Я кнопочку нажал, а картинка не сменилась, нужно подождать, подержать… Почему? А это из-за нашего быдлокодинга.
 

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

Шарманка
Сейчас я тебе покажу как можно сделать цифровую шарманку. Помнишь как она устроена?
 

Там барабан с торчащими гвоздями и пружинки на разные тона. Гвозди вращаются, дергают пружинки — они звякают. Получается расколбасный музон. А что если нашу шарманку развернуть в ленту. Не правда ли гвозди похожи на единички ? ;))))
 

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

Совпало? Делаем «ДРЫНЬ!»
 

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

Скажем, считает шарманка от 0 до 1000, а нам надо 10 раз мигнуть диодом. Не обязательно втыкать 10 обработчиков с разными значениями. Достаточно одного, но чтобы он ловил значение **10. Все, остальное нам не важно. И сработает он на 0010, 0110, 0210, 0310, 0410, 0510, 0610, 0710, 0810, 0910. Более частые интервалы также делятся как нам надо, достаточно влезть в другой разряд. Тут надо только не забыть отрезать нафиг старшие разряды, чтобы не мешались.
 

А учитывая что считать мы там будем в двоичном системе, то ловить кодовую посылку станет еще приятней — хлоп ее по битмаске и все. Даже проверять все байты не нужно.
 

Приступим. Вначале создадим наш счетчик в сегменте данных:

1
2
3
; RAM ========================================================
		.DSEG
CCNT:	.byte	4

; RAM ========================================================
.DSEG
CCNT: .byte 4

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

1
2
3
4
		LDS	R16,CCNT
		LDS	R17,CCNT+1
		LDS	R18,CCNT+2
		LDS	R19,CCNT+3

LDS R16,CCNT
LDS R17,CCNT+1
LDS R18,CCNT+2
LDS R19,CCNT+3

Все, теперь в R16 самый младший байт нашего счетчика, а в R19 самый старший.
 

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

Дальше надо к этому четырехбайтному числу прибавить 1. Да так, чтобы учесть все переполнения.
 

По можно сделать так:
 

1
2
3
4
5
6
7
		LDI	R20,1		; Нам нужна единичка
		CLR	R15		; А еще нолик.
 
		ADD	R16,R20	; Прибавляем 1 если в регистре 255, то будет С
		ADC	R17,R15	; Прибавляем 0+С 
		ADC	R18,R15	; Прибавляем 0+С
		ADC	R19,R15	; Прибавляем 0+С

LDI R20,1 ; Нам нужна единичка
CLR R15 ; А еще нолик.

ADD R16,R20 ; Прибавляем 1 если в регистре 255, то будет С
ADC R17,R15 ; Прибавляем 0+С
ADC R18,R15 ; Прибавляем 0+С
ADC R19,R15 ; Прибавляем 0+С

Пришлось потратить еще два регистра на хранение констант нашего сложения. Все от того, что AVR не умеет складывать регистры с непосредственным числом. Зато умеет вычитать.
 

Я уже показывал, что R-(-1)=R+1, а ведь никто не запрещает нам этот же прием устроить и тут — сделать сложение через вычитание.
 

1
2
3
4
		SUBI	R16,(-1)	
		SBCI	R17,(-1)
		SBCI	R18,(-1)
		SBCI	R19,(-1)

SUBI R16,(-1)
SBCI R17,(-1)
SBCI R18,(-1)
SBCI R19,(-1)

Даст нам инкремент четырехбайтного числа R19:R18:R17:R16
 

А теперь я вам покажу немного целочисленной магии
Почему это будет работать? Смотри сам:
 

SUBI R16,(-1) это, по факту R16 — 255 и почти при всех раскладах она нам даст нам заём из следующего разряда — С. А в регистре останется то число на которое больше.
 

Т.е. смотри как работает эта математика, вспомним про число в доп кодах. Покажу на четырехрязрядном десятичном примере. У Нас есть ВСЕГО ЧЕТЫРЕ РАЗРЯДА, ни больше ни меньше. Отрицательное число это 0-1 так? Окей.
 

1С 0000-1=9999+C
 

Т.е. мы как бы взяли как бы из пятиразрядной 1С 0000 отняли 1, но разрядов то у нас всего четыре! Получили доп код 9999 и флаг заема С (сигнализирующий о том, что был заем)
 

Т.е. в нашей целочисленной математике 9999=-1 🙂 Проверить легко -1+1 = 0 Верно?
 

9999+1 = 1С 0000 Верно! :))) А 1 старшего разряда банально не влезла в разрядность и ушла в флаг переноса C, сигнализирующего еще и о переполнении.
 

Оки, а теперь возьмем и сделаем R-(-1). Пусть R=4
 

1С0004-9999 = 0005+С
 

Вот так вот взяли и сложили через вычитание. Просто магия, да? 😉
 

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

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

Флаг С не выскочит лишь когда у нас дотикает до 255 (9999 в десятичном примере), тогда будет 255-255 = 0 и вскочит лишь Z, но нам он не нужен.
 

А дальше, вторая команда работает также, только с учетом знака
 

Т.е. R17-(-1)-1С, а поскольку С у нас всегда (кроме последнего случая перед переполнением), то до тех пор пока значение R16 не достигнет 255 (и флаг C не пропадет) у нас будет R17+1-1С=R17. Как только флаг пропадет, тогда и случится инкремент второго байта R17+1-0С. А там по цепочке и все остальные.
 

Короче, хоть 100 байтную переменную создавай и таким образом ее прощелкивай.
 

А потом выгружаешь регистры обратно в память:
 

1
2
3
4
		STS	CCNT,R16
		STS	CCNT+1,R17
		STS	CCNT+2,R18
		STS	CCNT+3,R19

STS CCNT,R16
STS CCNT+1,R17
STS CCNT+2,R18
STS CCNT+3,R19

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
		.MACRO	INCM
		LDS	R16,@0
		LDS	R17,@0+1
		LDS	R18,@0+2
		LDS	R19,@0+3
 
		SUBI	R16,(-1)
		SBCI	R17,(-1)
		SBCI	R18,(-1)
		SBCI	R19,(-1)
 
		STS	@0,R16
		STS	@0+1,R17
		STS	@0+2,R18
		STS	@0+3,R19
		.ENDM

.MACRO INCM
LDS R16,@0
LDS R17,@0+1
LDS R18,@0+2
LDS R19,@0+3

SUBI R16,(-1)
SBCI R17,(-1)
SBCI R18,(-1)
SBCI R19,(-1)

STS @0,R16
STS @0+1,R17
STS @0+2,R18
STS @0+3,R19
.ENDM

 

Теперь осталось прикинуть как будет мигать наш диодик по шарманке. Для этого давай посчитаем длительность цикла.

1
2
3
4
5
6
7
8
9
; Main =========================================================
 
Main:	SETB	PORTD,4		; Зажгли LED1
 
 
	INVB	PORTD,7,R16,R17	; Инвертировали LED3
 
Next:	INCM	CCNT
	JMP	Main

; Main =========================================================

Main: SETB PORTD,4 ; Зажгли LED1


INVB PORTD,7,R16,R17 ; Инвертировали LED3

Next: INCM CCNT
JMP Main

 

Запусти режим отладки и поставь точку останова (F9) на метку Main и загони курсор на первый брейкпоинт.
 

Увеличить
 

Сбрось StopWatch и Cycle Counter, а потом сделай одну итерацию цикла. У меня студия показала 25 машинных циклов и 3.3 микросекунды времени. Это длительность одной итерации.
 

Теперь посчитаем сколько нам надо таких итераций на секунду. 1/0.75E-6 = 303 030 оборота. Что дофига 🙂 Посчитаем это в байтах нашей шарманки, просто переведем в 16тиричную систему. Получим 0х049FB6 или, если побайтно, то
 

1
2
3
4
0хB6(CCNT)
0х9F(CCNT+1)
0х04(CCNT+2)
0x00(CCNT+3)

0хB6(CCNT)
0х9F(CCNT+1)
0х04(CCNT+2)
0x00(CCNT+3)

Осталось теперь только сравнить число с этим слепком.
 

Как сравнивать? Да довольно просто. Все зависит от того что мы хотим получить. Если ОДНО событие за ВЕСЬ период глобального счетчика нашей шарманки, то тупо, побайтно. В этом случае у тебя диодик моргнет через секунду после старта, а дальше ты будешь ждать пол часа до переполнения всего четырехбайтного счетчика.
 

Чтобы он моргал каждую секунду тебе надо при сравнении замаскировать старшие биты глобального счетчика, словно у него разрядность не 32 бита, а меньше (и переполняется он чаще).
 

Младшие байты сравниваем как есть, а самый старший только до его максимального разряда, остальные надо отрезать.
 

Т.е. самый старший разряд для этого случая это CCNT+2=0х04 если в двоичном представлении то 0х04 = 00000100 так вот, счетчик у нас четырех разрядный, значит событие с маской
 

00 04 9F B6 (00000000 00000100 10011111 10110110)

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

Их еще надо заполнить до переполнения и обнуления счетчика. Но если мы их замаскируем то их дальнейшая судьба нас не волнует. Пусть хоть до второго пришествия тикает — нам плевать.
 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
		LDS	R16,CCNT	; Грузим числа в регистры
		LDS	R17,CCNT+1
		LDS	R18,CCNT+2
		ANDI	R18,0x07	; Накладываем маску
 
		CPI	R16,0xB6	; Сравниванем побайтно
		BRNE	NoMatch
		CPI	R17,0x9F
		BRNE	NoMatch
		CPI	R18,0x04
		BRNE	NoMatch
 
; Если совпало то делаем экшн
Match:		INVB	PORTD,7,R16,R17	; Инвертировали LED3	
 
; Не совпало - не делаем :) 
NoMatch:
 
Next:		INCM	CCNT		; Проворачиваем шарманку
		JMP	Main

LDS R16,CCNT ; Грузим числа в регистры
LDS R17,CCNT+1
LDS R18,CCNT+2
ANDI R18,0x07 ; Накладываем маску

CPI R16,0xB6 ; Сравниванем побайтно
BRNE NoMatch
CPI R17,0x9F
BRNE NoMatch
CPI R18,0x04
BRNE NoMatch

; Если совпало то делаем экшн
Match: INVB PORTD,7,R16,R17 ; Инвертировали LED3

; Не совпало — не делаем 🙂
NoMatch:

Next: INCM CCNT ; Проворачиваем шарманку
JMP Main

 

Во, загрузили теперь мигает. Никаких затупов нет, нигде ничего не подвисает, а главный цикл пролетает со свистом, только успевай барабан шарманки проворачивать 🙂
 

Вот только мигает заметно медленней чем мы хотели. Не 1 секунда, а 8. Ну а что ты хотел — добавив процедуру сравнения мы удлиннили цикл еще на несколько команд. И теперь он выполняется не 25 тактов, а 36. Пересчитывай все циферки заново :)))))
 

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

А если код будет еще больше, то ваще труба и погрешность накапливается с каждой итерацией!
 

Зато, если добавить код обработки кнопок:

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
; Main =========================================================
Main:		SBIS	PIND,6		; Если кнопка нажата - переход
		RJMP	BT_Push
 
		SETB	PORTD,5	; Зажгем LED2
		CLRB	PORTD,4	; Погасим LED1
 
Next:		LDS	R16,CCNT	; Грузим числа в регистры
		LDS	R17,CCNT+1
		LDS	R18,CCNT+2
		ANDI	R18,0x07
 
		CPI	R16,0xB6	; Сравниванем побайтно
		BRNE	NoMatch
		CPI	R17,0x9F
		BRNE	NoMatch
		CPI	R18,0x04
		BRNE	NoMatch
 
; Если совпало то делаем экшн
Match:		INVB	PORTD,7,R16,R17	; Инвертировали LED3	
 
; Не совпало - не делаем :) 
NoMatch:	NOP
 
 
		INCM	CCNT
		JMP	Main
 
 
BT_Push:	SETB	PORTD,4	; Зажгем LED1
		CLRB	PORTD,5	; Погасим LED2
		RJMP	Next
 
; End Main =====================================================

; Main =========================================================
Main: SBIS PIND,6 ; Если кнопка нажата — переход
RJMP BT_Push

SETB PORTD,5 ; Зажгем LED2
CLRB PORTD,4 ; Погасим LED1

Next: LDS R16,CCNT ; Грузим числа в регистры
LDS R17,CCNT+1
LDS R18,CCNT+2
ANDI R18,0x07

CPI R16,0xB6 ; Сравниванем побайтно
BRNE NoMatch
CPI R17,0x9F
BRNE NoMatch
CPI R18,0x04
BRNE NoMatch

; Если совпало то делаем экшн
Match: INVB PORTD,7,R16,R17 ; Инвертировали LED3

; Не совпало — не делаем 🙂
NoMatch: NOP


INCM CCNT
JMP Main


BT_Push: SETB PORTD,4 ; Зажгем LED1
CLRB PORTD,5 ; Погасим LED2
RJMP Next

; End Main =====================================================

 
То увидим, что от тормозов и следов не осталось. Кнопки моментально реагируют на нажатия, а диодик мигает сам по себе. Многозадачность! 🙂
 

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

 
Например, периодически сканировать клавиатуру, скажем, каждые 2048 оборотов главного цикла. Сам прикинь какое число надо нагрузить на сравнение и какую маску наложить 🙂

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

 
А для точных вычислений времени существуют таймеры. Но о них разговор отдельный.

easyelectronics.ru

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

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

МнемоникаОписаниеОперацияФлаги
ADD Rd, RrСложение двух регистровRd ← Rd + RrZ, C, N, V, H
ADC Rd, RrСложение двух регистров с переносомRd ← Rd + Rr + СZ, C, N, V, H
ADIW Rd, KСложение регистровой пары с константойR(d+1):Rd ← R(d+1):Rd + KZ, C, N, V, S
SUB Rd, RrВычитание двух регистровRd ← Rd — RrZ, C, N, V, H
SUBI Rd, KВычитание константы из регистраRd ← Rd — KZ, C, N, V, H
SBC Rd, RrВычитание двух регистров с заёмомRd ← Rd — Rr — СZ, C, N, V, H
SBCI Rd, KВычитание константы из РОН с заёмомRd ← Rd — K — СZ, C, N, V, H
SBIW Rd, KВычитание константы из регистровой парыR(d+1):Rdl ← R(d+1):Rd — KZ, C, N, V, S
DEC RdДекремент регистраRd ← Rd – 1Z, N, V
INC RdИнкремент регистраRd ← Rd + 1Z, N, V
MUL Rd, RrУмножение чисел без знакаR1:R0 ← Rd * RrZ, C
MULS Rd, RrУмножение чисел со знакомR1:R0 ← Rd * RrZ, C
MULSU Rd, RrУмножение числа со знаком с числом без знакаR1:R0 ← Rd * RrZ, C
FMUL Rd, RrУмножение дробных чисел без знакаR1:R0 ← (Rd * Rr)Z, C
FMULS Rd, RrУмножение дробных чисел со знакомR1:R0 ← (Rd * Rr)Z, C
FMULSU Rd, RrУмножение дробного числа со знаком с числом без знакаR1:R0 ← (Rd * Rr)Z, C
МнемоникаОписаниеОперацияФлаги
CBR Rd, KОчистка разрядов регистраRd ← Rd and (0FFH – K)Z, N, V
SBR Rd, KУстановка разрядов регистраRd ← Rd or KZ, N, V
CBI P, bСброс разряда I/O-регистраP.b ← 0
SBI P, bУстановка разряда I/O-регистраP.b ← 1
BCLR sСброс флага SREGSREG.s ← 0SREG.s
BSET sУстановка флага SREGSREG.s ← 1SREG.s
BLD Rd, bЗагрузка разряда регистра из флага TRd.b ← T
BST Rr, bЗапись разряда регистра во флаг TT ← Rd.bT
CLCСброс флага переносаC ← 0C
SECУстановка флага переносаC ← 1C
CLNСброс флага отрицательного числаN ← 0N
SENУстановка флага отрицательного числаN ← 1N
CLZСброс флага нуляZ ← 0Z
SEZУстановка флага нуляZ ← 1Z
CLIОбщий запрет прерыванийI ← 0I
SEIОбщее разрешение прерыванийI ← 1I
CLSСброс флага знакаS ← 0S
SESУстановка флага знакаS ← 1S
CLVСброс флага переполнения дополнительного кодаV ← 0V
SEVУстановка флага переполнения дополнительного кодаV ← 1V
CLTСброс пользовательского флага TT ← 0T
SETУстановка пользовательского флага TT ← 1T
CLHСброс флага половинного переносаH ← 0H
SEHУстановка флага половинного переносаH ← 1H
МнемоникаОписаниеОперацияФлаги
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
LSR RdЛогический сдвиг вправо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Обмен местами тетрадRd(3..0) ↔ Rd(7..4)
МнемоникаОписаниеОперацияФлаги
MOV Rd, RrПересылка между регистрамиRd ← Rr
MOVW Rd, RrПересылка между парами регистровR(d +1):Rd ← R(r+1):Rr
LDI Rd, KЗагрузка константы в регистрRd ← K
LD Rd, XКосвенное чтениеRd ← [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]
LD 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]
LD 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
ST Y+q, RrКосвенная запись со смещением[Y+q] ← Rr
ST Z, RrКосвенная запись[Z] ← Rr
ST Z+, RrКосвенная запись с пост-инкрементом[Z] ← Rr, Z ← Z + 1
ST -Z, RrКосвенная запись с пред-декрементомZ ← Z — 1, [Z] ← Rr
ST Z+q, RrКосвенная запись со смещением[Z+q] ← Rr
STS A, RrНепосредственная запись в ОЗУ[A] ← Rr
LPMЗагрузка данных из памяти программыR0 ← {Z}
LPM Rd, ZЗагрузка данных из памяти программы в регистрRd ← {Z}
LPM Rd, Z+Загрузка данных из памяти программы с пост-инкрементом ZRd ← {Z}, Z ← Z + 1
SPMЗапись в программную память{Z} ← R1:R0
IN Rd, PПересылка из I/O-регистра в регистрRd ← P
OUT P, RrПересылка из регистра в I/O-регистрP ← Rr
PUSH RrСохранение регистра в стекеSTACK ← Rr
POP RdИзвлечение регистра из стекаRd ← STACK
МнемоникаОписаниеУсловиеФлаги
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
BRSH AПереход если больше или равноЕсли C = 0
BRLO AПереход если меньшеЕсли C = 1
BRMI AПереход если отрицательное значениеЕсли N = 1
BRPL AПереход если положительное значениеЕсли N = 0
BRGE AПереход если больше или равно (со знаком)Если (N и V) = 0
BRLT AПереход если меньше (со знаком)Если (N или V) = 1
BRHS AПереход по половинному переносуЕсли H = 1
BRHC AПереход если нет половинного переносаЕсли H = 0
BRTS AПереход если флаг 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

trolsoft.ru

AVR. Учебный курс. Устройство и работа портов ввода-вывода

С внешним миром микроконтроллер общается через порты ввода вывода. Схема порта ввода вывода указана в даташите:

Но новичку там разобраться довольно сложно. Поэтому я ее несколько упростил:

Итак, что же представляет собой один вывод микроконтроллера. Вначале на входе стоит небольшая защита из диодов, она призвана защитить ввод микроконтроллера от превышения напряжения. Если напряжение будет выше питания, то верхний диод откроется и это напряжение будет стравлено на шину питания, где с ним будет уже бороться источник питания и его фильтры. Если на ввод попадет отрицательное (ниже нулевого уровня) напряжение, то оно будет нейтрализовано через нижний диод и погасится на землю. Впрочем, диоды там хилые и защита эта помогает только от микроскопических импульсов от помех. Если же ты по ошибке вкачаешь в ножку микроконтроллера вольт 6-7 при 5 вольтах питания, то никакой диод его не спасет.

Конденсатор, нарисованный пунктиром, это паразитная емкость вывода. Хоть она и крошечная, но присутствует. Обычно ее не учитывают, но она есть. Не забивай голову, просто знай это, как нибудь я тебе даже покажу как её можно применить 😉

Дальше идут ключи управления. Это я их нарисовал рубильниками, на самом деле там стоят полевые транзисторы, но особой сути это не меняет. А рубильники наглядней.
Каждый рубильник подчинен логическому условию которое я подписал на рисунке. Когда условие выполняется — ключ замыкается. PIN, PORT, DDR это регистры конфигурации порта.

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

Например, смотри в даташите на цоколевку микросхемы:

Видишь у каждой почти ножки есть обозначение Pxx. Например, PB4 где буква «B» означает имя порта, а цифра — номер бита в порту. За порт «B» отвечают три восьмиразрядных регистра PORTB, PINB, DDRB, а каждый бит в этом регистре отвечает за соответствующую ножку порта. За порт «А» таким же образом отвечают PORTA, DDRA, PINA.

PINх
Это регистр чтения. Из него можно только читать. В регистре PINx содержится информация о реальном текущем логическом уровне на выводах порта. Вне зависимости от настроек порта. Так что если хотим узнать что у нас на входе — читаем соответствующий бит регистра PINx Причем существует две границы: граница гарантированного нуля и граница гарантированной единицы — пороги за которыми мы можем однозначно четко определить текущий логический уровень. Для пятивольтового питания это 1.4 и 1.8 вольт соответственно. То есть при снижении напряжения от максимума до минимума бит в регистре PIN переключится с 1 на 0 только при снижении напруги ниже 1.4 вольт, а вот когда напруга нарастает от минимума до максимума переключение бита с 0 на 1 будет только по достижении напряжения в 1.8 вольта. То есть возникает гистерезис переключения с 0 на 1, что исключает хаотичные переключения под действием помех и наводок, а также исключает ошибочное считывание логического уровня между порогами переключения.

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

DDRx
Это регистр направления порта. Порт в конкретный момент времени может быть либо входом либо выходом (но для состояния битов PIN это значения не имеет. Читать из PIN реальное значение можно всегда).

  • DDRxy=0 — вывод работает как ВХОД.
  • DDRxy=1 вывод работает на ВЫХОД.

PORTx
Режим управления состоянием вывода. Когда мы настраиваем вывод на вход, то от PORT зависит тип входа (Hi-Z или PullUp, об этом чуть ниже).
Когда ножка настроена на выход, то значение соответствующего бита в регистре PORTx определяет состояние вывода. Если PORTxy=1 то на выводе лог1, если PORTxy=0 то на выводе лог0.
Когда ножка настроена на вход, то если PORTxy=0, то вывод в режиме Hi-Z. Если PORTxy=1 то вывод в режиме PullUp с подтяжкой резистором в 100к до питания.

Есть еще бит PUD (PullUp Disable) в регистре SFIOR он запрещает включение подтяжки сразу для всех портов. По дефолту он равен 0. Честно говоря, я даже не знаю нафиг он нужен — ни разу не доводилось его применять и даже не представляю себе ситуацию когда бы мне надо было запретить использование подтяжки сразу для всех портов. Ну да ладно, инженерам Atmel видней, просто знай что такой бит есть. Мало ли, вдруг будешь чужую прошивку ковырять и увидишь что у тебя подтяжка не работает, а вроде как должна. Тогда слазаешь и проверишь этот бит, вдруг автор прошивки заранее где то его сбросил.

Общая картина работы порта показана на рисунке:

Теперь кратко о режимах:

  • Режим выхода
    Ну тут, думаю, все понятно — если нам надо выдать в порт 1 мы включаем порт на выход (DDRxy=1) и записываем в PORTxy единицу — при этом замыкается верхний ключ и на выводе появляется напряжение близкое к питанию. А если надо ноль, то в PORTxy записываем 0 и открывается уже нижний вентиль, что дает на выводе около нуля вольт.
  • Вход Hi-Z — режим высокоимпендансного входа.
    Этот режим включен по умолчанию. Все вентили разомкнуты, а сопротивление порта очень велико. В принципе, по сравнению с другими режимами, можно его считать бесконечностью. То есть электрически вывод как бы вообще никуда не подключен и ни на что не влияет. Но! При этом он постоянно считывает свое состояние в регистр PIN и мы всегда можем узнать что у нас на входе — единица или ноль. Этот режим хорош для прослушивания какой либо шины данных, т.к. он не оказывает на шину никакого влияния. А что будет если вход висит в воздухе? А в этом случае напряжение будет на нем скакать в зависимости от внешних наводок, электромагнитных помех и вообще от фазы луны и погоды на Марсе (идеальный способ нарубить случайных чисел!). Очень часто на порту в этом случае нестабильный синус 50Гц — наводка от сети 220В, а в регистре PIN будет меняться 0 и 1 с частотой около 50Гц
  • Вход PullUp — вход с подтяжкой.
    При DDRxy=0 и PORTxy=1 замыкается ключ подтяжки и к линии подключается резистор в 100кОм, что моментально приводит неподключенную никуда линию в состояние лог1. Цель подтяжки очевидна — недопустить хаотичного изменения состояния на входе под действием наводок. Но если на входе появится логический ноль (замыкание линии на землю кнопкой или другим микроконтроллером/микросхемой), то слабый 100кОмный резистор не сможет удерживать напряжение на линии на уровне лог1 и на входе будет нуль.

Также почти каждая ножка имеет дополнительные функции. На распиновке они подписаны в скобках. Это могут быть выводы приемопередатчиков, разные последовательные интерфейсы, аналоговые входы, выходы ШИМ генераторов. Да чего там только нет. По умолчанию все эти функции отключены, а вывод управляется исключительно парой DDR и PORT, но если включить какую-либо дополнительную функцию, то тут уже управление может полностью или частично перейти под контроль периферийного устройства и тогда хоть запишись в DDR/PORT — ничего не изменится. До тех пор пока не выключишь периферию занимающую эти выводы.
Например, приемник USART. Стоит только выставить бит разрешения приема RXEN как вывод RxD, как бы он ни был настроен до этого, переходит в режим входа.

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

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

Итак:

  • Самый безопасный для МК и схемы, ни на что не влияющий режим это Hi-Z.
  • Очевидно что этот режим и должен быть по дефолту.
  • Значения большинства портов I/O при включении питания/сбросе = 0х00, PORT и DDR не исключение.
  • Соответственно когда DDR=0 и PORT=0 это High-Z — самый безопасный режим, оптимальный при старте.
  • Hi-Z это вход, значит при DDR=0 нога настроена на вход. Запомнили.
  • Однако, если DDR=0 — вход, то что будет если PORT переключить в 1?
  • Очевидно, что будет другой режим входа. Какой? Pullup, другого не дано! Логично? Логично. Запомнили.
  • Раз дефолтный режим был входом и одновременно в регистрах нуль, то для того, чтобы настроить вывод на выход надо в DDR записать 1.
  • Ну, а состояние выхода уже соответствует регистру PORT — высокий это 1, низкий это 0.
  • Читаем же из регистра PIN.

Есть еще один способ, мнемонический:
1 похожа на стрелку. Стрелка выходящая из МК — выход. Значит DDR=1 это выход! 0 похож на гнездо, дырку — вход! Резистор подтяжки дает в висящем порту единичку, значит PORT в режиме Pullup должен быть в единичке!

Все просто! 🙂

Для детей в картинках и комиксах 🙂
Для большей ясности с режимами приведу образный пример:

Уровень напряжения на выводе словно планка, которая может двигаться вертикально вверх или вниз. В режиме Hi-Z мы можем на эту планку только смотреть, а двигать или как то на нее воздействовать мы не можем. Поэтому любая помеха может ее дрыгать как угодно, но зато если мы ее куда прицепим, то ее уровень будет зависеть только от другой цепи и ей мы не помешаем.

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

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

easyelectronics.ru

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

Лекция 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

Устройство и работа портов ввода-вывода микроконтроллеров AVR. Часть 1 / Хабр

Работа портов ввода/вывода

Изучив данный материал, в котором все очень детально и подробно описано с большим количеством примеров, вы сможете легко овладеть и программировать порты ввода/вывода микроконтроллеров AVR.

Пример будем рассматривать на микроконтроллере ATMega8.

Программу писать будем в Atmel Studio 6.0.

Эмулировать схему будем в Proteus 7 Professional.

С внешним миром микроконтроллер общается через порты ввода вывода. Схема порта ввода вывода указана в даташите:

Но новичку разобраться довольно со схемой довольно сложно. Поэтому схему упростим:

Pxn – имя ножки порта микроконтроллера, где x буква порта (A, B, C или D), n номер разряда порта (7… 0).
Cpin — паразитная емкость порта.
VCC — напряжение питания.
Rpu — отключаемый нагрузочный верхний резистор (pull-up).
PORTxn — бит n регистра PORTx.
PINxn — бит n регистра PINx.
DDRxn — бит n регистра DDRx.


Рассмотрим, что же представляет собой вывод микроконтроллера. На входе микроконтроллера стоит небольшая защита из двух диодов (см.1), она предназначенная для защиты ввода микроконтроллера от кратковременных импульсов напряжения, превышающих напряжение питания. Если напряжение будет выше питания, то верхний диод откроется и это напряжение будет стравлено на шину питания, где с ним будет уже бороться источник питания и его фильтры. Если на ввод попадет отрицательное (ниже нулевого уровня) напряжение, то оно будет нейтрализовано через нижний диод и погасится на землю. Впрочем, диоды там хилые и защита эта помогает только от микроскопических импульсов и помех. Если же на ножку микроконтроллера подать вольт 6-7 при 5 вольтах питания, то внутренние диоды его не спасут.

Конденсатор (см.2) — это паразитная емкость вывода. Хоть она и крошечная, но присутствует. Обычно ее не учитывают, но она есть. Не забивай голову, просто знай это.

Дальше идут ключи управления (см.3,4). Каждый ключ подчинен логическому условию, которые нарисованы на рисунке. Когда условие выполняется — ключ замыкается.

Каждый порт микроконтроллера AVR (обычно имеют имена A, B и иногда C или даже D) имеет 8 разрядов, каждый из которых привязан к определенной ножке корпуса. Каждый порт имеет три специальных регистра DDRx, PORTx и PINx (где x соответствует букве порта A, B, C или D). Назначение регистров:

DDRx – Настройка разрядов порта x на вход или выход.

PORTx – Управление состоянием выходов порта x (если соответствующий разряд настроен как выход), или подключением внутреннего pull-up резистора (если соответствующий разряд настроен как вход).

PINx –Чтение логических уровней разрядов порта x.

PINхn – это регистр чтения. Из него можно только читать. В регистре PINxn содержится информация о реальном текущем логическом уровне на выводах порта. Вне зависимости от настроек порта. Так что если хотим узнать что у нас на входе — читаем соответствующий бит регистра PINxn. Причем существует две границы: граница гарантированного нуля и граница гарантированной единицы — пороги за которыми мы можем однозначно четко определить текущий логический уровень. Для пятивольтового питания это 1.4 и 1.8 вольт соответственно. То есть при снижении напряжения от максимума до минимума бит в регистре PINx переключится с 1 на 0 только при снижении напряжение ниже 1.4 вольт, а вот когда напряжение нарастает от минимума до максимума переключение бита с 0 на 1 будет только по достижении напряжения в 1.8 вольта. То есть возникает гистерезис переключения с 0 на 1, что исключает хаотичные переключения под действием помех и наводок, а также исключает ошибочное считывание логического уровня между порогами переключения.

При снижении напряжения питания разумеется эти пороги также снижаются.

DDRxn – это регистр направления порта. Порт в конкретный момент времени может быть либо входом либо выходом (но для состояния битов PINxn это значения не имеет. Читать из PINxn реальное значение можно всегда).

DDRxy = 0 – вывод работает как ВХОД.

DDRxy = 1 – вывод работает на ВЫХОД.

PORTxn – режим управления состоянием вывода. Когда мы настраиваем вывод на вход, то от PORTх зависит тип входа (Hi-Z или PullUp, об этом чуть ниже).

Когда ножка настроена на выход, то значение соответствующего бита в регистре PORTx определяет состояние вывода. Если PORTxn=1 то на выводе лог.1, если PORTxn=0 то на выводе лог.0.

Когда ножка настроена на вход, то если PORTxn=0, то вывод в режиме Hi-Z. Если PORTxn=1 то вывод в режиме PullUpс подтяжкой резистором в 100к до питания.

Таблица. Конфигурация выводов портов.

DDRxn PORTxn I/O Comment

0 0 I (Input) Вход Высокоимпендансный вход. (Не рекомендую использовать, так как могут наводится наводки от питания)

0 1 I (Input) Вход Подтянуто внутренне сопротивление.

1 0 O (Output) Выход На выходе низкий уровень.

1 1 O (Output) Выход На выходе высокий уровень.

Общая картина работы порта показана на рисунках:

Рис. DDRxn=0 PORTxn=0 – Режим: HI-Z – высоко импендансный вход.

Рис. DDRxn=0 PORTxn=1 – Режим: PullUp – вход с подтяжкой до лог.1.

Рис. DDRxn=1 PORTxn=0 – Режим: Выход – на выходе лог.0. (почти GND)

Рис. DDRxn=1 PORTxn=1 – Режим: Выход – на выходе лог.1. (почти VCC)

Вход Hi-Z — режим высокоимпендансного входа.

Этот режим включен по умолчанию. Все ключи разомкнуты, а сопротивление порта очень велико. В принципе, по сравнению с другими режимами, можно его считать бесконечностью. То есть электрически вывод как бы вообще никуда не подключен и ни на что не влияет. Но! При этом он постоянно считывает свое состояние в регистр PINn и мы всегда можем узнать что у нас на входе — единица или ноль. Этот режим хорош для прослушивания какой либо шины данных, т.к. он не оказывает на шину никакого влияния. А что будет если вход висит в воздухе? А в этом случае напряжение будет на нем скакать в зависимости от внешних наводок, электромагнитных помех и вообще от фазы луны и погоды на Марсе (идеальный способ нарубить случайных чисел!). Очень часто на порту в этом случае нестабильный синус 50Гц — наводка от сети 220В, а в регистре PINn будет меняться 0 и 1 с частотой около 50Гц

Вход PullUp — вход с подтяжкой.

При DDRxn=0 и PORTxn=1 замыкается ключ подтяжки и к линии подключается резистор в 100кОм, что моментально приводит не подключенную никуда линию в состояние лог.1. Цель подтяжки очевидна — не допустить хаотичного изменения состояния на входе под действием наводок. Но если на входе появится логический ноль (замыкание линии на землю кнопкой или другим микроконтроллером/микросхемой), то слабый 100кОмный резистор не сможет удерживать напряжение на линии на уровне лог.1 и на входе будет лог.0.

Режим выхода.

Тут, думаю, все понятно — если нам надо выдать в порт лог.1, мы включаем порт на выход (DDRxn=1) и выдаем лог.1 (PORTxn=1) — при этом замыкается верхний ключ и на выводе появляется напряжение, близкое к питанию. А если надо лог.0, то включаем порт на выход (DDRxn=1) и выдаем лог.0 (PORTxn=1) — при этом открывается уже нижний вентиль, что дает на выводе около нуля вольт.

habr.com

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

Всем добрый вечер! Веду свою трансляцию из уютного мира, который называется «ассемблер». Сразу поясню что тема касается микроконтроллеров 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