Данный урок будет достаточно простым. Надеюсь, вы хорошо усвоили прошлые материалы: урок по таймерам и управление кнопками.
Чтобы понять зачем нужны внешние прерывания, приведу простой пример: допустим у вас в основном цикле программы используются задержки (например, для мигания светодиодом), при этом вам кнопкой нужно перевести работу светодиода в другой режим. Если обработка кнопки находится в основном цикле, то придется ждать пока не отработают все фрагменты кода и очередь не дойдет до обработки кнопки. Иногда это не удобно.
Поэтому в микроконтроллерах придумали такую удобную вещь, как внешнее прерывание. Это значит, что при подаче сигнала на ножку микроконтроллера, основная программа остановится и начнет выполняться такой код, который вы напишите в функции прерывания. После выполнения данной функции, основная программа продолжит выполняться с места, где ее прервали.
Количество ножек, отведенных для внешних прерываний, зависит от типа микроконтроллера, например у atmega8 их 2, у atmega16 их 3. Называются они INT0, INT1 и т.п.
Срабатывать прерывание может по нарастанию сигнала Rise edge, по спаду Falling edge, по любому изменению Any change, Low level по низкому уровню. В визарде выглядит так:
Теперь рассмотрим, как пример, необычное использование внешнего прерывания — частотомер.
Допустим, на ножку настроенную на внешнее прерывание, по нарастанию фронта, подается пульсирующий сигнал. Соответственно каждый период будет срабатывать прерывание, нам остается только подсчитывать их количество за одну секунду.
Для этого настроим таймер 1 на срабатывание 1 раз в секунду, как в 5 уроке. При срабатывании прерывания таймера, обнуляем счетчик и выводим результат на дисплей.
#include <mega8.h> // Alphanumeric LCD Module functions #asm .equ __lcd_port=0x18 ;PORTB #endasm #include <lcd.h> #include <stdio.h> unsigned long i = 0, freq=0; char lcd_buf[33]; interrupt [EXT_INT0] void ext_int0_isr(void) { i++; } interrupt [TIM1_COMPA] void timer1_compa_isr(void) { freq=i; i=0; TCNT1H=0x00; TCNT1L=0x00; } void main(void) { // Declare your local variables here // Input/Output Ports initialization // Port B initialization // Func7=In Func6=In Func5=In Func4=In Func3=In Func2=In Func1=In Func0=In // State7=T State6=T State5=T State4=T State3=T State2=T State1=T State0=T PORTB=0x00; DDRB=0x00; PORTD=0xFF; DDRD=0x00; // Timer/Counter 1 initialization // Clock source: System Clock // Clock value: 7,813 kHz // Mode: Normal top=FFFFh // OC1A output: Discon. // OC1B output: Discon. // Noise Canceler: Off // Input Capture on Falling Edge // Timer 1 Overflow Interrupt: Off // Input Capture Interrupt: Off // Compare A Match Interrupt: On // Compare B Match Interrupt: Off TCCR1A=0x00; TCCR1B=0x05; TCNT1H=0x00; TCNT1L=0x00; ICR1H=0x00; ICR1L=0x00; OCR1AH=0x1E; OCR1AL=0x85; OCR1BH=0x00; OCR1BL=0x00; // External Interrupt(s) initialization // INT0: On // INT0 Mode: Rising Edge // INT1: Off GICR|=0x40; MCUCR=0x03; GIFR=0x40; // Timer(s)/Counter(s) Interrupt(s) initialization TIMSK=0x10; // Global enable interrupts #asm("sei") lcd_init(8); while (1) { sprintf(lcd_buf,"freq=%d",freq); lcd_gotoxy(0,0); lcd_puts(lcd_buf); }; } |
Прошивка и схема доступны здесь.
не задавался этим вопросом, можно в отладке посмотреть.
Подскажите что не так делаю. Подключил мультиметр в режиме генерации импульсов (50 Гц 5в). Один провод на землю, а второй к INT0, но импульсы считать не хочет. На дисплее 0. Кнопкой через подтяжку к питанию импульсы считаются.
посмотрите приходят ли импульсы осциллографом
Нет у меня осциллографа. Но когда я подключаю мультиметр к входу акустических колонок, то можно услышать звук генерации. Может нужно поменять настройку PORTD=0xFF на PORTD=0xFB (0b11111011)? Т.е. перевести в режим Hi-Z.
И почему порт B настроен на ВХОД, если мы выводим информацию из него?
подтяжку нужно убрать, может у вас батарейка села в мультиметре или еще куча причин, без осцила можно только гадать.
Наконец-то! Заработало! А то уже неделю никак не получалось осуществлять счёт импульсов. А всё из-за режима с подтяжкой. Я уже сделал генератор на NE555. Ну никак не получалось. Кстати насчёт порта B — можно вообще убрать настройку. И так работает. Он наверно настраивается в функции инициализации LCD. Спасибо.
А вот во время обработки прерывания по переполнению, счетчик не перестает считать?
Уважаемый admin, подскажите, столкнулся вот с какой проблемой, вроде и простая, а голову уже сломал всю: сделал по аналогии с вашим частотомер для кулера, все нормально обороты снимает, показывает, но количество в секунду, пытаюсь перевести в количество оборотов в минуту, но чет как-то не сходиться, просто умножить на 60 — кратность большая получается, т.е. к примеру у кулера 2153 оборотов/минута, не могу логику вывести.
Всё, вроде разобрался!
Ув. Админ. Решил собрать велокомпьютер, но на таймере не могу добиться точного хода времени. Использую Таймер2 в асинхронном режиме, чтобы сам таймер работал от кварца на 32768 Гц, а МК тактировался от внутреннего генератора 8 Мгц. При запуске в протеусе, погрешность во времени просто поразила. 🙁 соответственно, ни скорость, ни расстояние он точно сосчитать не сможет, так же использовал Таймер1, но он для более,менее точного хода требует кварца на 10Мгц, которого у меня нет 😥 Что посоветуете? На железе не проверял, может протеус косячит? Вот код:
#include
// Alphanumeric LCD Module functions
#asm
.equ __lcd_port=0x12 ;PORTB
#endasm
#include
#include
#include
int count_interrupts = 0,time = 0, m = 0, hour =0, ci =0;
char lcd_buf[64];
float result;
float speed;
interrupt [EXT_INT1] void ext_int1_isr(void)
{
ci++;
count_interrupts++;
}
// Timer2 output compare interrupt service routine
interrupt [TIM2_COMP] void timer2_comp_isr(void)
{
time++ ;
if(time>59) // обнуляем секунды после 59
{
time=0;
m++ ;
if(m>59)
{
m=0;
hour++ ;
}
}
speed= (ci*2.083*3.6);
ci=0;
TCNT2=0x00;
}
void main(void)
{
// Declare your local variables here
// Input/Output Ports initialization
// Port B initialization
// Func7=In Func6=In Func5=In Func4=In Func3=In Func2=In Func1=In Func0=In
// State7=T State6=T State5=T State4=T State3=T State2=T State1=T State0=T
PORTB=0x00;
DDRB=0x00;
// Timer/Counter 2 initialization
// Clock source: System Clock
// Clock value: 0,128 kHz
// Mode: Normal top=0xFF
// OC2 output: Disconnected
// Timer Period: 2 s
ASSR=(1<<AS2);
TCCR2=(0<<PWM2) | (0<<COM21) | (0<<COM20) | (0<<CTC2) | (1<<CS22) | (0<<CS21) | (1<<CS20);
TCNT2=0x00;
OCR2=0x00;
// Timer(s)/Counter(s) Interrupt(s) initialization
TIMSK=(1<<OCIE2)|(1<<TOIE1);
// External Interrupt(s) initialization
// INT0: On
// INT0 Mode: Rising Edge
// INT1: Off
GICR|=(1<<INT1) | (0<<INT0);
MCUCR=(0<<ISC11) | (0<<ISC10) | (0<<ISC01) | (0<<ISC00);
GIFR=(1<<INTF1) | (0<<INTF0);
// Global enable interrupts
#asm("sei")
lcd_init(16);
while (1)
{
lcd_clear();
lcd_gotoxy(0,1);
sprintf(lcd_buf,"SPEED=%.1fkmh",speed);
lcd_puts(lcd_buf);
lcd_gotoxy(0,0);
result = (count_interrupts*2.083);
sprintf(lcd_buf,"DISTANCE=%.2fkm",result/1000);
lcd_puts(lcd_buf);
lcd_gotoxy(0,2);
lcd_putchar(hour/10+0x30);
lcd_putchar(hour%10+0x30);
lcd_gotoxy(2,2);
lcd_putsf(":");
lcd_gotoxy(3,2);
lcd_putchar(m/10+0x30);
lcd_putchar(m%10+0x30);
lcd_gotoxy(5,2);
lcd_putsf(":");
lcd_gotoxy(6,2);
lcd_putchar(time/10+0x30); //вывод десятков секунд
lcd_putchar(time%10+0x30); //вывод секунд
delay_ms(500);
};
}
в комментариях настроек таймера небольшая опечатка, таймер настроен на 256кГц.
проверяйте на железе
Извиняюсь, что задаю вопрос немного не по теме. Основываясь на код приведенный выше, прошил мегу8 и подключил дисплей LCD. При запуске устройства, МК как будто уходит в спящий режим раз в секунду, LCD дисплей гаснет, потом снова включается. В чем ошибка? Может таймер2 в асинхронном режиме не подходит для данной задачи, но игнорировать его не хочется, так как часики в нем тикают ровно, а это для измерений само то. Подскажите, что не так? 🙁
у вас по коду дисплей полностью очищается раз в полсекунды, именно это вы и видете
Доброго времени суток!
Подскажите правильно ли делаю?
В проектируемом устройстве нужно чтобы при выключении питания значение переменной из RAM сохранилось в eeprom, то есть при выключении координата сохранилась, а при включении контроллер продолжал работу с ее.
Хочу обойтись без прерывания, а просто в главном цикле условием поставить наличие логической единицы на ножке которая подключена к питанию перед конденсаторами с диодом и в теле main перед циклом инициализация из eeprom в RAM, а после, пока чип работает на конденсаторах, сохранение из RAM в eeprom.
У меня в программе используется таймер 1.
Или может кто-то что-то адекватней подскажет?!
если времени работы конденсаторов достаточно, то почему бы нет.
Ну то что чип работает на конденсаторах, то такое используют, мне бы просто знать правильно ли я решил задачу…
Проект у меня первый и сразу серьезный, многое учу по ходу…
никто вам не скажет правильно ли вы решили или нет. нужно погружаться в проект, разбираться что и как работает. так что в данный момент никто кроме вас не может решить что правильно а что нет. проведите несколько экспериментов, с различными вариантами — по таймеру, по прерыванию и в основном цикле и решите что подходит а что нет.
Добрый день скажите пожалуйста можно ли остановить программу в теле прерывания
interrupt [EXT_INT0] void ext_int0_isr(void)
{
условие
{ код
}
break; он тут не реагирует ,если условия истена то крутит код , как тут можно поступить
}
дело в том что надо чтоб программа остановилась ,а при следующем запуске МК она стала опять работать ?
вместо break используйте while(1)
Уважаемый Админ подскажите пожалуйста как можно сделать счетчик в прерывании , дапустем у меня на входе импульсы с частотой 2гц если мк. нужно считать определенное количества импульсов за определенный промежуток времени ..Надеюсь на помашь
почитайте http://avr-start.ru/?p=4500
Не знаю есть ли тут кто-то?! Нужна помощь: если использовать не atmega8, а AT90S4433, что изменится в коде программы?
настройки под ваш камень, codevizard вам в помощь, осваивайте.
времени уже нет осваивать 🙁 , но все равно спасибо
Добрый вечер.Подскажите пожалуйста как работать с двумя прерываниями INT0 и INT1.
Прерывание INT0 срабатывает каждые 25 ms , а на INT1 подключен TSOP2136 с пульта я не могу погасить INT0 .По отдельности прерывания работают отлично .Подскажите в каком направлении копать.
внутри прерываний, разрешите прерывания asm(«sei»)
Добрый день .Не получается так как INT0 срабатывает каждые 25ms когда же сработает другое прерывание.
Чтобы все работало, нужно чтобы прерывание отрабатывало быстро, поэтому как можно меньше кода внутри самого прерывания.
Какова максимальная частота внешних прерываний допускается, допустим нужно мерить частоту гетеродинов 7МНz.
точную цифру вам никто не скажет, ибо много уточняющих моментов и надо заморачиваться и считать. по даташиту 4 такта на вход и выход из прерывания прерывание. Итого — 7МГц вы не померите точно.
Добрый день.
Спасибо за Ваши уроки, с прирываниями никогда не работал и моему мозгу трудно сообразить.
Хочу сделать дозиметр, по сути, тот же частотомер. нужно использовать одновременно два внешних прерывания.
Первое прерывание от внешнего RTC тикает раз в секунду и запускает таймер, который за эту секунду считает тики от счетчика Гейгера.
Как это сделать тактируясь от секундного таймера из урока 5 я разобрался и даже в Протеусе что то получилось, а как использовать внешний тик не могу сообразить.
Спасибо!
получили прерывание от секундного таймера — сбросили счетчик, увеличиваете счетчик во внешнем прерывании, снова секунда набралась — вывели на экран, сбросили счетчик. 1 таймера достаточно.
Как это одним таймером?
RTC перезапускают таймер дергая ногу, не могу же я на эту же ногу сигнал от счетчика Гейгера подавать
зачем вам запускать таймер по ноге? вам же нужно постоянно выводить число срабатываний за секунду.
Добрый день.Вопрос по прерыванию я использую два прерывания INTO и INT1 на INTO заведён обработчик пульта с протоколом RC-5 ,а на INT1 происходит прерывание 16 раз в секунду я не могу изменить значение переменной с пульта ,так как я думаю что посылка с пульта длиннее чем прерывание INT1.Переменную можно изменить если увеличить время прерывания INT1.Длина пакета всегда одинакова и составляет 24,889 миллисекунд. Минимальная пауза между пакетами равна по длине 50 битам (88,889 миллисекунд).Может быть что посоветуйте.
не совсем понял, но возможно стоит сделать прерывания вложенными и выставить максимальную тактовую частоту, чтобы успевало и там и там.
Добрый день .Как понять вложенными ,а контроллер работает на внутреннем генераторе 8МГц
Тут нужно смотреть на логику. В авр после того как случилось прерывание, остальные выключаются до того момента, пока оно не закончится. Если разрешить внутри этого прерывания asm sei, то одно прерывание может срабатывать поверх другого, но естественно первое будет ждать завершения второго. Чем выше тактовая, тем быстрее выполнится второе прерывание, соответственно быстрее вернется управление к первому. Но нужно хорошо понимать логику работы, чтобы такое реализовывать.
Здравствуйте! Подскажите пожалуйста, все перекопал не пойму почему частота удваивается? Например подаю 200, считает как 400?