В отсутствии многозадачного режима,
вектора прервываний это единнственный
способ реализовать многозадачность
В одном из предыдущих постов я дизассемлировал 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
Вроде всё ОК.