Pull to refresh

Реализация Задержки в AVR assembler без таймеров

Reading time 2 min
Views 45K
А зачем?

Переходя с Си на ассемблер (нужда появилась) обнаружил для себя плохую вещь, на нем нет любимой функции _delay_ms(long millisecond) (поиск в интернете ничего не дал, может искал плохо), писать 8000 пустых команд (для 8 Мгц чтобы 1 мс удержать) конечно бред, отсюда появилась идея написать свой Delay.

Шаг За Шагом

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

.macro DELAY_MS
	push R16				
	push R17
	push R24
	push R28

	ldi R28, LOW(@0)
	ldi R24, HIGH(@0)
	rjmp cycMKS	

cycSEK: 				
	subi R24,1
	ldi R28, 255
cycMKS:					

	cpi R28, 1		
	brlo decMKS				
	subi R28,1				
	
ldi R16, LOW(@1/1000)	
	ldi R17, HIGH(@1/1000)
	rjmp _delay_c

new_cycle: 
	subi R17, 1
	ldi R16, 255
_delay_c:					
	subi R16, 4				
	cpi R16, 4				
	brsh _delay_c			
	NOP			
	NOP
	cpi R17, 0			
	brne new_cycle			
	rjmp cycMKS			

decMKS:						 
	cpi R24,0				
	brne cycSEK				 

	pop R28					
	pop R24					
	pop R17					
	pop R16					
.endm



Для разработки использую AVR Studio 4 + gcc, соответственно и тестировал код тоже там. Результат по окончании отладки:




.equ F_CPU = 8000000; Частота в Гц

DELAY_MS 4, F_CPU; подстановка макроса для 4 мс


Слишком большая погрешность, растущая при увеличении величины задержки, прямо ударила по глазу, оптимизировать написанное стало более не возможно. Решил пойти по порядку, написать сначала счетчик Циклов для 1 байта, 2 байтов, и уже после, склеив все это, получить задержку в миллисекундах.

Результатом явилось 3 макроса:

; Задержка в циклах
; @0 – параметр в диапазоне 9-255 (количество циклов)
.macro DELAY_CL
	;push R16
	
	ldi R16, LOW(@0)-5
_delay_cl:
	subi R16, 4
	cpi R16, 4
	brsh _delay_cl
	
	cpi R16, 1
	breq end_cl_1	
	cpi R16, 0
	breq end_cl
	cpi R16, 2
	breq end_cl
	rjmp end_cl
			
end_cl_1:
	NOP
	NOP
	NOP
end_cl:
.endm	



; Задержка в циклах
; @0 – параметр в диапазоне 15-65535 (количество циклов)
.macro DELAY_C
	ldi R16, LOW(@0)	
	cpi R16, 17			
	brsh fault			
	rjmp init_R17		
	
fault:					
	DELAY_CL LOW(@0-7)	
	
init_R17:
	ldi R17, HIGH(@0)	
	
	cpi R17, 0			
	breq end_c				
	
new_cycle:				
	subi R17, 1			
	DELAY_CL 252		 
	cpi R17, 0			
	brne new_cycle		
	NOP					
end_c:
.endm



Циклы в соответствующих диапазонах считает точно.

; Задержка в милесекундах
;  @0 – параметр в диапазоне 1 – 65535 (количество миллисекунд)
; @1 – Частота в Герцах ( >= 1,3 MHz)
.macro DELAY_MS
	push R19				
	push R18				
	push R17				
	push R16				

	ldi R18, LOW(@0)		
	ldi R19, HIGH(@0)		

	cpi R18, 0				
	breq re_init 			
_cicl_msl:
	DELAY_C @1/1000			
	subi R18, 1				
	cpi R18, 0				
	breq re_init 			
	rjmp _cicl_msl			
re_init:
	cpi R19, 0			
	breq _end_c				
	subi R19, 1				
	ldi R18, 255			
	DELAY_C (@1/1000)-255*5	
	rjmp _cicl_msl

_end_c:

	pop R16
	pop R17
	pop R18
	pop R19
.endm



Результаты этого кода более успешны:

Для 1 мс (Atmega8535, F_CPU = 8001000 Hz)

Для 300мс (Atmega8535, F_CPU = 8001000 Hz)

Для 32с (Atmega8535, F_CPU = 8001000 Hz)

Для 300мс (Atmega6490, F_CPU = 4000000 Hz)

Для 300мс (ATtiny43U, F_CPU = 2000000 Hz)


Погрешность лежит в диапазоне ~4-150 микросекунд. Этого вполне достаточно.
Хотел бы изменить макрос DELAY_MS на подпрограмму (логично, встраивать при каждом вызове столько кода не разумно), но с ассемблером ковыряюсь недели 2, и пока не понял, как вынести все это в отдельный модуль, и сделать функцию в нем соответствующую.
Tags:
Hubs:
+3
Comments 19
Comments Comments 19

Articles