Мигание светодиодом

Чтобы мигать светодиодом на ассемблере, нам потребуется как-то выполнять временные задержки. При программировании микроконтроллеров 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>