Главная arrow Программирование arrow AVR arrow AVR Studio: как написать обработчик прерывания Friday, March 24 2017  
ГлавнаяКонтактыАдминистрированиеПрограммированиеСсылки
UK-flag-ico.png English Version
GERMAN-flag-ico.png Die deutsche Version
map.gif карта сайта
нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.

Поделиться:

AVR Studio: как написать обработчик прерывания Версия для печати
Написал microsin   
10.01.2010

В этой статье на примере обработчика прерывания таймера 1 для ATmega16 рассказывается, как организовать обработчик прерывания в проекте GCC. Показаны два варианта реализации - на языке C и ассемблера. В примерах алгоритм работы таймера отличается, но это не важно для рассмотрения методов организации обработчика прерывания.

[Обработчик прерывания на C]

Это самый простой вариант. В данном примере используется следующий алгоритм - основная программа настраивает таймер 1 и запускает обработчик прерывания. Этот обработчик срабатывает раз в секунду и заботится о себе сам, подстраивая величину счетчика TCNT1 (чтобы прерывания происходили точно раз в секунду). Обработчик раз в секунду также декрементирует счетчик времени timer, который устанавливается и отслеживается в основной программе. Прерывание таймера разрешено постоянно (разрещается при старте программы). Таким образом, основная программа может получить любую задержку времени в интервале от 1 до 255 секунд.

Процесс по шагам:

1. Настраиваем таймер и разрешаем прерывание для него. Этот код должен вызываться однократно, при старте программы. Для этого можно написать отдельную процедуру, например:
#include <avr/io.h>
..

void SetupTIMER1 (void)
{
    //With 16 MHz clock and 65536 times counting T/C1 overflow interrupt
    // will occur every:
    //   1<<CS10                  4096 mkS  (no prescale Fclk)
    //   1<<CS11                  32.768 mS (Fclk/8)
    //  (1<<CS11)|(1<<CS10)       262.144 mS (Fclk/64)
    //   1<<CS12                  1048.576 mS (Fclk/256)
      TCCR1B = (1<<CS12);
    TCNT1 = 65536-62439;        //коррекция счетчика, чтобы время было ровно 1 секунда
    /* Enable timer 1 overflow interrupt. */
    TIMSK = (1<<TOIE1);
}

2. В любом из модулей проекта (можно в общем, где функция main, а можно в отдельном, например timer.c) пишем код обработчика прерывания таймера. Вот пример такого кода:
#include <avr/interrupt.h>
#include <avr/io.h>
..

u8 timer;

ISR (TIMER1_OVF_vect)
{
    //теперь прерывание будет происходить через 62439 тиков
    // таймера 1, что на частоте 16 МГц составит 1 сек.
    TCNT1 = 65536-62439;
    //Далее идет код, который будет работать каждую секунду.
    //Желательно, чтобы этот код был короче.
    if (timer)
        timer--;
}

3. В нужном месте разрешаем прерывания программы. Это делается также однократно, после того как сделаны все приготовления:
    sei();

[Обработчик прерывания на ASM]

Этот вариант не многим сложнее, просто организован по-другому. Я его сделал на основе отдельного файла, который содержит только код на языке ассемблера. Алгоритм тут тоже другой - обработчик прерывания срабатывает раз в секунду и сам себя запрещает. Основная программа отслеживает это событие и меняет секундные счетчики (выполняет все действия, которы нужны раз в секунду), и нова разрешает прерывание. Такой алгоритм позволяет ускорить обработку прерывания, что может быть критично для некоторых задач (например, только так можно организовать точный отсчет времени при использовании библиотеки V-USB). Процесс по шагам:

1. Настраиваем таймер. Это может делать код на C. Все точно так же, как и с обработчиком прерывания на C (см. шаг 1).

2. Готовим файл с нашим кодом обработчика на языке ассемблера. Вот пример кода:
#include <avr/io.h>
    .text
    .global TIMER1_OVF_vect
TIMER1_OVF_vect:
    push    R24
    ldi     R24, 0
    out     _SFR_IO_ADDR(TIMSK), R24
    pop     R24
    reti

Этот код будет работать очень быстро, поскольку короткий. Он почти ничего не делает, только запрещает прерывание от таймера 1 (в регистре TIMSK сбрасываются все флаги, в том числе и нужный нам флаг TOIE1). Запускать прерывание будет основная программа, как только обнаружит, что прерывание запрещено (путем анализа состояния флага TOIE1).

3. В нужном месте разрешаем прерывания программы. Это делается также однократно, после того как сделаны все приготовления:
    sei();

4. В основной программе, в главном цикле main, должен максимально часто вызываться следующий код:
..
void main (void)
{
    ..
    while (1)
    {
        ..
        if (0==(TIMSK & (1<<TOIE1)))
        {
            TCNT1 = ONE_SECOND;
            TIMSK = (1<<TOIE1);
            //далее действия, которые будут происходить
            // раз в секунду
            ..
        }
        ..
    }
}

[Общие замечания]

Можно заметить, что в обоих примерах использовалась именованная константа TIMER1_OVF_vect, которая задает адрес вектора прерывания таймера 1. Имена констант можно узнать во включаемом файле процессора. Для ATmega16, например, это будет файл c:\WinAVR-20080610\avr\include\avr\iom16.h. Чтобы имена стали доступны, не нужно добавлять именно этот файл в проект директивой #include, достаточно добавить #include <avr/io.h> и задать макроопределение, задающее тип процессора (например, MCU = atmega16. Это можно сделать либо в Makefile, либо в свойствах проекта).

При использовании одновременно нескольких прерываний в AVR важно помнить, что прерывания имеют фиксированный, ненастраиваемый приоритет. Чем меньше адрес вектора прерывания, тем приоритет у прерывания выше. Этот приоритет срабатывает, если при выходе из прерывания имеется несколько необработанных флагов прерывания. Прерывание с более высоким приоритетом НЕ может временно приостановить уже работающий обработчик прерывания с меньшим приоритетом, чтобы немедленно выполнить свой код. Работает система примерно так:

- когда общие прерывания разрешены (установлен бит I в регистре SREG, этот бит называют Global Interrupt Enable), то может быть вызвано любое разрешенное прерывание с любым приоритетом. Бит Global Interrupt Enable может быть принудительно сброшен или установлен командами CLI или SEI соответственно.
- для того, чтобы прерывание могло сработать и вызвать свой обработчик, кроме установки бита Global Interrupt Enable необходимо также установить бит разрешения соответствующего прерывания. Для таймеров-счетчиков это биты регистра TIMSK, для интерфейса SPI - бит SPIE в регистре SPCR, и т. д.
- когда срабатывает любое прерывание, то сразу очищается флаг I (Global Interrupt Enable), и автоматически запрещаются все прерывания, пока не произойдет выход из обработчика прерывания. Если во время работы обработчика прерывания возникали условия, при которых должны были сработать другие прерывания, то эти другие прерывания не вызываются, а просто запоминаются соответсвующие им флаги (прерывания "откладываются" на будущее). При выходе из обработчика прерывания запомненные флаги прерывания запустят нужный обработчик прерывания в соответствии с назначенным ему приоритетом (если на данный момент имеется несколько отложенных прерываний).
- разработчик может в обработчике прерывания вызвать команду SEI (которая установит флаг Global Interrupt Enable), тем самым разрешив выполнение других прерываний во время работы этого обработчика прерывания. Тогда, если произойдет новое другое прерывание до завершения текущего обработчика (в котором уже была вызвана команда SEI), текущий обработчик прерывания будет приостановлен, адрес возврата в него будет сохранен в стеке и будет вызвано новое прерывание. Таким способом можно обеспечить некое подобие соблюдения приоритета - в низкоприоритетных обработчиках прерывания должна первой стоять команда SEI, а в высокоприоритетных обработчиках команда SEI должна отсутствовать, что обеспечит выполнение этого обработчика полностью.

Отсутствие возможности четко настроить приоритет - довольно серьезный недостаток платформы AVR.

Последнее обновление ( 12.03.2010 )
 

Комментарии  

  1. #5 samsim
    2015-02-0712:18:07 Отлично! Все сразу стало ясно. Автору поста большое спасибо.
  2. #4 Сергей
    2012-08-1120:47:57 Можно ли для таймера 1 установить одновременно два прерывания по захвату с компаратора и по переполнению Если не произойдёт захвата чтоб программа прерывания сработала по переполнению.

    microsin: да, это возможно.
  3. #3 MikF
    2012-03-1810:05:45 Можно ли после прерывания попадать в другое место программы? Например прерывание функции delay()? Обьясните, пожалуйста, если не сложно…

    microsin: обычное поведение после возврата из прерывания - продолжение выполнения программы с того места, где оно было прервано запуском обработчика прерывания. Если Вы хотите такое поведение изменить, то Вам нужно модифицировать стек перед возвратом из обработчика прерывания. Однако имейте в виду, что если Вы не знаете, что делаете, то таким способом можно легко нарушить работу всей системы, и Ваша программа начнет глючить или вовсе перестанет работать. Подробности по работе прерываний см. в даташите на микроконтроллер .
  4. #2 anvm
    2010-09-1218:41:23 В USB консоли для atmega168 команду :
    out _SFR_IO_ADDR(TI MSK), R24
    заменил на
    sts TIMSK1, R24

    http://www.avrfreaks.net/index.php?name=PNphpBB2&file=printview&t=78203&start=0
  5. #1 Zigfrid
    2010-03-1215:34:15 цитата-"Прерывание с более высоким приоритетом может временно приостановить уже работающий обработчик прерывания с меньшим приоритетом, чтобы немедленно выполнить свой код" - разве?
    При запуске первым вызовется прерывание с меньшим адресом вектора. Внутри подпрограммы обработки прерывания другие прерывания(глоб ально) запрещены. Т. е. до конца прерывания оно не будет прервано. Если их разрешить, внутри прерывания, то сработает первое разрешённое прерывание(прио ритет не важен. Или я ошибаюсь?

    microsin: да, действительно, Вы правы. Оказывается, что приоритет работает только в случае нескольких запомненных (во время выполнения прерывания, когда другие прерывания не могут быть вызваны) флагов прерывания. Благодарю Вас, мне надо исправить старый текст.

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

:D:lol::-);-)8):-|:-*:oops::sad::cry::o:-?:-x:eek::zzz:P:roll::sigh:

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

< Пред.   След. >

Top of Page
 
microsin © 2017