Прерывание на контроллере AVR в Atmel AVR Studio. Учебный курс. Понятие прерывания. Подаем голос микроконтроллером ______________________________ Прерывание ___________________________

В состав AVR микроконтроллеров входит большое число периферийных устройств (ADC, Timer/Counters, EXTI, Analog Comparator, EEPROM, USART, SPI, I2C и т.д.), каждое из которых может выполнять определенные действия над данными/сигналами и пр. информацией. Эти устройства встроены в микроконтроллер для повышения эффективности приложения и снижения затрат при разработке всевозможных устройств на базе AVR микроконтроллеров.

Процессор общается/управляет периферийными устройствами посредством регистров ввода/вывода (I/O Registers), которые располагаются в памяти данных (Data Memory), что позволяет использовать их как обычные переменные. У каждого устройства имеются свои регистры ввода/вывода.

Все регистры ввода/вывода (I/O Registers) можно поделить на три группы: регистры данных, регистры управления и регистры состояния.

При помощи регистров управления (Control Registers) реализуется настройка устройства для работы в том или ином режиме, с определенной частотой, точностью и т.д., а при помощи регистров данных (Data Registers) считывается результат работы данного устройства (аналого-цифровое преобразование, принятые данные, значение таймера/счетчика и т.д.). Казалось бы, ничего сложного здесь нет (вообще-то здесь и вправду ничего сложного нет:)), включил устройство, указал желаемый режим работы а потом только остается стричь купоны читать готовенькие данные и использовать их в вычислениях. Весь вопрос заключается в том "когда” читать эти самые данные (завершило устройство работу или все еще обрабатывает данные), ведь все периферийные устройства работают параллельно с ядром микроконтроллера, да еще и на разных частотах. Встает вопрос реализации общения и синхронизации между процессором и периферийным устройством.

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

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

Проверка если определенный флаг установлен (произошло некое событие) :

if (RegX & (1 << Flag) ) // если флаг в регистре RegX установлен
{
// делай что-то
}

Ожидание завершения какого либо действия (событие) :

while(!(RegX & (1<

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

Для того чтобы повысить КПД программ для AVR микроконтроллеров, а также облегчить процесс создания и отладки данных программ, разработчики снабдили все периферийные устройства "источниками прерываний” (Interrupt sources ), у некоторых устройств может быть несколько источников прерывания.

При помощи источников прерываний реализуется механизм синхронизации , между процессором и периферийным устройством, то есть процессор начнет прием данных, опрос флагов и др. действия над периферийным устройством только тогда, когда устройство будет к этому готово (сообщит о завершении обработке данных, ошибке при обработке данных, регистр пуст, и т.д.), путем генерации "запроса на обработку прерывания” (Interrupt request ), в зависимости от значения некоторого флага (состояние устройства / функции / события).

В литературе, очень часто, всю цепочку событий, начиная от "запроса на обработку прерывания” (IRQ) и до "процедуры обработки прерывания” (ISR), сокращенно называют – прерывание (Interrupt ).

Что такое прерывание?


Прерывание (Interrupt) – сигнал, сообщающий процессору о наступлении какого-либо события. При этом выполнение текущей последовательности команд приостанавливается и управление передаётся процедуре обработки прерывания, соответствующая данному событию, после чего исполнение кода продолжается ровно с того места где он был прерван (возвращение управления). (Wiki)

Процедура обработки прерывания (Interrupt Service Routine) – это ни что иное как функция/подпрограмма, которую следует выполнить при возникновении определенного события. Будем использовать именно слово "процедура”, для того чтобы подчеркнуть ее отличие от всех остальных функций.

Главное отличие процедуры от простых функций состоит в том что вместо обычного "возврата из функции” (ассемблерная команда RET), следует использовать "возврат из прерывания” (ассемблерная команда RETI) – "RETurn from Interrupt ".

Свойства AVR прерываний:

  • У каждого периферийного устройства, что входит в состав AVR микроконтроллеров, есть как минимум один источник прерывания (Interrupt source). Ко всем этим прерываниям следует причислить и прерывание сброса – Reset Interrupt, предназначение которого отличается от всех остальных.
  • За каждым прерыванием, строго закреплен вектор (ссылка) указывающий на процедуру обработки прерывания (Interrupt service routine). Все векторы прерываний, располагаются в самом начале памяти программ и вместе формируют "таблицу векторов прерываний” (Interrupt vectors table).
  • Каждому прерыванию соответствует определенный "бит активации прерывания” (Interrupt Enable bit). Таким образом, чтобы использовать определенное прерывание, следует записать в его "бит активации прерывания” – лог. единицу. Далее, независимо от того активировали Вы или нет определенные прерывания, микроконтроллер не начнет обработку этих прерываний, пока в "бит всеобщего разрешения прерываний” (Global Interrupt Enable bit в регистре состояния SREG) не будет записана лог. единица. Также, чтобы запретить все прерывания (на неопределенное время), в бит всеобщего разрешения прерываний следует записать – лог. нуль.

Прерывание Reset, в отличие от всех остальных, нельзя запретить. Такие прерывания еще называют Non-maskable interrupts.

  • У каждого прерывания есть строго определенный приоритет. Приоритет прерывания зависит от его расположения в "таблице векторов прерываний”. Чем меньше номер вектора в таблице, тем выше приоритет прерывания. То есть, самый высокий приоритет имеет прерывание сброса (Reset interrupt), которое располагается первой в таблице, а соответственно и в памяти программ. Внешнее прерывание INT0, идущее следом за прерыванием Reset в "таблице векторов прерываний”, имеет приоритет меньше чем у Reset, но выше чем у всех остальных прерываний и т.д.

Таблица векторов прерываний, кроме вектора Reset, может быть перемещена в начало Boot раздела Flash памяти, установив бит IVSEL в регистре GICR. Вектор сброса также может быть перемещен в начало Boot раздела Flash памяти, путем программирования фьюз бита – BOOTRST.



Рис.1 Таблица векторов прерываний ATmega16

Прототип процедуры обработки прерывания


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

Во первых процедура обработки прерывания не может ничего принимать в качестве аргумента (void), а также не может ничего возвращать (void). Это связано с тем что все прерывания в AVR асинхронные, поэтому не известно в каком месте будет прервано исполнение программы, у кого принимать и кому возвращать значение, а также для минимизации времени входа и выхода из прерывания.

void isr(void )

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

Прототип процедуры обработки прерывания в среде AVR Studio

#include

ISR(XXX_vect)
{

}

В AVR Studio (AVR GCC), каждая процедура обработки прерывания начинается с макроопределения ISR, после чего, в круглых скобках следует конструкция:

XXX_vect

где "XXX” это имя вектора прерывания. Все имена векторов, для определенного AVR микроконтроллера, можно найти в "таблице векторов прерываний” даташита данного микроконтроллера или в его заголовочном файле. К примеру, "таблица векторов прерываний” для микроконтроллера ATmega16 приведена на рис.1, где в колонке Source, приведены все имена векторов прерываний. Также имена можно посмотреть в заголовочном файле данного микроконтроллера (C:\Program Files\Atmel\AVR Tools\AVR Toolchain\avr\include\avr\iom16.h), см. рис.2. Все что нам надо сделать, это найти в таблице имя нужного нам вектора и к нему прибавить суффикс "_vect".


Рис.2 Заголовочный файл ATmega16 для AVR Studio

Для примера, напишем процедуру обработки прерывания по приему байта через USART (USART, Rx Complete) :

ISR(USART_RXC_vect)
{
// Тело обработчика прерывания
}

Кстати: перед тем как использовать любое прерывание в AVR Studio, следует включить в проект заголовочные файлы io.h и interrupt.h:

#include
#include

Более подробно об обработчиках прерываний в AVR Studio (AVR GCC) можно почитать в разделе Introduction to avr-libc’s interrupt handling.

Прототип процедуры обработки прерывания в среде ImageCraft

#pragma interrupt_handler : iv_XXX
void < handler_name> (void )
{
// Тело обработчика прерывания
}

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

void < handler_name> (void )

где , это любое имя которое вы захотите дать данному обработчику прерывания. Одно из требований к объявлению процедур обработки прерываний гласит, что перед прототипом функции следует указать что она является обработчиком прерывания. Это делается при помощи pragma-директивы interrupt_handler :

#pragma interrupt_handler : iv_XXX

где это имя той функции что будет использоваться в качестве обработчика прерывания, а конструкция "iv_XXX”, это имя вектора прерывания (XXX) с префиксом "iv_". Как и в случае с AVR Studio, все имена векторов, для определенного AVR микроконтроллера, можно найти в "таблице векторов прерываний” даташита данного микроконтроллера или в его заголовочном файле (см. рис.3).


Рис.3 Заголовочный файл ATmega16 для ImageCraft IDE

К примеру процедура обработки прерывания по приему байта через USART (USART, Rx Complete) в среде ImageCraft, будет выглядит так:

#pragma interrupt_handler usart_rxc_isr: iv_USART_RXC
void usart_rxc_isr(void )
{
// Тело обработчика прерывания
}

Более подробно о процедурах обработки прерывания в ImageCraft IDE можно найти в меню Help->Programming the AVR->Interrupt Handlers среды разработки.

Иногда, если несколько обработчиков прерывания должны делать одно и то же, то для экономии памяти программ, можно направить несколько векторов прерывания на одну и ту же процедуру обработки прерывания.

В среде AVR Studio это выглядит так:

ISR(INT0_vect)
{
// Do something
}
ISR(INT1_vect, ISR_ALIASOF(INT0_vect) ) ;

Сначала идет процедура обработки прерывания для определенного вектора, в данном случае INT0. Все остальные процедуры могут ссылаться на любой обработчик прерывания при помощи конструкции:

ISR(YYY_vect, ISR_ALIASOF(XXX_vect) ) ;

где YYY это имя вектора прерывания который ссылается на ранее объявленный обработчик прерывания для вектора XXX.

В среде ImageCraft это выглядит так:

#pragma interrupt_handler : iv_XXX : iv_YYY
void < handler_name> (void )
{
// Тело обработчика прерывания
}

#pragma interrupt_handler : iv_XXX
#pragma interrupt_handler : iv_YYY
void < handler_name> (void )
{
// Тело обработчика прерывания
}

где векторы XXX и YYY ссылаются на один и тот же обработчик прерывания .

Как работает прерывание в AVR микроконтроллерах?

1. Предположим произошел "запрос на обработку прерывания ” (IRQ).

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

2. Проверка.

Если бит активации данного прерывания установлен (Interrupt enable bit), а также I-бит (бит всеобщего разрешения прерываний) регистра состояния процессора (SREG) установлен, то процессор начинает подготовку процедуры обработки прерывания, при этом бит всеобщего разрешения прерываний (I-бит регистра SREG) сбрасывается, запрещая таким образом все остальные прерывания. Это происходит для того чтобы никакое другое событие не смогло прервать обработку текущего прерывания.

Кстати: если в процедуре обработки прерывания установить I-бит в состояние лог. единицы, то любое активированное прерывание может в свою очередь прервать обработку текущего прерывания. Такие прерывания называются вложенные (Nested interrupts).

3. Подготовка .

Процессор завершает выполнение текущей ассемблерной команды, после чего помещает адрес следующей команды в стек (PC->STACK). Далее процессор проверяет какой источник прерывания подал "запрос на обработку прерывания” (IRQ), после чего воспользовавшись вектором данного источника (ссылка) из таблицы векторов (который железно закреплен за каждым источником прерывания), переходит в процедуру обработки прерывания (инструкция JMP). На все, про все процессор тратит минимум 4 такта! (в зависимости от момента появления запроса и длительность исполнения текущей инструкции). Это очень хорошее время реакции на IRQ, по сравнению с микроконтроллерами других производителей.

Кстати: если IRQ произойдет, когда микроконтроллер находится в спящем режиме (sleep mode), время реакции на IRQ увеличивается еще на четыре такта, плюс время заложенное в фьюз битах SUT1 и SUT0 (Start-Up Time).

Стек представляет собой область памяти, которую ЦПУ использует для сохранения и восстановления адресов возврата из подпрограмм.
Практически у всех микроконтроллеров 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.


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

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

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

За управление прерываниями отвечают несколько регистров
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. Называются такие операции – атомарными .
Желательно, чтобы программы прерываний были компактными и выполнялись с максимальной скоростью, потому, что целью любых прерываний является фиксация события. Если по разным причинам программа выполняется медленно, то достаточно зафиксировать событие и обработать его чуть позже.

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

Системы прерываний - важная часть любой управляющей системы.

От ее работы во многом зависит то, насколько эффективно микропроцессорная система выполняет свои функции. Общая структура системы пре рываний МК-51 представлена на рис. 14.3.

Микроконтроллеры семейства МК-51 обеспечивают поддержку пяти источников прерываний:

* двух внешних прерываний, поступающих по входам INT0 и INT1 (линии порта Р3:Р3.2 и Р3.3 соответственно);

* двух прерываний от таймеров/счетчиков Т/С0 и Т/С1;

* прерываниеотпоследовательногопорта.

Запросы на прерывание фиксируются в регистрах специальных функций микроконтроллера: флаги IE0, IE1, TF0, TF1 запросов на прерывание от INT0, INT1, T/C0 и T/C1 содержатся в регистре управления TCON (табл. 14.4), а флаги RI и TI запросов на прерывание от последовательного порта - в регистре SCON управления последовательным портом.

Таблица 14.4. Формат регистра TCON

0 IT0 Настройка вида прерывания INT0

1 IE0 Флаг запроса прерывания INT0

2 IT1 Настройка вида прерывания INT1

3 IE1 Флаг запроса прерывания INT1

4 TR0 Включение в работу таймера/счетчика 0

5 TF0 Флаг переполнения (запрос прерывания)таймера/счетчика 0

6 TR1 Включение в работу таймера/счетчика 1

7 TF1 Флаг переполнения (запрос прерывания)таймера/счетчика 1

Флаги TF0 и TF1 устанавливаются аппаратно при переполнении соответствующего таймера/счетчика (точнее, при переходе T/Cx из состояния "все единицы" в состояние "все нули").

Флаги IE0 и IE1 устанавливаются аппаратно от внешних прерываний IT0 и IT1 соответственно. Внешний запрос может вызвать установку флага либо при низком уровне сигнала на соответствующем входе, либо при переключении этого сигнала с высокого уровня на низкий (с частотой, не превышающей половины частоты внешней синхронизации МК).

Настройка на тип запроса осуществляется программной установкой бит IT0 и IT1 в регистре управления TCON. Установка ITx = 0 настраивает систему прерывания на запрос по низкому уровню сигнала, ITx = 1 - запрос на прерывание по спаду сигнала.

Флаги TI и RI устанавливаются аппаратно схемой последовательного интерфейса после окончания передачи или после окончания приема соответственно.

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

Флаги TF0 и TF1 сбрасываются аппаратно при передаче управления программе обработки соответствующего прерывания.

Сброс флагов IEx выполняется аппаратно при обслуживании прерывания только в том случае, если прерывание было настроено на восприятие спада сигнала INTx. Если прерывание было настроено на восприятие уровня сигнала запроса, то сброс флага IEx должна выполнять программа обслуживания прерывания, воздействуя на источник прерывания для снятия им запроса.

Флаги TI и RI сбрасываются только программным путем.

Каждый вид прерывания индивидуально разрешается или запрещается установкой или сбросом соответствующих разрядов регистра разрешения прерывания IE. Этот регистр содержит также и бит общего запрещения всех прерываний. ФорматрегистраIE приведен в табл. 14.5.

Таблица 14.5. Назначение разрядов регистра IE

Позиция в регистре

Мнемоника бита

Функция

Запрет прерывания от всех источников

Не используется

Не используется

Запрет прерывания от последовательного порта

Запрет прерывания от таймера/счетчика T/C1

Запрет прерывания от внешнего источника INT1

Запрет прерывания от таймера/счетчика T/C0

Запрет прерывания от внешнего источника INT0

Каждому виду прерывания может быть программно присвоен один из двух возможных приоритетов: 0 - низший или 1 - высший.

Настройка приоритетов осуществляется установкой или сбросом соответствующего бита регистра приоритетов прерываний IP. Формат этого регистра приведен в табл. 14.6.

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

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

IT0 -> TF0 -> IT1 -> TF1 -> (RI, TI)

Таблица 14.6. Назначение разрядов регистра IP

Позиция в регистре Мнемоника бита Функция

7 - Не используется

6 - Не используется

5 - Не используется

4 PS Приоритет прерыванияот последовательного порта

3 PT1 Приоритет прерывания от таймера/счетчика T/C1

2 PX1 Приоритет прерыванияот внешнего источника INT1

1 PT0 Приоритет прерывания от таймера/счетчика T/C0

0 PX0 Приоритет прерыванияот внешнего источника INT0

Аппаратно реализуемый вызов обработчика прерываний состоит из следующих действий:

* сохранение значения программного счетчика в стеке;

Точки входа вобработчик прерывания для каждого источника прерываний аппаратно зафиксированы. Их значения приведены в табл. 14.7.

Таблица 14.7. Адреса точек входа в обработчики прерываний

Источник прерывания

Адреса точек входа в обработчики прерываний

Внешнее прерывания(ITO )

Таймер-счетчик(TFO)

Внешнее прерывания(IT1)

Таймер-счетчик(TF1)

Последовательный порт(R1 или T1)

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

При переходе на подпрограмму обработки прерывания автоматически независимо от состояния регистра IE запрещаются все прерывания, которые имеют уровень приоритета, равный уровню приоритета обслуживаемого прерывания, - то есть вложенные прерывания с равным уровнем приоритета запрещены. Таким образом, низкоприоритетное прерывание (имеющее "0" в соответствующем разряде регистра IP) может прерываться высокоприоритетным (имеющим "1" в соответствующем разряде регистра IP), но не низкоприоритетным. Обслуживание высокоприоритетного прерывания не может быть прервано другим источником.

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

Для чего нужны внешние прерывания

Прерывание — это событие по которому прерывается исполнение основного кода программы (например функции main) и управление передаётся функции обработчику прерывания. Соответственно внешние прерывания — это некие внешние события прерывающие исполнение основного кода программы.

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

Принцип работы внешних прерываний в AVR

Для того что бы микроконтроллер узнал о внешних событиях используются дискретные входы INT0 INT1 и т.д. Дискретные означает что они работают с логическими уровнями: 0 и 1.
0 — это отсутствие напряжения на входе
1 — наличие на входе напряжения, которое равно напряжению питания микроконтроллера.

Внешние прерывания можно разделить на два типа:

  • внешние прерывания по уровню
  • внешние прерывания по фронту

Внешние прерывания по уровню

Срабатывание внешнего прерывания может быть настроено на низкий или высокий логический уровень. Например, если прерывание настроено на низкий логический уровень, то оно возникает когда на входе INT напряжение равно нулю. Если же прерывание настроено на высокий уровень, то оно возникает когда на входе логическая 1.
При работе с прерываниями по уровню надо помнить, что пока на входе INT соответствующий уровень, прерывание будет возникать постоянно. Т.е. если возникло прерывание, например по низкому уровню и программа его обработала, но если при выходе из обработчика прерывания на входе остается низкий уровень, то прерывание сработает еще раз, и опять будет вызван обработчик прерывания, и так будет продолжаться до тех пор пока на входе не появится высокий уровень. Что бы этого не происходило нужно в обработчике запрещать данный вид прерываний, или перенастраивать его на другой уровень.

Внешние прерывание по фронту

Прерывание по переднему фронту или, как иногда говорят, нарастанию сигнала, возникает когда происходит изменение уровня сигнала на входе INT с 0 на 1. Прерывание по заднему фронту (спаду сигнала), возникает при изменении уровня сигнала на входе INT с 1 на 0.
Так же возможно настроить прерывание что бы оно реагировало на любое изменение на входе INT т.е. оно будет возникать и по переднему и по заднему фронту.

Настройка внешних прерываний в AVR

Внешние прерывания в avr atmega8 настраиваются при помощи бит ISCxx регистра MCUCR .

Зависимость условия срабатывания внешнего прерывания INT0 от бит ISC0x регистра MCUCR в avr atmega8

Для внешнего прерывания INT1 , настройка производиться так же, только используются биты ISC11 ISC10 .

Пример настройки внешнего прерывания для avr atmega8:

//сбрасываем все биты ISCxx MCUCR & amp;= ~( (1 & lt;& lt; ISC11) | (1 & lt;& lt; ISC10) | (1 & lt;& lt; ISC01) | (1 & lt;& lt; ISC00) ) MCUCR |= (1 & lt;& lt; ISC01) | (1 & lt;& lt; ISC00) ;

//сбрасываем все биты ISCxx MCUCR &= ~((1<

Разрешение внешних прерываний в avr atmega

Для того что бы внешние прерывания заработали их надо разрешить, установив в 1 соответствующие биты в регистре GICR .

Бит INT0 отвечает за разрешение/запрещение внешнего прерывания INT0 , а бит INT1 , соответственно за внешне прерывание INT1 .

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

Пример кода разрешающего внешнее прерывание INT0 для avr atmega8:

//разрешаем внешнее прерывание INT0 GICR |= (1<

Пример использования внешних прерываний в AVR atmega

В качестве примера приведу программу счетчика импульсов. Программа подсчитывает количество импульсов на входе INT0, и раз в секунду выводит результат подсчета в uart.

#include #include #include #include //переменная счетчик volatile unsigned long int0_cnt = 0 ; //настройка внешнего прерывния INT0 void int0_init( void ) { //настраиваем на срабатывание INT0 по переднему фронту MCUCR |= (1 & lt;& lt; ISC01) | (1 & lt;& lt; ISC00) ; //разрешаем внешнее прерывание INT0 GICR |= (1 & lt;& lt; INT0) ; } //функция обработчик внешнего прерывания INT0 ISR( INT0_vect ) { int0_cnt++; } //настройка UART void uart_init( void ) { //настройка скорости обмена UBRRH = 0 ; UBRRL = 3 ; //115200 при кварце 7.3728 МГц //8 бит данных, 1 стоп бит, без контроля четности UCSRC = ( 1 & lt;& lt; URSEL ) | ( 1 & lt;& lt; UCSZ1 ) | ( 1 & lt;& lt; UCSZ0 ) ; //разрешить прием и передачу данных UCSRB = ( 1 & lt;& lt; TXEN ) | ( 1 & lt;& lt; RXEN ) ; } //передача байта по UART int uart_putc( char c, FILE * file ) { //ждем окончания передачи предыдущего байта while ( ( UCSRA & amp; ( 1 & lt;& lt; UDRE ) ) == 0 ) ; UDR = c; return 0 ; } FILE uart_stream = FDEV_SETUP_STREAM(uart_putc, NULL, _FDEV_SETUP_WRITE ) ; int main( ) { //временная переменная unsigned long tmp; stdout = & amp; uart_stream; int0_init() ; uart_init() ; sei() ; while (1 ) { //на время копирования значения счетчика запрещаем прерывания cli() ; tmp = int0_cnt; //разрешаем прерывания sei() ; printf ( "int0_cnt = %lu\r \n " , tmp ) ; //пауза 1 секунда _delay_ms( 1000 ) ; } return 0 ; }

#include #include #include #include //переменная счетчик volatile unsigned long int0_cnt = 0; //настройка внешнего прерывния INT0 void int0_init(void) { //настраиваем на срабатывание INT0 по переднему фронту MCUCR |= (1<