ATmega8: Вектора прерываний

разделы: AVR , дата: 27 июля 2015г.


В отсутствии многозадачного режима,
вектора прервываний это единнственный
способ реализовать многозадачность

В одном из предыдущих постов я дизассемлировал blink написаный на Си, и затем переписывал программу на ассемблере. Помнится тогда я выбросил таблицу прерываний под предлогом, что прерывания здесь не используются. Однако, в дальнейшем они будут активно использоваться и поэтому с ней следует разобраться.

Делать все буду на примере ATmega8, мне пока этот микроконтроллер кажется наиболее привлекательным.

В начале каждой программы на ассемблере должна идти таблица векторов прерываний, т.е. ссылок на продпрограммы, которые будут обрабатывать то или иное прерывание. Прерывание, это в свою очередь какое-то событие, при котором ход программы прерывается, и процесор бросается обработать это событите. Как не трудно догадаться, обрабатывать он это событие будет, с помощью той подпрограммы, ссылку на которую, мы запишем в таблице векторов прерывание.

Для каждой модели микроконтроллера набор прерываний свой, порядок их чередования задан на аппаратном уровне. Поэтому перед написанием первой программы для микроконтроллера, следует ознакомиться с этой таблицой в оффициальной документации. Дока на ATmega8: ATmega8/L datasheet. На странице 46 находим такую табличку:

Всего имеем 19 прерывний. В примечании к табличке читаем, что если фьюз бит BOOTRST установлен, то при сбросе управление передается на адрес записаный в загрузчике. Во втором нотисе написано, что если установлен IVSEL бит в регистре GICR, то адреса остальных прерываний смещаются на размер загрузчика.

Причитав предполетный инструктаж, можно приступать к делу. Берем исходник Blink из предыдущего поста:

// blink.c for AVR ATMega8

#include <avr/io.h>
#define F_CPU 16000000UL // частота резонатора 16МГц
#include <util/delay.h>
int main(void) {
 // макрос _BV(число) заменяет конструкцию (1 << число)
 DDRB |= _BV(PB5); // аналог pinMode(13,OUTPUT); в Wiring
 for (;;) {
  PORTB ^= _BV(PB5); // инвертируем состояние порта PB5
  _delay_ms(500); // ждем полсекунды
 }
 return 0;
}

Затем компилируем и дизассемблируем ее:

$ avr-gcc -mmcu=atmega8 -Wall -Os -o blink.elf blink.c
$ avr-objdump -S blink.elf
blink.elf:     file format elf32-avr


Disassembly of section .text:

00000000 <__vectors>:
   0:   12 c0           rjmp    .+36            ; 0x26 <__ctors_end>
   2:   19 c0           rjmp    .+50            ; 0x36 <__bad_interrupt>
   4:   18 c0           rjmp    .+48            ; 0x36 <__bad_interrupt>
   6:   17 c0           rjmp    .+46            ; 0x36 <__bad_interrupt>
   8:   16 c0           rjmp    .+44            ; 0x36 <__bad_interrupt>
   a:   15 c0           rjmp    .+42            ; 0x36 <__bad_interrupt>
   c:   14 c0           rjmp    .+40            ; 0x36 <__bad_interrupt>
   e:   13 c0           rjmp    .+38            ; 0x36 <__bad_interrupt>
  10:   12 c0           rjmp    .+36            ; 0x36 <__bad_interrupt>
  12:   11 c0           rjmp    .+34            ; 0x36 <__bad_interrupt>
  14:   10 c0           rjmp    .+32            ; 0x36 <__bad_interrupt>
  16:   0f c0           rjmp    .+30            ; 0x36 <__bad_interrupt>
  18:   0e c0           rjmp    .+28            ; 0x36 <__bad_interrupt>
  1a:   0d c0           rjmp    .+26            ; 0x36 <__bad_interrupt>
  1c:   0c c0           rjmp    .+24            ; 0x36 <__bad_interrupt>
  1e:   0b c0           rjmp    .+22            ; 0x36 <__bad_interrupt>
  20:   0a c0           rjmp    .+20            ; 0x36 <__bad_interrupt>
  22:   09 c0           rjmp    .+18            ; 0x36 <__bad_interrupt>
  24:   08 c0           rjmp    .+16            ; 0x36 <__bad_interrupt>

00000026 <__ctors_end>:
  26:   11 24           eor     r1, r1
  28:   1f be           out     0x3f, r1        ; 63
  2a:   cf e5           ldi     r28, 0x5F       ; 95
  2c:   d4 e0           ldi     r29, 0x04       ; 4
  2e:   de bf           out     0x3e, r29       ; 62
  30:   cd bf           out     0x3d, r28       ; 61
  32:   02 d0           rcall   .+4             ; 0x38 <main>
  34:   10 c0           rjmp    .+32            ; 0x56 <_exit>

00000036 <__bad_interrupt>:
  36:   e4 cf           rjmp    .-56            ; 0x0 <__vectors>

00000038 <main>:
  38:   bd 9a           sbi     0x17, 5 ; 23
  3a:   90 e2           ldi     r25, 0x20       ; 32
  3c:   88 b3           in      r24, 0x18       ; 24
  3e:   89 27           eor     r24, r25
  40:   88 bb           out     0x18, r24       ; 24
  42:   2f ef           ldi     r18, 0xFF       ; 255
  44:   39 e6           ldi     r19, 0x69       ; 105
  46:   88 e1           ldi     r24, 0x18       ; 24
  48:   21 50           subi    r18, 0x01       ; 1
  4a:   30 40           sbci    r19, 0x00       ; 0
  4c:   80 40           sbci    r24, 0x00       ; 0
  4e:   e1 f7           brne    .-8             ; 0x48 <__SREG__+0x9>
  50:   00 c0           rjmp    .+0             ; 0x52 <__SREG__+0x13>
  52:   00 00           nop
  54:   f3 cf           rjmp    .-26            ; 0x3c <main+0x4>

00000056 <_exit>:
  56:   f8 94           cli

00000058 <__stop_program>:
  58:   ff cf           rjmp    .-2             ; 0x58 <__stop_program>

Если присмотреться, то программа имеет такую структуру:

Главное тело прорграммы, отмеченное зеленым, я уже рассмотрел, сейчас меня интересует "шапка" с таблицей прерываний. Как видно, здесь определено только одно прерывание на RESET, остальные ведут на метку <__bad_interrupt> которая в свою очередь ведет на RESET. Т.е. срабатывание любого прерывания будет вести к перезагрузке программы. В книге "Практическое программирование AVR на ассемлере." Ревич рекемендует ставить RETI на неиспользуемые прерывание, что на мой взгляд будет более корректным.

Код обработчика RESET, отмечен синим. Там сначала ноль пишется в регистр состояний SREG, затем в указатель стека SPH+SPL заносится последний адрес оперативки, т.е. содержимое стека сбрасывается. После всего, управление передается на главное тело программы, и по ее завершении на метку <_exit:>. Адреса регистров можно посмотреть на стр. 309 документации. В документации также приведен шаблон для "шапки":

Сейчас мы может перегнать код Си на язык ассеблера командой:

$ avr-gcc -mmcu=atmega8 -Wall -Os -S  blink.c

После чего получаем файл blink.s такого содержания:

	.file	"main.c"
__SP_H__ = 0x3e
__SP_L__ = 0x3d
__SREG__ = 0x3f
__tmp_reg__ = 0
__zero_reg__ = 1
	.section	.text.startup,"ax",@progbits
.global	main
	.type	main, @function
main:
/* prologue: function */
/* frame size = 0 */
/* stack size = 0 */
.L__stack_usage = 0
	sbi 0x17,5
	ldi r25,lo8(32)
.L2:
	in r24,0x18
	eor r24,r25
	out 0x18,r24
	ldi r18,lo8(1599999)
	ldi r19,hi8(1599999)
	ldi r24,hlo8(1599999)
	1: subi r18,1
	sbci r19,0
	sbci r24,0
	brne 1b
	rjmp .
	nop
	rjmp .L2
	.size	main, .-main
	.ident	"GCC: (GNU) 4.8.1"

Если переписать своей рукой, то получим такой код:

.equ DDRB, 0x17
.equ PB5, 0x05
.equ PORTB, 0x18
.equ SREG, 0x3F
.equ SPL, 0x3D
.equ SPH, 0x3E
.equ RAMEND_H, 0x04
.equ RAMEND_L, 0x5F

.org 0x00 ; начало
	rjmp RESET ; Reset Handler
	reti ;rjmp EXT_INT0 ; IRQ0 Handler
	reti ;rjmp EXT_INT1 ; IRQ1 Handler
	reti ;rjmp TIM2_COMP ; Timer2 Compare Handler
	reti ;rjmp TIM2_OVF ; Timer2 Overflow Handler
	reti ;rjmp TIM1_CAPT ; Timer1 Capture Handler
	reti ;rjmp TIM1_COMPA ; Timer1 CompareA Handler
	reti ;rjmp TIM1_COMPB ; Timer1 CompareB Handler
	reti ;rjmp TIM1_OVF ; Timer1 Overflow Handler
	reti ;rjmp TIM0_OVF ; Timer0 Overflow Handler
	reti ;rjmp SPI_STC ; SPI Transfer Complete Handler
	reti ;rjmp USART_RXC ; USART RX Complete Handler
	reti ;rjmp USART_UDRE ; UDR Empty Handler
	reti ;rjmp USART_TXC ; USART TX Complete Handler
	reti ;rjmp ADC ; ADC Conversion Complete Handler
	reti ;rjmp EE_RDY ; EEPROM Ready Handler
	reti ;jmp ANA_COMP ; Analog Comparator Handler
	reti ;rjmp TWSI ; Two-wire Serial Interface Handler
	reti ;rjmp SPM_RDY ; Store Program Memory Ready Handler

RESET:
	eor	r1, r1
	out	SREG, r1
	ldi 	r16, RAMEND_H; Main program start
	out 	SPH,r16 ; Set Stack Pointer to top of RAM
	ldi 	r16, RAMEND_L
	out 	SPL,r16
	sei

        sbi     DDRB, PB5;   порт PB0 на передачу
        ldi     r25, 0x20;   r25=(1<<5)=32;
; главный цикл
loop:
        in      r24, PORTB;  r24=PORTB
        eor     r24, r25;    r24 = r24 xor r25
        out     PORTB, r24;  PORTB=r25
        ldi     r18, 0xFF;   r18=0x3F
        ldi     r19, 0x69;   r19=0x0D
        ldi     r24, 0x18;   r24=0x03
sleep:
        subi    r18, 0x01;  (r18r19r24)-1  вычитание трехбайтного целого числа
        sbci    r19, 0x00
        sbci    r24, 0x00
        brne    sleep;     если значение в r24 не равно нулю, то переход на начало операции вычитания
        rjmp    loop;      возврат на начало главного цикла

Компилируем:

$ avr-as -mmcu=atmega8 -o blink.o blink.asm
$ avr-ld -o blink.elf blink.o
$ avr-objcopy --output-target=ihex blink.elf blink.hex

Проверяем:

$ avr-objdump -m avr -D blink.hex

blink.hex:     file format ihex


Disassembly of section .sec1:

00000000 <.sec1>:
   0:   12 c0           rjmp    .+36            ;  0x26
   2:   18 95           reti
   4:   18 95           reti
   6:   18 95           reti
   8:   18 95           reti
   a:   18 95           reti
   c:   18 95           reti
   e:   18 95           reti
  10:   18 95           reti
  12:   18 95           reti
  14:   18 95           reti
  16:   18 95           reti
  18:   18 95           reti
  1a:   18 95           reti
  1c:   18 95           reti
  1e:   18 95           reti
  20:   18 95           reti
  22:   18 95           reti
  24:   18 95           reti
  26:   11 24           eor     r1, r1
  28:   1f be           out     0x3f, r1        ; 63
  2a:   04 e0           ldi     r16, 0x04       ; 4
  2c:   0e bf           out     0x3e, r16       ; 62
  2e:   0f e5           ldi     r16, 0x5F       ; 95
  30:   0d bf           out     0x3d, r16       ; 61
  32:   78 94           sei
  34:   bd 9a           sbi     0x17, 5 ; 23
  36:   90 e2           ldi     r25, 0x20       ; 32
  38:   88 b3           in      r24, 0x18       ; 24
  3a:   89 27           eor     r24, r25
  3c:   88 bb           out     0x18, r24       ; 24
  3e:   2f ef           ldi     r18, 0xFF       ; 255
  40:   39 e6           ldi     r19, 0x69       ; 105
  42:   88 e1           ldi     r24, 0x18       ; 24
  44:   21 50           subi    r18, 0x01       ; 1
  46:   30 40           sbci    r19, 0x00       ; 0
  48:   80 40           sbci    r24, 0x00       ; 0
  4a:   e1 f7           brne    .-8             ;  0x44
  4c:   f5 cf           rjmp    .-22            ;  0x38

Вроде всё ОК.