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

Вспомним самую простую программу из первого урока, светодиод включается, пауза, выключается, пауза и по кругу до бесконечности.

while (1)
{
      PORTB.0=1;
      delay_ms(500);
      PORTB.0=0;
      delay_ms(500);
}

Что если в эту программу добавить обработку нажатия кнопки?

while (1)
{
      if(PINB.1==0)
      {
         PORTD.0=1;
      }
      PORTB.0=1;
      delay_ms(500);
      PORTB.0=0;
      delay_ms(500);
}

Допустим пользователь нажал кнопку в момент, когда программа выполняет строчку PORTB.0=1, т.е. до того как будет проверено наше условие if(PINB.1==0), должны выполниться две строчки с задержкой по 500ms. Получается пользователь должен держать кнопку больше секунды, чтобы она сработала, это жутко не удобно.

Или, как например, в динамической индикации, индикаторы быстро загораются по очереди через определенные промежутки времени. Если этот код крутить в основном цикле и плюс к этому обрабатывать допустим измерения, то в лучшем случае индикаторы будут мерцать, а в худшем гореть будет только один.

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

Например, выполняется ваш цикл мигалки, при этом происходит некоторое событие, программа останавливается, допустим на строчке PORTB.0=0 и перескакивает на строчку if(PINB.1==0). После выполнения этой строчки, программа вернется к основному циклу на строчку delay_ms и будет крутить основной цикл, до тех пор пока снова не будет вызвано следующее прерывание.

interrupt [номер вектора] void имя функции обработчика прерывания(void)
{
if(PINB.1==0) { PORTD.0=1; }
}
 
void main(void)
{
 
while (1)
      {
       PORTB.0=1;
       delay_ms(500);
       PORTB.0=0;
       delay_ms(500);
      };
}

Так что же это за такие события, способные остановить выполнения основной программы. Например, переполнение таймера. Допустим, вы настроили таймер, который умеет считать до 255 и у него есть прерывание по переполнению. Это значит, что он досчитает до 255, и вызовет, тот самый важный кусок кода.

interrupt [TIM0_OVF] void timer0_ovf_isr(void)
{
}

INTERRUPT0

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

interrupt [TIM1_COMPA] void timer1_compa_isr(void)
{
min++;
}

Обратите внимание название функций содержат названия таймеров, в первом случае TIM0 уже понятно что прерывание относится к таймеру0, во втором TIM1 — таймер1. Также из названия функции понятно по какому событию происходит прерывание COMPA — compare — сравнение, OVF — overflow, переполнение.

Кроме того, существуют прерывания, которые не связаны с таймерами, например внешнее прерывание. У микроконтроллеров есть ножки отведенные под внешнее прерывание и при воздействии на эту ножку извне программа выполняет код указанный в прерывании. Можно настроить так, чтобы прерывание возникало при низком уровне на ножке, нарастании/спаду фронта  или по любому изменению. Об этом уже говорилось в статье про частотомер.

interrupt [EXT_INT0] void ext_int0_isr(void)
{
}

INTERRUPT_ExT

Также, прерывание может возникать, при приеме или передаче данных по UART, для этого достаточно настроить соответствующее прерывание.

INTERRUPT_UART

Кстати, иногда по тексту программы требуется отключить/включить прерывания.

// Разрешить глобальные прерывания
#asm("sei")
// Запретить глобальные прерывания
#asm("cli")

Как же узнать, какие прерывания есть у используемого микроконтроллера? Либо читать документацию (даташит) на него, либо посмотреть в codewizard список доступных прерываний. В визарде обычно прерывания включается галочкой interrupt. Настоятельно советую разобраться с визардом, он сэкономит кучу времени.

Update. По просьбам трудящихся. Как пользоваться CodeWizard

Запускаем визард.
wiz

Смотрим на имеющуюся периферию
wiz1

Сразу нужно понять, что это относится к самому камню, микроконтроллер не знает что такое кнопка, у него есть ножка и все манипуляции производятся именно с ней. Для манипуляций с ножкой имеется внешнее прерывание. Как им пользоваться описано в 13 уроке AVR.
wiz2
Для прерываний по таймеру, заходим в раздел Timers/Counter, основное прерывание по таймеру это по совпадению — аналог будильника, т.е. когда выставленное значение совпало с количеством тиков. Про этот режим читаем 5 урок.

Может так оказаться что у таймера нет режима по совпадению, но наверняка есть по переполнению, т.е. если таймер 8 битный, то его максимум 2^8, когда он дотикает до этого значения он обнулится, заодно вызовет прерывание.

22 комментария: Прерывания.

  • Прерывание возникает в течении нескольких тактов, точно не знаю. Вопрос, если в основном цикле программы выполняется именно функция delay_ms() она до конца отрабатывает или же она тоже «делится на части» ?.

  • #include
    #include
    interrupt (void)
    {
    if(PINB.1==0) { PORTD.0=1; }
    }

    void main(void)
    {

    while (1)
    {
    PORTB.0=1;
    delay_ms(500);
    PORTB.0=0;
    delay_ms(500);
    };
    }не работает
    ошибка Error: D:\AVR\knepa.c(3): ‘[‘ expected, but ‘(‘ found
    подскажите в чем причина

  • interrupt это общее название всех прерываний, но нужно понимать что вызвало прерывание — таймер, кнопка, АЦП поэтому там где у вас просто interrupt должно быть interrupt [TIM1_COMPA] void timer1_compa_isr(void) что означает прерывание от таймера1 по совпадению регистра OCRA. Названия прерываний учить не нужно, достаточно пользоваться генератором кода

  • Извините, я новичок. Подскажите где искать генератор кода? И если не сложно вкратце как им пользоваться?

  • З.Ы.Мне нужно чтобы кнопка прерывала выполнение программы

  • На примере мигающего светодиода

  • Подскажите… Использую функцию прерывания по сравнению timer1.Эта функция должна принимать и передавать int i и unsigned char a[ ]. Как правильно оформить описание функции?

  • Используйте глобальные переменные, ибо делать функцию внутри прерывания плохая идея, либо в прерывании выставляйте флаги, а в основном цикле делайте функцию.
    ЗЫ в конце статьи добавил чуток про прерывания

  • Здравствуйте. Подскажите пожалуйста как настраивается или откуда берется GIMSK? Я новичек еще, скачал пример кода, а cvavr пишет «undefined symbol ‘GIMSK'».

  • В даташите он называется General Interrupt Control Register

  • А он на всех мк работает? Все настройки проекта перекопал и дш, толку ноль. А в дш почти ничего нет по-этому поводу и в таблице тоже его нет, а eimsk есть…Это EIMSK не тоже самое?

  • настраивайте codewizard ом и не мучайтесь

  • Добрый Admin у тебя просто великолепный сайт. По твое сайту много научился. Можно опять попросить помощи. Пожалуйста, я вам отправлю архив с моем проектом в нем техническое задание можешь хотя бы меня проконсультировать. Codevision нормально компилирует без ошибок, вот Proteus вытворяет какие то чудеса. Кстати я уже выкладывал уже текст программы и там было ошибка (while).. OK?

  • можете присылать, но смотреть не обещаю

  • GIMSK это регистр разрешения прерывания от входов INT0 и INT1. В вашем случае он будет называться GICR.

  • На счёт протеуса. Удалите его и забудьте как страшный сон. Эта зараза на разных машинах работает как ей захочется. Если эмулировать пример с тупым миганием светодиода, то пойдёт. А если делать что-то серьёзное, то на выходе, то что работает в протеусе никогда не будет работать в живую. А теперь конкретные факты. В протеусе криво реализованы прерывания по таймера. Периоды плавают в зависимости от мощность процессора ПК и его загруженности. При работе с ЖК дисплеями не всегда корректно выводит информацию. Был косяк, графический дисплей 128х64 прекрасно работал в протеусе, а в железе ничего не выводил. Когда добился стабильной работы в железе, перестало работать в протеусе. Ну и если кому интересно изучить все косяки данного девайса, можно почитать на Казусе. Там о нем очень честно отзываются.

  • Таки нужно понимать, чего ждать от протеуса, вот и весь секрет. Рассмотрите вопрос с т.з. способов отладки, мигать светодиодом? выводить инфу в юарт? выводить инфу на дисплей? А если у вас дофига переменных, юарт занят, а дисплея в проекте нет, да еще и проект большой? Я никогда не отлаживаю в протеусе, то в чем не уверен на 100%, но если мне нужно посмотреть состояние сразу нескольких переменных, то намного быстрее поставить точку останова и посмотреть, альтернативы протеусу по скорости, в этом смысле просто нет. Просто не отлаживайте всякие дисплеи, карты памяти и прочую периферийную лабуду и будет вам счастье.

  • Как это нет? 😯 А куда же делся родной JTAG. Это круче любого протеуса. 😛

  • у мелких атмег типо 8 увы jtag нету, а таких камней лежит еще целая кучка.

  • А для таких камней CVAVR может передать файлы AtmelStudio и там провести эмуляцию. По шагово с заходом в функции, при этом можно не только переменные смотреть, но и даже смотреть во все регистра.

  • Извините, я новичок. Подскажите что делать если мне нужно запретить прерывание только по таймеру1, после нажатия кнопки, и разрешить его при нажатии второй кнопки, при этом остальные прерывания должны работать?

  • у таймера 1 есть регистр где включаются его прерывания. уберите бит который отвечает за прерывание

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

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Последние комментарии
  • Загрузка...
Счетчик
Яндекс.Метрика