Суббота, 20.04.2024, 05:49
Приветствую Вас Гость | RSS
Главная | Каталог статей | Регистрация | Вход
Меню сайта
Реклама Google
Форма входа
Категории раздела
Это нужно знать! [17]
Изучаем AVR [30]
Программаторы [12]
Необходимое ПО [8]
Готовые устройства [73]
Справочная [38]
Инструмент [0]
Технология [8]
Литература [0]
Arduino скетчи [18]
Поиск
Статистика

Онлайн всего: 1
Гостей: 1
Пользователей: 0
Микроконтроллеры - это просто!
Главная » Статьи » Изучаем AVR

Прерывания
В состав AVR микроконтроллеров входит большое число периферийных устройств (ADC, Timer/Counters,  EXTI, Analog Comparator, EEPROM, USART, SPI, I2C и т.д.), каждое из которых может выполнять определенные действия над данными/сигналами и пр. информацией. Эти устройства встроены в микроконтроллер для повышения эффективности приложения и снижения затрат при разработке всевозможных устройств на базе AVR микроконтроллеров.

Процессор общается/управляет периферийными устройствами посредством регистров ввода/вывода (I/O Registers), которые располагаются в памяти данных (Data Memory), что позволяет использовать их как обычные переменные. У каждого устройства имеются свои регистры ввода/вывода.

Все регистры ввода/вывода (I/O Registers) можно поделить на три группы : регистры данных, регистры управления и регистры состояния.

При помощи регистров управления (Control Registers) реализуется настройка устройства для работы в том или ином режиме, с определенной частотой, точностью и т.д., а при помощи регистров данных (Data Registers) считывается результат работы данного устройства (аналого-цифровое преобразование, принятые данные, значение таймера/счетчика и т.д.). Казалось бы, ничего сложного здесь нет (вообще-то здесь и вправду ничего сложного нет :) ), включил устройство, указал желаемый режим работы а потом только остается стричь купоны читать готовенькие данные и использовать их в вычислениях. Весь вопрос заключается в том "когда” читать эти самые данные (завершило устройство работу или все еще обрабатывает данные), ведь все периферийные устройства  работают параллельно с ядром микроконтроллера, да еще и на разных частотах. Встает вопрос реализации общения и синхронизации между процессором и периферийным устройством.

Как вы уже наверное догадались, для реализации общения и синхронизации между устройством и процессором используются "регистры состояния” (Status Registers), в которых хранится текущее состояние работы того или иного устройства. Каждому состоянию, в котором может находиться устройство, соответствует "бит в регистре состояния” (флаг), текущее значение которого, "говорит” о текущем состоянии данного устройства или его отдельно взятой функции (работа завершена/не завершена, ошибка при обработке данных, регистр пуст и т.д.).

Механизм общения, между процессором и периферийным устройством, реализуется путем опрашивания флагов (flag polling), отвечающих за ту или иную функцию данного устройства. В зависимости от значения того или иного флага (состояние устройства), можно менять ход исполнения программы (ветвление). К примеру :

Проверка если определенный флаг установлен (произошло некое событие) :

if(RegX & (1<<Flag))// если флаг в регистре RegX установлен
{
// делай что-то
}

Ожидание завершения какого либо действия (событие) :

while( !(RegX & (1<<Flag)) ); // пока флаг не установлен


Опрашивание флагов – занятие довольно ресурсоемкое, как в плане размера программы, так и в плане быстродействия программы. Поскольку общее число флагов в AVR микроконтроллерах довольно велико (преимущество), то реализация общения, между процессором и устройством, путем опроса флагов приводит к снижению КПД (быстродействие кода/размер кода) написанной вами программы, к тому же программа становится очень запутанной, что способствует появлению ошибок, которые трудно обнаружить даже при детальной отладке кода.

Для того чтобы повысить КПД программ для AVR микроконтроллеров, а также облегчить процесс создания и отладки данных программ, разработчики снабдили все периферийные устройства "источниками прерываний” (Interrupt sources), у некоторых устройств может быть несколько источников прерывания.

При помощи источников прерываний реализуется механизм синхронизации, между процессором и периферийным устройством, то есть процессор начнет прием данных, опрос флагов и др. действия над периферийным устройством только тогда, когда устройство будет к этому готово (сообщит о завершении обработке данных, ошибке при обработке данных, регистр пуст, и т.д.), путем генерации "запроса на обработку прерывания” (Interrupt request), в зависимости от значения некоторого флага (состояние устройства / функции / события).

В литературе, очень часто, всю цепочку событий, начиная от "запроса на обработку прерывания” (IRQ) и до "процедуры обработки прерывания” (ISR), сокращенно называют – прерывание (Interrupt).

Что такое прерывание?

Прерывание (Interrupt) – сигнал, сообщающий процессору о наступлении какого-либо события. При этом выполнение текущей последовательности команд приостанавливается и управление передаётся процедуре обработки прерывания, соответствующая данному событию, после чего исполнение кода продолжается ровно с того места где он был прерван (возвращение управления). (Wiki)

Процедура обработки прерывания (Interrupt Service Routine) – это ни что иное как функция/подпрограмма, которую следует выполнить при возникновении определенного события. Будем использовать именно слово "процедура”, для того чтобы подчеркнуть ее отличие от всех остальных функций.

Главное отличие процедуры от простых функций состоит в том что вместо обычного "возврата из функции” (ассемблерная команда RET), следует использовать "возврат из прерывания” (ассемблерная команда RETI) – "RETurn from Interrupt".

Свойства AVR прерываний :

  • У каждого периферийного устройства, что входит в состав AVR микроконтроллеров, есть как минимум один источник прерывания (Interrupt source). Ко всем этим прерываниям следует причислить и прерывание сброса – Reset Interrupt, предназначение которого отличается от всех остальных.
  • За каждым прерыванием, строго закреплен вектор (ссылка) указывающий на процедуру обработки прерывания (Interrupt service routine). Все векторы прерываний, располагаются в самом начале памяти программ и вместе формируют "таблицу векторов прерываний” (Interrupt vectors table).
  • Каждому прерыванию соответствует определенный "бит активации прерывания” (Interrupt Enable bit). Таким образом, чтобы использовать определенное прерывание, следует записать в его "бит активации прерывания” – лог. единицу. Далее, независимо от того активировали Вы или нет определенные прерывания, микроконтроллер не начнет обработку этих прерываний, пока в "бит всеобщего разрешения прерываний” (Global Interrupt Enable bit в регистре состояния SREG) не будет записана лог. единица. Также, чтобы запретить все прерывания (на неопределенное время), в бит всеобщего разрешения прерываний следует записать – лог. нуль.
Прерывание Reset, в отличие от всех остальных, нельзя запретить. Такие прерывания еще называют Non-maskable interrupts.



  • У каждого прерывания есть строго определенный приоритет. Приоритет прерывания зависит от его расположения в "таблице векторов прерываний”. Чем меньше номер вектора в таблице, тем выше приоритет прерывания. То есть, самый высокий приоритет имеет прерывание сброса (Reset interrupt), которое располагается первой в таблице, а соответственно и в памяти программ. Внешнее прерывание INT0, идущее следом за прерыванием Reset в "таблице векторов прерываний”, имеет приоритет меньше чем у Reset, но выше чем у всех остальных прерываний и т.д.
Таблица векторов прерываний, кроме вектора Reset, может быть перемещена в начало Boot раздела Flash памяти, установив бит IVSEL в регистре GICR. Вектор сброса также может быть перемещен в начало Boot раздела Flash памяти, путем программирования фьюз бита – BOOTRST.


Рис.1 Таблица векторов прерываний ATmega16

Прототип процедуры обработки прерывания

Чтобы объявить некоторую функцию в качестве процедуры обработки того или иного прерывания, необходимо следовать определенным правилам прототипирования, чтобы компилятор/компоновщик смогли правильно определить и связать нужное вам прерывание с процедурой ее обработки.

Во первых процедура обработки прерывания не может ничего принимать в качестве аргумента (void), а также не может ничего возвращать (void). Это связано с тем что все прерывания в AVR асинхронные, поэтому не известно в каком месте будет прервано исполнение программы, у кого принимать и кому возвращать значение, а также для минимизации времени входа и выхода из прерывания.

void isr(void)

Во вторых, перед прототипом функции следует указать что она является процедурой обработки прерывания. Как вам известно, в языке Си исполняется только тот код что используется в функции main. Поскольку процедура обработки прерывания в функции main нигде не используется, то для того чтобы компилятор не "выкинул” ее за ненадобностью, перед прототипом процедуры следует указать что эта функция является процедурой обработки прерывания.

Прототип процедуры обработки прерывания в среде AVR Studio

#include <avr/interrupt.h>

ISR(XXX_vect)
{
// Тело обработчика прерывания
}

В AVR Studio (AVR GCC), каждая процедура обработки прерывания начинается с макроопределения ISR, после чего, в круглых скобках следует конструкция :

XXX_vect

где "XXX” это имя вектора прерывания. Все имена векторов, для определенного AVR микроконтроллера, можно найти в "таблице векторов прерываний” даташита данного микроконтроллера или в его заголовочном файле. К примеру, "таблица векторов прерываний” для микроконтроллера ATmega16 приведена на рис.1, где в колонке Source, приведены все имена векторов прерываний. Также имена можно посмотреть в заголовочном файле данного микроконтроллера (C:\Program Files\Atmel\AVR Tools\AVR Toolchain\avr\include\avr\iom16.h), см. рис.2. Все что нам надо сделать, это найти в таблице имя нужного нам вектора и к нему прибавить суффикс "_vect".

Далее, в фигурных скобках, пишем "тело” процедуры обработки данного прерывания.


Рис.2 Заголовочный файл ATmega16 для AVR Studio

Для примера, напишем процедуру обработки прерывания по приему байта через USART (USART, Rx Complete) :

ISR(USART_RXC_vect)
{
// Тело обработчика прерывания
}

Кстати: перед тем как использовать любое прерывание в AVR Studio, следует включить в проект заголовочные файлы io.h и interrupt.h :

#include <avr/io.h>
#include <avr/interrupt.h>

Более подробно об обработчиках прерываний в AVR Studio (AVR GCC) можно почитать в разделе Introduction to avr-libc’s interrupt handling.

Прототип процедуры обработки прерывания в среде ImageCraft

#pragma interrupt_handler <handler_name> : iv_XXX
void <handler_name>(void)
{
// Тело обработчика прерывания
}

В среде ImageCraft, прототип процедуры обработки прерывания выглядит следующим образом :

void <handler_name>(void)

где <handler_name>, это любое имя которое вы захотите дать данному обработчику прерывания. Одно из требований к объявлению процедур обработки прерываний гласит, что перед прототипом функции следует указать что она является обработчиком прерывания. Это делается при помощи pragma-директивы interrupt_handler :

#pragma interrupt_handler <handler_name> : iv_XXX

где <handler_name> это имя той функции что будет использоваться в качестве обработчика прерывания, а конструкция "iv_XXX”, это имя вектора прерывания (XXX) с префиксом "iv_". Как и в случае с AVR Studio, все имена векторов, для определенного AVR микроконтроллера, можно найти в "таблице векторов прерываний” даташита данного микроконтроллера или в его заголовочном файле (см. рис.3).


Рис.3 Заголовочный файл ATmega16 для ImageCraft IDE

К примеру процедура обработки прерывания по приему байта через USART (USART, Rx Complete) в среде ImageCraft, будет выглядит так :

#pragma interrupt_handler usart_rxc_isr : iv_USART_RXC
void usart_rxc_isr(void)
{
// Тело обработчика прерывания
}

Более подробно о процедурах обработки прерывания в ImageCraft IDE можно найти в меню Help->Programming the AVR->Interrupt Handlers среды разработки.

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

В среде AVR Studio это выглядит так :

ISR(INT0_vect)
{
// Do something
}
ISR(INT1_vect, ISR_ALIASOF(INT0_vect));

Сначала идет процедура обработки прерывания для определенного вектора, в данном случае INT0. Все остальные процедуры могут ссылаться на любой обработчик прерывания при помощи конструкции :

ISR(YYY_vect, ISR_ALIASOF(XXX_vect));

где YYY это имя вектора прерывания который ссылается на ранее объявленный обработчик прерывания для вектора XXX.

В среде ImageCraft это выглядит так :

#pragma interrupt_handler <handler_name> : iv_XXX <handler_name> : iv_YYY
void <handler_name>(void)
{
// Тело обработчика прерывания
}

или так

#pragma interrupt_handler <handler_name> : iv_XXX
#pragma interrupt_handler <handler_name> : iv_YYY
void <handler_name>(void)
{
// Тело обработчика прерывания
}

где векторы XXX и YYY ссылаются на один и тот же обработчик прерывания <handler_name>.

Как работает прерывание в AVR микроконтроллерах?

1. Предположим произошел "запрос на обработку прерывания” (IRQ).

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

2. Проверка.

Если бит активации данного прерывания установлен (Interrupt enable bit), а также I-бит (бит всеобщего разрешения прерываний) регистра состояния процессора (SREG) установлен, то процессор начинает подготовку процедуры обработки прерывания, при этом бит всеобщего разрешения прерываний (I-бит регистра SREG) сбрасывается, запрещая таким образом все остальные прерывания. Это происходит для того чтобы никакое другое событие не смогло прервать обработку текущего прерывания.

Кстати : если в процедуре обработки прерывания установить I-бит в состояние лог. единицы, то любое активированное прерывание может в свою очередь прервать обработку текущего прерывания. Такие прерывания называются вложенные (Nested interrupts).

3. Подготовка.

Процессор завершает выполнение текущей ассемблерной команды, после чего помещает адрес следующей команды в стек (PC->STACK). Далее процессор проверяет какой источник прерывания подал "запрос на обработку прерывания” (IRQ), после чего воспользовавшись вектором данного источника (ссылка) из таблицы векторов (который железно закреплен за каждым источником прерывания), переходит в процедуру обработки прерывания (инструкция JMP). На все, про все процессор тратит минимум 4 такта! (в зависимости от момента появления запроса и длительность исполнения текущей инструкции). Это очень хорошее время реакции на IRQ, по сравнению с микроконтроллерами других производителей.

Кстати : если IRQ произойдет, когда микроконтроллер находится в спящем режиме (sleep mode), время реакции на IRQ увеличивается еще на четыре такта, плюс время заложенное в фьюз битах SUT1 и SUT0 (Start-Up Time).

4. Выполнение тела ISR.

Кстати : большинство Си-компиляторов, перед началом и завершением выполнения тела процедуры обработки прерывания, вставляют дополнительные "подпрограммы сохранения/воcстановления регистров” общего назначения (РОН) и регистра состояния (SREG). Так что программисту нет необходимости самостоятельно сохранять восcтанавливать РОН во время прерываний. Продвинутые Си-компиляторы могут высчитать сколько РОН понадобятся данному обработчику прерывания и вставить подпрограммы сохранения/воcстановления только нужного количества РОН.

5. Возврат управления

После того как выполнение процедуры обработки прерывания завершено, процессор извлекает адрес возврата из прерывания, который он сохранил в стеке (STACK->PC), прибавляет к указателю стека значение 2, что соответствует уменьшению стека на два байта, которые раньше занимал адрес возврата из прерывания. Далее процессор устанавливает I-бит (бит всеобщего разрешения прерываний) регистра состояния процессора (SREG). На все, про все процессор тратит ровно 4 такта! (здесь ему никто помешать не может). После этого выполнение программы продолжается ровно с того места где она была прервана.

Советы по созданию ISR

1. Старайтесь как можно быстрее выйти из процедуры обработки прерывания, от этого зависит отзывчивость вашего приложения. То есть если вы относительно долго будите находится в ISR, а в этот момент будут появляться новые запросы на обработку прерывания, то начнется накопление, а затем и потеря новых событий. Как вы сами понимаете такое поведение желательно избежать.

2. Вычисления в прерывания должны быть как можно проще, от этого зависит число сохраняемых / восстановляемых РОН.

Прерывание Reset

5 источников прерывания :

  • Сброс при включении питания (Power-on Reset). Микроконтроллер находится в состоянии сброса, пока напряжение питания находится ниже порогового значения VPOT.
  • Внешний сброс (External Reset). Микроконтроллер находится в состоянии сброса, пока на вывод RESET подан низкий уровень.
  • Сброс сторожевого таймера (Watchdog Reset). Микроконтроллер сбрасывается через некоторый период времени, заданный в сторожевом таймере и когда сторожевой таймер включен.
  • Сброс при выходе за предел (Brown-Out Reset). Микроконтроллер сбрасывается, когда напряжение питания VCC опускается ниже порогового значения VBOT и когда Brown-Out детектор включен.
  • JTAG сброс. Микроконтроллер находится в состоянии сброса, пока в однобитном регистре Reset Register находится значение лог. единица.
Пример использования прерывания

В данном примере микроконтроллер, считывает значения аналогового сигнала на входе АЦП и посылает обработанные данные через USART, внешнему устройству.

// AVR Studio v4.19

#include <avr/io.h>
#include <avr/interrupt.h>

/* макроопределение, для работы с битами */
#define BIT(n)        (1<<(n))
#define ENABLE(x,n)   ((x) |= BIT(n))
#define CHECKBIT(x,n) ((x) & BIT(n))

void port_init(void)
{
 PORTA = 0x00;
 DDRA = 0x00; // порт А делаем входным
 PORTB = 0x00;
 DDRB = 0x00;
 PORTC = 0x00; // m103 output only
 DDRC = 0x00;
 PORTD = 0x00;
 DDRD = 0x00;
}

// USART initialize
// desired baud rate: 9600
// actual: baud rate: 9615 (0,2%)
// char size: 8 bit
// parity: Disabled
void usart_init(unsigned baudrate)
{ // инициализация USART модуля
 UCSRB = 0x00; //disable while setting baud rate
 UCSRA = 0x00;
 UCSRC = BIT(URSEL) | 0x06;
 UBRRL = baudrate;
 UCSRB = 0x08;
}

// ADC initialize
// Conversion time: 104uS
void adc_init(void)
{ // инициализация АЦП модуля
 ADCSRA = 0x00; // disable adc
 ADMUX = 0x00|(1<<ADLAR); /* будем использовать первый канал АЦП,
в качестве референса потенциал поданный на вывод AREF,
8-бит разрядность АЦП и равнение на лево :) */
 ACSR = 0x80; // выключаем аналоговый компаратор, для экономии
 ADCSRA = 0xCD; /* включаем АЦП и запускаем одиночное преобразование,
включаем прерывание по окончанию преобразования,
устанавливаем частоту преобразования */
}

ISR(ADC_vect)
{
 while( !CHECKBIT(UCSRA,UDRE) ); // ждем пока освободится буфер
 UDR = ADCH; // отсылаем данные
 ENABLE(ADCSRA, ADSC); // запускаем новое преобразование
}

//call this routine to initialize all peripherals
void init_devices(void)
{
 cli(); // на время инициализации периферии, запрещаем все прерывания
 port_init();
 usart_init(25);
 adc_init();
 MCUCR = 0x00;
 GICR = 0x00;
 TIMSK = 0x00; // timer interrupt sources
 sei(); // разрешаем обратно все прерывания
}

int main(void)
{
 init_devices(); // можно переименовать в system_setup(), звучит круче :)
 while(1) // создаем бесконечный цикл
 {
 }
}

Как видно из этого примера, в функции main нет никакого кода кроме инициализации. Такое программирование еще называют interrupt driven programming, то есть все действия происходят исключительно в процедурах обработки прерываний, в нашем случае в обработчике прерывания АЦП преобразователя.

ISR(ADC_vect)
{
 while( !CHECKBIT(UCSRA,UDRE) ); // ждем пока освободится буфер
 UDR = ADCH; // отсылаем данные
 ENABLE(ADCSRA, ADSC); // запускаем новое преобразование
}

Прерывание ADC происходит по завершению аналого – цифрового преобразования. Вы скажете : "эй, да у тебя в обработчике прерывания есть цикл ожидания, а нам тут паришь мозги про то что не надо на долго задерживаться в прерываниях, к тому же флаг UDRE может генерить IRQ”.

while( !CHECKBIT(UCSRA,UDRE) ); // ждем пока освободится буфе


Во первых здесь нет никакого ожидания, поскольку в данном случае, передача данных через USART чуть – чуть быстрее чем А/Ц преобразование, а эта строчка всего лишь дополнительная проверка (перестраховка). Во вторых, если бы мы использовали прерывание UDRE, то тогда процессор переходил бы в обработчик прерывания даже когда А/Ц данные еще не готовы (нечего передавать). В третьих это единственное прерывание, поэтому потерь у нас быть не может, поскольку новое А/Ц преобразование начнется только по завершению обработчика старого.

ENABLE(ADCSRA, ADSC); // запускаем новое преобразование

В функции init_devices(), мы использовали два макроопределения cli() и sei(), которые предназначены для запрета всех прерываний – cli() (clear I-bit in SREG) и разрешения всех прерываний – sei() (set I-bit in SREG).

Итог

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

PS .. пока о них не знаешь – живешь счастливо и беззаботно, после того как о них узнал – жизнь осложняется, но без них уже не можешь жить.
Категория: Изучаем AVR | Добавил: Alex (29.12.2013)
Просмотров: 9977 | Рейтинг: 5.0/1
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
Copyright MyCorp © 2024