Прерывание на контроллере AVR в Atmel AVR Studio. Прерывания - Изучаем AVR - Каталог статей - Микроконтроллеры - это просто! Стек и прерывания в микроконтроллере

Стек представляет собой область памяти, которую ЦПУ использует для сохранения и восстановления адресов возврата из подпрограмм.
Практически у всех микроконтроллеров AVR стек размещается в SRAM. Для адресации текущего элемента (вершины стека) используется указатель стека SP (Stack Pointer). Это однобайтовый РВВ SPL у моделей с объемом памяти данных до 256 б, или двухбайтовый SPH:SPL (SPH – старший байт, SPL – младший байт).

Когда микропроцессор встречает одну из инструкций вызовов rcall/call/ecall/icall/eicall, то адрес следующего за ними слова в памяти программ аппаратно копируется в стек. В момент выхода из подпрограммы по команде ret адрес возврата восстанавливается из стека в программный счетчик. В моделях с объемом памяти программ 128 и 256 к/слов для сохранения PC в стеке потребуется 3 байта, для всех остальных – 2 байта. При сохранении каждого байта содержимое SP уменьшается на единицу, а при восстановлении, соответственно увеличивается.

Рис.9 Расположение стека в памяти данных

Программист должен самостоятельно определить местоположение стека в самом начале программы. С точки зрения максимальной его глубины, вершину стека нужно поместить в самом конце SRAM, как это показано на рис.9:

Include "m8def.inc" ldi temp,low(RAMEND) ;устанавливаем SP = RAMEND out SPL,temp ;для ATmega8 SP = 0x045F ldi temp,high(RAMEND) out SPH,temp

Константа RAMEND из стандартного заголовочного файла m8def.inc имеет значение адреса последней ячейки SRAM.

В диапазоне адресов SRAM между РВВ и текущим положением SP размещаются переменные прикладной программы. Поэтому очень важно предварительно оценить максимальный размер стека (глубину стека). Может случиться так, что вершина стека поднимется слишком высоко и начнет “затирать” пользовательские данные, а это одна из самых сложно-выявляемых ошибок!

Стек AVR, помимо сохранения адресов возврата, имеет еще одно очень важное предназначение. Он позволяет сохранять любые данные специально предназначенными для этого командами push Rr (загрузка в стек) и pop Rd (выгрузка из стека). Каждый раз при выполнении push Rr содержимое Rr копируется в стек, после чего SP уменьшается на единицу. При выполнении pop Rr содержимое ячейки стека, на которую указывает SP, восстанавливается в Rr, а само значение SP инкрементируется. Стек подобного рода имеет организацию Last In First Out (Последний Вошел Первый Вышел): регистр, сохраненный последней командой push, будет восстановлен первой командой pop:

; SP Уровень стека после команды push R16 ;сохраняем R16 0x045F R16 ? ? push R17 ;сохраняем R17 0x045E R16 R17 ? push R18 ;сохраняем R18 0x045D R16 R17 R18 ̣̣̣̣̣̣̣̣ pop R18 ;восстанавливаем R18 0x045D R16 R17 ? pop R17 ;восстанавливаем R17 0x045E R16 ? ? pop R16 ;восстанавливаем R16 0x045F ? ? ?

Через стек очень просто можно обменять содержимое регистров местами:

; Обмен R16 <-> R17 SP Уровень стека после команды push R16 ;сохраняем R16 0x045F R16 ? push R17 ;сохраняем R17 0x045E R16 R17 pop R16 ;восстанавливаем R16 0x045E R16 ? pop R17 ;восстанавливаем R17 0x045F ? ?


Рис.10 Пример работы стека

На рис.10 приведен небольшой фрагмент кода, в котором пошагово рассмотрен процесс изменения стека при входе и выходе из подпрограммы toggle и сохранении и восстановлении регистра R17. Это типичный пример, где могут понадобиться инструкции push/pop. Подпрограмма toggle использует РОН R17 в своих нуждах, но этот- же регистр может использоваться и в ходе основной программы. Поэтому, во избежание повреждения данных, R17 перед модификацией загружается в стек и восстанавливается из него перед командой ret.

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

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

Когда приходит прерывание то контроллер завершит текущую команду (машинную инструкцию!) сразу же кинется выполнять процедуру обработки прерывания, а как выполнит, то вернется к прерваной фоновой программе.

Прерывания можно, а часто необходимо запрещать, чтобы посреди критичного участка не ускакать выполнять невесть что. Запрещать их можно глобально, флагом I в регистре SREG, а можно локально — запрещая источник каждого прерывания индивидуально. По дефолту, при сбросе, все прерывания от устройств запрещены, глобальный флаг тоже сброшен. Включем мы их по мере надобности.

Поскольку прерывание приходит ВНЕЗАПНО, а у нас могут быть несохраненные данные, то обработчик их должен сохранить и при выходе в фоновую программу вернуть все как было.

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

Вектора прерываний
Как процессор узнает куда ему перепрыгивать? А по вектору! Помнишь я тебе показывал на таблицу векторов прерываний? Она в самом начале памяти идет. Вот это оно.

Вектор это адрес перехода. У каждого аппаратного события имеющего прерывание есть свой вектор. Аппаратных событий у AVR тьма, поэтому таблица прерываний весьма толстая, десятки адресов.

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

Приоритеты прерываний
Тут принцип просто — кто раньше встал того и тапки. Когда процессор уходит на обработку прерывания, то аппаратно происходит запрет всех остальных прерываний.

Его, конечно, можно включить программно, установив флаг I в регистре SREG и тогда у нас будут вложенные прерывания, но обращатся с этим следует ОЧЕНЬ осторожно. Так как множественные прерывания нагружают стек и может произойти его переполнение, что даст полный сбой работы и начнется невесть что. Полная упячка.

По выходу из обработчика, по команде RETI флаг I вернется в прежнее состояние.

У многих сразу возникнет вопрос, а что будет если во время обработки одного прерывания придет другое? Оно потеряется?

Нет, не потеряется — у него взведется флаг и как только мы завершим первый обработчик автоматом произойдет переход к отложенному прерыванию. Единственное, что мы можем потерять количество одинаковых прерываний. Т.е. если обрабатывается, например, INT0 и прерывания запрещены, а в это время придет три раза INT1, то на выходе INT1 обработается только один раз.

А если во время обработки первого прерывания случится не одно, а, скажем, два или три? Какое из этих отложеных прерываний будет выполнено первым? А по порядку в таблице векторов. У кого адрес младше тот и пойдет первым.

Теперь тезисно:

  • Прерывания это аппаратные события.
  • У каждого прерывания есть свой персональный адрес вектор, по которому и будет послан проц в случае чего.
  • По дефолту они запрещены локально и глобально.
  • Вызов прерывания может быть когда угодно и где угодно, между любыми двумя командами (хотя хрена там, между CLI CLI его не будет:))
  • В обработчике прерывания надо учитывать тот факт, что данные нужно сохранять и восстанавливать при выходе.
  • Приоритет прерываний работает по принципу «кто первый встал тот и ходит в тапках». Остальные запрещены аппаратно, но можно разрешить программно уже в обработчике.
  • Проснувшиеся после никуда не теряются и ждут своих тапок — права порулить процом, в порядке жесткой очереди по таблице прерываний.
  • Прерывания это колоссальный источник глюков и головная боль любого быдлокодера. Но без них никак, поэтому эту тему надо знать вдоль и поперек.
  • Посколькоу прерывание ВНЕЗАПНОЕ и может быть где угодно, то обработчик прерывания должен выполняться МАКСИМАЛЬНО КОРОТКО И БЫСТРО. Зашел, отметился, вышел. Только так. Никаких задержек и длинных циклов. Все сложные вещи только в фоновой задаче. Но об этом подробней позже.

Когда пишешь на Си, то прервания можно использовать только по назначению. Ассемблерщики же могут извращаться, например, для создания уберточного кода когда нужно выдерживать длительность с точностью до такта. .

Итак, теорию повторили, вернемся к практике.

Прерывание у нас будет от USART. Пришел байт — вызвалось прерывание.

Раз мы решили заюзать прерывания, то сначала надо подключить библиотеку прерываний:

ISR(вектор прерывания) { }

Так что берем и добавляем в код, где нибудь до или после процедуры main эту бодягу. Вот только как узнать что вписать в вектор прерывания? Мануалы к черту — считаем что их нет. Инфу будем добывать раскопками, выгрызая из подножного корма.

Прерывания вещь интимная и зависят от конкретной модели контроллера. Так что искать описание векторов прерываний надо искать в файле который описывает наш контроллер.

Какой это файл? А позырь в дерево проектов, ветка зависимостей. Что там? У меня Mega16 и там есть файл iom16.h Из всех файлов он больше всех похож на искомый — потому как только он явно для меги16 =).

Там куча всего, что искать? А все что связано с прерываниями — ищи Interrupt и все что рядом.

Очень скоро найдешь секцию /* Interrupt vectors */ и там будут все вектора, в том числе и на USART

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 /* Interrupt vectors */ /* Vector 0 is the reset vector. */ /* External Interrupt Request 0 */ #define INT0_vect _VECTOR(1) #define SIG_INTERRUPT0 _VECTOR(1) /* External Interrupt Request 1 */ #define INT1_vect _VECTOR(2) #define SIG_INTERRUPT1 _VECTOR(2) /* Timer/Counter2 Compare Match */ #define TIMER2_COMP_vect _VECTOR(3) #define SIG_OUTPUT_COMPARE2 _VECTOR(3) /* Timer/Counter2 Overflow */ #define TIMER2_OVF_vect _VECTOR(4) #define SIG_OVERFLOW2 _VECTOR(4) /* Timer/Counter1 Capture Event */ #define TIMER1_CAPT_vect _VECTOR(5) #define SIG_INPUT_CAPTURE1 _VECTOR(5) /* Timer/Counter1 Compare Match A */ #define TIMER1_COMPA_vect _VECTOR(6) #define SIG_OUTPUT_COMPARE1A _VECTOR(6) /* Timer/Counter1 Compare Match B */ #define TIMER1_COMPB_vect _VECTOR(7) #define SIG_OUTPUT_COMPARE1B _VECTOR(7) /* Timer/Counter1 Overflow */ #define TIMER1_OVF_vect _VECTOR(8) #define SIG_OVERFLOW1 _VECTOR(8) /* Timer/Counter0 Overflow */ #define TIMER0_OVF_vect _VECTOR(9) #define SIG_OVERFLOW0 _VECTOR(9) /* Serial Transfer Complete */ #define SPI_STC_vect _VECTOR(10) #define SIG_SPI _VECTOR(10) /* USART, Rx Complete */ /* USART, Tx Complete */ /* ADC Conversion Complete */ #define ADC_vect _VECTOR(14) #define SIG_ADC _VECTOR(14) /* EEPROM Ready */ #define EE_RDY_vect _VECTOR(15) #define SIG_EEPROM_READY _VECTOR(15) /* Analog Comparator */ #define ANA_COMP_vect _VECTOR(16) #define SIG_COMPARATOR _VECTOR(16) /* 2-wire Serial Interface */ #define TWI_vect _VECTOR(17) #define SIG_2WIRE_SERIAL _VECTOR(17) /* External Interrupt Request 2 */ #define INT2_vect _VECTOR(18) #define SIG_INTERRUPT2 _VECTOR(18) /* Timer/Counter0 Compare Match */ #define TIMER0_COMP_vect _VECTOR(19) #define SIG_OUTPUT_COMPARE0 _VECTOR(19) /* Store Program Memory Ready */ #define SPM_RDY_vect _VECTOR(20) #define SIG_SPM_READY _VECTOR(20) #define _VECTORS_SIZE 84

/* Interrupt vectors */ /* Vector 0 is the reset vector. */ /* External Interrupt Request 0 */ #define INT0_vect _VECTOR(1) #define SIG_INTERRUPT0 _VECTOR(1) /* External Interrupt Request 1 */ #define INT1_vect _VECTOR(2) #define SIG_INTERRUPT1 _VECTOR(2) /* Timer/Counter2 Compare Match */ #define TIMER2_COMP_vect _VECTOR(3) #define SIG_OUTPUT_COMPARE2 _VECTOR(3) /* Timer/Counter2 Overflow */ #define TIMER2_OVF_vect _VECTOR(4) #define SIG_OVERFLOW2 _VECTOR(4) /* Timer/Counter1 Capture Event */ #define TIMER1_CAPT_vect _VECTOR(5) #define SIG_INPUT_CAPTURE1 _VECTOR(5) /* Timer/Counter1 Compare Match A */ #define TIMER1_COMPA_vect _VECTOR(6) #define SIG_OUTPUT_COMPARE1A _VECTOR(6) /* Timer/Counter1 Compare Match B */ #define TIMER1_COMPB_vect _VECTOR(7) #define SIG_OUTPUT_COMPARE1B _VECTOR(7) /* Timer/Counter1 Overflow */ #define TIMER1_OVF_vect _VECTOR(8) #define SIG_OVERFLOW1 _VECTOR(8) /* Timer/Counter0 Overflow */ #define TIMER0_OVF_vect _VECTOR(9) #define SIG_OVERFLOW0 _VECTOR(9) /* Serial Transfer Complete */ #define SPI_STC_vect _VECTOR(10) #define SIG_SPI _VECTOR(10) /* USART, Rx Complete */ #define USART_RXC_vect _VECTOR(11) #define SIG_USART_RECV _VECTOR(11) #define SIG_UART_RECV _VECTOR(11) /* USART Data Register Empty */ #define USART_UDRE_vect _VECTOR(12) #define SIG_USART_DATA _VECTOR(12) #define SIG_UART_DATA _VECTOR(12) /* USART, Tx Complete */ #define USART_TXC_vect _VECTOR(13) #define SIG_USART_TRANS _VECTOR(13) #define SIG_UART_TRANS _VECTOR(13) /* ADC Conversion Complete */ #define ADC_vect _VECTOR(14) #define SIG_ADC _VECTOR(14) /* EEPROM Ready */ #define EE_RDY_vect _VECTOR(15) #define SIG_EEPROM_READY _VECTOR(15) /* Analog Comparator */ #define ANA_COMP_vect _VECTOR(16) #define SIG_COMPARATOR _VECTOR(16) /* 2-wire Serial Interface */ #define TWI_vect _VECTOR(17) #define SIG_2WIRE_SERIAL _VECTOR(17) /* External Interrupt Request 2 */ #define INT2_vect _VECTOR(18) #define SIG_INTERRUPT2 _VECTOR(18) /* Timer/Counter0 Compare Match */ #define TIMER0_COMP_vect _VECTOR(19) #define SIG_OUTPUT_COMPARE0 _VECTOR(19) /* Store Program Memory Ready */ #define SPM_RDY_vect _VECTOR(20) #define SIG_SPM_READY _VECTOR(20) #define _VECTORS_SIZE 84

Прорва, на каждый чих, но нам сейчас интересны те прерывания которые отвечают за USART

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

1 2 3 4 5 6 7 8 9 10 11 12 13 14 /* USART, Rx Complete */ #define USART_RXC_vect _VECTOR(11) #define SIG_USART_RECV _VECTOR(11) #define SIG_UART_RECV _VECTOR(11) /* USART Data Register Empty */ #define USART_UDRE_vect _VECTOR(12) #define SIG_USART_DATA _VECTOR(12) #define SIG_UART_DATA _VECTOR(12) /* USART, Tx Complete */ #define USART_TXC_vect _VECTOR(13) #define SIG_USART_TRANS _VECTOR(13) #define SIG_UART_TRANS _VECTOR(13)

/* USART, Rx Complete */ #define USART_RXC_vect _VECTOR(11) #define SIG_USART_RECV _VECTOR(11) #define SIG_UART_RECV _VECTOR(11) /* USART Data Register Empty */ #define USART_UDRE_vect _VECTOR(12) #define SIG_USART_DATA _VECTOR(12) #define SIG_UART_DATA _VECTOR(12) /* USART, Tx Complete */ #define USART_TXC_vect _VECTOR(13) #define SIG_USART_TRANS _VECTOR(13) #define SIG_UART_TRANS _VECTOR(13)

Мы собрались по входному сигналу делать экшн. Так что применяй первый. Какой из трех? Без разницы, они равнозначны и их тут столько исключительно из совместимости с разными версиями компиляторов.

Вписываешь в код вот такую шнягу:

1 2 3 4 ISR(USART_RXC_vect) { }

ISR(USART_RXC_vect) { }

Осталось прописать в код то что мы должны сделать из прерывания. Первое — нужно обязательно считать данные из регистра UDR иначе флаг прерывания останется висеть и оно будет непрерывно вызываться еще и еще.

Для определения байта можно применить конструкцию Switch-case
В итоге, получилась такая вещь:

1 2 3 4 5 6 7 8 9 ISR(USART_RXC_vect) { switch (UDR) { case "1" : LED_PORT = 1 << LED2; break ; case "0" : LED_PORT = 0 << LED2; break ; default : break ; } }

ISR(USART_RXC_vect) { switch(UDR) { case "1": LED_PORT = 1<

Как видишь, у нас в свитче берется UDR и в зависимости от того чему он равен, символу нуля или символу единицы осуществляется переход. Если не то и не другое, то переход на default и сразу выход из свитча — break. break это вообще выход из программной структуры. Например, если сделаешь break в цикле, то выпадешь из цикла.

В значении case можно было бы написать и код символа, было бы case 0x31: но, как я говорил, никаких цифр. Если есть возможность их избежать — избегай всеми силами. Пусть за тебя препроцессор компилятора отдувается.

В частности ‘символ’ это ASCII код символа. Удобно!

Да, обрати внимание на то, что в каждой строке case стоит break. Это неспроста! Если break там не будет, то пройдя на case 0 и выполнив там все процессор перейдет к следующему case и так далее, пока не выйдет из него совсем или не нарвется на break.

Что зажигания диодов, то тут, как видишь, пришлось добавить еще и LED2, прописав его в дефайнах. И не забыв проинициализировать его порт на выход! В итоге, стало выглядеть так:

1 LED_DDR = 1 << LED1| 1 << LED2;

LED_DDR = 1<

Правда сразу ничего у меня не заработало — посыпалось куча ошибок. Пришлось взять все дефайны в кучу и поднять их над всем кодом — компилятор же читает код сверху вниз так что вначале он должен прочитать все макроопределения прежде чем ему встретятся операции с ними.

Вот текущий код целиком:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 #define F_CPU 8000000L #define LED1 4 #define LED2 5 #define LED_PORT PORTD #define LED_DDR DDRD #include #include #include ISR(USART_RXC_vect) { switch (UDR) { case "1" : LED_PORT = 1 << LED2; break ; case "0" : LED_PORT = 0 << LED2; break ; default : break ; } } int main(void ) { volatile unsigned char i; #define XTAL 8000000L #define baudrate 9600L #define bauddivider (XTAL/(16*baudrate)-1) #define HI(x) ((x)>>8) #define LO(x) ((x)& 0xFF) UBRRL = LO(bauddivider) ; UBRRH = HI(bauddivider) ; UCSRA = 0 ; UCSRB = 1 << RXEN| 1 << TXEN| 1 << RXCIE| 1 << TXCIE; UCSRC = 1 << URSEL| 1 << UCSZ0| 1 << UCSZ1; LED_DDR = 1 << LED1| 1 << LED2; while (1 ) { i++; LED_PORT= 0 << LED1; _delay_ms(1000 ) ; LED_PORT= 1 << LED1; _delay_ms(1000 ) ; } return 0 ; }

#define F_CPU 8000000L #define LED1 4 #define LED2 5 #define LED_PORT PORTD #define LED_DDR DDRD #include #include #include ISR(USART_RXC_vect) { switch(UDR) { case "1": LED_PORT = 1<>8) #define LO(x) ((x)& 0xFF) UBRRL = LO(bauddivider); UBRRH = HI(bauddivider); UCSRA = 0; UCSRB = 1<

Вроде все написано. Запускай код на эмуляцию. Но вот как проверить прерывание?

В данном случае, в студии, для этого нет никаких эмуляторов терминала (вообще есть hapsim, а еще можно отлаживать в Proteus или VMLAB, но об этом я пожалуй расскажу попозже).

Но можно сделать вид, что пришло прерывание, словно терминал сработал. Процессор определяет, что произошло прерывание когда периферия, его генерирующая, поднимет флаг прерывания .

У USART на прием флаг RxC вот возьми и ткни его, мол прерывание свершилось.

Вручную выстави этот бит, найдя его в окне i/o view в ветви USART. Поставил, а потом сделай пару шагов по F11 и… нифига не произошло!

А почему? а потому, что мы забыли эти прерывания врубить. Частая ошибка начинающих. Дело в том, что мало разрешить прерывания при инициализации UART — это мы разрешили локальные прерывания, уартовские, а есть еще флаг глобального разрешения — флаг I он по дефолту сброшен.

Установка и сброс флага разрешения глобальных прерываний делается командой SEI и CLI соотвественно. В Си есть макрос sei(); и cli(); который просто подставит эту ассемблерную команду. Берем и добавляем его в конце инициализации:
Закопипащу немного кода оттуда, чтобы ты понял куда я его сунул:

1 2 3 4 5 UCSRC = 1 << URSEL| 1 << UCSZ0| 1 << UCSZ1; LED_DDR = 1 << LED1| 1 << LED2; sei() ; // <--- вот оно. while (1 )

UCSRC = 1<

Перезапусти эмуляцию и снова ткни флаг, сделав после пару шагов.

Во, теперь у тебя программа должна будет оказаться в ISR cтоя на заголове Switch. Пора проверить как работает.

В этот момент открой I/O View и натыкай в регистре UDR число 0х31 или просто ткни в окошко со значением и введи там код 0х31.

Нажми F11 опа! мы перешли к первой строке case и зажгли диодик, а после сразу же вышли из кейса и из прерывания вообще.

Вскрытие прерывания
Теперь пора показать тебе как работает прерывание изнутри. Переходи в дизассемблер и смотри что там поменялось. В первую очередь, изменился переход на векторе номер 11 — вектор RXC — мы ведь на него обработчик повесили.

….
+00000014: 940C0034 JMP 0x00000034 Jump
+00000016: 940C0036 JMP 0x00000036 Jump
+00000018: 940C0034 JMP 0x00000034 Jump
….

А со строки

@00000036: __vector_11

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

1 2 3 4 5 6 +00000036: 921F PUSH R1 Push register on stack +00000037: 920F PUSH R0 Push register on stack +00000038: B60F IN R0,0x3F In from I/O location +00000039: 920F PUSH R0 Push register on stack +0000003A: 2411 CLR R1 Clear Register +0000003B: 938F PUSH R24 Push register on stack

00000036: 921F PUSH R1 Push register on stack +00000037: 920F PUSH R0 Push register on stack +00000038: B60F IN R0,0x3F In from I/O location +00000039: 920F PUSH R0 Push register on stack +0000003A: 2411 CLR R1 Clear Register +0000003B: 938F PUSH R24 Push register on stack

По такой бодяге легко отличить обработчик прерывания. То что там затесался IN R0,0x3F это не случайно — таким образом идет сохранение регистра SREG в котором хранятся флаги. Их тоже сохранять обязательно, ведь от них зависят операции условий.

switch(UDR) +0000003C: B18C IN R24,0x0C //Берем значение из UDR +0000003D: 3380 CPI R24,0x30 // И сравниваем его последовательно с кодами +0000003E: F029 BREQ PC+0x06 // И переход на обработку если совпало +0000003F: 3381 CPI R24,0x31 // Аналогично и далее +00000040: F421 BRNE PC+0x05

И по очереди сравниваем с кодами 0х30 и 0x31 в случае совпадения или не совпадения осуществляем переход либо на default либо на нужный case: которые будут ниже по тексту:

1 2 3 4 5 6 7 case "1": LED_PORT = 1<

case "1": LED_PORT = 1<

Прерывание завершается массовым доставанием из стека данных которые мы туда сунули и командой RETI
Данные достаются в обратно порядке!

1 2 3 4 5 6 5: 918F POP R24 Pop register from stack +00000046: 900F POP R0 Pop register from stack +00000047: BE0F OUT 0x3F,R0 Out to I/O location +00000048: 900F POP R0 Pop register from stack +00000049: 901F POP R1 Pop register from stack +0000004A: 9518 RETI

5: 918F POP R24 Pop register from stack +00000046: 900F POP R0 Pop register from stack +00000047: BE0F OUT 0x3F,R0 Out to I/O location +00000048: 900F POP R0 Pop register from stack +00000049: 901F POP R1 Pop register from stack +0000004A: 9518 RETI

Работает! Задание вроде мы выполнили. Но …

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


Поговорим о прерываниях. Слово прерывание говорит само за себя, происходит остановка какого - то процесса на какое - то время, для того чтобы выполнить дополнительные действия. Прерывания могут быть внешними или внутренними. Приведу простой пример, услышанный из уст моего друга…

Собрался он помыть посуду на кухне, взялся с азартом, засучив рукава…но посуда оказалась жирной и он был вынужден прерваться, чтобы найти на одной из полок кухонного гарнитура средство для мытья жирной посуды, после чего снова продолжил свое занятие. Но в какой-то момент зазвонил телефон, и он опять прервался от своей работы, поднял трубку, звонила теща и сказала, что придет в гости, значит надо сходить в магазин купить продукты к ее приходу. Сходил в магазин и только после этого домыл посуду.

На этом примере видно два вида прерываний, первое – связано с выполнением основной работы - поиск средства для жирной посуды -внутреннее прерывание, второе – телефонный звонок – внешнее прерывание.
В микроконтроллере внешние прерывания возникают за счет сигналов поступающих от других источников, внутренние – за счет устройств встроенных в сам микроконтроллер. Чем же так привлекательны прерывания?
Первое - это то, что мы можем остановить основной процесс для выполнения каких либо других функции, с последующим продолжением этого процесса.
Вторым, и наверное во многих случаях основным считается ускорение процесса выполнения всех функций, за счет внутренних дополнительных устройств. Вернемся к нашему примеру. Допустим, мой друг взялся мыть посуду, когда его жена уже пришла домой. Увидев жирную посуду, он просит ее найти средство для мытья посуды, и пока он моет, она уже принесет ему это средство. Но, вот зазвонил телефон, трубку поднимет жена, поговорит с мамой и сходит в магазин. Совместно все дела сделаны очень быстро!
А еще проще зациклится – т.е. основной программы нет.
Мой друг сидит на диване и ничего не делает, домоработница увидев грязную посуду, говорит ему об этом, и получив разрешение, начинает мыть сама. Когда звонит телефон, он говорит жене, чтобы она подняла трубку, жена разговаривает по телефону, и поле разговора идет в магазин за продуктами… Красота! В таком случае в микроконтроллере одновременно работают несколько устройств ввода-вывода (в современных микроконтроллерах их может быть достаточно много) и общая производительность процессора возрастает во много раз, но прерывания от устройств обрабатываются последовательно одно за другим (не одновременно), в зависимости от приоритета (в нашем примере жена имеет больший приоритет, нежели домоработница).

За управление прерываниями отвечают несколько регистров
SREG –регистр статуса (состояния). Смотрим таблицу устройств ввода-вывода. Седьмой бит регистра SREG –флаг I (interrupt), который называется флагом глобального разрешения прерываний. Если флаг опущен (седьмой бит равен нулю), то все прерывания запрещены. Если флаг поднять (установить I в 1), мы разрешим прерывания.

Устанавливается и сбрасывается флаг I командами:
SEI - разрешить прерывания
CLI - запретить прерывания
Какие из прерываний будут работать, задается с помощью регистров называемых – масками прерываний .
Обозначаются маски прерываний следующим образом:
TIMSK,..,..,.. – управление прерываниями от таймеров и других встроенных устройств .
GIMSK (GIKR в семействе Mega) - управление всеми внешними прерываниями .
Маски прерываний в свою очередь зависят от флагов прерываний:
TIFR и GIFR соответственно (не путайте с флагом глобального разрешения прерываний).

Последовательность выполнения прерываний:
При включении микроконтроллера все флаги прерываний сброшены в 0. Для включения прерываний программа должна установить флаг I регистра SREG в 1. После этого прописать регистры маски с установленными локальными прерываниями (прерывания, которые нам нужны).
Когда приходит (сигнал) запрос на прерывание, то он поднимает флаг прерывания (даже в том случае если прерывание запрещено, для организации вложенных прерываний и приоритета между разными прерываниями). Если нет запрета прерываний, то контроллер обратится к соответствующему (Interrupt Vectors) - вектору прерываний , приостанавливая текущую программу.
Вектор прерывания – это фиксированная строка программной области, куда переходит программа в случае возникновения прерывания.
Весь список векторов прерывания – называется таблицей векторов прерывания , который располагается в начале программного кода .
Итак, в момент обращения к вектору прерывания, флаг I регистра SREG и флаг вызвавший прерывание сбрасывается в 0, запрещая другие прерывания. Если в процессе выполнения прерывания, возникли другие запросы прерываний, флаги этих прерываний остаются поднятыми. По окончании выполнения текущего прерывания флаг I регистра SREG поднимается, разрешая выполнение следующего. Если пришли несколько запросов, и их флаги окажутся поднятыми то первым будет выполнено прерывание, чей вектор меньше по адресу в таблице, ближе к началу памяти. За ним второй, и так далее. Кроме этого программист может организовать так называемое вложенное прерывание, когда в процессе выполнения программы прерывания возникает еще одно прерывание. Тогда прекращается выполнение текущего прерывания и выполняется новое, после завершения которого, возобновляется выполнение остановленного прерывания.

В качестве примера приведена таблица векторов прерывания для ATtiny2313

Таблица векторов прерывания для Атмега16 выглядит следующим образом:

При сравнении, таблицы совершенно не совпадают.
В семействе ATtiny строка вектора прерывания занимает 16 бит, а в семействе Mega занимают 32 бита (обратите внимание на адреса векторов прерывания, напомню, что адресная строка в программной области представлена 16 битным словом).

Программный код для ATtiny2313 может выглядеть следующим образом:
.cseg .org 0 rjmp Reset rjmp INT_0 rjmp INT_1 rjmp Timer1_capt1 rjmp Timer1_comp1 rjmp Timer1_OVF1 rjmp Timer0_OVF0 rjmp UART_RX rjmp UART_UDRE rjmp UART_TX rjmp ANA_COMP rjmp PCINT rjmp Timer1_compB rjmp Timer0_compA rjmp Timer0_compB rjmp USI_START rjmp USI_OVERFLOW rjmp EE_READY rjmp WDT_ OVERFLOW

Как видно, вектор прерывания создает относительный переход на метки программ прерываний. Ниже в таблице показаны варианты; 1. Когда нет прерываний; 2, 3. с внешним прерыванием по входу INT_1.
Если метки «пустые” (под меткой нет программы), то ничего не происходит, и программа последовательно «пробежавшись” по оставшимся меткам благополучно доходит до команды RETI- Interrupt return - выход из обработчика прерывания как показано в первом столбце таблицы.

Чтобы выполнить программу прерывания, например по входу INT_1, нужно метку INT_1: вынести из списка. Это схематично показано во втором столбце таблицы.
Но, программисту неудобно каждый раз прописывать все прерывания и отдельно метки к ним, особенно в последних моделях, где таблица достаточно большая, проще в строке вектора прерывания сразу написать команду RETI, если прерывание не используется. Тогда программа будет выглядеть, как показано в третьем столбце таблицы.

В AVR-контроллерах в зависимости от модели может быть от 1 до 8 входов внешних прерываний .
Рассмотрим систему управления внешними прерываниями. Для этого предусмотрены следующие комбинации I/O-регистров в зависимости от модели (см. соответствующий DataSheet):
- GIMSK, EIFR, PCMSK, MCUCR;
- GIKR, GIFR, MCUCR;
- EIMSK, EICR, EIFR;
GIMSK, GIKR, EIMSK - маски прерываний,
EIFR, PCMSK, GIFR, EIFR – флаги прерываний
Для разрешения или запрещения внешних прерываний предназначены управляющие регистры: GIMSK-(General Interrupt Mask Register)(Tiny), GICR- (General Interrupt Control Register)(Mega), MCUCR – (MCU Control Register)




EIFR- External Interrupt Flag Register: 1- разрешено, 0 – запрещено. Каждый бит (флаг) разрешает соответствующему выводу работать в качестве источника прерываний.

Биты управления регистра GIMSK:
Бит 7 – INT1 : External Interrupt Request 1 Enable – бит разрешения прерывания INT1: 1 – разрешено, 0 – запрещено. Прерывание будет формироваться, даже если вывод INT1 настроен как выход. Бит INT1 настраиваются на прерывание в регистре флагов EIFR. Вывод INT1 синхронизирован с тактовым генератором.

Бит 6 – INT0 : External Interrupt Request 0 Enable - бит разрешения прерывания INT0: 1 – разрешено, 0 – запрещено. Прерывание будет формироваться, даже если вывод INT0 настроен как выход. Бит INT0 настраиваются на прерывание в регистре флагов EIFR. Вывод INT10 синхронизирован с тактовым генератором.

Бит 5 – PCIE : Pin Change Interrupt Enable – бит разрешения прерывания на выводах PCINT0…7: 1- разрешено, 0 – запрещено. Любое изменение на любом из выводов PCINT0…7 будет формировать прерывание. Выводы PCINT0…7 настраиваются на прерывание индивидуально, битами в регистре флагов PCMSK.

PCMSK - Pin Change Mask Regiser - регистр флагов PCMSK: 1- разрешено, 0 – запрещено. Каждый бит (флаг) разрешает соответствующему выводу работать в качестве источника прерываний. Выводы PCINT0…7 не синхронизированы с тактовым генератором, т.е. прерывание наступает по факту изменения на любом из выводов.

Mega8

и соответствующий ему регистр флагов


Бит 7

Бит 6 – INT0 : External Interrupt Request 0 Enable - бит разрешения прерывания INT0: 1 – разрешено, 0 – запрещено. Прерывание будет формироваться, даже если вывод INT0 настроен как выход. Бит INT0 настраиваются на прерывание в регистре флагов GIFR



GIFR– General Interrupt Flag Register: 1- разрешено, 0 – запрещено. Каждый бит (флаг) разрешает соответствующему выводу работать в качестве источника прерываний.

Биты управления регистра GICR:
Бит 7 – : External Interrupt Request 1 Enable – бит разрешения прерывания INT1 : 1 – разрешено, 0 – запрещено. Прерывание будет формироваться, даже если вывод INT1 настроен как выход. Бит INT1 настраиваются на прерывание в регистре флагов GIFR

Бит 6 – INT0 : External Interrupt Request 0 Enable - бит разрешения прерывания INT0 : 1 – разрешено, 0 – запрещено. Прерывание будет формироваться, даже если вывод INT0 настроен как выход. Бит INT0 настраиваются на прерывание в регистре флагов GIFR

Бит 5 – INT2 : External Interrupt Request 2 Enable - бит разрешения прерывания INT2 : 1 – разрешено, 0 – запрещено. Прерывание будет формироваться, даже если вывод INT2 настроен как выход. Бит INT2 настраиваются на прерывание в регистре флагов GIFR

Функциями входов INT0 и INT1во всех контроллерах управляют младшие биты регистра MCUCR

MCUCR– MCU Control Register
Биты управления:
Биты 1, 0 – ISC01, ISC00 (Interrupt Sense Control 0 Bit 1 and Bit 0) – состояние данных битов определяет событие на выводе INT0, при котором формируется прерывание INT0:
ISC01=0, ISC00=0 – уровень логического нуля;
ISC01=0, ISC00=1 – любая смена логического состояния;
ISC01=1, ISC00=0 – по спадающему фронту;
ISC01=1, ISC00=1 – по нарастающему фронту.

Биты 3, 2 – ISC11, ISC10 (Interrupt Sense Control 1 Bit 1 and Bit 0) – состояние данных битов определяет уровень сигнала на выводе INT1, по которому формируется прерывание INT1:
ISC11=0, ISC10=0 – уровень логического нуля;
ISC11=0, ISC10=1 – любая смена логического состояния;
ISC11=1, ISC10=0 – по спадающему фронту;
ISC11=1, ISC10=1 – по нарастающему фронту.

Ну вот, вроде как с минимумом о внешних прерываниях поговорили.
Понятно, что для того, чтобы прерывания работали, нужно соответственно их прописывать.
Допишем начатую для tiny, инициализацию прерывания на INT1 по возрастающему фронту сигнала:

Ldi r16,0x80 ; запишем в r16 число 0b10000000 ldi r17,0x0C ; запишем в r17 число 0b00001100 out MCUCR,r17 ; прерывание сформируется по нарастающему фронту ISC11=1, ISC10=1 out GIMSK,r16 ; выставим маску INT0 sei
Кстати на tiny2313 можно сформировать прерывание на любых выводах PCINT0…7 , на Mega до 48 серии эти возможности отсутствуют…
Есть такие операции, при выполнении которых, возникшие прерывания могут вызвать сбой программы. В таких случаях перед началом выполнения операции пишем CLI, а после SEI. Называются такие операции – атомарными .
Желательно, чтобы программы прерываний были компактными и выполнялись с максимальной скоростью, потому, что целью любых прерываний является фиксация события. Если по разным причинам программа выполняется медленно, то достаточно зафиксировать событие и обработать его чуть позже.

Чтобы не загромождать лишней информацией изложенный материал, рекомендую читателям пользоваться даташитами, а если не все понятно, то чаще задавать вопросы на форумах.
Дальше, подробно рассмотрим внутренние прерывания на основе встроенных таймеров. читателей. Для участия в голосовании зарегистрируйтесь и войдите на сайт с вашими логином и паролем.

Частенько бывает, что микросхемка должна работать-работать себе спокойненько, а на какое-то событие бросать все дела и выполнять что-то иное. А потом - снова возвращаться к первоначальному делу... Это как в часах, например, - они показывают время до тех пор, пока не настанет время будильника. И вроде как никаких внешних воздействий - нажатия там кнопки, ресета - а микросхема сама переключается.

Это и можно реализовать с помощью прерываний - сигналов, сообщающих процессору о наступлении какого-либо события.

Вот пример из бытовой жизни - сидите Вы на кухне, пьете чай с малиновым вареньем и вкусняшками неприменно, и ждете гостей. А как узнать, что кто-то пришел? Тут два варианта: либо мы каждые пять минут будем отвлекаться от варенья, в смысле, чая и бегать проверять, а не стоит ли кто за дверью, либо купить дверной звонок и спокойненько ждать на нагретом месте, пока кто-нибудь в него не позвонит.

Так вот, когда гость звонит - это событие. Соответственно, мы прерываемся и кидаемся к двери.

Итак, у микросхемы есть прерывания. И не одно. Прерывания делятся на внешние - такие срабатывают при определённом напряжении на некоторых выводах микросхемы (INT0, INT1 и также иногда целый порт PCINT) - и внутренние - при переполнении счётчика, срабатывании сторжевого таймера, при использовании USART, при прерывании аналогового компаратора, АЦП и прочей периферии.

Соответственно, возникает проблема приоритета. Это как мы все также сидим и пьем чай, но звонят нам уже не только в дверь, но и по телефону... И ведь не разорвешься, что-то нужно сделать первым. Поэтому в даташите есть таблица векторов прерываний. Чем меньше номер прерывания, тем более оно приоритетно.

Здесь получается несколько тонкостей...

Вот произошло событие - пошел запрос на прерывание, то есть выставляется так называемый "флаг запроса на прерывание". Если все хорошо, прерывание разрешено, то жизнь прекрасна и происходит его обработка.

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

НО! Получается, что даже если прерывание обрабатывается, не факт, что событие, которое его вызвало, ещё живо... Это как позвонили в дверь и по телефону одновременно, Вы ответили по телефону, а гости уже решили, что никого дома нету и ушли. И вроде как событие - звонок в дверь - было, а за дверью никого нет.

Ещё проблема - что пока обрабатывается другое прерывание и флаг запроса уже поднят, событие может произойти ещё несколько раз. Ответили на звонок по телефону, открываем дверь - а там уже целая куча гостей! Страшно? Страшно...

Ещё одна особенность использования прерываний - да и не только прерываний: реентерабельность (или повторная входимость).

Реентерабельная программа - та, которую могут вызвать несколько пользователей (или процессов), и при этом как минимум не возникнет ошибка, а как максимум - не будет потери вычислений - например, другому пользователю не придется выполнять код снова.

Иными словами, если на кухне во то время, пока Вы встречаете гостей, никто не утащит себе вкусняшки, то значит все реентерабельно)

В общем, серезная штука - если её не учитывать, можно долго мучится с "а чего же она не работает?!". Нужно её учитывать, например, если обрабатываются несколько прерываний, и каждое изменяет какую-то глобальную переменную...

Обычно прерывания НЕ реентерабильны. То есть, в момент работы прерывания нельзя повторно вызвать это же прерывание.. Именно для защиты от повторного вхождения в обработчик прерывания автоматически запрещаются в момент его обработки (если вам захотелось разрешить прерывания в процедуре обработки прерываний, надо десять, двадцать раз подумать, прежде чем сделать такой опрометчивый шаг).

Рассмотрим работу с внешними прерываниями: нам нужно, во-первых, настроить, по какому событию будет происходить прерывание, а, во-вторых, разрешить микросхеме вообще обрабатывать это самое прерывание.

За первое в микросхеме ATmega8 отвечает регистр MCUCR - биты ISC11-ISC10, отвечающие за INT1, и ISC01-ISC00, отвечающие за INT0.

Таблица 1. Определение событий для генерации прерывания по INT1

Соответственно, с INT0 аналогично.

А теперь остается разрешить прерывания по нужному нам выводу - в регистре GIGR есть биты INT0 и INT1; поставить на нужный "1" - и внешнее прерывание разрешено! Но ещё рано радоваться - помимо внешних прерываний надо разрешить прерывания вообще - ставим крайний левый бит I регистра SREG в "1". Это же можно сделать и ассемблерной командой: asm sei;

Рассмотрим простой примерчик: на ножку INT0 (D.2) микросхемы ATmega8 присоединена кнопка (к ножке и на ноль); нажимаем - возникает прерывание и включается светодиодик на выводе B.0. Светодиод, соответственно, подключен к ножке и на единицу:

//программа для ATmega8 при нажатии на кнопку на выводе INT0 (D.2) - присоединена к 0 - //включает по внешнему прерыванию светодиодик на выводе B.0 - присоединён к 1 //переопределяем типы typedef unsigned char byte; sbit ddrButton at ddD2_bit; //кнопка генерации sbit pinButton at pinD2_bit; sbit portButton at portD2_bit; sbit ddrLight at ddB0_bit; //вывод для светодиода, на выход с подтяжкой, включается 0 по кнопке sbit portLight at portB0_bit; byte flagButton = 0; //флаг нажатия кнопки; нажата - 1 void INT0_interrupt() org IVT_ADDR_INT0 //нужно написать как минимум пустую функцию - //потому что компилятор сам не создает. Иначе не работает { flagButton = 1; } //обработка нажатия кнопки - с учётом дребезга void buttonLight() { if(flagButton) //если нажата кнопка { portLight = 0; //включаем светодиод delay_ms(500); portLight = 1; //выключаем светодиод flagButton = 0; } } void main() { //инициализация всех используемых портов ddrB = 0; portB = 0; ddrD = 0; portD = 0; //инициализация кнопки - на вход с подтяжкой portButton = 1; ddrButton = 0; //инициализация светодиодика, которая включается по 0 и нажатию кнопки - на выход и в 1 portLight = 1; ddrLight = 1; //настраиваем внешние прерывания MCUCR.ISC00 = 0; //прерывание генерируется по логическому 0 на INT0 MCUCR.ISC01 = 0; GICR.INT0 = 1; //разрешаем внешнее прерывание INT0 asm sei;//SREG.B7 = 1; //разрешаем прерывания в принципе (бит I); команды аналогичны while(1) { buttonLight(); } }

Немного о синтаксисе. Функция прерывания написана так: void имя_функции() org IVT_ADDR_INT0.

Ключевое слово org указывает, что дальше будет идти адрес прерывания из даташита. У нас же есть название прерывания из библиотечки: набираем IVT и и дальше нажимаем Ctrl + Пробел (люблю я такие вещички ˆˆ). Ещё вместо слова org можно использовать iv, судя по справке компилятора.

Ещё маленькое замечание: во-первых, прерывание никогда не должно быть громоздким - несколько строк и все. Это связано с тем, что во время обработки прерывания микросхема не может отвлечься на что-либо иное, азначит, если у нас прерываний несколько, то можно пропустить наступление какого-нибудь события.

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

Одним из преимуществ микроконтроллера ATmega8 является широкий диапазон различных прерываний.

Прерывание представляет собой событие, при наступлении которого выполнение основной программы приостанавливается и вызывается функция, обрабатывающая прерывание определённого типа.

Прерывания делятся на внутренние и внешние. К источникам внутренних прерываний относятся встроенные модули микроконтроллера (таймеры, приёмопередатчик USART и т.д). Внешние прерывания возникают при поступлении внешних сигналов на выводы микроконтроллера (например сигналы на выводы RESET и INT). Характер сигналов, приводящих к возникновению прерывания задаётся в регистре управления MCUCR , в частности в разрядах - ISC00 (бит 0) и ISC01 (бит 1) для входа INT 0; ISC10 (бит2) и ISC11 (бит3) для входа INT1.

В микроконтроллере ATmega8 каждому прерыванию соответствует свой вектор прерывания (адрес в начале области памяти программ, в которой хранится команда для перехода к заданной подпрограмме обработки прерывания). В mega8 все прерывания имеют одинаковый приоритет. В случае одновременного возникновения нескольких прерываний первым будет обрабатываться прерывание с меньшим номером вектора.

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

Адрес Источник прерывания Описание
0x0000 RESET Сигнал сброса
0x0001 INT0 Внешний запрос на прерывание по входу INT0
0x0002 INT1 Внешний запрос на прерывание по входу INT1
0x0003 T/C1 Захват по таймеру T/C1
0x0004 T/C1 Совпадение с регистром сравнения A таймера T/C1
0x0005 T/C1 Совпадение с регистром сравнения B таймера T/C1
0x0006 T/C1 Переполнение счётчика T/C1
0x0007 T/C0 Переполнение счётчика T/C0
0x0008 SPI Передача данных по интерфейсу SPI завершена
0x0009 UART Приём данных приёмопередптчиком UART завершен
0x000A UART Регистр данных UART пуст
0x000B UART Передача данных приёмопередптчиком UART завершена
0x000C ANA_COMP Прерывание от аналогового компаратора

Управления прерываниями

За управление прерываниями в ATmega8 отвечают 4 регистра:

GIMSK (он же GICR) - запрет/разрешение прерываний по сигналам на входах INT0, INT1

GIFR - управление всеми внешними прерываниями

TIMSK , TIFR - управление прерываниями от таймеров/счётчиков

Регистр GIMSK(GICR)

INTFx=1: произошло прерывание на входе INTx. При входе в подпрограмму обработки прерывания INTFx автоматически сбрасывается в сотояние лог. 0

Регистр TIMSK

7 6 5 4 3 2 1 0
TOIE1
OCIE1A
OCIE1B
-
TICIE
-
TOIE0
-

TOIE1=1 : прерывание по переполнению T/C1 разрешено

OCIE1A=1 : прерывание при совпадении регистра сравнения A с содержимым счётчика T/C1 разрешено

OCIE1B=1 : прерывание при совпадении регистра сравнения B с содержимым счётчика T/C1 разрешено

TICIE=1 : разрешено прерывание при выполнении условия захвата

TOIE0=1 : прерывание по переполнению T/C0 разрешено

Регистр TIFR

7 6 5 4 3 2 1 0
TOV1
OCF1A
OCF1B
-
ICF1
-
TOV0
-

TOV1=1 : произошло переполнение T/C1

OCF1A=1 : произошло совпадение регистра сравнения A с содержимым счётчика T/C1 разрешено

OCF1B=1 : произошло совпадение регистра сравнения B с содержимым счётчика T/C1 разрешено

ICF=1 : выполнилось условия захвата

TOV0=1 : произошло переполнение T/C0

При входе в подпрограмму обработки прерывания соответствующий прерыванию флаг регистра TIFR автоматически сбрасывается в сотояние лог. 0

Прерывания работают только тогда, когда в регистре состояния SREG разрешены общие прерывания (бит 7 = 1). В случае наступления прерывания этот бит автоматически сбрасывается в 0, блокируя выполнение последующих прерываний.

В данном примере вывод INT0 включён в режиме входа с подтяжкой. При замыкании вывода на землю при помощи кнопки на нём устанавливается лог.0 (фронт сигнала ниспадает с напряжения питания до 0) и срабатывает обработчик прерывания, включающий лампочку, подключённую к нулевому выводу порта B

void lampON()
{
PORTB.0=1;
DDRB.0=1;
}

interrupt void ext_int0_isr(void)
{
lampON();
}

DDRD.2=0;
PORTD.2=1;

SREG|= (1 while(1) {

На приведённом примере также видно, как задаются векторы прерываний в Code Vision AVR (interrupt void ext_int0_isr(void)). Аналогично задаются вектора прерываний и для других случаев:

EXT_INT0 2
EXT_INT1 3
TIM2_COMP 4
TIM2_OVF 5
TIM1_CAPT 6
TIM1_COMPA 7
TIM1_COMPB 8
TIM1_OVF 9
TIM0_OVF 10
SPI_STC 11
USART_RXC 12
USART_DRE 13
USART_TXC 14
ADC_INT 15
EE_RDY 16
ANA_COMP 17
TWI 18
SPM_READY 19