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 ========================================

 

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

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

Да не вопрос. На демоплате уже смонтированы светодиоды, почему бы их не заюзать? Они висят на выводах порта 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 ===================================

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

1
2
3
4
5
; 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 =====================================================

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

А вот фиг — горят оба, но один чуть-чуть тусклей. На самом деле он мерцает, но очень очень быстро. Если ткнуть осциллографом в вывод 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 ================================================

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

При параметрах 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

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

Осталось теперь проверить нажата ли кнопка или нет. Если нажата, то в бите 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 =====================================================

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


 

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

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

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

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

Лентой будет счетчик, считающий от нуля, скажем, до 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

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

1
2
3
4
		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+С

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

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

1
2
3
4
		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, но нам он не нужен.
 

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

1
		SBCI	R17,(-1)

Т.е. 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

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

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

 

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

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

 

Запусти режим отладки и поставь точку останова (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)

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

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

Чтобы он моргал каждую секунду тебе надо при сравнении замаскировать старшие биты глобального счетчика, словно у него разрядность не 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

 

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

Вот только мигает заметно медленней чем мы хотели. Не 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 =====================================================

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



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

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

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

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

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

  1. странно, почему к этому посту нету комментариев?
    Привет Di! :)

    Извини за возможно глупые вопросы, но три раза перечитал, перепробовал в эмуляторе и все равно не мигает.

    1. у тебя написано в статье, где ты вычисляешь количество «оборотов»: 1/0.75E-6 = 303 030 оборота, что такое 0.75 если в статье у тебя вышло 3.3мкс — очепятка?

    2. не понял, почему «событие с маской 00 04 9F B6 (00000000 00000100 10011111 10110110) до переполнения возникнет дофига число раз», как прикинуть/просчитать сколько раз возникнет событие с какой-то маской?

    3. «а вот пред старший надо через AND по маске 00000111 продавить» — насколько я понял чтобы его продавить — нужна маска 00000011 для пред-старшего 00000100, ведь AND выставит 1 там где 1 в обоих числах, верно или я в чем то ошибся?

    4. Зачем нужно «CPI R18,0x04 и BRNE NoMatch» если мы в R18 маской «продавливаем» (насколько я понимаю — выставляем нули), получается что R18-0x04 всегда будет меньше нуля и флаг Z тут будет 0, стало быть BRNE будет сразу переходить к Match. Получается эта команда смысле не имеет?

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

    1. такс, «покурив» в нный раз пост и булеву алгебру понял, что маска таки должна быть 0x07 чтобы при CPI получался 0 только в случае когда предстарший разряд равен 4.

      и тогда мои 3й и 4й вопросы автоматом «уходят», разобрался :)

    2. получается так (скриншот): http://my.jetscreenshot.com/2946/20100417-ojmb-79kb
      получается 1.2сек, что в принципе то что и нужно, но эмулятор зараза долго отрабатывает это время и проц под 100%.
      вот только еще не понял когда следующий раз сработает…
      второй раз сработала через 2.2 сек после первой, это и есть «погрешность»?
      что-то уж слишком большая получается, это диод мигать будет все реже и реже как я понял :(

      ладно, извини DiHalt, нафлудил я тут, пошел разбираться.

    3. Она новая и ее мало кто еще видел. Потому и нету.

      1. Просто опечатка, дело в том что вначале я кое что неправильно посчитал и правил время в тексте статьи. Мог не везде поправить.

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

      У нас там 00000000 00000 нулей т.е. число повторово до переполнения будет 1FFF раз.

      3. Да, именно так. От этого байта маска 7 оставит младшие три бита. В результатет наш предстарший байт будет меняться лишь от 0 до 7, а сравнивать мы его будем на число 4.

      4. Будет. Нам же надо сравнить то что осталось после масок с тем что нам надо xxx с 100.

  2. собрал на макетке с тинькой — работает :)

    Спасибо DiHalt, не было бы этого сайта, хрен знает когда я бы что-либо смастерил. А так вот уже азы понял и пощупал.

    Пришел правда к выводу что неплохо бы даже под обучающие устройства — плату ЛУТом делать, а то на макетке каша получается. Ну или все же выделить финансы на Pinboard :) благо стоит вполне разумно.

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

  4. Может я что-то не понимаю, может ошибка
    Delay: LDI R16,LowByte ; Грузим три байта
    …….
    BRCC Loop ; Если нет переноса — переход
    >>> тут RET не надо?<<<

  5. Привет, DI! Пытаюсь вместо компилятора и МК использвоать свой мозг ))). Возникли вопросы:
    1. В маркосе SETBM в строке
    IN R17,@1
    и в строке
    LDS R17,@1
    нет ошибки? Может вместо @1, должно быть @0 ?
    Такая же ситуация и с маркосом CLRBM.
    2. В мароксе SETBM команды PUSH/POP используются и в ветке if и в ветке else. А нельзя их вынести за пределы внутреннего if/else? Тогда, получится, что можно сэкономить место на 2-х команадах (PUSH и POP)?
    Аналогичная ситуация, кстати, во всех макросах.
    3. В макросе INVB ты в ветке if поступаешь с параметрами @0,@2,@3 так:
    EOR @3,@2
    OUT @0,@3
    а в ветке else немного по-другому:
    EOR @2,@3
    STS @0,@2
    Т.е. @2 и @3 поменялись местами. В этом есть какой-то глубокий смысл, или, поскольку смысла нет, ты особо за этимм не смотрел?

    1. 1. Да, была ошибка
      2. Не, у нас выполняется одна из трех вариаций. Так что лишних команд там быть не может. Это же макроподстановка.
      3. Там не имеет значения. 2 и 3 это Temp1 и Temp2 соответственно. Какая разница в каком их порядке юзать? Главное чтобы результат вычислений шел в нужном направлении. А там он совпадает (результат в 3 аут 3 и результат в 2 стс в 2)

      1. Т.е. при макроподстановке код макроса подставляется в программу не целиком, а только та его часть, которая соответствует условиям?

    1. Ползу по строкам кода как муравей: что ни строка — вопрос.
      Макрос SETB:
      1.параметр temp(@2)- надо ли определять рон ранее как temp дерективой .def;
      2.этот макрос устанавливает бит по любому адресу (если я правильно понял)- тогда из какого адресного пространства параметр byte(@0)решает сам компилятор, или нет;
      3. 1<<@1 задает битовую маску для ORI, т.е. сдвигает 1 на bit(@1)позиций влево: сдвигать можем только 0 или 1, параметр bit в hex или dec, или все равно;
      Надеюсь дальше поползется быстрее.

      1. 1. где там параметр зовется темп? Это я для примера синтаксис макроса описал. В реале там вписываются имена регистров. Ну или дефайненные имена регистров.

        2. Так там же условие в макросе. Младшие адреса в ИО, а после уже в пространстве памяти.

        3. Двигать можно любой число, не обязательно 1 или 0. МОжно и 3 например. Будут двигаться две единички. Такое (и подобное) чем тормозить и спрашивать, лучше бы в отладчике экспериментально проверил =))) наглядней бы было.

  6. какой вопрос! дерективу .if можно использовать только внутри макроса???
    просто почему студия ругается на запись .if r19>=128????или регистр нельзя в этом случае использовать, а нужно ставить только параметр макроса???

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

      1. Что характерно, если вызывать любой из этих макросов с регистром в качестве первого параметра (н-р setbm r16, 4), то компилятор тоже начнет материться. Причина видима та же, он пытается подставить в .if @0 > 0x20 значение регистра, вместо того чтобы использовать его адресс. Есть какая-нибудь директива, указывающая на то, что нужно использовать адресс, а не значение (аналог сишного &var)?

      2. ===Только внутри макроса===
        Разве? Разве директивы условной компиляции нельзя исп-ть в основном тексте?

        .equ DEBUG=1 ; вкл отладочный код

        .if DEBUG
        ; === отладочный код
        nop ;
        nop ; пискнуть динамиком
        nop ;
        .endif

        ; === рабочий код
        nop ;
        nop ; работать работу
        nop ;

        Если в начале напишем «.equ DEBUG=1», то скомпилится и выполнится сначала отладочный код, а потом рабочий, если «.equ DEBUG=0» — только рабочий.

  7. мне кажется, событие с маской 00 04 9F B6 (00000000 00000100 10011111 10110110) будет возникать каждый 08 00 00 (=524288) такт, а не 0x049FB6 (=303 030). Поскольку второе событие произойдет при значении счетчика
    (00000000 00001100 10011111 10110110)
    и далее
    (00000000 00010100 10011111 10110110)
    (00000000 00011100 10011111 10110110)
    итд, что равносильно маске в 19 нулей в младших битах.

    1. Именно. Только в первый раз будет нужный интервал, все последующие длиннее. Чтобы получить именно этот интервал нужно сбрасывать счетчик.

    2. Блин, Ди =)) Эти люди правы. У тебя длительность цикла от числа 04 9F B6 никак не зависит, а только от маски. Блин, раз шесть статью прочитал, но пока в студии не загнал и циклы не подсчитал — не допер.

      То есть длительность цикла кратна степени двойки, которую указывает маска и событие с маской 00000000 00000001 xxxx xxxx будет действительно каждые 524288 такта.

      Ты бы хоть намек в статье сделал, что расчет длительности вообще не нужен =)
      А то в камментах одни вопросы по макросам.
      З.Ы. Курс клевый, респект.

  8. ну вот как бы выдержка из даташита:
    «Port A is a 3-bit bi-directional I/O port with internal pull-up resistors (selected for each
    bit). The Port A output buffers have symmetrical drive characteristics with both high sink
    and source capability. As inputs, Port A pins that are externally pulled low will source
    current if the pull-up resistors are activated. The Port A pins are tri-stated when a reset
    condition becomes active, even if the clock is not running»

    и в AVRstudio портА тоже показывается, где я туплю?

    спасибо

    1. Попутал с мегой8, где порта А нет.

      В тини2313 в самом деле он есть и прописан в инклюдах. Вот тебе копипаста из моего tn2313def.inc

      .equ EECR = 0x1c
      .equ PORTA = 0x1b
      .equ DDRA = 0x1a
      .equ PINA = 0x19
      .equ PORTB = 0x18

    2. Скорей всего ты перепутал и поставил инклюд от АТ90S2313 2313def.inc там действительно портА не определен (т.к. в 90 2313 его не было)

  9. Здравствуй DIHALT!
    Помоги разобраться в следующих вопросах:
    1.
    Кнопки группы А-Д подключены на землю через резисторы. Поэтому при нажатии получается цепочка: Vcc -> PullUp -> кнопка -> резистор -> GND. В таком случае на кнопке будет не ноль. Но в то же время работает. Как это объяснить? В предыдущих статьях писалось что кнопки нужно подключать прямо на землю.

    2.
    В ниже приведенном примере постоянно включен светодиод. Планировалось что он буде светится только при нажатии кнопки. Глянь своим опытным глазом, в чем причина?
    CBI DDRC, 3
    SBI PORTC, 3

    Main: SBIS PINC,3
    RJMP Light_on

    SBIC PINC,3
    RJMP Light_off

    Light_on: SBI PORTD, 7
    RJMP Main

    Light_off: CBI PORTD, 7
    RJMP Main

    RJMP Main

    Спасибо.

    1. Если ты про Pinboard то резисторы там стоят на 100ом. А pullup на 100кОм т.е. в 1000раз больше. Так что эти 100 ом просто ничто. Я тот резистор поставил на всякий случай, чтобы по неопытности пользователи не попалили порты, выставив их на выход и не нажав кнопку.

      2.
      Алгоритм верный, а на выводе точно лог0? Если кнопка не нажата, то не значит что нет какой либо другой утечки. Тот же светодиод подцепленный через перемычку канает за нажатие кнопки. Плюс у Mega16 порты отвечающие за JTAG по дефолту ВЫКЛЮЧЕНЫ т.е. четыре вывода TMS TCK TDO TDI на команды управления портом не реагируют.

      1. Здравствуй DIHALT!
        Помоги разобраться в следующих вопросах:
        1.
        Кнопки группы А-Д подключены на землю через резисторы. Поэтому при нажатии получается цепочка: Vcc -> PullUp -> кнопка -> резистор -> GND. В таком случае на кнопке будет не ноль. Но в то же время работает. Как это объяснить? В предыдущих статьях писалось что кнопки нужно подключать прямо на землю.

        2.
        В ниже приведенном примере постоянно включен светодиод. Планировалось что он буде светится только при нажатии кнопки. Глянь своим опытным глазом, в чем причина?
        CBI DDRC, 3
        SBI PORTC, 3

        Main: SBIS PINC,3
        RJMP Light_on

        SBIC PINC,3
        RJMP Light_off

        Light_on: SBI PORTD, 7
        RJMP Main

        Light_off: CBI PORTD, 7
        RJMP Main

        RJMP Main

        Спасибо.
        Да речь идет о Pinboard. Поставил пермычку на кнопку С. А как их вернуть чтобы порты РС2-РС5 работали как порты ввода\вывода?

        1. Да речь идет о Pinboard. Поставил пермычку на кнопку С. А как их вернуть чтобы порты РС2-РС5 работали как порты ввода\вывода?

          1. Варианта два:

            1) Сбросить Fuse бит JTAGEN, но лучше туда не лезть если не знаешь как править Fuse. Можно кристалл заблокировать.

            2) Записать единичку в бит JTD регистра MCUCSR
            Возможно, чтобы она установилась, придется записать ее туда дважды подряд, в течении 4х тактов. Почитай даташит про регистр MCUCSR там вроде бы что то такое было.

  10. Здравствуй DI HALT!
    Для начала спасибо за полезный сайт и pinboard!
    Прошил программу все работает!
    Стал разбираться в коде и никак не могу понять следующее:
    1) команда SETB DDRD,4,R16
    дальше смотрим макрос
    .MACRO SETB
    .if @0 < 0x20 ; Low IO
    SBI @0,@1
    как я понял вместо @0 подставляется DDRD верно же??? но в описании команды
    SBI написано:(i)SBI A,b 0 ≤ A ≤ 31, 0 ≤ b ≤ 7 PC ← PC + 1 т.е. вместо @0 должно быть число от 0 до 31, а у нас DDRD, которую мы нигде не объявляли….
    Вопрос: как работает в этом случае команда SBI????????????????

    2)что означает в этом же макросе строка: .if @0 < 0x20 ; Low IO
    и строка: .if @0<0x40 ; High IO ??????????????????

    3)если можно поподробнее расскажите о работе макроса на примере SETB……

    1. 1) DDRD обьявлена в m16def.inc

      2) Это макроусловие. Дело в том, что доступ в младшие IO регистры (До 0х20) делается через одну команду, в старшие уже через IN/OUT. А выше 0х40 уже через группу доступа к памяти. Вот макрос и сортирует все это, чтобы нам голову не парить и не думать куда мы обращаемся.

      1. спасибо за оперативный ответ)
        1)Я использую AVR Studio 5 и в теле программы я не указывал m16def.inc, но при выборе проекта я указал, что использую ATMega16A(который стоит в pinboard)
        2) а там же условие .if @0 < 0×20 ; Low IO т.е. DDRD — это адрес регистра? а тогда вопрос а если мне нужно к другим регистрам обратится не DDRD, где есть описание какой регистр как будет называться???
        Скажите, где про это можно почитать — это уже не даташит микроконтроолера, а описание Макроассемблера????

        1. Даташит ессесно. Там названия всех регистров есть.

          Макроассемблер он всего лишь позволяет делать некоторые фишки с кодом перед компиляцией. В основном это всякие автозамены и условная компиляция.

  11. Ага все смекнул, спасибо!
    Только еще один вопросик, а вроде в ATMega16A в даташите указано что все регистры I/O укладываютcя в своем адресном пространстве с максимальным адресом $3F($005F), т.е. запись в макросе SETB:
    .else ; Memory
    LDS @2,@0
    ORI @2,1<<@1
    STS @0,@2
    .endif
    .endif
    .ENDM
    для ATMega16A лишняя????????

    1. Ну я же пишу не только под атмегу16, но и под многие другие. А вот во всяких мега168 там половина периферии уже вылезает в раздел memory mapped

  12. Есть вопрос по портам.
    Имеется следующий код — http://pastebin.com/NEQAgmUE
    Проблема заключается в следующем: в AVRStudio нажатия на кнопки (висят на пинах 0-3 порта B, замыкая их на землю) прослеживаются нормально (если в дебаге клацать по битам этого порта, меняя их значение, ест), а в Протеусе нет никакой реакии на них — там присутствуют закоменченные строки для мигания 0-ым пином порта А при нажатии на кнопки (на первую — загорается, на вторую — гаснет).Где же я накосячил? Регистры, используемые для работы с клавиатурой, нигде более не используются…

    1. Разобрался сам — в коде ошибок нет, косячит сам Протеус. Суть в том, что в этот порт конкретно в эти биты я по прерыванию для отображения инфы на дисплее записываю лог.1 (PullUP) и Протеус считает, что оттуда 100% считается именно лог.1 (даже если этот пин будет закорочен на землю). Вот такие вот мега-грабли :(

  13. Имеет ли смысл в трехбайтной задержке такая конструкция:
    /*
    .equ LowByte = 255
    .equ MedByte = 255
    .equ HighByte = 255

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

    Ведь после прогона LowByte после переполнения(или как там называется когда обнуляется переменная) LowByte установится в 0хFF.
    Я имею в виду что не получится устанавливать LowByte и MedByte меньше чем в 255?

    1. Насколько я понимаю, при уменьшении 0xFF на 1 произойдет установка флага N и он учтется при следующей операчии вычитания, поскольку код выглядит примерно так:
      subi R16,1
      sbci R17,0
      sbci R18,0
      В данном случае на первом операторе при вычитании до 0xFF установится флаг отрицательного значения и флаг переноса. Следующие операторы действуют аналогично, но с учетом флага переноса — смотрим систему комманд AVR:
      SUBI Rd,k (Substract Immediate) — Rd<-Rd-k
      SBCI Rd,k (Substract Immediate with Carry) — Rd<-Rd-k-C
      где С — флаг переноса.
      Вот так это и работает :)

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

  14. Мне кажется в расчете выдержки ждущего цикла есть косяк.
    Смотри, нам нужно сделать 255 * 255 * 255 вычетаний, чтобы вывалиться из цикла. Ты получил 2.1 секунды поделив 255 * 255 * 255 / 8 * 10^6, т.е. предположив, что каждый тик происходит вычетание.
    НО, вычетанице происходит далеко не каждый тик, а в среднем раз в 4 тика, т.е. формула расчета выдерки будет 255 * 255 * 255 * 4 / 8 * 10^6, т.е. выдержка получается около 8.3 с., что больше похоже на правду судя по первому видео.

      1. Да, еще какая-то погрешность точно есть.
        Почему-то уведомления об ответе на email не падают.. должны?

          1. Нее, спам первым делом проверил, когда в инбоксе не нашел. Попробую позже акк c другим мылом завести, может действительно в gmail дело.

            1. Мне приходит. С точки зрения админки я ничем не отличаюсь от других. Там еще галочка такая есть, подписывание на комментарии. Но это на все.

    1. Чтобы программа на 255*255*255 не зацикливалась, нужно обнулять счетчик, когда он досчитал до нужного числа (в примере 0х049FB6).
      Сразу после:

      Match:
      INVB PORTD,7,R16,R17

      Пишем:

      CLR R16
      CLR R17
      CLR R18
      STS CCNT,R16
      STS CCNT+1,R17
      STS CCNT+2,R18

  15. DI скажи не сталкивался ли ты с отсутствием в AVRStudio портов при отладке ?
    Я решил тут раскурить SPI (mega16A и mega8) и вдруг при отладке программы обнаружил что студия не эмулирует порт А меги16 т.е. его в периферии какбэ нет ваще …(( … просто любопютство раздирает …. протсто глюк ?

  16. Пересоздал проек. Теперь с мегой16А нормально все, а в проекте с мегой 8 при отладдке есть порт А, тогда как его здесь не должно быть…что за фигня ?

  17. А зачем в счётчике младший байт слева? Как-то связано с архетиктурой МК? …Непривычно…
    По мне, так удобней так:

    LDS R19,@0 ; Старший байт в R19
    LDS R18,@0+1
    LDS R17,@0+2
    LDS R16,@0+3 ; Младший байт в R16
    SUBI R16,(-1) ; Увеличивем на 1 младший байт
    SBCI R17,(-1) ; Дальше, по-цепочке…
    SBCI R18,(-1)
    SBCI R19,(-1)
    STS @0,R19 ; Сохраняем старшим байтом слева
    STS @0+1,R18
    STS @0+2,R17
    STS @0+3,R16 ; Младшим байтом — справа.

    Так как-то глазу привычней…

  18. Чтобы светодиод мигал с другой частотой нам надо слепок пересчитать(в статье считали для задержки 1сек/3.3мксек = 00 04 9F B6)? Или же хватит бит маску поменять?

    А можно в такой задержке…….время задержки менять в реальном времени инкрементом/декрементом? С шагом к примеру 0.1сек.

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

Ваш e-mail не будет опубликован.

Перед отправкой формы:
Human test by Not Captcha