AVR. Учебный Курс. Типовые конструкции

Распечатать

При написании программ постоянно приходится использовать разные стандартные конструкции вроде циклов, операторов выбора, перехода, сравнения. Всякие там if-then-else или case-switch. В высокоуровневых языках это все уже готово, а на ассемблере приходится изобретать каждый раз заново.
Впрочем, такие вещи пишутся один раз, а дальше просто по наезженной тропинке применяются везде, где потребуется. Заодно и оптимизируются по ходу процесса.

Условие if-then-else
Тут проще всего методом последовательной проверки проложить маршрут до нужного блока кода. Приведу пример:

1
2
3
4
5
6
7
8
9
if (А>=B)
 	{
	action_a
	}
else 
	{
	action_b
	}
next_action

Как это проще всего сделать на ассемблере?

Считаем, что А в R16,B в R17, а полезные действия, которые могли бы быть там, заменяем на NOP,NOP,NOP.
Сравнение делается путем вычитания. По итогам вычитания выставляются флаги. Заем (когда A стало меньше B) означает, что условие не выполнилось.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
		CP	R16,R17		; Сравниваем два значения
		BRCS	action_b	; когда A>=B флага С не будет
					; Перехода не произойдет
action_a:	NOP			; и выполнится действие А
		NOP
		NOP
		RJMP next_action	; Но чтобы действие B не произошло
					; в ходе естественного выполнения
					; кода -- его надо перепрыгнуть.
 
action_b:	NOP			; Действие B
		NOP
		NOP
 
 
next_action:	NOP
		NOP
		NOP

Но надо учитывать тот момент, что в случае А=B флаг С не вылезет, зато будет флаг Z. Но переход то у нас исходя из какого-нибудь одного флага (по С=0 или по С=1). И, исходя из выбора команды для построения конструкции if-then-else (BRCC или BRCS), будет разная трактовка результата условия if (А>=B) в случае А=B.

В одном случае (нашем), где переход идет на else по С=1, А=B будет эквивалентно А>B — флага C не будет. И в том и другом случае услове if (А>=B) даст True и переход по BRCS на then.

Если перестроить конструкцию наизнанку, через команду BRCC то условия (А>=B) уже не получится. При A=B не будет флага С и произойдет переход по BRCC на else.

Для создания строгих неравенств нужна проверка на ноль.
Теперь А больше B:

1
2
3
4
5
6
7
8
9
if (А>B)
 	{
	action_a
	}
else 
	{
	action_b
	}
next_action

Получили:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
		CP	R16,R17		; Сравниваем два значения
		BREQ	action_b	; Если равно (флаг Z), то переход сразу.
					; Потом проверяем второе условие.
		BRCS	action_b	; когда A>B флага С не будет
					; Перехода не произойдет
 
action_a:	NOP			; и выполнится действие А
		NOP
		NOP
		RJMP next_action	; Но чтобы действие B не произошло
					; в ходе естественного выполнения
					; кода -- его надо перепрыгнуть.
 
action_b:	NOP			; Действие B
		NOP
		NOP
 
 
next_action:	NOP
		NOP
		NOP

Сложно? Думаю нет.

Усложним еще раз наше условие:

1
2
3
4
5
6
7
8
9
if (C<A AND A<B)
 	{
	action_a
	}
else 
	{
	action_b
	}
next_action

Считаем, что A=R16, B=R17, С=R18

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
;IF
		CP	R16,R18		; Сравниваем С и А
		BREQ	action_b	; Переход сразу на ELSE если С=А
		BRCS	action_b	; Если А оказалось меньше, то будет С 
					; и сразу выходим отсюда на else
					; Но если C<A то идем дальше и проверяем
					; Второе условие (A<B)
 
		CP	R16,R17		; Сравниваем два значения
		; BREQ	action_b 	; Переход на ELSE если B=А. Команда BREQ специально
					; закомментирована -- она тут не нужна.  Я ее поставил
					; для наглядности. Ведь в случае А=B
					; флага С не будет и однозначно будет переход по BRCC.
 
		BRCC	action_b	; Когда A>B флага С не будет
					; А переход по условию С clear сработает.
;THEN		
action_a:	NOP			; Выполнится действие А
		NOP
		NOP
		RJMP next_action	; Но чтобы действие B не произошло
					; в ходе естественного выполнения
					; кода -- его надо перепрыгнуть.
;ELSE
action_b:	NOP			; Действие B
		NOP
		NOP
 
 
next_action:	NOP
		NOP
		NOP

Просто же! Вот так вот, комбинируя условия, можно размотать любую логическую конструкцию.

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

Есть замечательные команды SBI и CBI первая ставит указанный бит в порту, вторая сбрасывает. Например:

CBI PORT,7 — обнулить 7й бит в регистре ввода-вывода PORT
SBI PORT,6 — выставить 6й бит в регистре ввода-вывода PORT

Все замечательно, но работают эти команды только в пределах первых 31 адресов в пространстве регистров ввода вывода. Для мега16 это:

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
.equ	EEARH	= 0x1f
.equ	EEDR	= 0x1d
.equ	EECR	= 0x1c
.equ	PORTA	= 0x1b
.equ	DDRA	= 0x1a
.equ	PINA	= 0x19
.equ	PORTB	= 0x18
.equ	DDRB	= 0x17
.equ	PINB	= 0x16
.equ	PORTC	= 0x15
.equ	DDRC	= 0x14
.equ	PINC	= 0x13
.equ	PORTD	= 0x12
.equ	DDRD	= 0x11
.equ	PIND	= 0x10
.equ	SPDR	= 0x0f
.equ	SPSR	= 0x0e
.equ	SPCR	= 0x0d
.equ	UDR	= 0x0c
.equ	UCSRA	= 0x0b
.equ	UCSRB	= 0x0a
.equ	UBRRL	= 0x09
.equ	ACSR	= 0x08
.equ	ADMUX	= 0x07
.equ	ADCSRA	= 0x06
.equ	ADCH	= 0x05
.equ	ADCL	= 0x04
.equ	TWDR	= 0x03
.equ	TWAR	= 0x02
.equ	TWSR	= 0x01
.equ	TWBR	= 0x00

И ни байтом дальше. А с более старшими адресами — облом.

Ну ничего, что нам мешает взять и записать в регистр ввода-вывода (далее РВВ, а то я уже задолбался). Этот бит самому?

Только то, что доступ там к порту идет не побитный, а сразу целыми байтами.
Казалось бы, в чем проблема? Надо выставить биты 1 и 3 в регистре TWCR, например, взял да записал двоичное число 00001010 и выставил.

1
	LDI	R16,1<<3|1<<1

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

В таком случае, алгоритм у нас такой:

  • Взять старое значение
  • Подправить в нем биты
  • Записать обратно

Запишем это в коде. Взять значение просто:

1
	IN 	R16,TWCR

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

1
	ORI	R16,1<<3|1<<1	; Битовая маска 00001010

А затем записать уже измененный байт обратно

1
	OUT	TWCR,R16

Сброс битов тоже можно сделать через битовую маску, но уже операция AND. И там где мы хотим сбросить нам надо поставить в маске 0, а где не хотим менять 1

Удобней делать инверсную маску. Для инверсии маски применяется операция побитового НЕ ~

Выглядеть будет так:

1
2
3
	IN	R16,TWCR
	ANDI	R16,~(1<<3|1<<1)
	OUT	TWCR,R16

Обрати внимание, для сброса у нас внутри конструкции используется 1. Ведь байт то потом инвертируется!

Для инверсии битов применяется маска по XOR. Нули в этой маске не меняют биты, а единичка по XOR бит инвертирует.

Смотри сам, вверху произвольное число, внизу маска:

1
2
3
4
1100 1100 XOR
1111 0000
_________
0011 1100

Правда есть одно ограничение — нет команды XOR маски по числу, поэтому через регистр.

1
2
3
4
	IN	R17,TWCR
	LDI	R16, 3<<1|2<<1 	; Маска
	EOR	R17,R16			; Ксорим 
	OUT	TWCR,R17		; Сгружаем обратно

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

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

Макрос может быть таким:

1
2
3
4
	.MACRO	XRI
	LDI	@2,@1
	EOR	@0,@2
	.ENDM

А вызов макроса

1
	XRI	R16,mask,R17

Последний параметр — промежуточный регистр. Указываем его вручную т.к. мало ли какой из регистров нам будет удобней в данный момент.

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

1
2
3
4
5
6
	.MACRO	XRI
	PUSH	R16
	LDI	R16,@1
	EOR	@0,R16
	POP	R16
	.ENDM

Вызов, в этом случае

1
	XRI 	R17,Mask

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

Также масками можно смело отрезать неиспользованные биты. Например, в регистре TWSR первые два бита это настройки, а остальные — код состояние интерфейса TWI. Так зачем нам эти два бита настройки каждый раз учитывать? Отдавили их по AND через маску 11111100 да дело в шляпе. Или сдвинули вправо.

Сдвиги
Ну тут просто — можно двигать биты в РОН влево и вправо. Причем сдвиг бывает двух типов.

LSR Rn — логический вправо, при этом слева в байт лезут нули.
LSL Rn — логический влево, при этом справа в байт лезут нули.

и через перенос.

ROL и ROR

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

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

Смотри как просто:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
	CLR	R18		; Сюда будем считать единички. Сбросим пока
	LDI	R17,9		; Счетчиком циклов будет
	LDI	R16,0xAA	; В этом байте будем считать единички
	CLC			; Сбросим флаг С, чтобы не мешался.
 
Loop:	DEC 	R17		; уменьшим счетчик
	BREQ	End		; если весь байт дотикали - выход.
 
	ROL	R16		; Сдвигаем байт
	BRCC	Loop		; если нет 1 в С, еще одну итерацию
 
	INC 	R18		; Сосчтитали единичку
	RJMP	Loop
 
End:	NOP

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

1
2
	ROL	R16
	BRCS	Label	; Переход если бит 7 в R16 есть.

или

1
2
	ROR	R16
	BRCS	Laber	; Переход если бит 0 в R16 есть.

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

Например, выполнить цикл 20 раз.

1
2
3
4
5
6
7
8
9
10
	LDI	R17,20		; Счетный регистр
 
Loop:	NOP
	NOP
	NOP
	NOP
	NOP
 
	DEC	R17		; Уменьшаем счетчик
	BRNE	Loop

; Переход если не ноль (нет флага Z)

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

Еще один пример цикла — тупые задержки. Почему я их называю тупыми? Да потому, что они сами ничего не делают и другим не дают. Крайне не рекомендую их использовать, но для простеньких программ подойдет.

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

Вот решение в лоб:

1
2
3
	LDI	R16, Delay
Loop:	DEC	R16
	BRNE	loop

Процессор потратит примерно Delay*2 тактов на бессмысленные операции. При длительности такта (на 8мгц) 1.25Е-7 секунд максимальная выдержка будет 6.4E-5 секунды. Немного, но, например, хватит на то чтобы дисплей прожевал отданый ему байт и не подавился следующим.

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

Но есть более красивое решение, нежели вложенные циклы — вычитание многобайтного числа с переносом.

1
2
3
4
5
6
7
8
9
	LDI	R16,LowByte		; Грузим три байта
	LDI	R17,MidleByte		; Нашей выдержки
	LDI	R18,HighByte
 
loop:	SUBI	R16,1			; Вычитаем 1
	SBCI	R17,0			; Вычитаем только С
	SBCI	R18,0			; Вычитаем только С
 
	BRCC	Loop 			; Если нет переноса - переход.

В результате, вначале у нас отнимается 1 из первого числа, а перенос возникает только тогда, когда заканчивается очередной байт. Получается, что из R16 отнимают на каждой итерации, из R17 на каждой 256 итерации, а из R18 на каждой 65535 итерации.

А всего на таких трех регистрах можно замутить тупняк на 256*256*256 тактов. И никто не мешает в том же ключе навешать еще регистров, вплоть до R31 =) И длительность задержки будет куда точней, т.к. в отличии от вложенных циклов, команда BRCC всегда будет выполняться за 2 такта и лишь в последней случае за один.

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

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

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

  • Делаем раз
  • Делаем два
  • Делаем три
  • Тупим
  • Делаем то, ради чего тупили
  • Делаем четыре

Как понимаешь, пока длится этап «Тупим» ничего не работает. Если в «делай раз» было, например, обновление экрана, то оно зависнет. На период этапа «Тупим»

Для этого можно сделать финт ушами — полезную работу ВСЕЙ программы внести в цикл задержки.

Получается так:

  • Делай раз
  • Делай два
  • Делай три
  • Тик!
  • Натикало? -да- Делаем то, ради чего тупили сбрасываем счетчик.
  • Делай четыре

Покажу пример (инициализации стека, и таблицы векторов я опускаю для краткости):

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
	.DSEG
Counter:	.byte	3			; Наш счетчик циклов. Три байта. 
 
		.CSEG
 
MainLoop:
 
Do_One:		NOP
 
Do_Two:		NOP
 
Do_Three:	NOP
 
		LDS	R16,Counter		; Грузим наш счетчик
		LDS	R17,Counter+1
		LDS	R18,Counter+2
 
		SUBI	R16,1			; Вычитаем 1
		SBCI	R17,0			; Вычитаем только С
		SBCI	R18,0			; Вычитаем только С
 
		BRCC	DoNothing		; Не натикало? Переход
 
; Натикало!
YES:		NOP				; Делаем то, ради чего тупили
		NOP
		NOP
 
		LDI	R16,LowByte		; Грузим три байта
		LDI	R17,MidleByte		; Нашей выдержки
		LDI	R18,HighByte
 
 
DoNothing:	STS	Counter,R16		; Сохраняем обратно в память
		STS	Counter+1,R17
		STS	Counter+2,R18
 
		RJMP	MainLoop

Обрати внимание, что другие операции Do_one, Do_Two, Do_Three не ждут когда натикают, они выполняются в каждую итерацию. А операция требующая задержку ее получает, не тормозя всю программу! И таких таймеров можно налепить сколько угодно, пока оперативка не кончится.

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

Но это уже конечные автоматы. К ним я, возможно, вернусь позже. Еще не решил стоит освещать эту тему. А то могу и увлечься. люблю я их :)

Горыныч
Писал я тут библиотечку для подключения LCD к AVR. А чтобы не вкуривать в команды контроллера дисплея я распотрошил Сишный код, дабы подглядеть какими байтами и в каком порядке надо кормить контроллер, чтобы он вышел на нужный режим.

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

Итак, мы имеем две функции обращения к LCD — запись данных и запись команд.

О! Вот эти ребята:

1
2
3
4
5
6
7
8
9
10
11
12
13
CMD_WR:		CLI
		RCALL	BusyWait
 
		CBI	CMD_PORT,RS
		CBI	CMD_PORT,RW			
		SBI	CMD_PORT,E	
		LCD_PORT_OUT
		OUT	DATA_PORT,R17
		RCALL	LCD_Delay
		CBI	CMD_PORT,E
		LCD_PORT_IN		
		SEI
		RET

Нененененене Девид Блейн!!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
DATA_WR:	CLI
		RCALL	BusyWait
 
		SBI	CMD_PORT,RS
		CBI	CMD_PORT,RW			
		SBI	CMD_PORT,E				
		LCD_PORT_OUT
		OUT	DATA_PORT,R17
		RCALL	LCD_Delay
		CBI	CMD_PORT,E
		LCD_PORT_IN		
		SEI
		RET

Сейчас я вам покажу особую, оптимизаторскую, магию!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
CMD_WR:		CLI
		RCALL	BusyWait
 
		CBI	CMD_PORT,RS
		RJMP	WR_END
 
DATA_WR:	CLI
		RCALL	BusyWait
		SBI	CMD_PORT,RS
WR_END:	CBI	CMD_PORT,RW			
		SBI	CMD_PORT,E	
		LCD_PORT_OUT
		OUT	DATA_PORT,R17
		RCALL	LCD_Delay
		CBI	CMD_PORT,E
		LCD_PORT_IN		
		SEI
		RET

Ты что наделал Блейн!!! Ты зачем функцию скукожил??? Нука раскукожь ее обратно! У меня теперь функция беби сайз!!!

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

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
void lcdControlWrite(u08 data) 
{
lcdBusyWait();
cbi(LCD_CTRL_PORT, LCD_CTRL_RS);	
cbi(LCD_CTRL_PORT, LCD_CTRL_RW);
 
sbi(LCD_CTRL_PORT, LCD_CTRL_E);
outb(LCD_DATA_DDR, 0xFF);				
outb(LCD_DATA_POUT, data);
LCD_DELAY;
LCD_DELAY;
cbi(LCD_CTRL_PORT, LCD_CTRL_E);
 
outb(LCD_DATA_DDR, 0x00);
outb(LCD_DATA_POUT, 0xFF);
}
 
 
void lcdDataWrite(u08 data) 
{
lcdBusyWait();
sbi(LCD_CTRL_PORT, LCD_CTRL_RS);	
cbi(LCD_CTRL_PORT, LCD_CTRL_RW);
sbi(LCD_CTRL_PORT, LCD_CTRL_E);
outb(LCD_DATA_DDR, 0xFF);
outb(LCD_DATA_POUT, data);
LCD_DELAY;
LCD_DELAY;
cbi(LCD_CTRL_PORT, LCD_CTRL_E);
outb(LCD_DATA_DDR, 0x00);
outb(LCD_DATA_POUT, 0xFF);
}

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

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

И финальная процедура инициализации на сях и на макроасме:

1
2
3
4
5
6
7
8
9
10
void lcdInit()
{
	lcdInitHW();
	lcdControlWrite(LCD_FUNCTION_DEFAULT);
	lcdControlWrite(1<<LCD_CLR);
	lcdControlWrite(1<<LCD_ENTRY_MODE | 1<<LCD_ENTRY_INC);
	lcdControlWrite(1<<LCD_ON_CTRL | 1<<LCD_ON_DISPLAY );
	lcdControlWrite(1<<LCD_HOME);
	lcdControlWrite(1<<LCD_DDRAM | 0x00);
}

Ну и у меня:

1
2
3
4
5
6
7
8
	.MACRO	INIT_LCD
	RCALL	InitHW
	WR_CMD	0x38
	WR_CMD	0x01
	WR_CMD	0x06
	WR_CMD	0x0F
	WR_CMD	0x02
	.ENDM

Конструкции вида (1<<LCD_HOME) обусловены тем, что Сишный исходник подразумевает конфигурацию LCD — это универсализация кода.

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

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

Хотя… чем черт не шутит, может заморочусь и напишу свой крошечный USB драйвер.

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

84 комментария: AVR. Учебный Курс. Типовые конструкции

  1. XDN говорит:

    Оптимизация -O0 стояла в avr-gcc? У меня самописный драйвер hd44780 с поддержкой чтения добавляет 84 байта при -0s. Тут, конечно, надо учитывать, что код всей прошивки — 3244 Кб. На первых порах, когда ты добавляешь #инклады, размер может резко ползти вверх, но потом этот рост прекратиться.

    • DI HALT говорит:

      Да на -O0. При -Os код ужимается где то до 5кб. Но при Os не зная что да как легко получить полностью нерабочий код. Т.к. в этом режиме компилер может код так заоптимизировать, что хрен потом концы найдешь. У меня помню по неопытности был прикол, когда я пол дня убил пытаясь понять чего у меня протокол не работает ,а оказывается компилер загнал проверку бита куда то в память и сравнивал ее при проверке с сохраненным значением. Поставил volatile сразу же заработало, но для того чтобы вкурить в эти грабли пришлось кучу документации взрыть.

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

      • XDN говорит:

        Использование volatile в таких случая формально указано в стандарте. Оптимизатор — тоже машина, ею управлять надо умеючи. :)

        Ну да ладно, не будем холиворить.

        • DI HALT говорит:

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

          • XDN говорит:

            volatile в C99, если не раньше, был введён как раз для таких ситуаций.
            Но специфика есть, не спорю. Правда она вся хорошо описывается в документации.

            Книгу по Си для МК знаю только одну: «Программирование на языке С для AVR и PIC микроконтроллеров. Ю.А.Шпак.».

            Но если взять, например, «Микроконтроллеры AVR семейства Mega. Руководство пользователя. А.В.Евстиеев», то там идут примеры как на Асме, так и на Си. В даташитах Atmel’а тоже с недавних пор стали так делать.

          • DI HALT говорит:

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

          • nwanomaly говорит:

            у него они скорее как для примера.
            и кстати, описывают общий вариант без оптимизаций. я это, например, заметил, когда смотрел его работу с еепромом для меги. всё правильно, но можно сократить )

      • XDN говорит:

        Проверил размер своего драйвера «в чистом» виде — 950 байт.

  2. Cyber_RAT говорит:

    сколько пишу на микроконтроллерах (микропроцессорах начиная от вм80, z80 и конечно же x86) больше всего люблю асм… (я не говорю о большом софте, а об девайсах на этих контроллерах и проциках)…
    ассемблер — более прозрачен что-ли.. или склад ума у меня такой, что линейное программирование для меня проще :(
    даже сейчас девайс на меге32 с несколькими протоколами обмена по rs232, mmc+lcd siemens s65 + rtc — все на асме…
    p.s. хотя иногда хочется плюнуть и переписать на си (вроде быстрее и проще), но пару попыток это «проще» похоронили в зародыше.
    p.p.s. вот такой вот сумбур получился ;)

  3. SWG говорит:

    Когда в начале 90х я, поработав уже почти десяток лет на Ассемблере (для 8080, 8086, 8048, Z80) и нескольких BASICах, а также DBASE III+ и CLIPPER, немножко FORT, решил попробовать что — нибудь более серьезное, естественно, начал с C++ 3.0. Про него ходила молва, что он шибко крутой, ужасно мощный и гибкий, но скрытный, как закопанный в землю шланг. В общем, «Настоящие программисты пишут только на C» !!!
    Первое, на что обратил внимание, — бедность встроенного набора функций. Даже для элементарного консольного ввода — вывода уже нужна библиотека (stdio.h, если не ошибаюсь). Не было даже такого элементарного понятия, как СТРОКА (string). Вместо нее — используй одномерный символьный массив, не забывая добавлять в конце символы ВК и ПС. Программку намахал быстро, откомпилировалась без проблем, но чтобы нормально заработала, пришлось попотеть. То вешалась без обьяснения причин, то вообще делала непонятно что.
    Для сравнения попробовал написать то же самое на Борланд Паскале 7.0. Написал, компилятор ткнул меня несколько раз во всякие точки и запятые, ругнулся на несоответствие типов данных, после исправления запускаю, и — О, ЧУДО !!! Все работает! и именно так, как надо!
    С тех пор я нафиг забросил C, писал на Паскале, затем Дельфи (1, 2, 3, 4, 5, 6…). Работа у меня в последние лет 12 в основном связана с обработкой текстовой информации (биллинговые записи, базы данных, всевозможная статистика, автоматизация отчетности).
    Конечно, за эти годы С тоже не стоял на месте, многое позаимствовав у других языков, но что — то меня к нему не тянет. А тем более использовать его в микроконтроллерах для пересылки регистр — регистр, чтобы он перед этим на всякий случай сохранил кучу информации в стеке, потом долго возвращал ее обратно…
    Правда, языки высокого уровня с микроконтроллерами упрощают такие вещи, как инициализация UART, ADC, I2C, LCD и прочего. Удобно, например, дать одну команду «PWM1_Init(500);» — и ШИМ1 уже настроен на работу на частоте 500гц, вместо того чтобы распихивать битики по десятку служебных регистров.
    Или, например:
    USART_Init(9600);//-инициализирует USART (9600 baud rate, 1 stop bit, no parity).
    А сколько пришлось бы распихать вручную? Или: USART_Write(‘#’); — и символ ушел в линию. А как вам такое:

    program ADC_USART;
    var temp_res : word;
    begin
    USART_Init(19200); // initalize USART (19200 baud rate, 1 stop bit, no parity…)
    ANSEL := $04; // configure AN2 pin as analog input
    TRISA := $FF;
    ANSELH := 0; // configure other AN pins as digital I/O
    while TRUE do
    begin
    temp_res := ADC_Read(2) shr 2; //read 10-bit ADC from AN2 and discard 2 LS bits
    USART_Write(temp_res); // send ADC reading as byte
    Delay_ms(100);
    end;
    end.

    Две команды — и напряжение батареи измерено и ушло в RS232 !
    Ради этого стоит написать такие вещи на МикроПаскале или МикроБэйсике, откомпилировать, отладить, а потом взять ассемблерный листинг и дописать на ассемблере все остальное, попутно проанализировав и оптимизировав код, выданный компилятором до этого. Часто это намного ускоряет работу. Например вчера, не шибко напрягаясь, я так проверил работу двух ШИМ, UART, ввод и обработку команд платой управления двигателями своего робота, о чем и похвастался ночью в комментах в теме о робототехнике. Там я написал подробнее, повторяться не буду.

    • DI HALT говорит:

      У меня под всякие иниты куча макросов обычно пишется :) Так что инклюд файл с макросами и дальше тот же самый UART_INIT_9600 :)

      • SWG говорит:

        В конечном итоге так и получается, просто я с микроконтроллерами работаю в последние годы периодически, для души, и не успел еще накопить свою библиотечку макросов, хотя уже кое — что вырисовывается. Конечно, если бы я занимался ими на работе, в серьезных разработках, как было до развала страны, было бы гораздо проще. А так то времени нет, то стимула, то просто лень… Вот и приходится использовать то, что ускоряет и упрощает написание, пока азарт не пропал, и обьемы свободной памяти позволяют. Вот уж наворочу побольше, тогда и займусь оптимизацией и кода, и самого алгоритма, пока же — удерживаясь в рамках поставленной задачи, хочу накопить побольше готовых, функционально законченных, отлаженных кусков, потому что давно уже заметил: чем больше функциональность программы, тем легче добавлять новое. Иногда пара строк или дополнительное условие в уже работающей программе дают такие новые возможности, которые на начальном этапе потребовали бы несколько страниц.
        Например, сейчас я уже пытаюсь запустить контроль напряжения батареи на своей плате, для начала запуская кнопочкой на одной из линий порта B, с выдачей результата по RS232. Правда, по ходу выяснилось, что удобнее бало бы использовать для измерения не A4, а например, А0, но два года назад я об этом не подумал, решив при разработке схемы, что подойдет и A4. Но у микроконтроллеров редко входы бывают равноценными, что-нибудь да вылезет.
        Но проблема не очень велика и в принципе решаема, в пределах моих требований.
        Зато есть возможность шевельнуть мозгой, и получить удовольствие от процесса.

    • XDN говорит:

      Re: «Первое, на что обратил внимание, — бедность встроенного набора функций. Даже для элементарного консольного ввода — вывода уже нужна библиотека (stdio.h, если не ошибаюсь). Не было даже такого элементарного понятия, как СТРОКА (string). Вместо нее — используй одномерный символьный массив, не забывая добавлять в конце символы ВК и ПС. Программку намахал быстро, откомпилировалась без проблем, но чтобы нормально заработала, пришлось попотеть. То вешалась без обьяснения причин, то вообще делала непонятно что.»

      А что понимается под встроенным набором функций? Так была же библиотека, так? И в названии указано «Standart I/O» (stdio.h). Чем плохо то, что набор функций подключаются опционально?

      Чем неудобны нуль-терминированные строки?

  4. FAndrey говорит:

    Кстати рекомендую сходить на http://code.google.com/p/vector06cc/ они там Вектор эмулируют в АВРке и он у них работает и с ЖК в том числе.

    • SWG говорит:

      А что тут удивительного? Процессор Вектора обеспечивал около 400 тыс. операций/сек, и то только на самых коротких, типа MOV A,C. Для AVR это частота порядка 400 — 500 кгц. Кто сейчас такую использует? Для периферии многие микросхемы семейства MCS 51 имеют полноценную системную шину данных и адреса, да еще с отдельными командами для устройств ввода — вывода. И адресация внешней памяти не меньше тех же 64кб. А некоторые так такие обьемы уже внутри имеют. В общем, аппаратно все, что было в 8080 — есть. Система команд тоже похожа, из за чего мне, например, в 80х годах после 8080 легко было освоить 8048. Но кроме того: добавлены команды косвенной адресации, циклы со счетчиком, и даже умножение и деление! А это тоже повышает эффективность кода. Да, еще битовые операции забыл! Так что с современными микроконтроллерами можно многое натворить. Жалко только, что это никому не нужно. Проще нефть продавать, пока не кончилась, а все остальное покупать у китайцев… По крайней мере у нас, в Казахстане, электронщику делать нечего… Программисты еще нужны, и то часто только чтобы научить остальных работников пользоваться 1C или LOTUS. А это уже не программирование, а ликбез.

      • FAndrey говорит:

        Да я в принципе в теме :)
        Просто проект показался созвучным тому что задумал dihalt поэтому и дал ссылку вдруг еше что полезное найдется, посмотреть чужие реализации иногда не вредно

    • svofski говорит:

      Вектор работает в FPGA, никакой AVR-ки там нету (хоть я и хотел бы иметь мягкую и компактную АВР для эмулятора дисковода).

  5. Cluster говорит:

    Ох, не знаю что ты там делал, но явно что-то не то. У меня на готовых библиотеках «Hello world» весит меньше двух килобайт. И то, можно оптимизировать её под свои нужды.

    А вообще не могу без улыбки читать комменты выше… Глупо опускать какой-то язык, не умея писать на нём.

    • XDN говорит:

      Так у тебя hd44780 работал на готовом драйвере?

    • DI HALT говорит:

      А какую библиотеку ты взял? Они же разные есть. Я потащил себе кода на все случаи жизни с http://hubbard.engr.scu.edu/avr/avrlib/

      Заюзал библиотеку lcd.c оттуда. Можешь сам посмотреть во что она компилется на Os и О0. Выглядит монструозно.

      З.Ы.
      Хеллоу ворлд должен быть не более 1к. В идеале байт 300-400.

      • Cluster говорит:

        Ту, которая юзается в примерах WinAVR. Там основной минимум. Если убрать все навороты, которые я дописал, то оно будет совсем мелким. Посмотрел то, что по твоей ссылке — там даже какие-то шрифты зачем-то засунули. Если бы ты нашел такой модуль на асме, он весил бы столько же. А оптимизацию ниже O2 я никогда не юзаю, смысла нет.

      • SWG говорит:

        Сейчас интереса ради попробовал на МикроПаскале. (Для PIC16F873):

        program LSD;
        var text : string[16];
        begin
        text := ‘Hello, World !’;
        Lcd_Config(PORTB,3,2,1,0,PORTB,4,7,5); // Lcd_Init
        LCD_Cmd(LCD_CLEAR); // clear display
        LCD_Cmd(LCD_CURSOR_OFF); // turn cursor off
        LCD_Out(1,1, text); // print text to LCD, 1st row, 1st column
        end.

        Результат — 616 байт (15%) ROM, 54 байта (30%) RAM
        И на симуляторе, и в натуре — все работает.
        Добавил еще пару строк:
        Delay_ms(1000); // 1 second delay
        LCD_Out(2,3,’mikroPascal’); // print text to LCD, 2nd row, 3rd column
        Результат — 676 байт (16%) ROM, 66 байт (37%) RAM.

        Все заняло минут 15, большее время из них ушло на подключение проводков от индикатора к порту B. И пока еще никакой оптимизации не использовал.
        А то: «Os, О0…»

        • DI HALT говорит:

          А нафига делать конфиг ножек в исполняемой программе? Это же можно и нужно делать в макросах.

          • SWG говорит:

            А какой смысл, если я использовал готовую команду (одну!) Паскаля для настройки. Опять же, при другой раскладке ног достаточно перечислить их в самой команде. Вот ее синтаксис из Help:
            procedure Lcd_Config(var data_port : byte; db3, db2, db1, db0 : byte; var ctrl_port : byte; rs, ctrl_rw, enable : byte);
            То есть я сразу могу указать любые ноги любого порта, остальное сделает компилятор. Но главной целью было поглядеть, какой окажется величина кода, безо всяких оптимизаций, чтобы знать, на что ориентироваться. Так что пока хваленый C в пролете, не смотря на его продвинутую оптимизацию.
            Попутно проверил еще один кирпичик к своей программе робота. Быстро и удобно. И всего пять команд, при всей универсальности: Любой текст, в любую строку, с любой позиции, вместе с инициализацией, очисткой и управлением курсором. 5 строк! Куда еще проще — то, чтобы, например, быстро проверить индикатор? Другое дело, когда надо будет уже впихнуть это в готовую сложную программу, я гляну в ассемблерном листинге, что также выдал компилятор Паскаля, какие байтики куда пихаются, и напишу те же макросы или подпрограммки на асме, только уже не столь универсальные (Для экономии ПЗУ).
            А вообще, был приятно удивлен полученным результатом. Ожидал, что будет больше, чем на крутом C, да еще с оптимально подобранными оптимизаторами… Правда, тайно надеялся, что ненамного. Но чтоб такое!… Теперь у меня тем более не появится стимула осваивать C для микроконтроллеров. Тем более начитавшись того, что листинги на C одной фирмы не понимаются компиляторами другой, или даже той же, но другой версии… Нет, такой хоккей нам не нужен!

          • XDN говорит:

            «какой окажется величина кода, безо всяких оптимизаций» — там настройки оптимизации имеются, надеюсь?

            «Другое дело, когда надо будет уже впихнуть это в готовую сложную программу, я гляну в ассемблерном листинге, что также выдал компилятор Паскаля, какие байтики куда пихаются, и напишу те же макросы или подпрограммки на асме,»

            А почему не сразу на асме, пользуясь документацией?

          • SWG говорит:

            Я уже обьяснял, что, читая эти комменты, решил просто для сравнения посмотреть, сколько будет на Паскале. Конкретно этим компилятором МикроПаскаля я пользуюсь всего с неделю, (раньше пробовал другие компиляторы и С, и Паскаля для микроконтроллеров, но ни один не понравился). Про Микропаскаль же я знал года с 2005, но руки не доходили попробовать. Недавно на каком — то форуме прочитал хорошие отзывы о нем, вот и решил попробовать. А до этого для PIC и AVR использовал asm, как более привычный, да и экономный. Но когда надо быстро проверить разные функции платы контроллера, и размеры программы не имеют пока значения, (Такая ситуация у меня сейчас с платой, сделанной 2 года назад и заброшенной из за семейных обстоятельств), оказалось, что МикроПаскаль вне конкуренции.
            Несколько строчек — и уже работает терминал, или АЦП, или ШИМ. На Asmе на это ушло бы на порядок больше времени, а жизнь коротка, и интересного в ней еще так много…
            Управления опциями оптимизации в МикроПаскале я пока не нашел, но судя по результатам, он и так достаточно оптимален. Размер кода не так уж и велик, а скорость написания и интуитивность понимания — выше всяких похвал. Работаю с ним недолго, но явных ляпов пока не обнаружил. Кстати, у той же фирмы есть еще и МикроСи, и МикроБэйсик, отдельно для PIC, AVR, и еще каких — то контроллеров. Если будет время, попробую проверить еще и их, как на PIC, так и AVR, хотя бы до Меги 32.

          • XDN говорит:

            «Тем более начитавшись того, что листинги на C одной фирмы не понимаются компиляторами другой, или даже той же, но другой версии…»

            Прекрасно понимаются, если разработчик придерживается стандарта, заданного «свыше». Основная проблема начинается в библиотеках (и Паскаль тут не исключение). Если библиотеки IAR и avr-libc ещё более-менее взаимозаменяемы, то у множества малоизвестных компиляторов — нет.

        • SWG говорит:

          В 3 строчке снизу должно быть «подключение». Работаю на клаве вслепую, иногда пальцы промахиваются. Жалко, нет редактирования…

      • Agrin говорит:

        Для примера на CodeVisionAVR v1 то же самое и статистика. Библиотека встроенная, время 10 минут.
        Исходник сокращённый:
        #include
        // Alphanumeric LCD Module functions
        #asm
        .equ __lcd_port=0×18 ;PORTB
        #endasm
        #include

        void main(void)
        {
        lcd_init(16);

        while (1)
        {
        lcd_gotoxy(0,0);
        lcd_putsf(«Hello, World !»);
        };
        }

        Статистика самого компилятора:

        Data Stack size: 256 byte(s)
        Estimated Data Stack usage: 7 byte(s)
        Global variables size: 4 byte(s)
        Hardware Stack size: 764 byte(s)
        Heap size: 0 byte(s)
        EEPROM usage: 0 byte(s) (0,0% of EEPROM)
        Program size: 291 words (7,1% of FLASH)

        ..и асма:

        ATmega8 memory use summary [bytes]:
        Segment Begin End Code Data Used Size Use%
        —————————————————————
        [.cseg] 0×000000 0×000246 556 26 582 8192 7.1%
        [.dseg] 0×000060 0×000164 0 4 4 1024 0.4%
        [.eseg] 0×000000 0×000000 0 0 0 512 0.0%

        вполне оптимально.

  6. SWG говорит:

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

    char *text = «Hello, World !»;
    void main() {
    Lcd_Init(&PORTB); // Lcd_Init
    LCD_Cmd(LCD_CLEAR); // Clear display
    LCD_Cmd(LCD_CURSOR_OFF); // Turn cursor off
    LCD_Out(1,1, text); // Print text to LCD, 1st row, 1st column
    }

    Результат — 295 байт (7%) ROM, 40 байт (22%) RAM

    Добавил пару строк:
    Delay_ms(1000);
    LCD_Out(2,6,»mikroE»); // Print text to LCD, 2nd row, 6th column
    Результат — 345 байт (8%) ROM, 47 байт (26%) RAM

    Стало уже интересней… И никакой оптимизации…
    Надо будет еще, например, на Меге 8 для сравнения проверить МикроПаскаль и МикроСи, а также заодним интереса ради МикроБэйсик, тоже на PIC и AVR…

    • SWG говорит:

      Для сравнения попробовал еще МикроБэйсик той же фирмы, на том же PIC16F873, с аналогичной программой.

      program Lcd_Test
      dim text as string[16]
      main:
      text = «Hello, World !»
      Lcd_Config(PORTB,3,2,1,0,PORTB,4,7,5) ‘ Lcd_Init
      LCD_Cmd(LCD_CLEAR) ‘ Clear display
      LCD_Cmd(LCD_CURSOR_OFF) ‘ Turn cursor off
      LCD_Out(1,1, text) ‘ Print text to LCD, 1st row, 1st column
      Delay_ms(1000)
      LCD_Out(2,4,»mikroBasic») ‘ Print text to LCD, 2nd row, 6th column
      end.

      Результат — 675 байт (16%) ROM, 65 байт (36%) RAM.

    • XDN говорит:

      Очень интересно. Похоже на то, что версии библиотек разные.

      Если бы не было оптимизации — прошивка была бы в 15-20 Кбайт. Она есть, но не настраивается.

  7. Int_13h говорит:

    Сорри за оффтоп но хелп!.. прерывания не пашут почему то ни в авр студио ни в реальном мк вот код

    .INCLUDE «m16def.inc»
    .CSEG
    .org 0
    rjmp main
    .org OVF0addr rjmp Tmr0_OVF ;Overflow0 Interrupt Vector Address

    main:

    OUTI TCNT0,0
    OUTI TCCR0,2
    OUTI TIMSK,1<<TOIE0

    LOOP:
    nop
    rjmp LOOP

    Tmr0_OVF:

  8. SWG говорит:

    Почитал еще раз все комментарии, и жутко стало. Вспомнил, как я, бывало, лет 20 назад писал программки для своих контроллеров, и в основном размер был 2-4 кб…
    И это при одновременной и независимой работе в реальном времени с несколькими телеграфными каналами, накоплением и выдачей статистики непосредственно после измерений, или по запросу за заданный интервал, или автоматически через задаваемые отрезки времени (например, раз в два часа)… Да что там мои программы, даже операционная система CP/M 80 была всего 6,5 Кб! (Все три ее секции, вместе с BIOS)! А тут драйвер паршивенького индикатора на пару коротеньких строчек — на несколько килобайт, да еще с разными оптимизациями. Вот до чего техника дошла!
    У меня есть несколько приборчиков с этим индикатором (частотомеры, измерители LCF), в основном повторение готовых конструкций с Интернета, и размер кода у них обычно меньше 2 килобайт.
    Так чего же надо было напихать в этот паршивый «драйвер на C», чтобы он стал 2-3, а то и 5, (и даже 15)кб? Это же ведь не WINDOWS! Богато жить стали. Не глядя пару килобайт туда, пару сюда, мало памяти — поставим монстрика на сотню ног, хоть и мелких…
    И уже лень мозгой шевельнуть, лучше с опциями оптимизации компилятора поиграться. А ведь когда — то, почти 40 лет назад, Луноходы чуть ли не на лампах делали, и ничего — по Луне по полгода бегали…
    Интересно, а сейчас кто-нибудь на территории бывшего Союза смог бы такое смастерить, хотя бы на современной элементной базе? Да чтоб не на пару дней в комнате, а с месяц по Луне? Куда катимся… Стыдно, мужики. И за державу обидно.

    • XDN говорит:

      «И уже лень мозгой шевельнуть, лучше с опциями оптимизации компилятора поиграться.»
      Что вы подразумеваете под «шевелением мозгой»?

      • SWG говорит:

        Да просто написать простенькую подрограммку или макрос на ассемблере. Привыкли, как в Виндах: Чуть что — сразу давай готовый драйвер, вместо того, чтобы просто в нужном месте несколько команд написать. Конечно, универсальность хороша, удобно иметь сразу все на все случаи жизни. Да только во что это выливается, мы уже видим. Чего уж такого сложного в работе с этим индикатором, чтобы тратить на него несколько килобайт? (Ведь это ТЫСЯЧИ команд!). Но в реале всего — то надо настроить ему режим при старте, да затем писать в него время от времени, зачастую даже не в конкретную позицию, а строку целиком, когда работает внутренний счетчик в индикаторе, только пихай ему байты. Читать из символьного индикатора — вообще какой смысл? проще сделать буфер вывода в программе. Вот и получается, что драйвер как таковой и не нужен. Несколько команд инициализации в начале и простенькая функция вывода строки на дисплей. Кинул ей инфу в буфер или указал адрес первого символа в ПЗУ — и несколько команд в простеньком цикле перекидают все в индикатор. Это же не картинки рисовать.

        • XDN говорит:

          Можно использовать чтение, чтобы проверить присутствие модуля как такового.
          Когда идёт проверка флага занятости, то почему бы и не прочитать текущий адрес, если его уже и так выдадут?

          «Да просто написать простенькую подрограммку или макрос на ассемблере.»
          Я так понимаю, вы предлагаете для каждого нового проекта переписывать код управления заново?

          • SWG говорит:

            Не так уж он велик, этот код. Просто последовательность из нескольких команд, можно просто выделить в редакторе несколько строчек и вставить в новую программу. Если нужно часто, можно эти строчки сохранить в отдельном файле. Что уж в этом сложного, если вы действительно делаете сложные вещи? А простую программу вообще иногда проще написать заново, чем приспосабливать готовые куски. Ну, а если программы как таковой еще нет, а нужно всего лишь быстро проверить уже готовое железо, как у меня сейчас, — так вообще никаких драйверов не надо, на том же МикроПаскале, к примеру, написал кусочек с парой команд — проверил индикатор, еще несколько команд — RS232, еще несколько команд — проверил ШИМ, и т.д.
            Когда же буду писать окончательно уже рабочую программу, скорее всего, все же буду использовать ассемблер, поглядывая иногда на листинги, скомпилированные Паскалем, чтобы меньше лазить по документации (У меня от нее уже глаза болят, зрение сдавать стало. Четверть века пялился на мониторы, часто довольно хреновые). Вот опробую еще I2C, пищалку, ИК локаторы, одометры, еще кое — что из периферии, раскидаю все по прерываниям, — и начну уже писать все начисто. Правда, надо еще уголь привезти и печки отремонтировать, да и отпуск через неделю кончится, а работу еще никто не отменял, и до пенсии еще 7 лет пахать (у нас мужикам в 63года).

          • XDN говорит:

            А чем «копипастинг» отличается от инкладов в самом ассемблере? Думаю, что ничем. А набор инкладов — уже библиотека. Чем она будет лучше самописной библиотеки на любом другом языке?

          • SWG говорит:

            В отличие от обсуждавшихся библиотек на С — размером! Десятки ассемблерных команд вместо тысяч (Судя по размерам драйвера LCD в C).

          • XDN говорит:

            Обсуждавшиеся библиотеки на Си — это не самописные библиотеки на Си, о коих говорилось постом выше, хотя я и не привязывался к конкретному ЯВУ.

        • Cluster говорит:

          Дык драйвер, который я юзаю, только это и делает — инициализация, да запись. Ну ещё я работу с несколькими буферами прикрутил, что в принципе при желании можно убрать…

    • DI HALT говорит:

      Не забывай, что у АВР жутко громоздкая система команд. Все команды минимум двубайтные — какой нибудь сраный NOP и тот занимает два байта. А если уж переход куда-нибудь, то целых четыре байта. Плюс ко всему нет переходов с проверкой. Т.е. вначале ты проверь, а потом переходи. Я тут поглядел на свой код который у меня на AT89C2051 был так я в шоке был. Мне там память в 2кб безграничной казалась, а тут тока начал уже и 2кб пролетели. Пишу на асме и зажимаю каждый байт, а все равно.

      • XDN говорит:

        Кстати, да. Почему-то совершенно забыл про то, что с развитием МК растёт и средний размер команд. Не знаешь, как с этим делом у PIC’ов?

        • SWG говорит:

          У PIC12 — 12 бит, PIC16 — 14бит, 33-35команд.
          У PIC17 — 16 бит, 58 команд (добавлен встроенный аппаратный CAN).
          PIC18 и выше — пока не интересовался. Пока мне PIC16F877 за глаза.
          Все команды — 1 слово. Выполнение — 1 цикл (4такта). Команды сравнения и перехода — 2 цикла (8 тактов). Все команды условных переходов при совпадении или несовпадении условия (есть для обоих вариантов) просто пропускают следующую команду, выполняя вместо нее NOP. Обычно, пропускаемая команда содержит переход. Внутренняя структура делает ненужным сохранение чего-либо в стеке, кроме адреса возврата и флагов, что делается автоматически, поэтому и стек небольшой (у PIC16 — 8 вложений, обычно хватает. У Intel 8048 было аналогично, мне хватало).
          Команды условных переходов есть как с проверкой байт, так и бит, а также автодекрементные и автоинкрементные циклы.

  9. smex говорит:

    Вот немогу понять где сохраняется конфигурация после инициализации устройства. Например SWG писал Lcd_Config(PORTB,3,2,1,0,PORTB,4,7,5); // Lcd_Init. Но ведь где то надо хранить все эти параметры? Пытался найти эти либы в WinAVR — не вышло (. Предполагаю что настройки закидываются в стек — но если так — то необходимо почистить за собой потом — типо lcd_destoy(). Вообщем вопрос — как же на самом деле настройки храняться?

    • DI HALT говорит:

      Конфиг просто записывается в регистры портов и сидит там. Т.к. кроме LCD к ним ничего не подключено, то пускай там и остаются. Стек при этом не используется :)

      • smex говорит:

        Ну тогда еще нюансик. Конфиг пускай сидит в регистрах портов ). Но ведь оно должно где то должно помнить что lcd инициализированн именно на PORTB — а не на каком то другом. А там помойму для этого места не хватит…

  10. legion говорит:

    Когнитивный диссонанс.

    CP R16,R17
    BRCS action_b
    action_a: NOP
    ; Сравниваем два значения
    ; когда A>B флага С не будет
    ; Перехода не произойдет
    ; и выполнится действие А

    Но ведь если R17=R16 переход вроде как тоже не происходит, флаг C=0 и мы попадаем в action_a. А по условию if (А>B) при A=B мы должны выполнять action_b.

    Такая же фигня с BRCS и при (А>=B), и (C<A AND A<B). Мозг взорван.

    • DI HALT говорит:

      Так и есть. Вариант А=В должен обрабатываться дополнительным условием на флаг Z

    • DI HALT говорит:

      Вот, поправил статью. Так стало куда логичней.

      • legion говорит:

        Для четких неравенств не обязательно проверять на ноль, достаточно СP.
        И фраза «разная трактовка результата условия if (А>B) в случае А=B» сносит крышу немного.

        Следуя правилу «Критикуешь — предлагай» опишу момент с ветвлением if then так, как я его понял, будучи новичком в асме:

        Рассмотрим результат операции CP R16,R17.
        1) R16>R17
        Результат вычитания R16-R17 больше нуля, перенос не происходит и флаг С=0.
        2) R16=R17
        Результат вычитания R16-R17 равен нулю, перенос не происходит и флаг С=0.
        3) R16=R17 мы можем воспользоваться командой ветвления BRCC и перейти к действию при С=0.
        А для четкого неравенства if (A меньше B) then (action_a) else (action_b) ассемблерный вариант будет выглядеть так:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        
        ;Пусть C16=A, С17=B.
         
        		CP	C16,C17
        		BRCC	action_a
         
        action_b:	NOP
        		NOP
        		NOP
        		RJMP	next_actions
         
        action_a:	NOP
        		NOP
        		NOP
         
        next_actions: 	NOP
        		NOP
        		NOP

        Для конструкции if (A<B>=B) и (A<B) уместно дать читателю задание переписать код для (AB), и более сложных неравенств. Это позволит закрепить понимание работы CP и ветвлений в целом. Ответ можно разместить в комментариях либо в конце статьи.

        • legion говорит:

          Блин, на тэг нарвался.

          Для конструкции if (A меньше B) then (action_a) else (action_b):

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          
          		CP	C16,C17
          		BRCS	action_a
           
          action_b:	NOP
          		NOP
          		NOP
          		RJMP	next_actions
           
          action_a:	NOP
          		NOP
          		NOP
           
          next_actions: 	NOP
          		NOP
          		NOP

          Кстати, если говорить от задачках, то после описания кода при (A больше или равно B) и (A меньше B) уместно дать читателю задание переписать код для (A меньше или равно B), (A больше B), и более сложных неравенств. Это позволит закрепить понимание работы CP и ветвлений в целом. Ответ можно разместить в комментариях либо в конце статьи.

        • DI HALT говорит:

          Можно и на одном СР я же добавил в вариант где C<A AND A<B пример работы через BRCC, где при определеном построении конструкции проверка на 0 не нужна.

        • legion говорит:

          Мне кажется в варианте где С в сравнении А и С BREQ тоже можно убрать, достаточно лишь поменять местами операнды в CP. Также и в примере для (A больше B)

  11. legion говорит:

    А как проверить крайние биты с использованием битовой маски так же быстро, как сдвигом через С?

  12. auara говорит:

    Можно подправить, хотя не принципиально
    >> нет команды XOR маски по числу, поэтому через регистр.
    >> IN R17,TWCR
    >> LDI R16, 3<<1|2<<1 ; Маска
    наверно хотели так:
    LDI R16, 1<<3|1<<2

  13. netouch говорит:

    Доброго дня суток!
    Объясните пожалуйста. Перед использованием команды CP флаг С в sreg нужно сбрасывать? (или весь sreg в стек пихать)?
    Ведь возможен вариант, когда флаг С будет установлен еще до выполнения команды СР, и тогда последующий переход по BR.. будет неправильным.

  14. shiva говорит:

    «ORI R16,1<<3|1<<1 ; Битовая маска 00001001″

    разве битовая маска в этом случае будет не 00001010?

  15. tooth_fairy говорит:

    прошу помочь разобраться с задержками, разобранными в примерах
    LDI R16, Delay
    Loop: DEC R16
    BRNE loop
    в данной программе delay мы сами подбираем для получения нужной задерки или что?

    LDI R16,LowByte
    LDI R17,MidleByte
    LDI R18,HighByte

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

    BRCC Loop ; Если нет переноса — переход.

  16. tooth_fairy говорит:

    LDI R16,LowByte
    LDI R17,MidleByte
    LDI R18,HighByte

    loop: SUBI R16,1
    SBCI R17,0
    SBCI R18,0

    BRCC Loop
    аналогичный вопрос по этому коду. Значения lowByte, midbyte,highbyte тоже как-то подбираются? и как вы посчитали что задержка будет 256*256*256 почему не 65535?

  17. tooth_fairy говорит:

    а как применять эти задержки, где их используют?

    • DI HALT говорит:

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

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