Мигание светодиодом
Чтобы мигать светодиодом на ассемблере, нам потребуется как-то выполнять временные задержки. При программировании микроконтроллеров ATmega на языке C мы использовали функцию _delay_ms библиотеки avr-libc, но в случае с ассемблером гораздо удобнее использовать для этого один из таймеров-счётчиков микроконтроллера, например таймер-счётчик 0.
Если микроконтроллер тактируется от кварцевого резонатора с частотой 16 МГц, то с предделителем /1024 переполнение регистра TCNT0 наступает примерно 61 раз в секунду (с погрешностью 0.06%). Соответственно, если нам требуется мигать светодиодом с частотой 1 Гц, то выделив для счётчика переполнений один из регистров микроконтроллера, например регистр r18, наша программа должна будет каждое 61-е переполнение включать и выключать светодиод.
Чтобы продемонстрировать использование указателей на функцию в ассемблере, я решил в данном примере выделить для указателя на функцию управления светодиодом регистровую пару r19:r20. В ней будет хранится адрес текущей функции включения (led_on) или выключения (led_off) светодиода, которая будет вызываться из обработчика прерывания TIMER0_OVF_vect.
Итак, функции led_on и led_off:
led_on:
push r24
push r31
push r30
; LED on
ldi r30, lo8(LEDPORT)
ldi r31, hi8(LEDPORT)
ld r24, Z
ori r24, 1<<LED
st Z, r24
; LEDF <- led_off
ldi LEDFH, pm_hi8(led_off)
ldi LEDFL, pm_lo8(led_off)
; DLYCNT <- ONDLY
ldi DLYCNT, ONDLY
pop r30
pop r31
pop r24
ret
led_off:
push r24
push r31
push r30
; LED off
ldi r30, lo8(LEDPORT)
ldi r31, hi8(LEDPORT)
ld r24, Z
andi r24, ~(1<<LED)
st Z, r24
; LEDF <- led_on
ldi LEDFH, pm_hi8(led_on)
ldi LEDFL, pm_lo8(led_on)
; DLYCNT <- OFFDLY
ldi DLYCNT, OFFDLY
pop r30
pop r31
pop r24
ret
Обработчик прерывания TIMER0_OVF_vect:
.global TIMER0_OVF_vect
TIMER0_OVF_vect:
push r0
in r0, _SFR_IO_ADDR(SREG)
push r0
; --DLYCNT == 0 ?
dec DLYCNT
and DLYCNT, DLYCNT
breq 1f
; No. Return from interrupt
2: pop r0
out _SFR_IO_ADDR(SREG), r0
pop r0
reti
; Yes. Call LEDF
1: push r30
push r31
mov r31, LEDFH
mov r30, LEDFL
icall
pop r31
pop r30
rjmp 2b
Функция main:
.text
.global main
main:
; LED pin to output mode
ldi r30, lo8(LEDDDR)
ldi r31, hi8(LEDDDR)
ld r24, Z
ori r24, 1<<LED
st Z, r24
call led_on
; DLYCNT <- ONDLY
ldi DLYCNT, ONDLY
; Timer0 setup. /1024 prescaler
ldi r24, (1<<CS02) | (1<<CS00)
ldi r30, lo8(TCCR0)
ldi r31, hi8(TCCR0)
st Z, r24
sei
; Enable Timer0 overflow interrupt.
ldi r30, lo8(TIMSK)
ldi r31, hi8(TIMSK)
ld r24, Z
ori r24, (1<<TOIE0)
st Z, r24
; Idle sleep mode
ldi r30, lo8(MCUCR)
ldi r31, hi8(MCUCR)
ld r24, Z
ori r24, (1<<SE)
st Z, r24
1: sleep
rjmp 1b
Константы и определения:
LEDPORT = PORTB ; LED connected to PB5
LEDDDR = DDRB
LED = 5
LEDFH = 20 ; LED control function pointer r19:r20
LEDFL = 19
ONDLY = 3 ; LED on delay
OFFDLY = 58 ; LED off delay
DLYCNT = 18 ; Delay counter
Сохраним исходный код примера в файл с названием blink.S, добавим пример в Makefile:
PROGRAMS = empty sleep blink
blink_SOURCES = blink.S
Скомпилируем его командой make и загрузим файл прошивки blink.hex в микроконтроллер командой make upload-blink.
Мигание светодиодом
Автор: Андрей Шаройко <vanyamboe@gmail.com>