Учебный курс AVR. Использование внешних прерываний в AVR

14/09/2011 - 17:38

Введение

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

Внешние прерывания

   Для примера будем рассматривать микроконтроллер ATmega16. 
   У него три внешних прерывания - INT0, INT1 и INT2. Эти прерывания жестко «привязаны» к выводам PD2, PD3 и PB2 и переназначить их на другие выводы нельзя. 
   Когда используются внешние прерывания, выводы PD2, PD3 и PB2  конфигурируются на вход. Однако если они настроены на  выход, внешние прерывания тоже будут генерироваться при изменении их состояния, что позволяет реализовать программные прерывания. 

 
 
   Для разрешения или запрещения внешних прерываний предназначен управляющий  регистр GICR (General Interrupt Control Register).

 
   Установка битов INT1, INT0 или INT2 разрешает прерывания при возникновении события на соответствующем выводе микроконтроллера AVR, а сброс — запрещает.
 
   Естественно нужно установить еще и флаг глобального разрешения прерываний -  I, который расположен в регистре SREG. Без него вообще ни одно прерывание вызываться не будет.
 
Внешнее прерывание может происходить по одному из условий:
– по низкому уровню на выводах INT0, INT1, 
– по любому изменению логического уровня на выводах INT0, INT1
– по спадающему фронту сигнала на выводах INT0, INT1, INT2,
– по нарастающему фронту на выводах INT0, INT1, INT2.
 
Небольшая иллюстрация, чтобы уяснить разницу между фронтом и уровнем сигнала. 
 
 
   Условия генерации прерываний устанавливаются с помощью конфигурационных регистров. Для INT0, INT1 – это регистр MCUCR (MCU Control Register). Для INT2 – MCUCSR (MCU Control and Status Register)



   
   В таблице ниже приведены возможные значения разрядов ISC01, ISC00 и соответствующие им условия генерации внешнего прерывания. 

 
   Для прерывания INT1 таблица выглядит аналогично, только управляющие разряды другие — это ISC11 и ISC10. 
 
   Прерывание INT2 может происходить только по фронтам сигнала, поэтому для установки условий используется всего один бит - это бит ISC2 регистра MCUCSR.
 


  Кстати, при смене значения бита ISC2 может быть сгенерировано прерывание INT2. Чтобы этого не происходило, нужно производить модификацию бита ISC2 так: запретить внешнее прерывание, поменять бит ISC2, сбросить флаг прерывания — INTF2 (смотри ниже) и опять разрешить прерывание INT2.
 
    Обнаружение фронтов сигналов на выводах INT0/INT1 осуществляется синхронно, то есть по сигналу тактового генератора. Минимальная длительность входного импульса, гарантирующая генерацию прерывания, составляет один период тактового сигнала микроконтроллера AVR.
    Внешние прерывания INT0/INT1 сконфигурированные на срабатывание по низкому уровню обрабатываются асинхронно. Для генерации прерывания, уровень должен удерживаться до окончания выполнения текущей команды. Если после обработки прерывания уровень еще удерживается, прерывание будет вызвано снова. 
    Обнаружение перепадов сигнала на выводе INT2 тоже осуществляется асинхронно. Минимальная длительность импульса, гарантирующая генерацию прерывания, составляет 50 нс.
    Внешние прерывания, обнаруживаемые асинхронно, могут быть использованы для пробуждения микроконтроллера, находящегося в любом из шести режимов пониженного энергопотребления. Об этом будет отдельный материал.  
 
   Последний регистр, имеющий отношение к внешним прерываниям, - это статусный регистр GIFR (General Interrupt Flag Register). В нем содержатся флаги, устанавливаемые в случае формирования запроса на внешнее прерывание. 
   Флаги сбрасываются аппаратно, когда вызывается обработчики прерываний. Также их можно сбросить программно, записав в регистр единицы. Причем сброс нужно производить перезаписью регистра GIFR, на не операцией побитового ИЛИ.

Неправильно: GIFR |= (1<<INTF1)    Правильно: GIFR = (1<<INTF1).   Почему? Читаем в статье про таймеры AVR.
 
 

Пример использования внешнего прерывания

   Рассмотрим простой пример использования внешнего прерывания — опрос тактовой кнопки.

Использование внешних прерываний в AVR
 
   

//***************************************************************************
//  Author(s)...: Pashgan    http://ChipEnable.Ru   
//  Target(s)...: mega16
//  Compiler....: IAR EWA 5.11A
//  Description.: Использование внешнего прерывания.
//  Data........:  14.09.2011
//***************************************************************************
#include <ioavr.h>
#include <intrinsics.h>
#include "lcd_lib.h"
 
#define PIN_INT0 PD2
volatile unsigned char flagInt0 = 0;
 
int main( void )
{
  LCD_Init(); 
  
  //настраиваем вывод на вход
  DDRD &= ~(1<<PIN_INT0);
  //включаем подтягивающий резистор
  PORTD |= (1<<PIN_INT0); 
  //разрешаем внешнее прерывание на int0
  GICR |= (1<<INT0);
  //настраиваем условие прерывания  
  MCUCR |= (1<<ISC01)|(0<<ISC00);
  
  __enable_interrupt();
  
  while(1){
    if (flagInt0){
       LCD_Goto(0,0);
       LCD_SendString("on ");
       flagInt0 = 0;  
       __delay_cycles(16000000);
       LCD_Goto(0,0);
       LCD_SendString("off");
    } 
  }
  return 0;
}
 
//внешнее прерывание. обработчик.
#pragma vector=INT0_vect
__interrupt void int0(void
{
  unsigned char i = 0;
  unsigned char count = 0;
  
  while(i < 16){
    if ((PIND & PIN_INT0) == 0) count++;
    i++;  
  }
  if (count > 10) flagInt0 = 1;  
}


   В начале функции main мы инициализируем дисплей, настраиваем вывод PD2 на вход и включаем подтягивающий резистор. Разрешаем внешнее прерывание INT0 и задаем условие его генерации – по спадающему фронту. Далее устанавливается флаг глобального разрешения прерываний, и программа бесконечно выполняет цикл while, в теле которого происходит опрос программного флага. 
   Если флаг установлен, то на дисплей выводится строка “On” и флаг сбрасывается. А после секундной задержки на дисплей выводится строка “Off”. Если флаг не установлен, то ничего не происходит. 
   Вывод PD2 подтянут с помощью внутреннего резистора к плюсу питания. Пока кнопка не нажата на выводе присутствует напряжение логической единицы. Когда кнопка нажимается, потенциал вывода PD2 становится равен напряжению логического нуля.  
   Обнаружив перепад сигнала, микроконтроллер вызывает обработчик внешнего прерывания, в теле которого происходит многократный опрос вывода PD2. Если в течение этой процедуры уровень сигнала все еще остается низким,  о чем свидетельствует значение счетчика, - взводится флаг. Далее обработчик прерывания заканчивает свою работу, и программа возвращается в основной цикл. 

Файлы

Комментарии   

# anonymous 15.09.2011 06:16
Спасибо за статью. Вижу у вас IAR 5.11, не скажите как исправить старую программу написанную на IAR 4? А то 5й не воспринимает подобную запись:

struct
{
...
} _PORTA @ 0x21;


Выдаёт ошибку Error[e46]: Undefined external
Ответить | Ответить с цитатой | Цитировать
# Pashgan 15.09.2011 06:44
А так?
Код:
__io struct{
...

}_PORTA @ 0x21;
Ответить | Ответить с цитатой | Цитировать
# anonymous 15.09.2011 07:44
Ну да там так и написано
static volatile __io struct
{
BYTE _P0:1, _P1:1, _P2:1, _P3:1, _P4:1, _P5:1, _P6:1, _P7:1;
} _DDRA @ 0x1A;

, а линковщик IAR 5.51 ругается на такие определения, говорит Error[e46]: Undefined external "_MCUCR" referred in TCore

Тут же запускаю версию IAR 4.20 и компиляция работает без проблем, всё линкуется
Ответить | Ответить с цитатой | Цитировать
# Pashgan 15.09.2011 11:36
Скинь проект мне на почту, я посмотрю.
microsmaster собакаяндексточ кару
Ответить | Ответить с цитатой | Цитировать
# anonymous 15.09.2011 12:10
Сорри, весь проект не могу, коммерческая тайна, а в условиях Белоруссии может и национальная. Отправил только заголовочный файл, где описываются эти регистры sfr.h
Если так ничего не придумаете то ничего, в принципе на версии 4.20 работает, а IAR в параллель работает нормально.
Ответить | Ответить с цитатой | Цитировать
# Cudi 15.09.2011 09:54
а что программа делает?
Ответить | Ответить с цитатой | Цитировать
# JoJo 15.09.2011 10:27
При нажатии на кнопку выводит на дисплей строку "On", а после секундной задержки строку "Off".
Ответить | Ответить с цитатой | Цитировать
# Cudi 15.09.2011 10:26
Записал программу, а она не работает...
Ответить | Ответить с цитатой | Цитировать
# Cudi 15.09.2011 10:46
А всё окей)) JTAG забыл вырубить))
Ответить | Ответить с цитатой | Цитировать
# JoJo 15.09.2011 10:50
Я почему то так и подумал
Ответить | Ответить с цитатой | Цитировать
# Cudi 15.09.2011 10:58
Пару вопросов:
1) if (flagInt0) - почему именно flagint0?
2) Почему в цикле есть flagInt0 = 0; итак же ясно что флагинт0 = 0, ещё в самом верху.
3) flagInt0 = 1; в конце. т.е. в 1 вопросе флагинт0 = 1 ?
Ответить | Ответить с цитатой | Цитировать
# Pashgan 15.09.2011 11:24
1. flagInt0 - это произвольная переменная. С таким же успехом можно назвать ее, например, button1.
2. В начале программы выполняется инициализация flagInt0.Если этого не сделать в переменной может быть что угодно.
флаг сигнализирует о том что была нажата кнопка, после обработки нажатия (вывод строки) он сбрасывается.
3. не понял вопроса
Ответить | Ответить с цитатой | Цитировать
# iVIT 15.09.2011 13:54
Хорошая статья для начинающих. Единственное замечание:
Цитата - "Флаги сбрасываются аппаратно, когда вызывается обработчики прерываний. Также их можно сбросить программно, записав в регистр нули."

В AVR флаги сбрасываются записью в них логической единицы, исправьте.
Ответить | Ответить с цитатой | Цитировать
# Pashgan 15.09.2011 14:09
Да, исправлю. Мой косяк.
Ответить | Ответить с цитатой | Цитировать
# Cudi 15.09.2011 14:02
Всё понял спасибо
Ответить | Ответить с цитатой | Цитировать
# Dima 16.09.2011 09:49
Спасибо, наконец то разобрался.
Ответить | Ответить с цитатой | Цитировать
# Cudi 16.09.2011 10:25
Pashgan, напиши пожалуйста статью про UART, посылка сигналов, установка битов и т.д.))
И очень интересно увидеть как с компьютера можно управлять МК, помигать светиком и т.п.))
Ответить | Ответить с цитатой | Цитировать
# Гость 16.09.2011 10:51
Так вот же статья на эту тему
http://chipenable.ru/index.php/programming-c/43-avr-usart-uart.html
Ответить | Ответить с цитатой | Цитировать
# Имярек 17.09.2011 16:22
Не одними портами может общаться AVR с внешним миром. Мой тиня-AVR много лет общается с удаленным Хостом при помощи изменения собственного потребляющего тока. И отлично себя чувствует. :)
Ответить | Ответить с цитатой | Цитировать
# Pashgan 17.09.2011 21:32
Ну а как насчет того, чтобы поделиться с общественностью тонкостями реализации данного интерфейса? Думаю не одному мне это было бы интересно.
Ответить | Ответить с цитатой | Цитировать
# Имярек 18.09.2011 08:20
Ну какой еще секрет? Пины все были заняты, вот и пришлось для связи устройства по двум проводам с Хостом применить токовые "ШИМ" посылки. Ширина импульса определяет 0 или 1. Запустил таймер - и на заданное время в Idle-sleep. Получается, что по линии идут токовые импульсы, т.к. потребление Activ vs Sleep раза в 3-4 различается.
Ответить | Ответить с цитатой | Цитировать
# Валентин 18.09.2011 15:17
Хорошая статья, спасибо! Как раз тут делал заказ, пригодились нюансы!
Ответить | Ответить с цитатой | Цитировать
# Максимка 19.09.2011 05:26
1."Флаги сбрасываются аппаратно, когда вызывается обработчики прерываний. Также их можно сбросить программно, записав в регистр нули". Разве нули?
2. Наверно надо указать, что при прерывании по уровню его флаг всегда == 0.
Ответить | Ответить с цитатой | Цитировать
# Pashgan 19.09.2011 06:09
1. Единицы, единицы... Мне уже сделали замечание по этому поводу, просто еще руки не дошли исправить текст.
Ответить | Ответить с цитатой | Цитировать
# Pashgan 24.09.2011 13:04
Исправил
Ответить | Ответить с цитатой | Цитировать
# Имярек 27.09.2011 13:26
Последний штрих к шедевру, весьма важный :)
Опишите, что на Си нельзя применять для программного сброса флага прерывания операцию:
GIFR |= (1
Ответить | Ответить с цитатой | Цитировать
# Имярек 27.09.2011 13:29
Последний штрих к шедевру, весьма важный. Опишите, что на Си нельзя применять для программного сброса флага прерывания операцию:
Код:
GIFR |= (1 << INTF0);

Надо только так:
Код:
GIFR = (1 << INTF0);
Ответить | Ответить с цитатой | Цитировать
# foxit 22.10.2012 14:11
Нужно ли выводы внешних прерываний притягивать через резистор к питанию или земле, чтобы не ловить внешние наводки?
Ответить | Ответить с цитатой | Цитировать
# Pashgan 22.10.2012 21:23
Это зависит от приложения и условий его использования. Для повышения надежности обычно ставят.
Ответить | Ответить с цитатой | Цитировать
# Павел 14.12.2012 16:51
У вас код написан в синтаксе IAR, а я пишу в CVAVR, подскажите пожалуйста что означает сторчка:
__enable_interrupt();
и каков ее аналог в CVAVR?
Я понимаю что это связано как то с разрешением или обьявлением прерываний но разаобратья не могу.
Ответить | Ответить с цитатой | Цитировать
# Pashgan 17.12.2012 19:12
Это означает, установить глобальный флаг разрешения прерываний. В CodeVision эта строчка выглядит так - #asm("sei")
А вообще в конце статьи есть проект для CodeVision AVR.
Ответить | Ответить с цитатой | Цитировать
# Юрий_789 03.02.2013 15:00
Подскажите можно по прерыванию сделать переход на начало основной программы. Или куда важней перейти по определённому адресу программы. (наподобие команды "goto").
Ответить | Ответить с цитатой | Цитировать
# alexan9er 10.11.2014 00:01
Поэкспериментир овал тут с кнопкой и пришёл к выводу что нельзя так делать как вы пишите. Внешнее прерывание на AVR самое приоритетное и любые паузы (у вас цикл) тормозят таймеры. Если считается время это будет существенно.
Кроме того от длительного нажатия вы защитились,зада в прерывание по спадающему фронту, но так нельзя будет обрабатывать события по долгому нажатию (например короткое нажатие - одно событие, долгое - другое).
Получается что кнопки на внешние прерывания лучше вообще не вешать, а опрашивать их состояние периодически?
Ответить | Ответить с цитатой | Цитировать
# Валентин 09.05.2015 03:52
А как быть с защитой от дребезга? Скажем, мне надо чтобы переменная по нажатию инвертировалась .
Ответить | Ответить с цитатой | Цитировать
# Azerhud 25.03.2016 05:49
при компиляции в codevision avr выдаёт
Linker error: D:\Ci\Book_C\Ex ternalInterrupt -CVAVR\External Interrupt\lcd_l ib.h(49): function 'LCD_Init' declared, but never defined
Ответить | Ответить с цитатой | Цитировать

Добавить комментарий

Защитный код
Обновить