AVR:Прерывания/Приоритетность

Материал из roboforum.ru Wiki
Перейти к: навигация, поиск


Введение

Данная статья предназначена для ознакомления с написанием программ для микроконтроллеров (в дальнейшем МК). Побудительным мотивом для её написания послужили многочисленные обращения с вопросами типа "Подскажите, пожалуйста, как в ATmega128 поменять приоритеты прерываний, например чтобы таймер T3 имел приоритет выше чем T0." и тому подобными. В ходе дискуссий выяснилось что многие из начинающих (да и не только) программистов путаются в понятиях и не совсем понимают суть вопроса, который задают. Попытаюсь в данной статье расставить точки над "i".

Замечание 1. Данный раздел, как в общем-то и вся статья есть мой взгляд на программирование для МК. Этот взгляд формировался годами исходя, как правило, из собственного опыта написания программ. Тем не менее, изучая программы и рекомендации других программистов, наблюдаешь что они зачастую прибегают к таким же либо аналогичным подходам. Однако изменения в стиле и методах происходят постоянно. И, возможно, завтра я буду с улыбкой смотреть на моё сегодняшнее творчество. :)

Замечание 2. Я ни в коей мере не собираюсь навязывать свой стиль и свои подходы кому бы то ни было. Я лишь пытаюсь суммировать свои знания и даю рекомендации исходя из своего опыта. Я надеюсь что думающий хоть и начинающий программист опираясь на мои знания и знания почерпнутые из других источников, выработает свой стиль и свои методы работы. Иначе говоря - подходите творчески к написанному.

Замечание 3. Согласен на обсуждение критических замечаний по поводу данной статьи по адресу sasa@c32dvina.com

.

Пример обсуждения данного вопроса на форуме.

Одним прекрасным вечером вижу такую вот дискуссию на http://www.fulcrum.ru/cgi-bin/bbs/topic_sel.pl?v=p&FID=1 Дискуссия разрастается как снежный ком. Попробую в следующем примере разъяснить некоторые вопросы по данной теме. Естественно с примерами используя стенд Nix-06.

_Aleksey , Добавлено: 20.09.2006, 12:37
Заголовок сообщения: Приоритеты прерываний


Подскажите, пожалуйста, как в ATmega128 поменять приоритеты прерываний, например чтобы таймер T3
имел приоритет выше чем T0.
Заранее благодарен!

Armer , Добавлено: 20.09.2006, 13:12
Заголовок сообщения: RE[1]: Приоритеты прерываний


Никак, приоритеты установлены аппаратно.
Можно только программно в обработчике Т0 проверить флаг T3 и при необходимости скокнуть на него.

@ndrey , Добавлено: 20.09.2006, 20:03
Заголовок сообщения: RE[2]: Приоритеты прерываний


или в обработчике T0 разрешить прерывание

sh , Добавлено: 21.09.2006, 10:59
Заголовок сообщения: RE[3]: Приоритеты прерываний


Самое правильно- распределять задачи по приоритетам. Если Т0- не приоритетная задача- есть Т2, и т.д.

Armer , Добавлено: 21.09.2006, 11:25
Заголовок сообщения: RE[3]: Приоритеты прерываний


А вот с этим надо быть осторожнее...

VA , Добавлено: 21.09.2006, 11:42
Заголовок сообщения: RE[3]: Приоритеты прерываний


Вполне согласен с @ndrey-ем.
В подпрограмме обработчике прерываний разрешаются прерывания (SEI).

Armer , Добавлено: 22.09.2006, 11:18
Заголовок сообщения: RE[4]: Приоритеты прерываний


Это разрешит ВСЕ прерывания, что не всегда удобно. Если требуется лишь обеспечить приоритет, то грамотнее и "чище" проверить флаг в самом начале обработчика T0 и по факту установки такового вызвать обработчик T3. Или, как сказал sh, решить задачу путем выбора дугого таймера, если конечно это допускает их функционал (нет под рукой даташита меги 128).

dmm , Добавлено: 22.09.2006, 13:29
Заголовок сообщения: RE[5]: Приоритеты прерываний


1 Я тоже так делаю - первой командой в прерывании более высокого уровня ставлю SEI и забочусь от достаточной длине стека. А в программе обработки приоритетного прерывания естественно этого не делаю и достигаю более высокой скорости обработки прервания второго десятка в списке (задерка в среднем по 4 такта на действующее прерывание). Это не самый лучший вариант, но приемлимый. Далее нужно просмотреть, то что получилось на предмет исключения одновременного использования ресурсов. Прерывания нужно писать покороче и на ассемблере. Внимательно с программным стеком (особенно на Си)!
2 Рекомендуется вынести из программ прерывания все, что необязательно быстро делать. Только переписывать данные и ставить флажки и, по которым их обрабатывать в главной программе.


Немного из теории.

Итак я перечитал литературу (заслуживающую доверия) и вижу что в данном вопросе много "не устоявшихся" понятий. Не буду претендовать на роль стандартизатора данных понятий. Где-то я прочитал такую классификацию и ей пользуюсь:

Уровень прерывания, а внутри уровня ранг. ("ранжированы внутри уровня"). Я для себя так и пользуюсь.

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

Хочу отметить что за всю мою практику (начиная с 1984года) ни разу не приходилось использовать уровень прерывания. И многократно использовалось многоуровневые прерывания. Хотя я и допускаю, что в редких случаях синхронных процессов возможно использование простых уровней. Приходится признать что это частный случай. Например автор одного из топиков упомянутой мной дискуссии привёл такой пример:

По таймеру запускаются ворота, а второй таймер (в его варианте более высокоуровневый) считает события внутри этих ворот. Ну и на границе необходимо включение пограничного события. Вот пример использования одноуровых прерываний. Это подпадает под описание "синхронных процессов".

Но чаще всего когда говорят об изменении или использовании прерывания, то имеют ввиду совсем не то. Людей вводит в заблуждение следующий текст из даташита: "The list also determines the priority levels of the different interrupts. The lower the address the higher is the priority level. RESET has the highest priority, and next is INT0 – the External Interrupt Request 0." Приведу пример ситуации когда хотелось бы изменить приоритет обработки прерывания. И не совсем по Atmel-овски.

В некоторых случаях не удастся написать короткое прерывание (или реализуется некрасиво). Ну например реализация RS232 двухсторонняя с кольцевыми буферами и управлением потоком (хотя за счёт буферизации двух символов это процедура не срочная).

В то же время необходимо следить за каким-то внешним временным процессом. Например реализовать уникальный протокол, или следить за изменениями на датчике. (и это прерывание очень срочное. Должно обрабатываться максимально быстро). Казалось бы приоритет прерывания от INT0 выше чем у USART (по даташиту). И не о чём беспокоится. На самом деле не всё так просто и, оказывается, что при простом написании такой программы человек сталкивается с потерей событий от INT0. Как этого избежать?

В этом случае необходимо ввести многоуровневое прерывание. А именно чтобы прерывание более высокого уровня вызывалось из более низкого, но не на оборот. Некоторые МК имеют аппаратные такие средства. (Например МК семейства х51). Они называются регистром "Interrupt Priority". AVR не имеет таких средств. Поэтому данное действие приходится делать программно. Осуществляется это простым разрешением прерывания в начале обработки прерывания более низкого уровня. Если приходится делать трёх и более уровневое прерывание, то необходимо в более высоком уровне запрещать все более низкие.

Например. Реализуем трёх-уровневое прерывание.

Первый уровень: Таймеры 0, 1, 2.

Второй уровень: Int0, Int1

Третий уровень: SPI.

П/п SPI пишется обычно.

В п/п Int0, Int1 в начале прерывания пишется запрещение прерываний от Таймеров 0,1,2 и потом пишется SEI

В п/п Tim0,1,2 в начале прерывания пишется SEI

Естественно необходимо следить за возможным разделением ресурсов в "перекрывающихся" процедурах прерываний.

Данная реализация изменения приоритета прерывания называется "статическим". Существуют и более сложные способы. Например "карусельного" типа. Как понятно из названия, тому прерыванию которое было обработано последним присваивается найнизший приоритет.

В следующем разделе рассмотрим программную реализацию, поясняющую данную статью.


Пример программы.

Приведём пример простенькой программы на языке Си для среды IAR C AVR.

<source lang="c">

  1. include "iom16.h"
  2. include "inavr.h"
  3. include "stdio.h"
  4. include "stdlib.h"

//////////////////////////////////////////////////////////// //********************************************************// // Р е а л и з а ц и я к о н т р о л л е р а // // п р и о р и т е т о в п р е р ы в а н и й . // //********************************************************// ////////////////////////////////////////////////////////////

  1. define PORTOUT PORTA // Порт вывода
  2. define DDROUT DDRA // Порт вывода
  3. define POUT 0 // Пин вывода
  1. define FCLK 8000000 // Частота процессора
  2. define FOUT 100000 // частота выходная на ноге POUT
  3. define FWAIT 5000 // частота выызова прерывания от таймера 2
  1. pragma vector=TIMER2_COMP_vect // Вывод пилы на ногу POUT

__interrupt static void FreqOut(void) {

   PORTOUT ^= (1<<POUT);                   // Инвертировать ногу

}


  1. pragma vector=TIMER0_COMP_vect // Задержка

__interrupt static void FreqWait(void) {

   //__enable_interrupt();                 // Разрешить прерывания (Прерывание с более низким уровнем приоритета)
   __delay_cycles(5*FCLK/FOUT);            // Задержка

}

__task void main( void ) {

   // Инициализация портов
   DDROUT = (1<<POUT);                     // рабочий пин на вывод
   // Инициализация таймера 2
   TCCR2 = 0x09;                           // Запустить таймер 0 в режиме СTC с прескалером 1
   OCR2 = FCLK/FOUT/2;                     // FOUT Гц
   // Инициализация таймера 0
   TCCR0 = 0x0a;                           // Запустить таймер 2 в режиме СTC с прескалером 8
   OCR0 = FCLK/FWAIT/8;                    // FWAIT Гц
   TIMSK = 0x82;                           // Прерывание от сравнения T0 и T2 разрешить
   __enable_interrupt();                   // Разрешить прерывания
   for(;;);                                // Голова ни чем не занята.

} </source>

Пояснения к пограмме и диаграммы работы.

Пояснения к программе следующие. Имеются два прерывания от таймеров 0 и 2 соответственно. Одно из них (TIMER2_COMP) более высокоуровневое согласно даташиту от Atmel на Atmega16, второе (TIMER0_COMP) более низкоуровневое. Иными словами прерывание TIMER2_COMP выполнится, по нашему замыслу, в первую очередь. В этом прерывании выводится меандр на ногу POUT порта PORTOUT. Более низкоуровневое прерывание занято какой-то работой (в данном случае - никакой. Просто задержка имитирующая работу). Необходимые порты и частоту процессора можно изменить настройками вначале проекта.

Итак запускаем программу на выполнение и на осциллографе видим следующую картинку.

Avr int priority 1.jpg

Как мы видим на фоне регулярных импульсов наблюдаются провалы. Давайте поменяем таймера между собой. Для этого перепишем имена прерываний в обработчиках ( изменим TIMER2_COMP на TIMER0_COMP и наоборот). И изменим инициализацию таймеров в процедуре main (TCCR2 на TCCR0, OCR2 на OCR0 и наоборот). Запускаем видоизменённую программу на исполнение и видим, что картинка абсолютно не поменялась. Это говорит о том, что в данном случае изменение уровня приоритета без введение многоуровневой системы прерываний ничего не дало.

Введём многоуровневую структуру прерываний. Для чего в более низкоуровневом прерывании разрешим работу более высокоуровнего. Для этого уберём комментарий со строки "//__enable_interrupt(); // Разрешить прерывания (Прерывание с более низким уровнем приоритета)". Перекомпилируем и запустим программу на выполнение. На осциллографе видим следующую картинку.

Avr int priority 2.jpg

Как видим картинка сильно изменилась. Провалы исчезли! Это и есть работа и основная задача реализации многоуровневой структуру прерываний. Надеюсь данная статья поможет вам применять её в своих проектах.

Внимание: При использовании команды SEI в п/п обработки прерываний не забывайте что: возникает опасность совместного использования ресурсов МК (память, регистры, SFR); Увеличивается глубина вложенности п/п и, соответственно, глубина стека

Автор: SasaVitebsk
E-Mail: sasa@c32dvina.com
Профиль на электрониксе: SasaVitebsk