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

В данном случае мы будем использовать режим работы таймера – сброс при совпадении(СТС). В этом режиме таймер сам периодически вырабатывает запросы на прерывания с заранее заданным периодом.

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

Алгоритм основной программы:

- Настроить порты ввода-вывода;
- Настроить таймер и систему прерываний;
- Записать в рабочий регистр начальное значение;
- Разрешить работу таймера;
- Разрешить прерывания;
- Перейти к выполнению основного цикла.

Алгоритм процедуры обработки прерываний:

- Проверяем состояние переключателя режимов;
- Если контакты разомкнуты произвести сдвиг вправо;
- Если контакты замкнуты произвести сдвиг вправо;
- Вывести содержимое рабочего регистра в порт D;
- Закончить процедуру обработки прерывания.

Пишем программу. Текст будет содержать две функции: главная функция main и функция обработки прерываний ISR. Функция main будет содержать только строки инициализации. Разберем функцию прерываний. Сначала подключим стандартную библиотеку прерываний <avr/interrupt.h>, заголовочный файл находится в директории AVR.

ISR (TIMER1_COMPA_vect)

ISR это управляющее слово указывает транслятору на то, что данная функция является процедурой обработки прерываний. Вид прерывания , которое будет вызывать данную функцию указывается в скобках, это TIMER1_COMPA_vect, означает, что данная функция является процедурой обработки прерывания по совпадению таймера Т1. Функция обработки прерываний занимается сдвигом содержимого переменной temp на один шаг вправо или влево, в зависимости от положения переключателя S1.

 //***Процедура обработки прерывания по Таймеру 1***/  
ISR (TIMER1_COMPA_vect) 
{ 
if ((PINC&(1 << PC0)) == 0)  // Проверка состояния переключателя 
{ 
temp = temp >> 1;  // Сдвиг разрядов 
if (temp==0) {temp = 0b10000000;}  // Сдвиг вправо 
}  
else 
{ 
temp = temp << 1;  // Сдвиг разрядов 
if (temp==0) {temp = 0b00000001;}  // Сдвиг влево 
}  
PORTD = temp;  // Запись в порт D 
} 

Для проверки состояния переключателя служит команда if. Эта команда проверяет значение младшего разряда порта C. Если значение разряда равно нулю, то выполняется процедура сдвига на один бит вправо, если равно единице выполняется сдвиг влево. Так же еще один оператор if проверяет не дошла ли сдвигаемая единица до конца байта. Признаком того, сто единица уже дошла до конца , является равенство переменной temp нулю. Если условие выполняется, то переменной temp будет присвоено значение 0b00000000. То есть дойдя до правого края, единица появляется слева. Таким образом реализуется эффект кругового движения единичного бита. Процедура сдвига влево построена таким же образом. После выполнения одной из процедур сдвига производится запись содержимого переменной temp в порт D.

Полный текст программы:

/***Занятие №6 "Бегущие огни" Использованием прерывания по таймеру***/

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

unsigned char temp;

/***Процедура обработки прерывания по Таймеру 1***/

ISR (TIMER1_COMPA_vect)
{
if ((PINC&(1 << PC0)) == 0) // Проверка состояния переключателя
{
temp = temp >> 1; // Сдвиг вправо
if (temp == 0)
{ temp = 0b10000000;}
}
else
{
temp = temp << 1; // Сдвиг влево
if (temp == 0)
{ temp = 0b00000001;}
}
PORTD = temp; // Запись в порт PD
}

int main(void)
{

/***настраиваем порты ввода-вывода***/

DDRC = 0x00;
PORTC |= (1 << PC0); // Подключаем внутренний резистор
DDRD = 0xFF;
PORTD = 0x00;

/***Настраиваем таймер***/

TCCR1A = 0x00;
TCCR1B = (1 << CS12)|(0 << CS11)|(1 << CS10)|(1 << WGM12); //предделитель clk/1024, режим таймера СТС
TCNT1 = 0x00;
OCR1A = 780; // максимальный предел счета
TIMSK |= (1 << OCIE1A); // разрешение прерывания по совпадению

temp = 0b00000000; // Присвоение начального значения

sei(); // Разрешение прерываний

while (1) {}; // Бесконечный цикл
}

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

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

unsigned char temp, direction;

ISR (TIMER1_COMPA_vect)
{
if(direction) // Проверка состояния
{
temp = temp >> 1; // Сдвиг вправо
if (temp == 0) temp = 0x80;
}
else
{
temp = temp << 1; // Сдвиг влево
if (temp == 0) temp = 0x01;
}
PORTD = temp; // Запись в порт D
}

int main(void)
{
DDRC = 0x00;
PORTC |= (1 << PC0); // Подключаем внутренний подтягивающий резистор
DDRD = 0xFF;
PORTD = 0x00;

TCCR1B = (1 << CS12)|(1 << CS10)|(1 << WGM12); //предделитель clk/1024, режим таймера СТС
OCR1A = 780; // Выбор коэффициента деления
TIMSK |= (1 << OCIE1A); // Разрешение прерывания по совпадению

temp = 0; // Присвоение начального значения

sei(); // Разрешение прерываний

while(1)
{
if((PINC&(1 << PC0)) == 0) // Если кнопка нажата
{
_delay_ms(20);
direction ^= 1; // Переключаем состояние
while((PINC&(1 << PC0)) == 0){} // Ждем отпускания кнопки
}
}
}

В статье были использованы материалы из книги Белова А.В. "Самоучитель разработчика устройств на AVR"