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

от автора

А зачем?

Переходя с Си на ассемблер (нужда появилась) обнаружил для себя плохую вещь, на нем нет любимой функции _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 – параметр в диапазоне 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 – параметр в диапазоне 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, и пока не понял, как вынести все это в отдельный модуль, и сделать функцию в нем соответствующую.

ссылка на оригинал статьи http://habrahabr.ru/post/167319/


Комментарии

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *