Прерывания. Что такое прерывания в микроконтроллерах Сохраняем проект и workspace

Сегодня будем разбирать понятие прерывания и как его использовать. Естественно не обойдется без учебной программы, но на этот раз моргать светодиодами не будем. Хорош уже. Сделаем подобие дверного звонка.

Задача: заставить микроконтроллер по нажатию кнопки издавать звуковой сигнал.
Схема для нашего примера . Файлы проекта .

Создаем в старом workspace проект ring.
Задаем настройки проекта для конфигурации 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

Сохраняем проект и workspace.

______________________________ Прерывание ___________________________

Представьте себе ситуацию. Вы сидите на работе и корпите над очередной микроконтроллерной програмулиной. Подходит к вам начальник и говорит: “Слушай, Паш, нам осциллографы в отдел закупили - Tektronix, четырехканальные. Помоги Васе притащить их”. Вы думаете: ”Ну, е-мое, только мысль поперла.. и на тебе”. А начальник так смотрит на вас, а глаза у него такие добрые, такие добрые. Как тут ему откажешь. Ну, вы все бросаете и идете с другом за осциллографами. Притащили. Отчитались. И снова сели за свою программу. Вот примерно так и выглядит механизм прерывания.

Довольно просто, но есть ряд принципиальных моментов.
Во-первых:
- вы делали свою работу
- параллельно кто-то покупал осциллографы
- по наступлению события «осциллографы закупили» - вы прерываете выполнение своей работы
- некоторое время вы занимаетесь другой работой – тащите осциллографы
- потом вы возвращаетесь на рабочее место и продолжаете делать свою работу с того места, на котором остановились

Во-вторых:
- вы вполне могли бы послать начальника и никуда не идти
- уйдя за осциллографами, вы могли задержаться там надолго, а то и вовсе не вернуться
- вернувшись на рабочее место, вы могли бы уже позабыть свои гениальные идеи

Все это очень похоже на то, что происходит в микроконтроллере. Микроконтроллеры AVR имеют в своем составе целую тучу периферийных устройств (таймеры/счетчики, аналого-цифровой преобразователь, аналоговый компаратор, асинхронный приемопередатчик…и т.д). Мощь микроконтроллера в том, что все эти устройства могут работать параллельно и независимо друг от друга, а также параллельно выполняемой программе. Каждое периферийное устройство может вызывать прерывание по наступлению определенного события. Прерывание будет происходить только в том случае, если оно разрешено. Разрешение прерываний устанавливается для каждого устройства отдельно. Помимо этого есть флаг глобального разрешения/запрещения всех прерываний – это I флаг в регистре SREG. При возникновении прерывания микроконтроллер сохраняет содержимое счетчика команд PC в стеке, то есть запоминает место, на котором его прервали. Загружает в счетчик команд адрес соответствующего вектора прерывания и переходит на этот адрес. Попадает на команду безусловного перехода по которой переходит на подпрограмму обработки прерывания. Запрещает прерывания сбросом флага I, выполняет подпрограмму. Выполнив подпрограмму обработки прерывания, микроконтроллер разрешает прерывания, устанавливая флаг I, и восстанавливает содержимое счетчика команд, то есть возвращается на то же место программы, на котором его прервали.

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

_______________________________________________________________

Теперь поговорим о таймере. ATmega8535 имеет на борту три таймера/счетчика - два восьмиразрядных (T0, T2) и один шестнадцатиразрядный (T1). Мы будем использовать восьмиразрядный таймер/счетчик T0. В состав этого таймера входят три регистра - регистр управления TCCR0, счетный регистр TCNT0 и регистр сравнения OCR0. Когда таймер запущен, счетный регистр TCNT0 увеличивает свое значение на единицу на каждый перепад тактового сигнала. Частота тактового сигнала выбирается из нескольких возможных значений в регистре управления TCCR0. Также с помощью этого регистра устанавливается режим работы таймера. Таймер T0 может вызвать прерывание по наступлению события “переполнение” – это когда переполняется счетный регистр TCNT0 и по наступлению события “совпадение” – это когда значение счетного регистра TCNT0 становится равным значению регистра сравнения OCR0. Флаги разрешающие эти прерывания находятся в регистре TIMSK.
Мы настроим таймер/счетчик Т0 так, чтобы он вызывал прерывание по событию “совпадение” с частотой 5 кГц. В функции обработчике будем инвертировать состояние вывода микроконтроллера, к которому подключен пьезодинамик. Таким образом, частота пищания пьезика будет равна 2,5 кГц. (Подключен именно пьезодинамик! Не перепутайте. У пьезодинамика сопротивление зависит от частоты и на 2,5 КГц оно обычно еденицы Ком, поэтому его можно подключать к выводу микроконтроллера напрямую, без ограничительного резистора).

Теперь о программе. Построчно писать программу уже не получится, поэтому я сразу приведу ее текст. Ниже мы последовательно разберем все ее строки, и все станет понятно. Макросы я намеренно не стал использовать, программа маленькая, не хочется ее загромождать.

int main(void )
{
//настраиваем порты ввода-вывода
DDRD = (0<PORTD = (1<

//настраиваем таймер Т0
TCCR0 = (1< TCNT0 = 0;
OCR0 = 0xc8;

//разрешаем прерывания
__enable_interrupt();

//основной цикл программы – опрос кнопки
while (1){
if ((PIND & (1<TIMSK = (1<else
TIMSK = 0;
}
return 0;
}

//обработчик прерывания таймера Т0

__interrupt void Timer0CompVect(void )
{
PORTD ^= (1<}

Настройка портов

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

DDRD = (0<PORTD = (1<

Настройка таймера

Режим работы таймера Т0 – СТС(сброс при совпадении), тактовый сигнал – clk/8. Отражаем это в регистре TCCR0

TCCR0 = (1<

Обнуляем на всякий случай счетный регистр TCNT0

В регистр сравнения OCR0 записываем 0xc8. Почему? Потому что я посчитал это на калькуляторе . Ну а на бумаге этот расчет выглядит так.
Тактовая частота микроконтроллера 8 МГц
Тактовый сигнал таймера равен 8000000 Гц/8 = 1000000 Гц.
Время одного такта таймера 1/1000000 = 1 мкс
Время одного такта нужной нам частоты 1/5000 Гц = 200 мкс
Сколько тактов таймера укладывается в 200 мкс? 200/1 = 200 тактов
200 в шестнадцатеричной системе = 0xс8

Подробное описание таймера T0 смотрите в документации на ATMega8535.

Таймер мы настроили, разрешаем общее прерывание, используя встроенную функцию.

__enable_interrupt();

Опрос кнопки

Когда кнопка не нажата, вывод микроконтроллера через внутренний подтягивающий резистор подключен к питанию, то есть на выводе присутствует единичка, когда кнопка нажата, вывод замкнут на землю, то есть на выводе ноль. Чтобы определить нажата ли кнопка, нужно считать содержимое регистра PIND и проверить значение нулевого бита (к PD0 подключена кнопка). Опрашивать кнопку будем в бесконечном цикле.

while (1)
{
if ((PIND & (1<//если кнопка нажата – микроконтроллер должен верещать
}
else {
//если нет - молчать как рыба
}
}

Не забывайте == это не оператор присваивания =.

Обработка нажатия/отпускания кнопки

По нажатию кнопки мы будем разрешать прерывание таймера T0, по отпусканию - запрещать. Для этого будем манипулировать битом OCIE0 регистра TIMSK

TIMSK = (1<// разрешаем прерывание таймера Т0 по событию совпадение

TIMSK = 0; //запрещаем прерывание

Поскольку мы используем всего один таймер, то нет нужды в установке или сбросе отдельных битов.

Функция прерывания

_____________________ Cинтаксис функции прерывания _____________________

Функция прерывания задается с помощью директивы #pragma vector= и служебного слова __interrupt. Функция должна иметь тип void и не должна принимать никаких параметров.

#pragma vector = Address
__interrupt void Name(void )
{
//здесь располагается наш код
}

Name – имя функции, выбираем на наше усмотрение
Address – адрес вектора прерывания, можно задавать числом, можно именами определенными в заголовочном файле микроконтроллера (iom8535.h – раздел Interrupt Vector Definitions)

______________________________________________________________

Для нашей задачи функция-обработчик прерывания выглядит так

#pragma vector = TIMER0_COMP_vect
__interrupt void Timer0CompVect(void )
{
PORTD ^= (1<//инвертируем сигнал на выводе PD1
}

Ну вот собственно и все. Надеюсь все понятно.
В следующий статье заставим микроконтроллер играть мелодию.

Системы прерываний - важная часть любой управляющей системы.

От ее работы во многом зависит то, насколько эффективно микропроцессорная система выполняет свои функции. Общая структура системы пре рываний МК-51 представлена на рис. 14.3.

Микроконтроллеры семейства МК-51 обеспечивают поддержку пяти источников прерываний:

* двух внешних прерываний, поступающих по входам INT0 и INT1 (линии порта Р3:Р3.2 и Р3.3 соответственно);

* двух прерываний от таймеров/счетчиков Т/С0 и Т/С1;

* прерываниеотпоследовательногопорта.

Запросы на прерывание фиксируются в регистрах специальных функций микроконтроллера: флаги IE0, IE1, TF0, TF1 запросов на прерывание от INT0, INT1, T/C0 и T/C1 содержатся в регистре управления TCON (табл. 14.4), а флаги RI и TI запросов на прерывание от последовательного порта - в регистре SCON управления последовательным портом.

Таблица 14.4. Формат регистра TCON

0 IT0 Настройка вида прерывания INT0

1 IE0 Флаг запроса прерывания INT0

2 IT1 Настройка вида прерывания INT1

3 IE1 Флаг запроса прерывания INT1

4 TR0 Включение в работу таймера/счетчика 0

5 TF0 Флаг переполнения (запрос прерывания)таймера/счетчика 0

6 TR1 Включение в работу таймера/счетчика 1

7 TF1 Флаг переполнения (запрос прерывания)таймера/счетчика 1

Флаги TF0 и TF1 устанавливаются аппаратно при переполнении соответствующего таймера/счетчика (точнее, при переходе T/Cx из состояния "все единицы" в состояние "все нули").

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

Настройка на тип запроса осуществляется программной установкой бит IT0 и IT1 в регистре управления TCON. Установка ITx = 0 настраивает систему прерывания на запрос по низкому уровню сигнала, ITx = 1 - запрос на прерывание по спаду сигнала.

Флаги TI и RI устанавливаются аппаратно схемой последовательного интерфейса после окончания передачи или после окончания приема соответственно.

Все указанные флаги запросов на прерывание программно доступны для установки и сброса. Программная установка флага запроса на прерывание приводит к такой же реакции микроконтроллера, что и аппаратная установка того же самого флага.

Флаги TF0 и TF1 сбрасываются аппаратно при передаче управления программе обработки соответствующего прерывания.

Сброс флагов IEx выполняется аппаратно при обслуживании прерывания только в том случае, если прерывание было настроено на восприятие спада сигнала INTx. Если прерывание было настроено на восприятие уровня сигнала запроса, то сброс флага IEx должна выполнять программа обслуживания прерывания, воздействуя на источник прерывания для снятия им запроса.

Флаги TI и RI сбрасываются только программным путем.

Каждый вид прерывания индивидуально разрешается или запрещается установкой или сбросом соответствующих разрядов регистра разрешения прерывания IE. Этот регистр содержит также и бит общего запрещения всех прерываний. ФорматрегистраIE приведен в табл. 14.5.

Таблица 14.5. Назначение разрядов регистра IE

Позиция в регистре

Мнемоника бита

Функция

Запрет прерывания от всех источников

Не используется

Не используется

Запрет прерывания от последовательного порта

Запрет прерывания от таймера/счетчика T/C1

Запрет прерывания от внешнего источника INT1

Запрет прерывания от таймера/счетчика T/C0

Запрет прерывания от внешнего источника INT0

Каждому виду прерывания может быть программно присвоен один из двух возможных приоритетов: 0 - низший или 1 - высший.

Настройка приоритетов осуществляется установкой или сбросом соответствующего бита регистра приоритетов прерываний IP. Формат этого регистра приведен в табл. 14.6.

При одновременном поступлении запросов прерывания от источников, имеющих различные приоритеты, сначала обрабатывается запрос от более приоритетного источника.

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

IT0 -> TF0 -> IT1 -> TF1 -> (RI, TI)

Таблица 14.6. Назначение разрядов регистра IP

Позиция в регистре Мнемоника бита Функция

7 - Не используется

6 - Не используется

5 - Не используется

4 PS Приоритет прерыванияот последовательного порта

3 PT1 Приоритет прерывания от таймера/счетчика T/C1

2 PX1 Приоритет прерыванияот внешнего источника INT1

1 PT0 Приоритет прерывания от таймера/счетчика T/C0

0 PX0 Приоритет прерыванияот внешнего источника INT0

Аппаратно реализуемый вызов обработчика прерываний состоит из следующих действий:

* сохранение значения программного счетчика в стеке;

Точки входа вобработчик прерывания для каждого источника прерываний аппаратно зафиксированы. Их значения приведены в табл. 14.7.

Таблица 14.7. Адреса точек входа в обработчики прерываний

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

Адреса точек входа в обработчики прерываний

Внешнее прерывания(ITO )

Таймер-счетчик(TFO)

Внешнее прерывания(IT1)

Таймер-счетчик(TF1)

Последовательный порт(R1 или T1)

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

При переходе на подпрограмму обработки прерывания автоматически независимо от состояния регистра IE запрещаются все прерывания, которые имеют уровень приоритета, равный уровню приоритета обслуживаемого прерывания, - то есть вложенные прерывания с равным уровнем приоритета запрещены. Таким образом, низкоприоритетное прерывание (имеющее "0" в соответствующем разряде регистра IP) может прерываться высокоприоритетным (имеющим "1" в соответствующем разряде регистра IP), но не низкоприоритетным. Обслуживание высокоприоритетного прерывания не может быть прервано другим источником.

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

Одним из преимуществ микроконтроллера ATmega8 является широкий диапазон различных прерываний.

Прерывание представляет собой событие, при наступлении которого выполнение основной программы приостанавливается и вызывается функция, обрабатывающая прерывание определённого типа.

Прерывания делятся на внутренние и внешние. К источникам внутренних прерываний относятся встроенные модули микроконтроллера (таймеры, приёмопередатчик USART и т.д). Внешние прерывания возникают при поступлении внешних сигналов на выводы микроконтроллера (например сигналы на выводы RESET и INT). Характер сигналов, приводящих к возникновению прерывания задаётся в регистре управления MCUCR , в частности в разрядах - ISC00 (бит 0) и ISC01 (бит 1) для входа INT 0; ISC10 (бит2) и ISC11 (бит3) для входа INT1.

В микроконтроллере ATmega8 каждому прерыванию соответствует свой вектор прерывания (адрес в начале области памяти программ, в которой хранится команда для перехода к заданной подпрограмме обработки прерывания). В mega8 все прерывания имеют одинаковый приоритет. В случае одновременного возникновения нескольких прерываний первым будет обрабатываться прерывание с меньшим номером вектора.

Векторы прерываний в Atmega8

Адрес Источник прерывания Описание
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 Прерывание от аналогового компаратора

Управления прерываниями

За управление прерываниями в ATmega8 отвечают 4 регистра:

GIMSK (он же GICR) - запрет/разрешение прерываний по сигналам на входах INT0, INT1

GIFR - управление всеми внешними прерываниями

TIMSK , TIFR - управление прерываниями от таймеров/счётчиков

Регистр GIMSK(GICR)

INTFx=1: произошло прерывание на входе INTx. При входе в подпрограмму обработки прерывания INTFx автоматически сбрасывается в сотояние лог. 0

Регистр TIMSK

7 6 5 4 3 2 1 0
TOIE1
OCIE1A
OCIE1B
-
TICIE
-
TOIE0
-

TOIE1=1 : прерывание по переполнению T/C1 разрешено

OCIE1A=1 : прерывание при совпадении регистра сравнения A с содержимым счётчика T/C1 разрешено

OCIE1B=1 : прерывание при совпадении регистра сравнения B с содержимым счётчика T/C1 разрешено

TICIE=1 : разрешено прерывание при выполнении условия захвата

TOIE0=1 : прерывание по переполнению T/C0 разрешено

Регистр TIFR

7 6 5 4 3 2 1 0
TOV1
OCF1A
OCF1B
-
ICF1
-
TOV0
-

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()
{
PORTB.0=1;
DDRB.0=1;
}

interrupt void ext_int0_isr(void)
{
lampON();
}

DDRD.2=0;
PORTD.2=1;

SREG|= (1 while(1) {

На приведённом примере также видно, как задаются векторы прерываний в Code Vision AVR (interrupt void ext_int0_isr(void)). Аналогично задаются вектора прерываний и для других случаев:

EXT_INT0 2
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

Для чего нужны внешние прерывания

Прерывание — это событие по которому прерывается исполнение основного кода программы (например функции 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<

Разрешение внешних прерываний в avr atmega

Для того что бы внешние прерывания заработали их надо разрешить, установив в 1 соответствующие биты в регистре GICR .

Бит INT0 отвечает за разрешение/запрещение внешнего прерывания INT0 , а бит INT1 , соответственно за внешне прерывание INT1 .

Так же необходимо что бы был выставлен флаг глобального разрешения прерываний.

Пример кода разрешающего внешнее прерывание INT0 для avr atmega8:

//разрешаем внешнее прерывание INT0 GICR |= (1<

Пример использования внешних прерываний в AVR atmega

В качестве примера приведу программу счетчика импульсов. Программа подсчитывает количество импульсов на входе INT0, и раз в секунду выводит результат подсчета в uart.

#include #include #include #include //переменная счетчик volatile unsigned long int0_cnt = 0 ; //настройка внешнего прерывния INT0 void int0_init( void ) { //настраиваем на срабатывание INT0 по переднему фронту MCUCR |= (1 & lt;& lt; ISC01) | (1 & lt;& lt; ISC00) ; //разрешаем внешнее прерывание INT0 GICR |= (1 & lt;& lt; INT0) ; } //функция обработчик внешнего прерывания INT0 ISR( INT0_vect ) { int0_cnt++; } //настройка UART void uart_init( void ) { //настройка скорости обмена UBRRH = 0 ; UBRRL = 3 ; //115200 при кварце 7.3728 МГц //8 бит данных, 1 стоп бит, без контроля четности UCSRC = ( 1 & lt;& lt; URSEL ) | ( 1 & lt;& lt; UCSZ1 ) | ( 1 & lt;& lt; UCSZ0 ) ; //разрешить прием и передачу данных UCSRB = ( 1 & lt;& lt; TXEN ) | ( 1 & lt;& lt; RXEN ) ; } //передача байта по UART int uart_putc( char c, FILE * file ) { //ждем окончания передачи предыдущего байта while ( ( UCSRA & amp; ( 1 & lt;& lt; UDRE ) ) == 0 ) ; UDR = c; return 0 ; } FILE uart_stream = FDEV_SETUP_STREAM(uart_putc, NULL, _FDEV_SETUP_WRITE ) ; int main( ) { //временная переменная unsigned long tmp; stdout = & amp; uart_stream; int0_init() ; uart_init() ; sei() ; while (1 ) { //на время копирования значения счетчика запрещаем прерывания cli() ; tmp = int0_cnt; //разрешаем прерывания sei() ; printf ( "int0_cnt = %lu\r \n " , tmp ) ; //пауза 1 секунда _delay_ms( 1000 ) ; } return 0 ; }

#include #include #include #include //переменная счетчик volatile unsigned long int0_cnt = 0; //настройка внешнего прерывния INT0 void int0_init(void) { //настраиваем на срабатывание INT0 по переднему фронту MCUCR |= (1<

Стек представляет собой область памяти, которую ЦПУ использует для сохранения и восстановления адресов возврата из подпрограмм.
Практически у всех микроконтроллеров AVR стек размещается в SRAM. Для адресации текущего элемента (вершины стека) используется указатель стека SP (Stack Pointer). Это однобайтовый РВВ SPL у моделей с объемом памяти данных до 256 б, или двухбайтовый SPH:SPL (SPH – старший байт, SPL – младший байт).

Когда микропроцессор встречает одну из инструкций вызовов 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 Пример работы стека

На рис.10 приведен небольшой фрагмент кода, в котором пошагово рассмотрен процесс изменения стека при входе и выходе из подпрограммы toggle и сохранении и восстановлении регистра R17. Это типичный пример, где могут понадобиться инструкции push/pop. Подпрограмма toggle использует РОН R17 в своих нуждах, но этот- же регистр может использоваться и в ходе основной программы. Поэтому, во избежание повреждения данных, R17 перед модификацией загружается в стек и восстанавливается из него перед командой ret.