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 оборотов главного цикла. Сам прикинь какое число надо нагрузить на сравнение и какую маску наложить :)

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

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

Запись опубликована в рубрике AVR. Учебный курс с метками , , , , . Добавьте в закладки постоянную ссылку.

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

  1. tyler.graip говорит:

    странно, почему к этому посту нету комментариев?
    Привет 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,0×04 и BRNE NoMatch» если мы в R18 маской «продавливаем» (насколько я понимаю — выставляем нули), получается что R18-0×04 всегда будет меньше нуля и флаг Z тут будет 0, стало быть BRNE будет сразу переходить к Match. Получается эта команда смысле не имеет?

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

    • tyler.graip говорит:

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

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

    • tyler.graip говорит:

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

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

    • DI HALT говорит:

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

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

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

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

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

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

  2. tyler.graip говорит:

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

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

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

  3. dima_m говорит:

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

  4. d1ver говорит:

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

  5. Jan говорит:

    Читаю, вроде все понятно написано. Только не разобрался что значит << в коде?

  6. trak400 говорит:

    Привет, 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 поменялись местами. В этом есть какой-то глубокий смысл, или, поскольку смысла нет, ты особо за этимм не смотрел?

    • DI HALT говорит:

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

  7. trak400 говорит:

    Вот оно как! Отлично! Теперь многое прояснилось. Спасибо!

    • moroz говорит:

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

      • DI HALT говорит:

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

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

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

  8. dundich говорит:

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

    • DI HALT говорит:

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

  9. alexus говорит:

    мне кажется, событие с маской 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 нулей в младших битах.

  10. ostrov говорит:

    подскажите почему в тини2313 в файле макроопределений (inc)никак не прописан портА?????

    спасибо

  11. ostrov говорит:

    ну вот как бы выдержка из даташита:
    «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 портА тоже показывается, где я туплю?

    спасибо

  12. Vitalii говорит:

    Здравствуй 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

    Спасибо.

    • DI HALT говорит:

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

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

      • Vitalii говорит:

        Здравствуй 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 работали как порты ввода\вывода?

        • Vitalii говорит:

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

          • DI HALT говорит:

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

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

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

  13. Crimson_Dynamo говорит:

    Здравствуй DI HALT!
    Для начала спасибо за полезный сайт и pinboard!
    Прошил программу все работает!
    Стал разбираться в коде и никак не могу понять следующее:
    1) команда SETB DDRD,4,R16
    дальше смотрим макрос
    .MACRO SETB
    .if @0 < 0×20 ; 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 < 0×20 ; Low IO
    и строка: .if @0<0×40 ; High IO ??????????????????

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

    • DI HALT говорит:

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

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

      • Crimson_Dynamo говорит:

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

        • DI HALT говорит:

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

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

  14. Crimson_Dynamo говорит:

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

  15. andreil говорит:

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

    • andreil говорит:

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

  16. XanderEVG говорит:

    Имеет ли смысл в трехбайтной задержке такая конструкция:
    /*
    .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?

    • andreil говорит:

      Насколько я понимаю, при уменьшении 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
      где С — флаг переноса.
      Вот так это и работает :)

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