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