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

Рейтинг:  5 / 5

Звезда активнаЗвезда активнаЗвезда активнаЗвезда активнаЗвезда активна
 

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

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

Все функции управления движением огней выполняет процедура обработки прерывания. При каждом вызове прерывания процедура производит сдвиг огней на 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"

Печать Электронная почта

Комментарии  

0 #21 Kivikoc 12.03.2017 14:17
Подскажите, пожалуйста, как организовать отработку короткого нажатия на клавишу и длительно? Допустим, чтобы условие выполнялось сразу же при мгновенном нажатии на клавишу(однокра тно)?
Сообщить модератору
+1 #22 AntonChip 12.03.2017 16:39
Цитирую Kivikoc:
Подскажите, пожалуйста, как организовать отработку короткого нажатия на клавишу и длительно? Допустим, чтобы условие выполнялось сразу же при мгновенном нажатии на клавишу(однократно)?

Код:
if ((PINC&(1 << PC0)) == 0) // Если кнопка нажата
{
temp++; // Увеличиваем переменную например
_delay_ms(5);
while ((PINC&(1 << PC0)) == 0) // Ждем отжатия кнопки
}
Сообщить модератору
0 #23 Kivikoc 13.03.2017 16:59
AntonChip, а не затруднит вас воткнуть его в код для данной практики и прикрепить в комментарии?
А вообще, цель заключается в том, чтобы прерывание происходило при однократном нажатии на кнопку, а не длительном
Сообщить модератору
0 #24 AntonChip 13.03.2017 23:12
Цитирую Kivikoc:
AntonChip, а не затруднит вас воткнуть его в код для данной практики и прикрепить в комментарии?

Добавил в статью
Сообщить модератору
0 #25 kostya_unix 17.11.2017 06:47
Спасибо автору за пример с однократно нажатой кнопкой. Может быть в дальнейших уроках будет пример по кнопкам: выполнение кода по отжатию кнопки.
Сообщить модератору
0 #26 AntonChip 17.11.2017 14:02
Цитирую kostya_unix:
Спасибо автору за пример с однократно нажатой кнопкой. Может быть в дальнейших уроках будет пример по кнопкам: выполнение кода по отжатию кнопки.
работать по отжатию будет если использовать этот кусок кода в последнем примере
Код:
if((PINC&(1 << PC0)) == 0) // Если кнопка нажата
{
_delay_ms(20);
while((PINC&(1 << PC0)) == 0){} // Ждем отпускания кнопки
direction ^= 1; // Переключаем состояние
}
Сообщить модератору
0 #27 kostya_unix 19.11.2017 06:02
AntonChip спасибо. Я, задавая вопрос, не обдумал его. Уже написав догадался, что после условия while() {} ,по логике ,код сработает после отпускания кнопки.
Сообщить модератору
0 #28 uuu000 19.04.2019 01:56
Не могу сообразить каким образом происходит смена направления командой direction ^= 1;
и что такое запись ^=
Сообщить модератору
0 #29 kostya_unix 19.04.2019 07:01
Цитирую uuu000:
Не могу сообразить каким образом происходит смена направления командой direction ^= 1;
и что такое запись ^=

запись ^= означает (выполняет) битовую операцию "исключающая ИЛИ" ( инверсия).
Т.е. при входе в главную функцию mai переменная direction = 0 . Битовым оператором ^ значение direction инвертируется в 1 при следующем цикле опять переводится в 0..... И так далее. Ну а в условии if(direction) // Проверка состояния// переменная проверяется на истинность или ложь (1 или 0) и уже в зависимости от это значения выполняется тот или иной оператор в условии if.
Полная запись инверсии переменной выглядит примерно так: direction = direction^1;
Как то так....
Сообщить модератору
0 #30 uuu000 19.04.2019 09:14
Действия с лог. 0 и 1 я понимаю и разобрался
с сокращенной записью.Но ведь переменная
'direction' заявлена как 'unsignet char' , т.е. обычное число без знака(0-255).
Любое число здесь это лог.1,кроме 0.
Поясните этот момент,пожайлус та.
Сообщить модератору
0 #31 kostya_unix 19.04.2019 09:52
Цитирую uuu000:
Действия с лог. 0 и 1 я понимаю и разобрался
с сокращенной записью.Но ведь переменная
'direction' заявлена как 'unsignet char' , т.е. обычное число без знака(0-255).
Любое число здесь это лог.1,кроме 0.
Поясните этот момент,пожайлуста.

Цитирую uuu000:
Действия с лог. 0 и 1 я понимаю и разобрался
с сокращенной записью.Но ведь переменная
'direction' заявлена как 'unsignet char' , т.е. обычное число без знака(0-255).
Любое число здесь это лог.1,кроме 0.
Поясните этот момент,пожайлуста.

Все правильно у Вас (все, что больше 0 то истина). В данном уроке переменная direction кроме 1 и 0 не принимает ни какие другие значения(ни какой другой цифры). ^1 =0 и 0^=1. В уроке direction ^= 1, после применения ^ она равна нулю (в нашем случае цифрой). И условие проверяет на наличие единицы или нуля. Так же в условии можно было бы проверить на равенство переменной другим значением (if lbrection ==3) к примеру)) если бы в программе она принимала бы это значение в зависимости от математики с этой переменной. Но у нас присутствует только значение 1 или 0.
Сообщить модератору
+1 #32 uuu000 19.04.2019 10:02
Извините за назойливость.Зн ачит ли это,
что само присутствие этой переменной в операторе if(direction) определяет ее как
логическое число(т.е. или лог. 0 или лог. 1).
Спасибо.
Сообщить модератору
0 #33 kostya_unix 19.04.2019 13:32
Цитирую uuu000:
Извините за назойливость.Значит ли это,
что само присутствие этой переменной в операторе if(direction) определяет ее как
логическое число(т.е. или лог. 0 или лог. 1).
Спасибо.

Вы не назойливы. А задаёте вопросы, потому что хотите понять суть, что очень хорошо! Но вернёмся к нашим баранам :) if(переменная define), я думаю, не определяет конкретно переменную как логическую единицу или ноль (само выражение (проверка в условии) -да это логическое выражение( меньше всего сейчас хочется, что бы вы запутались)). Дело в том, что мы могли бы переменной присвоить не только единицу, но и любое другое значение например 3. И при битовой операции ^ это будет уже не 3, а 252, опять же при следующей битовой операции ^ значение вернётся к 3. В условии мы проверяем - присутствует в равенстве цифра 1 или нет и исходя из этого выполняется условие.Еще раз: - сама цифра 1 в инициализации переменной не несёт истинность как true end false, но вот проверка условия if ( проверяет истинно оно или ложно). Вы не стесняйтесь задавать вопросы, я сам не семи пядей во лбу и знаю не больше вас, но общение помогает нам найти ту самую единицу ( истину) :)
Сообщить модератору
0 #34 uuu000 19.04.2019 13:41
Спасибо,вопрос снят.
Благодарю за терпение.
Сообщить модератору
+1 #35 kostya_unix 19.04.2019 13:43
P.S. Сам код программы( примера) написан очень хорошо( сам стремлюсь к такому стилю), но если бы мы ( или вы) составили условие как if(direction ==3) или if(!(direction ==3)) большого значения не имело бы, как только в читабельности кода. А так if(direction){к од1}else{код2} выглядит более изящно.
P.P.S. if(directions) можно прочитать как равна ли переменная direction истине .
Сообщить модератору
0 #36 uuu000 19.04.2019 14:15
Я попробовал разные варианты.Работа ет нормально при подстановке if(direction ==0)
или if(direction ==1).При других значениях
переменной(1,2,...255) работает только в одну сторону и не переключается кнопкой.
То есть не работает условие direction ^= 1
Сообщить модератору
0 #37 kostya_unix 19.04.2019 16:09
Цитирую uuu000:
Я попробовал разные варианты.Работает нормально при подстановке if(direction ==0)
или if(direction ==1).При других значениях
переменной(1,2,...255) работает только в одну сторону и не переключается кнопкой.
То есть не работает условие direction ^= 1

Ну я думаю, что при direction ^= 2 в условии нужно проверять if(direction ==2); или не равно 2 (if (!(direction ==2)) и так же при любых других( в пределах бытности переменной) значениях. Ведь (грубо говоря) при операции ^ первоначальное значение переменной принимает другое значение ( переключается), а при следующем проходе через ^ принимает начальное значение ( опять переключается в исходное состояние). Может быть я выражаюсь не корректно. Прошу нам подсказать более грамотных форумчан.
Сообщить модератору

Авторизация