Прерывания
Частенько бывает, что микросхемка должна работать-работать себе спокойненько, а на какое-то событие бросать все дела и выполнять что-то иное. А потом - снова возвращаться к первоначальному делу... Это как в часах, например, - они показывают время до тех пор, пока не настанет время будильника. И вроде как никаких внешних воздействий - нажатия там кнопки, ресета - а микросхема сама переключается.
Это и можно реализовать с помощью прерываний - сигналов, сообщающих процессору о наступлении какого-либо события.
Вот пример из бытовой жизни - сидите Вы на кухне, пьете чай с малиновым вареньем и вкусняшками неприменно, и ждете гостей. А как узнать, что кто-то пришел? Тут два варианта: либо мы каждые пять минут будем отвлекаться от варенья, в смысле, чая и бегать проверять, а не стоит ли кто за дверью, либо купить дверной звонок и спокойненько ждать на нагретом месте, пока кто-нибудь в него не позвонит.
Так вот, когда гость звонит - это событие. Соответственно, мы прерываемся и кидаемся к двери.
Итак, у микросхемы есть прерывания. И не одно. Прерывания делятся на внешние - такие срабатывают при определённом напряжении на некоторых выводах микросхемы (INT0, INT1 и также иногда целый порт PCINT) - и внутренние - при переполнении счётчика, срабатывании сторжевого таймера, при использовании USART, при прерывании аналогового компаратора, АЦП и прочей периферии.
Соответственно, возникает проблема приоритета. Это как мы все также сидим и пьем чай, но звонят нам уже не только в дверь, но и по телефону... И ведь не разорвешься, что-то нужно сделать первым. Поэтому в даташите есть таблица векторов прерываний. Чем меньше номер прерывания, тем более оно приоритетно.
Здесь получается несколько тонкостей...
Вот произошло событие - пошел запрос на прерывание, то есть выставляется так называемый "флаг запроса на прерывание". Если все хорошо, прерывание разрешено, то жизнь прекрасна и происходит его обработка.
А вот если прерывание запрещено - например, уже происходит обработка более приоритетного прерывания - тогда этот флаг запроса так и остается висеть до момента разрешения прерываний. После этого чип проверяет регистр запроса в порядке приоритета, и если есть флаг - обрабатывает его.
НО! Получается, что даже если прерывание обрабатывается, не факт, что событие, которое его вызвало, ещё живо... Это как позвонили в дверь и по телефону одновременно, Вы ответили по телефону, а гости уже решили, что никого дома нету и ушли. И вроде как событие - звонок в дверь - было, а за дверью никого нет.
Ещё проблема - что пока обрабатывается другое прерывание и флаг запроса уже поднят, событие может произойти ещё несколько раз. Ответили на звонок по телефону, открываем дверь - а там уже целая куча гостей! Страшно? Страшно...
Ещё одна особенность использования прерываний - да и не только прерываний: реентерабельность (или повторная входимость).
Реентерабельная программа - та, которую могут вызвать несколько пользователей (или процессов), и при этом как минимум не возникнет ошибка, а как максимум - не будет потери вычислений - например, другому пользователю не придется выполнять код снова.
Иными словами, если на кухне во то время, пока Вы встречаете гостей, никто не утащит себе вкусняшки, то значит все реентерабельно)
В общем, серезная штука - если её не учитывать, можно долго мучится с "а чего же она не работает?!". Нужно её учитывать, например, если обрабатываются несколько прерываний, и каждое изменяет какую-то глобальную переменную...
Обычно прерывания НЕ реентерабильны. То есть, в момент работы прерывания нельзя повторно вызвать это же прерывание.. Именно для защиты от повторного вхождения в обработчик прерывания автоматически запрещаются в момент его обработки (если вам захотелось разрешить прерывания в процедуре обработки прерываний, надо десять, двадцать раз подумать, прежде чем сделать такой опрометчивый шаг).
Рассмотрим работу с внешними прерываниями: нам нужно, во-первых, настроить, по какому событию будет происходить прерывание, а, во-вторых, разрешить микросхеме вообще обрабатывать это самое прерывание.
За первое в микросхеме ATmega8 отвечает регистр MCUCR - биты ISC11-ISC10, отвечающие за INT1, и ISC01-ISC00, отвечающие за INT0.
ISC11 | ISC10 | Событие, по которому генерируется прерывание |
---|---|---|
0 | 0 | Нижний уровень на выводе INT1 |
0 | 1 | Любое изменение уровня на выводе INT1 |
1 | 0 | Нисходящий фронт сигнала (смена 1 на 0) на выводе INT1 |
1 | 1 | Восходящий фронт сигнала (смена 0 на 1) на выводе INT1 |
Таблица 1. Определение событий для генерации прерывания по INT1
А теперь остается разрешить прерывания по нужному нам выводу - в регистре 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, судя по справке компилятора.
Ещё маленькое замечание: во-первых, прерывание никогда не должно быть громоздким - несколько строк и все. Это связано с тем, что во время обработки прерывания микросхема не может отвлечься на что-либо иное, азначит, если у нас прерываний несколько, то можно пропустить наступление какого-нибудь события.
А ещё может оказаться так, что обработка прерывания нам вообще не нужна - например, достаточно, что схема вышла из спящего режима. Но в этом случае все равно надо писать функцию прерывания, пусть даже пустую - так называемую "заглушку". В принципе, некоторые компиляторы автоматически пишут пустые функции для каждого прерывания, но это не наш случай - приходится делать ручками.
Автор - Moriam