Учебный курс 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. Если в течение этой процедуры уровень сигнала все еще остается низким,  о чем свидетельствует значение счетчика, - взводится флаг. Далее обработчик прерывания заканчивает свою работу, и программа возвращается в основной цикл. 

Файлы

Comments   

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

struct
{
...
} _PORTA @ 0x21;


Выдаёт ошибку Error[e46]: Undefined external
# Pashgan 2011-09-15 06:44
А так?
Code:
__io struct{
...

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

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

Надо только так:
Code:
GIFR = (1 << INTF0);
# foxit 2012-10-22 14:11
Нужно ли выводы внешних прерываний притягивать через резистор к питанию или земле, чтобы не ловить внешние наводки?
# Pashgan 2012-10-22 21:23
Это зависит от приложения и условий его использования. Для повышения надежности обычно ставят.
# Павел 2012-12-14 16:51
У вас код написан в синтаксе IAR, а я пишу в CVAVR, подскажите пожалуйста что означает сторчка:
__enable_interrupt();
и каков ее аналог в CVAVR?
Я понимаю что это связано как то с разрешением или обьявлением прерываний но разаобратья не могу.
# Pashgan 2012-12-17 19:12
Это означает, установить глобальный флаг разрешения прерываний. В CodeVision эта строчка выглядит так - #asm("sei")
А вообще в конце статьи есть проект для CodeVision AVR.
# Юрий_789 2013-02-03 15:00
Подскажите можно по прерыванию сделать переход на начало основной программы. Или куда важней перейти по определённому адресу программы. (наподобие команды "goto").
# Pashgan 2013-02-03 17:11
# alexan9er 2014-11-10 00:01
Поэкспериментир овал тут с кнопкой и пришёл к выводу что нельзя так делать как вы пишите. Внешнее прерывание на AVR самое приоритетное и любые паузы (у вас цикл) тормозят таймеры. Если считается время это будет существенно.
Кроме того от длительного нажатия вы защитились,зада в прерывание по спадающему фронту, но так нельзя будет обрабатывать события по долгому нажатию (например короткое нажатие - одно событие, долгое - другое).
Получается что кнопки на внешние прерывания лучше вообще не вешать, а опрашивать их состояние периодически?
# Валентин 2015-05-09 03:52
А как быть с защитой от дребезга? Скажем, мне надо чтобы переменная по нажатию инвертировалась .
# Azerhud 2016-03-25 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

У вас недостаточно прав для комментирования.