Главная » Статьи » Полезные материалы

Прерывания в PIC микроконтроллерах. Теория

В качестве рабочей среды я, по привычке, возьму Proton+. В нем организация прерываний производится проще и по структуре своей правильно. Под словом «правильно» я понимаю так, как рекомендует производитель микроконтроллера.

Что такое прерывание в отношении к PIC микроконтроллеру? Ответ лежит в основе самого этого слова. Это временный останов выполнения программы для того, чтобы выполнить специальную подпрограмму. Эта подпрограмма находится по определенному адресу в адресном пространстве микроконтроллера. В ней выполняются действия, вызванные необходимостью, возникновение которой предусмотрел программист. Такой необходимостью может быть выдача в порт импульса в определенный момент времени, или по сигналу с внешнего устройства, тик таймера для подсчета времени или вывод на индикатор какой-то информации – все, что угодно, любое действие, которое надо выполнить во что бы то ни стало. Структурно работа прерывания выглядит как показано на рисунке ниже.

Описать ее можно просто:

В начале программы мы настраиваем микроконтроллер, манипулируя битами регистров специального назначения, отвечающими за прерывания от тех или иных источников. Указываем адрес, по которому будет находиться подпрограмма обработки прерывания. После этого выполняем основную программу(на схеме это условные инструкции Оператор 1, Оператор 2 и т. д.).  Как только наступит момент прерывания, программа закончит текущую инструкцию и перейдет на подпрограмму обработки прерывания. Естественно, источников прерывания может быть несколько и на первый взгляд может показаться, что мы не в силах определить, что вызвало прерывание. Но это не так. Мы всегда можем проверить, чем вызвано прерывание, опросив флаги, которые устанавливаются при прерывании и которые, собственно и вызывают само прерывание.

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

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

Для начала поставим задачу. Пусть нам требуется переключать состояние порта PORTB.4 каждые 300 мСек на противоположное.

Что нам для этого нужно? Отсчет времени – это первое и главное. Нам нужно отсчитывать 300 миллисекунд. Для этого лучше всего подойдет таймер TMR1, так как он без проблем позволит организовать такой период. Определимся с микроконтроллером и частотой генератора. Пусть это будет PIC16F877, работающий на частоте 4 МГц. Это не важно. Ну, пусть будет так, тем более, он имеет три таймера, в том числе и TMR1.

Тщательно разберем алгоритм будущей программы.

1). Выбираем микроконтроллер – здесь мы уже определились.

2). Указываем частоту – здесь тоже определились.

3). Настроим порты на нужные нам режимы работы. Мы коснемся PORTB и PORTD (чтобы не усложнять статью лишним материалом, я решил, что в теле основной программы мы будем вести счет от нуля до 255 и выводить это число в порт PORTD с некоторой задержкой).

4). Настраиваем прерывание от TMR1 через каждые 300 мСек. Для этого нам нужно, во-первых, разрешить прерывание именно от переполнения этого таймера. Это делается установкой бита TMR1IE. Он находится в регистре PIE1 в нулевой позиции. Таким образом, чтобы разрешить это прерывание, мы напишем PIE1.0 = 1. Во-вторых нам требуется разрешить прерывания от периферийных устройств микроконтроллера, так как таймер TMR1 является периферийным устройством. В-третьих, нам нужно, по аналогии с нашей бюрократией (J), разрешить обработку прерываний вообще, установив бит глобального разрешения прерываний. Этот бит – как последняя инстанция – если он не установлен, то все запрещено, даже если нижние уровни все разрешили. Чтобы разрешить прерывания от периферии, нужно установить в единицу бит 6 регистра INTCON. Называется он PEIE. А чтобы разрешить все прерывания (последняя инстанция) – нужно установить бит 7 регистра INTCON. Он называется GIE (Global Interrupt Enable). Вообще, стоит познакомиться с документацией на микроконтроллер, тем более, на этот есть документ полностью на русском языке.

5). Указываем адрес подпрограммы обработчика прерывания.

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

7). Пишем подпрограмму, ответственную за обработку прерываний. В этом пункте следует соблюдать несколько простых правил. Первое – сохранение контекста. Что это, спросите вы? Если вы хоть немного знакомы с PIC микроконтроллерами или с микроконтроллерами вообще, то вы должны знать, что есть специальные, так сказать, системные регистры.  В PIC это два самых важных регистра – регистр STATUS и аккумулятор W. Эти регистры участвуют практически во всех операциях, выполняемых микроконтроллером. Сохранение их значений (текущего состояния этих регистров) при переходе на обработчик прерываний очень важно. Поясню. Например, в какой-то момент времени микроконтроллер вычисляет объем воздуха, подаваемого пациенту, подключенному к аппарату искусственной вентиляции легких, используя данные – время хода штока меха и объем. Во время вычислений обязательно фигурируют два системных регистра – W и STATUS, так как регистр W присутствует везде, где оперируют числами более одного байта, да и с однобайтными операндами – тоже, а STATUS – он независим ни от кого и устанавливает биты в зависимости от результата операций (и не только). И тут происходит прерывание – врач нажал кнопку (ну, например, для увеличения дыхательного объема или для смены режима работы аппарата). Если не сохранить содержимое системных регистров, то по выходу из прерывания, они будут иметь значения, отличные от тех, что были при входе в прерывание. Соответственно, результаты математических операций по вычислению объема воздуха будут неверны. Для того, чтобы этого избежать и предусмотрено сохранение контекста. Здесь подразумевается контекст исполняемой программы – логичность и последовательность ее выполнения.

Второе – это проверка на правильность реакции. Это значит, что при неправильной организации, мы можем среагировать не на то прерывание. Поясню. Мы ожидали прерывание от переполнения TMR1, а произошло прерывание от TMR0 (допустим, каким-то образом было разрешено прерывание от TMR0 – вариантов много? Все мы люди, в конце концов). В обработчике мы просто сбросили бит глобального разрешения прерываний GIE (нам больше не требуются прерывания). После этого мы не попадем в прерывания по TMR1, которое, собственно, нам и нужно было. Это чревато, так называемыми, случайными ошибками, которые возникают, как бы случайно, непонятно от чего, а на самом деле – это результат взаимодействия определенных обстоятельств в данный момент. Чтобы избежать такой ситуации, рекомендуется проверять источник запроса прерывания. То есть, если мы предполагаем, что должно быть прерывание от переполнения TMR1, то в обработчике опрашиваем флаг, который устанавливается при переполнении TMR1. Если мы предполагали, что будет прерывание по фронту импульса на PORTB.0, то и опрашиваем флаг, отвечающий за это прерывание – INTF.

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

Четвертое – восстановление контекста. Логично, не правда ли? При входе мы сохранили значения системных регистров, так как они все равно изменятся при обработке прерываний, при выходе – вернули прежние значения. Программа вернется к выполнению с того места, где была прервана, причем, на данные, с которыми работала программа до вход в прерывание, никак не повлияет остановка. Это важно помнить и использовать, если вы хотите, чтобы ваши программы были надежны.

Возвращаются из обработчика прерываний с помощью команды Retfie. C помощью этой инструкции из стека извлекается адрес команды, следующей за той, при которой возникло прерывание.

Это все. Вот сама программа:

DEVICE = 16F877 'Выберем микроконтроллер

XTAL = 4        'Укажем тактовую частоту

DIM Timer AS TMR1L.WORD 'Для удобства зададим имя для TMR1

TRISD = 0       'Порты PORTB и PORTD - на выход

TRISB = 0

ON_INTERRUPT GOTO ISR  'Зададим адрес обработчика прерываний

                       '(он находится по адресу метки ISR)                       

Timer = 28036          'Загрузим таймер значением

                       'Это значение я вычислил с помощью программы ProtonHeaderMaker

                       'чтобы он переполнился через 300 мСек

PIE1.0 = 1            

INTCON.6 = 1           'Разрешим прерывание от периферийных устройств микроконтроллера

                       'так как TMR1 - периферия

INTCON.7 = 1           'Установим бит глобального разрешения прерываний

                       'Этот бит разрешает работу всех запросов вообще

T1CON = %00110001      'Настроим предделитель для TMR1 и включим таймер

GOTO Main              'После всех настроек перейдем на выполнение главной программы

'А таймер уже тикает и увеличивает свое значение с каждым тиком

'Как вы помните, мы его загрузили значением 28036 в начале

'Как только он дотикает до 65535 и придет следующий тик - это вызовет его

'переполнение и, соответственно, прерывание по переполнению TMR1

ISR:

CONTEXT SAVE          'Сохраним регистры STATUS и аккумулятор

IF PIR1.0 = 1 THEN    'Проверим, было ли вызвано прерывание именно переполнением таймера TMR1

PIR1.0 = 0            'Если это так, то сразу сбросим флаг этого прерывания, чтобы

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

NOP                   'Эти NOPы - корректирующие, поскольку до того, как мы

NOP                   'выполним нужное действие (переключение PORTB.4),

NOP                   'мы производим еще действия

NOP

NOP

NOP

NOP

PORTB.4 = ~PORTB.4    'Непосредственно нужная нам операция - смена состояния порта PORTB.4

Timer = 28040         'Загружаем таймер новым значением. Обратите внимание - оно отлично от начального

ENDIF

CONTEXT RESTORE       'Восстановим значения регистров W и STATUS

RETFIE                'Возврат из обработчика

Main:

INC PORTD             'Увеличиваем значение 8-разрядного регистра PORTD

DELAYMS 100           'Задержка для видимости человеком смены состояния порта

GOTO Main             'Отправляемся на начало

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

Хотелось бы увидеть комментарии и оценить ваш интерес к моим статьям вообще.

Материалы статьи вы можете скачать здесь. Архив содержит проект MPLAB, с помощью которого легко отследить время выполнения программы.




Категория: Полезные материалы | Добавил: ADMIN (18.11.2011)
Просмотров: 27945 | Комментарии: 14 | Теги: interrupt, прерывания, пример, полезные материалы, PROTON | Рейтинг: 4.0/11
Всего комментариев: 14
1 vanish   (18.11.2011 20:47) [Материал]
Как долго я искал подобную статью! Спасибо, мне понравилось!

2 mikhail09p   (18.11.2011 22:16) [Материал]
Спасибо, изучаю. wink
только обработку прерывание проводите не в самом обработчике, а чуть позже, выйдя из него.
А это не понятно... Как так можно? Успеется ли? И где мы будем в программе по выходе из прерывания? А если посредине подпрограммы обработки прерывания...

3 ADMIN   (19.11.2011 04:43) [Материал]
Насчет этого, так и быть, напишу отдельной статьей. В одну статью все не входит.

4 Uncle_Dizel   (19.11.2011 15:40) [Материал]
прерывания по возрастающему фронту RA2/INT это как?

5 ADMIN   (19.11.2011 17:11) [Материал]
RA2/INT первый раз слышу. Вход прерывания по фронту или срезу в PIC обычно запараллелен с PORTB.0. Откуда такая информация?

6 Uncle_Dizel   (19.11.2011 18:29) [Материал]
регистор INTCON.4 бит 4-ый прерывание по INT что за прерывание такое?

7 ADMIN   (19.11.2011 19:27) [Материал]
Я могу сюда отправить. Там, на мой взгляд, все понятно.
INT - как раз это на PORTB.0 прерывание по фронту или срезу импульса

8 Uncle_Dizel   (19.11.2011 19:39) [Материал]
я просто не совсем понимаю в чем разница между прерыванием INTCON.(4,3) битов
один по изменению логического уровня а другой по фронту или срезу это как?

9 ADMIN   (20.11.2011 03:59) [Материал]
INTCON.3 - это флаг разрешения/запрета прерывания от изменения логического состояния на входах PORTB.4..7
INTCON.4 - флаг разрешения/запрета прерывания по входу PORTB.0.
Переход состояния от нуля к единице - фронт импульса, переход от единицы к нулю - срез. Для входа PORTB.0(INT) прерывание настраивается либо по фронту, либо по срезу, в регистре OPTION_REG

10 Uncle_Dizel   (20.11.2011 23:37) [Материал]
INTCON.4 - флаг разрешения/запрета прерывания по входу PORTB.0. INTCON.4 - это не флаг, это бит разрешающей прерывание по INT, а тогда какая разница получается если они оба по изменению уровня ( 0- 1) ? ну (INTCON.4,3)

11 ADMIN   (21.11.2011 07:12) [Материал]
Ну да, неверно выразился, заговорился

12 ADMIN   (21.11.2011 07:17) [Материал]
Да в принципе разницы нет, только RB4..7 нужно сначала просто прочитать, ну например в переменную какую-нибудь, разрешить прерывание, естественно. Например, мы считали порт и получили в 4-7 битах число %0110. Как только изменится хоть один уровень из этих четырех, произойдет прерывание. Вот в прерывании можно опросить порт и узнать, на какой ноге произошло прерывание. Удобно для организации клавиатур. Но прежде чем разрешать следующее прерывание, нужно считать. Я не до конца понимаю этот механизм. Пользовался всего один раз.

13 Uncle_Dizel   (22.11.2011 17:43) [Материал]
мне как раз интересно как можно узнать какая именно нога МК сработала на прерывание)

14 ADMIN   (01.12.2011 08:27) [Материал]
Так в прерывании и считай порт и узнаешь, какая нога сейчас не в том состоянии.

Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]