Примеры программ с прерываниями

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

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

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

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

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

Прерывания в микроконтроллерах? Что это, и как с ними работать. Краткий ликбез.

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

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

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

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

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

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

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

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

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

Уроки Ардуино #16 — аппаратные прерывания

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

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

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

//сбрасываем все биты ISCxx MCUCR <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 |= (1INT0); //выставляем флаг глобального разрешения прерываний sei();

//разрешаем внешнее прерывание 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 ) = (1ISC01) //функция обработчик внешнего прерывания INT0 ISR( INT0_vect ) { int0_cnt++; } //настройка UART void uart_init( void ) ( 1 UCSZ1 ) //передача байта по 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 = %lurn», tmp ); //пауза 1 секунда _delay_ms( 1000 ); } return 0; }

Читайте также:
Программа упражнений на неделю в домашних условиях

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

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

Источник: mainloop.ru

Введение

Микроконтроллер общается с внешним миром посредством портов ввода/вывода. Порт представляет собой совокупность выводов микроконтроллера объединенных в группу. Каждый вывод порта можно независимо от других выводов конфигурировать на вход или на выход. Также многие выводы микроконтроллеров AVR имеют альтернативные функции.

Одна из таких функций – это прерывание по изменению состояния вывода, так называемое внешнее прерывание. Об этом прерывании мы сегодня и поговорим.

Внешние прерывания

Для примера будем рассматривать микроконтроллер ATmega16.

У него три внешних прерывания — INT0, INT1 и INT2. Эти прерывания жестко «привязаны» к выводам PD2, PD3 и PB2 и переназначить их на другие выводы нельзя.

Когда используются внешние прерывания, выводы PD2, PD3 и PB2 конфигурируются на вход. Однако если они настроены на выход, внешние прерывания тоже будут генерироваться при изменении их состояния, что позволяет реализовать программные прерывания.

Для разрешения или запрещения внешних прерываний предназначен управляющий регистр GICR (General Interrupt Control Register).

Установка битов INT1, INT0 или INT2 разрешает прерывания при возникновении события на соответствующем выводе микроконтроллера AVR, а сброс — запрещает.

Естественно нужно установить еще и флаг глобального разрешения прерываний — I, который расположен в регистре SREG. Без него вообще ни одно прерывание вызываться не будет.

Внешнее прерывание может происходить по одному из условий:
– по низкому уровню на выводах INT0, INT1,
– по любому изменению логического уровня на выводах INT0, INT1
– по спадающему фронту сигнала на выводах INT0, INT1, INT2,
– по нарастающему фронту на выводах INT0, INT1, INT2.
Небольшая иллюстрация, чтобы уяснить разницу между фронтом и уровнем сигнала.

Условия генерации прерываний устанавливаются с помощью конфигурационных регистров. Для INT0, INT1 – это регистр MCUCR (MCU Control Register). Для INT2 – MCUCSR (MCU Control and Status Register)

В таблице ниже приведены возможные значения разрядов ISC01, ISC00 и соответствующие им условия генерации внешнего прерывания.

Для прерывания INT1 таблица выглядит аналогично, только управляющие разряды другие — это ISC11 и ISC10.

Прерывание INT2 может происходить только по фронтам сигнала, поэтому для установки условий используется всего один бит — это бит ISC2 регистра MCUCSR.

Кстати, при смене значения бита ISC2 может быть сгенерировано прерывание INT2. Чтобы этого не происходило, нужно производить модификацию бита ISC2 так: запретить внешнее прерывание, поменять бит ISC2, сбросить флаг прерывания — INTF2 (смотри ниже) и опять разрешить прерывание INT2.

Обнаружение фронтов сигналов на выводах INT0/INT1 осуществляется синхронно, то есть по сигналу тактового генератора. Минимальная длительность входного импульса, гарантирующая генерацию прерывания, составляет один период тактового сигнала микроконтроллера AVR.

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

Обнаружение перепадов сигнала на выводе INT2 тоже осуществляется асинхронно. Минимальная длительность импульса, гарантирующая генерацию прерывания, составляет 50 нс.

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

Последний регистр, имеющий отношение к внешним прерываниям, — это статусный регистр GIFR (General Interrupt Flag Register). В нем содержатся флаги, устанавливаемые в случае формирования запроса на внешнее прерывание.

Флаги сбрасываются аппаратно, когда вызывается обработчики прерываний. Также их можно сбросить программно, записав в регистр единицы . Причем сброс нужно производить перезаписью регистра GIFR, на не операцией побитового ИЛИ.

Пример использования внешнего прерывания

Рассмотрим простой пример использования внешнего прерывания — опрос тактовой кнопки.

Источник: chipenable.ru

Примеры программ с прерываниями

Микроконтроллеры без использования прерываний практически ничего не стоят. Прерывание (interrupt) получило свое название из того факта, что при срабатывании прерывания по какому-нибудь событию нормальное выполнение программы прерывается, и выполняется особый код — код обработчика прерывания (interrupt handler). Прерывания можно рассматривать просто как подпрограммы, которые приостанавливают нормальный процесс выполнения программы до тех пор, пока код подпрограммы обработчика прерывания не будет завершен. После того, как обработчик прерывания завершит свою работу, ядро микроконтроллера вернется к исполнению основной программы точно в той точке, где она была прервана прерыванием. События прерывания обычно происходят от каких-то внешних асинхронных воздействий на микроконтроллер — например, переключение логического уровня на внешнем выводе порта, либо переполнение регистра таймера, что означает истечение заданного интервала времени, и т. д.

Читайте также:
Какой программой открыть файл sam

Почему же прерывания так важны? Например, мы могли бы обойтись без прерываний, в бесконечном цикле опрашивая возникновение интересующих нас событий. Такое функционирование программы называется опросом (polling). Но опрос имеет много недостатков и неудобств — к примеру, программа будет тратить на циклы ресурс ядра, который мог быть потрачен на выполнение других действий.

Это одна из причин, почему у микроконтроллера есть много источников прерывания, которые могут использоваться при необходимости. Вместо проверки событий на возникновение микроконтроллер имеет возможность прервать нормальный поток программы при возникновении события и переключиться на действия обработчика события прерывания (ISR, interrupt service routine), затем вернуться обратно и продолжить выполнение основной программы.

[Векторы прерывания]

Векторами прерывания называют адреса перехода на обработчик прерывания. Список таких адресов называется таблицей векторов прерываний, и он находится в памяти программ по заранее известному адресу. У микроконтроллеров AVR таблица векторов прерываний находится в самом начале памяти программ FLASH по адресу 0. Содержимое таблицы векторов прерываний определяет программист, когда ему нужно реализовать обработку прерываний.

Каждый вектор прерывания AVR занимает в памяти 2 байта (1 слово кода инструкций AVR), и представляет из себя команду rjmp относительный_адрес. Вот так, например, выглядит на языке ассемблера полностью заполненная таблица прерываний микроконтроллера ATmega16:

$000 rjmp RESET ;сброс (начало программы)
$001 rjmp INT0_vect ;внешнее прерывание 0

$002 rjmp INT1_vect ;внешнее прерывание 1
$003 rjmp TIMER2_COMP_vect ;прерывание совпадения сравнения таймера 2

$004 rjmp TIMER2_OVF_vect ;прерывание переполнения таймера 2
$005 rjmp TIMER1_CAPT_vect ;прерывание события захвата таймера 1

$006 rjmp TIMER1_COMPA_vect ;прерывание совпадения сравнения A таймера 1
$007 rjmp TIMER1_COMPB_vect ;прерывание совпадения сравнения B таймера 1

$008 rjmp TIMER1_OVF_vect ;прерывание переполнения таймера 1
$009 rjmp TIMER0_OVF_vect ;прерывание переполнения таймера 0

$00A rjmp SPI_STC_vect ;прерывание завершения передачи SPI
$00B rjmp USART_RXC_vect ;прерывание завершения приема байта UART

$00C rjmp USART_UDRE_vect ;прерывание опустошения регистра передачи UART
$00D rjmp USART_TXC_vect ;прерывание завершения передачи байта UART

$00E rjmp ADC_vect ;прерывание завершения преобразования АЦП
$00F rjmp EE_RDY_vect ;прерывание готовности EEPROM

$010 rjmp ANA_COMP_vect ;прерывание изменения сигнала на выходе компаратора
$011 rjmp TWI_vect ;прерывание двухпроводного интерфейса TWI (I2C)

$012 rjmp INT2_vect ;внешнее прерывание 2
$013 rjmp TIMER0_COMP_vect ;прерывание совпадения сравнения таймера 0

$014 rjmp SPM_RDY_vect ;прерывание готовности к записи памяти программ

Такая полностью заполненная векторами таблица никогда не применяется. На практике обычно используется только 1..4 прерывания, в этом случае не используемые адреса векторов могут остаться не инициализированными. Обратите внимание, что в левом столбце метками обозначены шестнадцатеричные адреса инструкций, соответствующие байтовые адреса будут в 2 раза больше (потому что инструкция rjmp занимает 2 байта).

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

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

Это самый простой вариант. В данном примере используется следующий алгоритм — основная программа настраивает таймер 1 и запускает обработчик прерывания. Этот обработчик срабатывает раз в секунду и заботится о себе сам, подстраивая величину счетчика TCNT1 (чтобы прерывания происходили точно раз в секунду).

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

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

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

#include .. void SetupTIMER1 (void) //На частоте тактов 16 МГц прерывание переполнения T/C1 // произойдет через (счет до 65535): // 1 // 1 // (1 // 1 TCCR1B = (1 ); TCNT1 = 65536-62439; //коррекция счетчика, чтобы время было ровно 1 секунда /* Разрешение прерывания overflow таймера 1. */ TIMSK = (1 );>

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

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

Читайте также:
Программа турбо бухгалтер это

//Желательно, чтобы этот код был короче. if (timer) timer—;>

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

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

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

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

2. Готовим файл с нашим кодом обработчика на языке ассемблера. Вот пример кода:

#include /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. В нужном месте разрешаем прерывания программы. Это делается также однократно, после того как сделаны все приготовления:

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

..void main (void) .. while (1) .. if (0==(TIMSK TIMSK = (1 ); //далее действия, которые будут происходить // раз в секунду .. > .. >>

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

Можно заметить, что в обоих примерах использовалась именованная константа TIMER1_OVF_vect, которая задает адрес вектора прерывания таймера 1. Имена констант можно узнать во включаемом файле процессора. Для ATmega16, например, это будет файл c:WinAVR-20080610avrincludeavriom16.h. Чтобы имена стали доступны, не нужно добавлять именно этот файл в проект директивой #include, достаточно добавить #include и задать макроопределение, задающее тип процессора (например, 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.

[Ссылки]

Источник: microsin.net

Рейтинг
( Пока оценок нет )
Загрузка ...
EFT-Soft.ru