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