В состав 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< Опрашивание флагов – занятие довольно ресурсоемкое, как в плане размера программы, так и в плане быстродействия программы. Поскольку общее число флагов в AVR микроконтроллерах довольно велико (преимущество), то реализация общения, между процессором и устройством, путем опроса флагов приводит к снижению КПД (быстродействие кода/размер кода) написанной вами программы, к тому же программа становится очень запутанной, что способствует появлению ошибок, которые трудно обнаружить даже при детальной отладке кода.
Для того чтобы повысить КПД программ для AVR микроконтроллеров, а также облегчить процесс создания и отладки данных программ, разработчики снабдили все периферийные устройства "источниками прерываний” (Interrupt sources
), у некоторых устройств может быть несколько источников прерывания.
При помощи источников прерываний реализуется механизм синхронизации
, между процессором и периферийным устройством, то есть процессор начнет прием данных, опрос флагов и др. действия над периферийным устройством только тогда, когда устройство будет к этому готово (сообщит о завершении обработке данных, ошибке при обработке данных, регистр пуст, и т.д.), путем генерации "запроса на обработку прерывания” (Interrupt request
), в зависимости от значения некоторого флага (состояние устройства / функции / события).
В литературе, очень часто, всю цепочку событий, начиная от "запроса на обработку прерывания” (IRQ) и до "процедуры обработки прерывания” (ISR), сокращенно называют – прерывание (Interrupt
).
Что такое прерывание?
Прерывание (Interrupt) – сигнал, сообщающий процессору о наступлении какого-либо события. При этом выполнение текущей последовательности команд приостанавливается и управление передаётся процедуре обработки прерывания, соответствующая данному событию, после чего исполнение кода продолжается ровно с того места где он был прерван (возвращение управления). (Wiki)
Процедура обработки прерывания
(Interrupt Service Routine) – это ни что иное как функция/подпрограмма, которую следует выполнить при возникновении определенного события. Будем использовать именно слово "процедура”, для того чтобы подчеркнуть ее отличие от всех остальных функций.
Главное отличие процедуры от простых функций состоит в том что вместо обычного "возврата из функции” (ассемблерная команда RET), следует использовать "возврат из прерывания” (ассемблерная команда RETI) – "RETurn from Interrupt
".
Свойства AVR прерываний:
Прерывание Reset, в отличие от всех остальных, нельзя запретить. Такие прерывания еще называют Non-maskable interrupts.
Таблица векторов прерываний, кроме вектора Reset, может быть перемещена в начало Boot раздела Flash памяти, установив бит IVSEL в регистре GICR. Вектор сброса также может быть перемещен в начало Boot раздела Flash памяти, путем программирования фьюз бита – BOOTRST.
Рис.1 Таблица векторов прерываний ATmega16
Прототип процедуры обработки прерывания
Чтобы объявить некоторую функцию в качестве процедуры обработки того или иного прерывания, необходимо следовать определенным правилам прототипирования, чтобы компилятор/компоновщик смогли правильно определить и связать нужное вам прерывание с процедурой ее обработки.
Во первых процедура обработки прерывания не может ничего принимать в качестве аргумента (void), а также не может ничего возвращать (void). Это связано с тем что все прерывания в AVR асинхронные, поэтому не известно в каком месте будет прервано исполнение программы, у кого принимать и кому возвращать значение, а также для минимизации времени входа и выхода из прерывания.
void
isr(void
)
Во вторых, перед прототипом функции следует указать что она является процедурой обработки прерывания. Как вам известно, в языке Си исполняется только тот код что используется в функции main. Поскольку процедура обработки прерывания в функции main нигде не используется, то для того чтобы компилятор не "выкинул” ее за ненадобностью, перед прототипом процедуры следует указать что эта функция является процедурой обработки прерывания.
Прототип процедуры обработки прерывания в среде AVR Studio
#include 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
#include
Более подробно об обработчиках прерываний в AVR Studio (AVR GCC) можно почитать в разделе Introduction to avr-libc’s interrupt handling.
Прототип процедуры обработки прерывания в среде ImageCraft
#pragma interrupt_handler
void
<
handler_name>
(void
)
{
// Тело обработчика прерывания
}
В среде ImageCraft, прототип процедуры обработки прерывания выглядит следующим образом:
void < handler_name> (void )
где
#pragma interrupt_handler
где
Рис.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
void
<
handler_name>
(void
)
{
// Тело обработчика прерывания
}
#pragma interrupt_handler
#pragma interrupt_handler
void
<
handler_name>
(void
)
{
// Тело обработчика прерывания
}
где векторы XXX и YYY ссылаются на один и тот же обработчик прерывания
Как работает прерывание в 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).
Для чего нужны внешние прерывания
Прерывание — это событие по которому прерывается исполнение основного кода программы (например функции 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
Для внешнего прерывания INT1 , настройка производиться так же, только используются биты ISC11 ISC10 .
Пример настройки внешнего прерывания для avr atmega8:
//сбрасываем все биты ISCxx MCUCR & amp;= ~( (1 & lt;& lt; ISC11) | (1 & lt;& lt; ISC10) | (1 & lt;& lt; ISC01) | (1 & lt;& lt; ISC00) ) MCUCR |= (1 & lt;& lt; ISC01) | (1 & lt;& lt; ISC00) ; |
//сбрасываем все биты ISCxx
MCUCR &= ~((1< Для того что бы внешние прерывания заработали их надо разрешить, установив в 1 соответствующие биты в регистре GICR
. Бит INT0
отвечает за разрешение/запрещение внешнего прерывания INT0
, а бит INT1
, соответственно за внешне прерывание INT1
. Так же необходимо что бы был выставлен флаг глобального разрешения прерываний. Пример кода разрешающего внешнее прерывание INT0 для avr atmega8:
//разрешаем внешнее прерывание INT0
GICR |= (1< В качестве примера приведу программу счетчика импульсов. Программа подсчитывает количество импульсов на входе INT0, и раз в секунду выводит результат подсчета в uart. #include Стек представляет собой область памяти, которую ЦПУ использует для сохранения и восстановления адресов возврата из подпрограмм. Когда микропроцессор встречает одну из инструкций вызовов rcall/call/ecall/icall/eicall, то адрес следующего за ними слова в памяти программ аппаратно копируется в стек. В момент выхода из подпрограммы по команде ret адрес возврата восстанавливается из стека в программный счетчик. В моделях с объемом памяти программ 128 и 256 к/слов для сохранения PC в стеке потребуется 3 байта, для всех остальных – 2 байта. При сохранении каждого байта содержимое SP уменьшается на единицу, а при восстановлении, соответственно увеличивается. Рис.9 Расположение стека в памяти данных Программист должен самостоятельно определить местоположение стека в самом начале программы. С точки зрения максимальной его глубины, вершину стека нужно поместить в самом конце SRAM, как это показано на рис.9: Include "m8def.inc"
ldi temp,low(RAMEND) ;устанавливаем SP = RAMEND
out SPL,temp ;для ATmega8 SP = 0x045F
ldi temp,high(RAMEND)
out SPH,temp
Константа RAMEND из стандартного заголовочного файла m8def.inc имеет значение адреса последней ячейки SRAM. В диапазоне адресов SRAM между РВВ и текущим положением SP размещаются переменные прикладной программы. Поэтому очень важно предварительно оценить максимальный размер стека (глубину стека). Может случиться так, что вершина стека поднимется слишком высоко и начнет “затирать” пользовательские данные, а это одна из самых сложно-выявляемых ошибок! Стек AVR, помимо сохранения адресов возврата, имеет еще одно очень важное предназначение. Он позволяет сохранять любые данные специально предназначенными для этого командами push Rr (загрузка в стек) и pop Rd (выгрузка из стека). Каждый раз при выполнении push Rr содержимое Rr копируется в стек, после чего SP уменьшается на единицу. При выполнении pop Rr содержимое ячейки стека, на которую указывает SP, восстанавливается в Rr, а само значение SP инкрементируется. Стек подобного рода имеет организацию Last In First Out (Последний Вошел Первый Вышел): регистр, сохраненный последней командой push, будет восстановлен первой командой pop:
; SP Уровень стека после команды
push R16 ;сохраняем R16 0x045F R16 ? ?
push R17 ;сохраняем R17 0x045E R16 R17 ?
push R18 ;сохраняем R18 0x045D R16 R17 R18
̣̣̣̣̣̣̣̣
pop R18 ;восстанавливаем R18 0x045D R16 R17 ?
pop R17 ;восстанавливаем R17 0x045E R16 ? ?
pop R16 ;восстанавливаем R16 0x045F ? ? ?
Через стек очень просто можно обменять содержимое регистров местами:
; Обмен R16 <-> R17 SP Уровень стека после команды
push R16 ;сохраняем R16 0x045F R16 ?
push R17 ;сохраняем R17 0x045E R16 R17
pop R16 ;восстанавливаем R16 0x045E R16 ?
pop R17 ;восстанавливаем R17 0x045F ? ?
На рис.10 приведен небольшой фрагмент кода, в котором пошагово рассмотрен процесс изменения стека при входе и выходе из подпрограммы toggle и сохранении и восстановлении регистра R17. Это типичный пример, где могут понадобиться инструкции push/pop. Подпрограмма toggle использует РОН R17 в своих нуждах, но этот- же регистр может использоваться и в ходе основной программы. Поэтому, во избежание повреждения данных, R17 перед модификацией загружается в стек и восстанавливается из него перед командой ret. Одним из преимуществ микроконтроллера ATmega8 является широкий диапазон различных прерываний. Прерывание
представляет собой событие, при наступлении которого выполнение основной программы приостанавливается и вызывается функция, обрабатывающая прерывание определённого типа. Прерывания делятся на внутренние и внешние. К источникам внутренних прерываний относятся встроенные модули микроконтроллера (таймеры, приёмопередатчик USART и т.д). Внешние прерывания возникают при поступлении внешних сигналов на выводы микроконтроллера (например сигналы на выводы RESET и INT). Характер сигналов, приводящих к возникновению прерывания задаётся в регистре управления MCUCR
, в частности в разрядах - ISC00 (бит 0) и ISC01 (бит 1) для входа INT 0; ISC10 (бит2) и ISC11 (бит3) для входа INT1. В микроконтроллере ATmega8 каждому прерыванию соответствует свой вектор прерывания
(адрес в начале области памяти программ, в которой хранится команда для перехода к заданной подпрограмме обработки прерывания). В mega8 все прерывания имеют одинаковый приоритет. В случае одновременного возникновения нескольких прерываний первым будет обрабатываться прерывание с меньшим номером вектора. Векторы прерываний в Atmega8
За управление прерываниями в ATmega8 отвечают 4 регистра: GIMSK
(он же GICR) - запрет/разрешение прерываний по сигналам на входах INT0, INT1 GIFR
- управление всеми внешними прерываниями TIMSK
, TIFR
- управление прерываниями от таймеров/счётчиков Регистр GIMSK(GICR) INTFx=1: произошло прерывание на входе INTx. При входе в подпрограмму обработки прерывания INTFx автоматически сбрасывается в сотояние лог. 0 Регистр TIMSK TOIE1=1
: прерывание по переполнению T/C1 разрешено OCIE1A=1
: прерывание при совпадении регистра сравнения A с содержимым счётчика T/C1 разрешено OCIE1B=1
: прерывание при совпадении регистра сравнения B с содержимым счётчика T/C1 разрешено TICIE=1
: разрешено прерывание при выполнении условия захвата TOIE0=1
: прерывание по переполнению T/C0 разрешено Регистр TIFR TOV1=1
: произошло переполнение T/C1 OCF1A=1
: произошло совпадение регистра сравнения A с содержимым счётчика T/C1 разрешено OCF1B=1
: произошло совпадение регистра сравнения B с содержимым счётчика T/C1 разрешено ICF=1
: выполнилось условия захвата TOV0=1
: произошло переполнение T/C0 При входе в подпрограмму обработки прерывания соответствующий прерыванию флаг регистра TIFR автоматически сбрасывается в сотояние лог. 0
Прерывания работают только тогда, когда в регистре состояния SREG разрешены общие прерывания (бит 7 = 1). В случае наступления прерывания этот бит автоматически сбрасывается в 0, блокируя выполнение последующих прерываний.
В данном примере вывод INT0 включён в режиме входа с подтяжкой. При замыкании вывода на землю при помощи кнопки на нём устанавливается лог.0 (фронт сигнала ниспадает с напряжения питания до 0) и срабатывает обработчик прерывания, включающий лампочку, подключённую к нулевому выводу порта B void lampON() interrupt void ext_int0_isr(void) DDRD.2=0; SREG|= (1 while(1) { На приведённом примере также видно, как задаются векторы прерываний в Code Vision AVR (interrupt void ext_int0_isr(void)). Аналогично задаются вектора прерываний и для других случаев: EXT_INT0 2 Сегодня будем разбирать понятие прерывания и как его использовать. Естественно не обойдется без учебной программы, но на этот раз моргать светодиодами не будем. Хорош уже. Сделаем подобие дверного звонка. Задача:
заставить микроконтроллер по нажатию кнопки издавать звуковой сигнал. Создаем в старом workspace проект ring. Выбираем тип микроконтроллера.
Разрешаем использование имен битов определенных в хидер файле
Меняем тип выходного файла.
Сохраняем проект и workspace.
Представьте себе ситуацию. Вы сидите на работе и корпите над очередной микроконтроллерной програмулиной. Подходит к вам начальник и говорит: “Слушай, Паш, нам осциллографы в отдел закупили - Tektronix, четырехканальные. Помоги Васе притащить их”. Вы думаете: ”Ну, е-мое, только мысль поперла.. и на тебе”. А начальник так смотрит на вас, а глаза у него такие добрые, такие добрые. Как тут ему откажешь. Ну, вы все бросаете и идете с другом за осциллографами. Притащили. Отчитались. И снова сели за свою программу. Вот примерно так и выглядит механизм прерывания. Довольно просто, но есть ряд принципиальных моментов. Во-вторых: Все это очень похоже на то, что происходит в микроконтроллере. Микроконтроллеры AVR имеют в своем составе целую тучу периферийных устройств (таймеры/счетчики, аналого-цифровой преобразователь, аналоговый компаратор, асинхронный приемопередатчик…и т.д). Мощь микроконтроллера в том, что все эти устройства могут работать параллельно и независимо друг от друга, а также параллельно выполняемой программе. Каждое периферийное устройство может вызывать прерывание по наступлению определенного события. Прерывание будет происходить только в том случае, если оно разрешено. Разрешение прерываний устанавливается для каждого устройства отдельно. Помимо этого есть флаг глобального разрешения/запрещения всех прерываний – это I флаг в регистре SREG. При возникновении прерывания микроконтроллер сохраняет содержимое счетчика команд PC в стеке, то есть запоминает место, на котором его прервали. Загружает в счетчик команд адрес соответствующего вектора прерывания и переходит на этот адрес. Попадает на команду безусловного перехода по которой переходит на подпрограмму обработки прерывания. Запрещает прерывания сбросом флага I, выполняет подпрограмму. Выполнив подпрограмму обработки прерывания, микроконтроллер разрешает прерывания, устанавливая флаг I, и восстанавливает содержимое счетчика команд, то есть возвращается на то же место программы, на котором его прервали. По идее, обработчик прерывания не должен повреждать содержимое регистров микроконтроллера, потому что они могут содержать данные программы выполняемой в тот момент. Для этого в начале подпрограммы обработки прерывания содержимое регистров микроконтроллера сохраняют в стеке, а в конце подпрограммы восстанавливают. Таким образом, выйдя из прерывания, микроконтроллер сможет продолжить выполнение программы, как ни в чем не бывало. Программируя на ассемблере, сохранение регистров прописывает сам программист, на Си – это делает компилятор. Теперь поговорим о таймере. ATmega8535 имеет на борту три таймера/счетчика - два восьмиразрядных (T0, T2) и один шестнадцатиразрядный (T1). Мы будем использовать восьмиразрядный таймер/счетчик T0. В состав этого таймера входят три регистра - регистр управления TCCR0, счетный регистр TCNT0 и регистр сравнения OCR0. Когда таймер запущен, счетный регистр TCNT0 увеличивает свое значение на единицу на каждый перепад тактового сигнала. Частота тактового сигнала выбирается из нескольких возможных значений в регистре управления TCCR0. Также с помощью этого регистра устанавливается режим работы таймера. Таймер T0 может вызвать прерывание по наступлению события “переполнение” – это когда переполняется счетный регистр TCNT0 и по наступлению события “совпадение” – это когда значение счетного регистра TCNT0 становится равным значению регистра сравнения OCR0. Флаги разрешающие эти прерывания находятся в регистре TIMSK. Теперь о программе. Построчно писать программу уже не получится, поэтому я сразу приведу ее текст. Ниже мы последовательно разберем все ее строки, и все станет понятно. Макросы я намеренно не стал использовать, программа маленькая, не хочется ее загромождать. int
main(void
) //настраиваем таймер Т0
//разрешаем прерывания
//основной цикл программы – опрос кнопки
//обработчик прерывания таймера Т0
Настройка портов
В нашей схеме к порту D подключена кнопка и пьезодинамик. Вывод, к которому подключена кнопка, нужно настроить на вход и включить подтягивающий резистор. Вывод, к которому подключен пьезодинамик, нужно настроить на выход. DDRD = (0< Настройка таймера
Режим работы таймера Т0 – СТС(сброс при совпадении), тактовый сигнал – clk/8. Отражаем это в регистре TCCR0 TCCR0 = (1< Обнуляем на всякий случай счетный регистр TCNT0 В регистр сравнения OCR0 записываем 0xc8. Почему? Потому что я посчитал это на калькуляторе
. Ну а на бумаге этот расчет выглядит так. Подробное описание таймера T0 смотрите в документации на ATMega8535. Таймер мы настроили, разрешаем общее прерывание, используя встроенную функцию. __enable_interrupt();
Опрос кнопки
Когда кнопка не нажата, вывод микроконтроллера через внутренний подтягивающий резистор подключен к питанию, то есть на выводе присутствует единичка, когда кнопка нажата, вывод замкнут на землю, то есть на выводе ноль. Чтобы определить нажата ли кнопка, нужно считать содержимое регистра PIND и проверить значение нулевого бита (к PD0 подключена кнопка). Опрашивать кнопку будем в бесконечном цикле. while
(1) Не забывайте == это не оператор присваивания =. Обработка нажатия/отпускания кнопки
По нажатию кнопки мы будем разрешать прерывание таймера T0, по отпусканию - запрещать. Для этого будем манипулировать битом OCIE0 регистра TIMSK TIMSK = (1< TIMSK = 0; //запрещаем прерывание
Поскольку мы используем всего один таймер, то нет нужды в установке или сбросе отдельных битов. Функция прерывания задается с помощью директивы #pragma
vector= и служебного слова __interrupt.
Функция должна иметь тип void и не должна принимать никаких параметров. #pragma
vector = Address Name
– имя функции, выбираем на наше усмотрение Для нашей задачи функция-обработчик прерывания выглядит так #pragma
vector = TIMER0_COMP_vect Ну вот собственно и все. Надеюсь все понятно. Разрешение внешних прерываний в avr atmega
Пример использования внешних прерываний в AVR atmega
#include
Практически у всех микроконтроллеров AVR стек размещается в SRAM. Для адресации текущего элемента (вершины стека) используется указатель стека SP (Stack Pointer). Это однобайтовый РВВ SPL у моделей с объемом памяти данных до 256 б, или двухбайтовый SPH:SPL (SPH – старший байт, SPL – младший байт).
Рис.10 Пример работы стекаАдрес
Источник прерывания
Описание
0x0000
RESET
Сигнал сброса
0x0001
INT0
Внешний запрос на прерывание по входу INT0
0x0002
INT1
Внешний запрос на прерывание по входу INT1
0x0003
T/C1
Захват по таймеру T/C1
0x0004
T/C1
Совпадение с регистром сравнения A таймера T/C1
0x0005
T/C1
Совпадение с регистром сравнения B таймера T/C1
0x0006
T/C1
Переполнение счётчика T/C1
0x0007
T/C0
Переполнение счётчика T/C0
0x0008
SPI
Передача данных по интерфейсу SPI завершена
0x0009
UART
Приём данных приёмопередптчиком UART завершен
0x000A
UART
Регистр данных UART пуст
0x000B
UART
Передача данных приёмопередптчиком UART завершена
0x000C
ANA_COMP
Прерывание от аналогового компаратора
Управления прерываниями
7
6
5
4
3
2
1
0
TOIE1
OCIE1A
OCIE1B
-
TICIE
-
TOIE0
-
7
6
5
4
3
2
1
0
TOV1
OCF1A
OCF1B
-
ICF1
-
TOV0
-
{
PORTB.0=1;
DDRB.0=1;
}
{
lampON();
}
PORTD.2=1;
EXT_INT1 3
TIM2_COMP 4
TIM2_OVF 5
TIM1_CAPT 6
TIM1_COMPA 7
TIM1_COMPB 8
TIM1_OVF 9
TIM0_OVF 10
SPI_STC 11
USART_RXC 12
USART_DRE 13
USART_TXC 14
ADC_INT 15
EE_RDY 16
ANA_COMP 17
TWI 18
SPM_READY 19
Схема для нашего примера . Файлы проекта .
Задаем настройки проекта для конфигурации Release:
General Options > Target > Processor configuration
У меня это ATmega8535.
В General Options > System ставим галочку Enable bit definitions in I/O-Include files
До сих пор мы не пользовались именами битов, но сегодня они нам понадобятся.
Linker > Output.
B поле Output file cтавим галочку Override default и заменяем расширение d90 на hex
В поле Format выбираем Other и в выпадающем меню Output format выбираем тип файла intel-standart______________________________ Прерывание ___________________________
Во-первых:
- вы делали свою работу
- параллельно кто-то покупал осциллографы
- по наступлению события «осциллографы закупили» - вы прерываете выполнение своей работы
- некоторое время вы занимаетесь другой работой – тащите осциллографы
- потом вы возвращаетесь на рабочее место и продолжаете делать свою работу с того места, на котором остановились
- вы вполне могли бы послать начальника и никуда не идти
- уйдя за осциллографами, вы могли задержаться там надолго, а то и вовсе не вернуться
- вернувшись на рабочее место, вы могли бы уже позабыть свои гениальные идеи_______________________________________________________________
Мы настроим таймер/счетчик Т0 так, чтобы он вызывал прерывание по событию “совпадение” с частотой 5 кГц. В функции обработчике будем инвертировать состояние вывода микроконтроллера, к которому подключен пьезодинамик. Таким образом, частота пищания пьезика будет равна 2,5 кГц. (Подключен именно пьезодинамик! Не перепутайте. У пьезодинамика сопротивление зависит от частоты и на 2,5 КГц оно обычно еденицы Ком, поэтому его можно подключать к выводу микроконтроллера напрямую, без ограничительного резистора).
{
//настраиваем порты ввода-вывода
DDRD = (0<
TCCR0 = (1<
OCR0 = 0xc8;
__enable_interrupt();
while
(1){
if
((PIND & (1<
TIMSK = 0;
}
return
0;
}
__interrupt
void
Timer0CompVect(void
)
{
PORTD ^= (1<
Тактовая частота микроконтроллера 8 МГц
Тактовый сигнал таймера равен 8000000 Гц/8 = 1000000 Гц.
Время одного такта таймера 1/1000000 = 1 мкс
Время одного такта нужной нам частоты 1/5000 Гц = 200 мкс
Сколько тактов таймера укладывается в 200 мкс? 200/1 = 200 тактов
200 в шестнадцатеричной системе = 0xс8
{
if
((PIND & (1<
}
else
{
//если нет - молчать как рыба
}
}
Функция прерывания
_____________________ Cинтаксис функции прерывания
_____________________
__interrupt
void
Name(void
)
{
//здесь располагается наш код
}
Address
– адрес вектора прерывания, можно задавать числом, можно именами определенными в заголовочном файле микроконтроллера (iom8535.h – раздел Interrupt Vector Definitions)______________________________________________________________
__interrupt
void
Timer0CompVect(void
)
{
PORTD ^= (1<
}
В следующий статье заставим микроконтроллер играть мелодию.
Способы увеличения звука на планшете
Как пользоваться TeamViewer или удаленное управление компьютером через интернет
Фазировка акустических систем Фазировка динамиков в 2 х полосной ас
Установка Google Play на смартфонах Meizu
Как узнать битная система windows 10