AVR: Обработка внешних прерываний

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

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

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

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

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

atmega8 external interrupts

atmega8

 

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

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

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

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

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

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

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

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

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

 ISC01  ISC00 Условие срабатывания
 0  0  Низкий уровень на INT0
 0  1  Любое изменение на INT0
 1  0  Задний фронт на INT0
 1  1  Передний фронт на INT0

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

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

//сбрасываем все биты ISCxx
MCUCR &= ~( (1<<ISC11)|(1<<ISC10)|(1<<ISC01)|(1<<ISC00) )
//настраиваем на срабатывание INT0 по переднему фронту
MCUCR |= (1<<ISC01)|(1<<ISC00);

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

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

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

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

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

//разрешаем внешнее прерывание INT0 
GICR |= (1<<INT0);
//выставляем флаг глобального разрешения прерываний
sei();

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

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

#include <stdio.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
 
//переменная счетчик
volatile unsigned long int0_cnt = 0;
 
//настройка внешнего прерывния INT0
void int0_init( void )
{
  //настраиваем на срабатывание INT0 по переднему фронту
  MCUCR |= (1<<ISC01)|(1<<ISC00);
  //разрешаем внешнее прерывание INT0 
  GICR |= (1<<INT0);
}
 
//функция обработчик внешнего прерывания INT0
ISR( INT0_vect )
{
  int0_cnt++;
}
 
//настройка UART
void uart_init( void )
{
  //настройка скорости обмена
  UBRRH = 0;
  UBRRL = 3; //115200 при кварце 7.3728 МГц
  //8 бит данных, 1 стоп бит, без контроля четности
  UCSRC = ( 1 << URSEL ) | ( 1 << UCSZ1 ) | ( 1 << UCSZ0 );
  //разрешить прием и передачу данных
  UCSRB = ( 1 << TXEN ) | ( 1 <<RXEN );
}
 
//передача байта по UART
int uart_putc(  char c, FILE *file )
{
  //ждем окончания передачи предыдущего байта
  while( ( UCSRA & ( 1 << UDRE ) ) == 0 );
  UDR = c;
  return 0;
}
 
FILE uart_stream = FDEV_SETUP_STREAM(uart_putc, NULL, _FDEV_SETUP_WRITE );
 
int main( )
{
  //временная переменная
  unsigned long tmp;
 
  stdout =  &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;
}

Скачать исходники примера работы с внешними прерываниями в avr atmega8 для avr-gcc ( winavr ) можно тут

Запись опубликована в рубрике Микроконтроллеры avr с метками , , . Добавьте в закладки постоянную ссылку.

15 комментариев: AVR: Обработка внешних прерываний

  1. Михаил говорит:

    Спасибо большое за материал, но он бесполезен, если ты ищешь пример кода для Ассемблера.

  2. Артём Двинин говорит:

    Я бы не сказал, что он бесполезен. Взяв за основу код на Си, можно написать свой на ассемблере. Или скомпилировать сишный код, и дизассемблировать его :)

  3. Артур говорит:

    Здравствуйте Артём.
    Я новичок. Пишу на atmel studio 6 под atmega168pa.
    При инициализации внешнего прерывания у меня возникли некоторые трудности.
    GICR не определяется. Полез в datasheet нет такого регистра нашел EIMSK
    правильной ли будет следующая запись:
    EIMSK|=(1<<INT0);
    MCUCR|=(1<<ISC01)|(0<<ISC00);

  4. Артём Двинин говорит:

    Правильной, на мой взгляд, будет вот такая запись:

    EIMSK = ( 1 << INT0 );
    EICRA = (1<<ISC01) | (0<<ISC00);

    Причём (0<<ISC00), можно не писать, т.к. это выражение равно нулю.

  5. Аноним говорит:

    Здравствуйте Артем. Очень полезный материал! Спасибо за урок по прерываниям. Я сейчас пытаюсь приспособить датчик расстояния HC-SR04 своей работе. Очень нужна программа на ассемблере. Если это Вас не затруднит буду очень признателен, ибо в си работал только в DOCе.

  6. Артём Двинин говорит:

    День добрый!
    А давайте вместе напишем эту программу на ассемблере. Или могу Вам помочь разобраться с Си под AVR, особой разницы с Си под DOS нет.
    P.S. Пожалуйста, укажите свой email когда будете оставлять комментарий (его кроме меня никто не увидит), некоторые вопросы по электронной почте быстрее решаются, чем через блог :)

  7. Андрей говорит:

    можно ли помочь мне накидать блок схему работы микроконтроллера atmega328 по следующему коду:

    #define F_CPU 1000000L
    #define BAUD 9600
     
    int main(void)
    {
      DDRD &amp;= ~((1&lt;&lt;DDD3) | (1&lt;&lt;DDD4) | (1&lt;&lt;DDD5) | (1&lt;&lt;DDD6) | (1&lt;&lt;DDD7));
      PORTD |= (1&lt;&lt;PORTD3) | (1&lt;&lt;PORTD4) | (1&lt;&lt;PORTD5) | (1&lt;&lt;PORTD6) | (1&lt;&lt;PORTD7);
      EICRA = (1&lt;&lt;ISC01) | (1&lt;&lt;ISC00);
      EIMSK = (1&lt;&lt;INT0);
      UBRR0H = UBRRH_VALUE;
      UBRR0L = UBRRL_VALUE;
    #if USE_2X
      UCSR0A |= _BV(U2X0);
    #else
      UCSR0A &amp;= ~_BV(U2X0);
    #endif
      UCSR0C = _BV(UCSZ01) | _BV(UCSZ00); /* 8-N-1 */
      UCSR0B = _BV(RXEN0) | _BV(TXEN0);
      sei();
      while(1);
    }
     
    ISR(INT0_vect)
    {
      loop_until_bit_is_set(UCSR0A, UDRE0);
      UDR0 = ((PINB &gt;&gt; 1) &amp; 0x60) | (PIND &lt;&lt; 3);
    }

    Заранее спасибо!

  8. Артём Двинин говорит:

    Андерй, пришлите свой вариант, если найду в нём ошибки, сообщу Вам.

  9. Сергей говорит:

    Здравствуйте Артем!А не поможете разобрать с программой генератора импульсов,есть исходник написанный в CVAVR,все работает в железе,но есть одно но,периодически пропадает сигнал на несколько секунд,точнее когда вращаешь энкодер меняя частоту.

  10. Игорь говорит:

    Здравствуйте. Моя схема почему-то не реагирует на прерывание. Можно с вами пообщаться по почте? Я бы выслал все свои наработки.

  11. Михаил говорит:

    Здравствуй) мог бы ты мне помочь сам, ну или статью какую дал бы? До меня не как не доходит как настраивать и работать с таймерами и шим! Много чего прочел, но так и не доходит

  12. Valentin говорит:

    Здравствуйте. Хотел узнать, почему в обычном режиме (прерывание при низком уровне на входе INTx) прерывания вызываются шустро в любом режиме и в режимах SLEEP (как и должно быть), а в режиме прерывание «по фронту», «по спаду» или «по изменению уровня» начинает тормозить, а в спящем режиме вообще не реагирует?

  13. Андрей говорит:

    Здравствуйте.А каким терминалом вы пользуетесь?
    При работе с контроллером?

  14. Артём Двинин говорит:

    Доброго времени суток.
    Под линуксом использую minicom. Под виндой юзал «Terminal 1.9b».

  15. Алексей говорит:

    А возможен такой вариант, кнопки INT0 и INT1 одновременно замыкаются и далее на индикатор выводится заданный параметр и если возможно то как записать условие?

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

Ваш e-mail не будет опубликован.

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>