В отсутствии многозадачного режима,
вектора прервываний это единнственный
способ реализовать многозадачность
В одном из предыдущих постов я дизассемлировал blink написаный на Си, и затем переписывал программу на ассемблере. Помнится тогда я выбросил таблицу прерываний под предлогом, что прерывания здесь не используются. Однако, в дальнейшем они будут активно использоваться и поэтому с ней следует разобраться.
Делать все буду на примере ATmega8, мне пока этот микроконтроллер кажется наиболее привлекательным.
В начале каждой программы на ассемблере должна идти таблица векторов прерываний, т.е. ссылок на продпрограммы, которые будут обрабатывать то или иное прерывание. Прерывание, это в свою очередь какое-то событие, при котором ход программы прерывается, и процесор бросается обработать это событите. Как не трудно догадаться, обрабатывать он это событие будет, с помощью той подпрограммы, ссылку на которую, мы запишем в таблице векторов прерывание.
Для каждой модели микроконтроллера набор прерываний свой, порядок их чередования задан на аппаратном уровне. Поэтому перед написанием первой программы для микроконтроллера, следует ознакомиться с этой таблицой в оффициальной документации. Дока на ATmega8: ATmega8/L datasheet. На странице 46 находим такую табличку:
Если разобраться в предыдущем посте: дизассемблирование blink.hex, где подробно разобрана реализация blink на ассемблере AVR, то проблем с написанием собственного варианта Blink на ассемблере не будет. Вообще-то программа там уже выложена. Осталось только разобраться с преобразованием исходников в hex файлы.
Мой вариант blink.asm:
.equ DDRB, 0x17 .equ PB0, 0x00 .equ PORTB, 0x18 .org 0x00 ; начало sbi DDRB, PB0; порт PB0 на передачу ldi r25, 0x01; r25=1 ; главный цикл loop: in r24, PORTB; r24=PORTB eor r24, r25; r24 = r24 xor r25 out PORTB, r24; PORTB=r25 ldi r18, 0x3F; r18=0x3F ldi r19, 0x0D; r19=0x0D ldi r24, 0x03; r24=0x03 sleep: subi r18, 0x01; (r18r19r24)-1 вычитание трехбайтного целого числа sbci r19, 0x00 sbci r24, 0x00 brne sleep; если значение в r24 не равно нулю, то переход на начало операции вычитания rjmp loop; возврат на начало главного цикла
Компиляция:
avr-as -mmcu=attiny13 -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: b8 9a sbi 0x17, 0 ; 23 2: 91 e0 ldi r25, 0x01 ; 1 4: 88 b3 in r24, 0x18 ; 24 6: 89 27 eor r24, r25 8: 88 bb out 0x18, r24 ; 24 a: 2f e3 ldi r18, 0x3F ; 63 c: 3d e0 ldi r19, 0x0D ; 13 e: 81 e0 ldi r24, 0x03 ; 3 10: 21 50 subi r18, 0x01 ; 1 12: 30 40 sbci r19, 0x00 ; 0 14: 80 40 sbci r24, 0x00 ; 0 16: e1 f7 brne .-8 ; 0x10 18: f5 cf rjmp .-22 ; 0x4
Ассемблер считается сложным языком программирования. Хотя, припоминая, к слову, функциональное программирование, где нет ни переменных, ни циклов, а вместо них одни рекурсии и функции, я бы поспорил. Но, мне кажется, что я знаю причину такого мнения. Если взять рядовую книжку по предмету, то прежде чем написать свой blink, сперва придется прочитать добрую сотню страниц, если не больше, про архитектуру, регистры, шины, порты и т.д. Не самое легкое чтиво, надо сказать. Взяв для сравнения книжку Кернигана&Ритчи мы найдем "Hello World!" на первой же странице. Весомый аргумент в пользу Си, на мой взгляд.
В предыдущем посте я упоминал, что размер прошивки с программой Blink написанной на Си для ATtiny45 составляет 82 байта. В AVR каждая инструкция вместе с аргументом занимает два байта, т.е. одно слово. 82 делим на 2, получаем 41 ассемблерная инструкция. Наверно, должен сразу оговориться. Если вы не получаете за это деньги, то изучать Ассемблер нет никакого практического смысла, языка Си хватит "за глаза" для написания своих прошивок. НО... иногда желание заглянуть "под капот" бывает слишком сильным;)
Если уж экспериментировать с ассемблером, то для начала, разумно будет выбрать чип попроще. Мой выбор пал на ATtiny13. Его флеш-память составляет всего 1 Кбайт. Опять же 1024 делим пополам, получаем размер программы до 500 инструкций, что для ассемблера совсем немного. По правде говоря, многие устройства работают на этом чипе, на мой взгляд один из самых популярных микроконтроллеров. По счастливой случайности, распиновка ATtiny13 совпадает с распиновкой ATtiny45, следовательно и исходник blink.c не меняется:
// blink.c for AVR ATtiny 13/25/45/85 #include <avr/io.h> #define F_CPU 1000000UL // частота резонатора 1МГц #include <util/delay.h> int main(void) { // макрос _BV(число) заменяет конструкцию (1 << число) DDRB |= _BV(PB0); // аналог pinMode(PB0,OUTPUT); в Wiring for (;;) { PORTB ^= _BV(PB0); // инвертируем состояние порта PB0 _delay_ms(1000); // ждем 1 секунду } return 0; }
Ах, чуть не забыл. Про книжки по ассемблеру AVR.
Немного раньше, я расказывал о том, как с помощью Arduino IDE программировать младшее семейство микроконтролеров AVR на примере ATtiny45. В последующем посте я обратил внимание на то, что вес прошивки составил 802 байта. Для ATtiny45 эта цифра не выглядит страшно, но в лишний раз подумаешь что бы связываться c ATtiny 25/24 с их двумя килобатами флеш-памяти. Давайте напишем Blink на Си и посмотрим на размер бинарника.
Как видно на 5-ом пине назначен порт PB0. На него и будем подключать светодиод.
Исходник:
// blink.c for AVR ATtiny 25/45/85 #include <avr/io.h> #define F_CPU 1000000UL // частота резонатора 1МГц #include <util/delay.h> int main(void) { // макрос _BV(число) заменяет конструкцию (1 << число) DDRB |= _BV(PB0); // аналог pinMode(PB0,OUTPUT); в Wiring for (;;) { PORTB ^= _BV(PB0); // инвертируем состояние порта PB0 _delay_ms(1000); // ждем 1 секунду } return 0; }
Компиляция:
$ avr-gcc -mmcu=attiny45 -Wall -Os -o blink.elf blink.c $ avr-objcopy -O ihex blink.elf blink.hex