Arduino прерывания по таймеру 0. Использование прерываний в Arduino

С учетом всего сказанного напишем программу, переключающую светодиод. В данном случае она будет это делать по событию переполнения таймера‑счетчика Timer 1 (вектор у нас обозначен: TIM1_OVF). Так как счетчик 16‑разрядный, то событие переполнения будет возникать при каждом 65 536‑м импульсе входной частоты. Если мы зададим коэффициент деления тактовой частоты на входе Timer 1 равным 64, то при 4 МГц частоты генератора мы получим примерно 1 Гц: 4 000 000/64/65 536 = 0,953674 Гц.

Это не совсем то, что нам требуется, и к тому же частота неточно равна одному герцу. Для того чтобы светодиод переключался точно раз в полсекунды (т. е. период его был равен секунде), программу придется немного усложнить, загружая каждый раз в счетные регистры определенное значение, которое рассчитывается просто: если период одного тактового импульса таймера равен 16 мкс (частота 4 000 000/64), то для получения 500 000 микросекунд надо отсчитать таких импульсов 31 250. Так как счетчик суммирующий, а прерывание возникает при достижении числа 65 536, то нужно предварительно загружать в него необходимое число 65 536 – 31250 = 34 286.

Это не единственный способ, но наиболее универсальный, годящийся для всех таймеров. Кстати, именно таким способом реализован отсчет времени в Arduino (см. главу 21 ). Иной способ – использовать прерывание по достижению определенного числа, загруженного в регистр сравнения А или В . Как это делается, мы увидим далее в этой главе. Для того чтобы осуществить само переключение из красного в зеленый, нам придется поступить как раньше, т. е. по каждому событию переполнения перебрасывать два бита в регистре PortD .

Полностью программа тогда будет выглядеть так:

Я не буду комментировать подробно каждый оператор, т. к. это заняло бы слишком много места. После выполнения всех команд начальной установки МК зацикливается, но бесконечный цикл будет прерываться возникновением прерывания – здесь все аналогично операционной системе Windows, которая также представляет собой бесконечный цикл ожидания событий. Как вы узнаете из последующих глав, в Arduino такой цикл – одна из главных составляющих любой программы, как раз потому что прерывания там почти не используются. Внутрь бесконечного цикла здесь можно поставить знакомую команду sleep , без дополнительных настроек режима энергопотребления она будет экономить около 30 % питания. А вот сэкономить еще больше просто так не получится, поскольку придется останавливать процессорное ядро, и таймер перестанет работать.

Заметки на полях

Кстати, а как остановить запущенный таймер, если это потребуется? Очень просто: если обнулить регистр TCCR1B (тот, в котором задается коэффициент деления тактовой частоты), то таймер остановится. Чтобы запустить его опять с коэффициентом 1/64, нужно снова записать в этот регистр значение 0b00000011.

Обратите внимание, что оператор reti (окончание обработки прерывания) при обработке прерывания таймера встречается дважды – это вполне нормальный прием, когда подпрограмма разветвляется. Можно, конечно, и пометить последний оператор reti меткой, и тогда текст процедуры стал бы неотличим от первого варианта, но так будет корректнее.

Обратите также внимание на форму записи ldi temp, (1 << TOIE1) . Поскольку бит, обозначаемый как TOIE1, в регистре TIMSK имеет номер 7, то эта запись эквивалентна записи ldi temp,0b10000000 – можно писать и так, и так, и еще кучей разных способов. Например, для запуска таймера с коэффициентом 1/64 требуется, как видно из текста программы, установить младшие два бита регистра TCCR1B. Здесь мы устанавливаем их в temp напрямую, но поскольку эти биты называются CS11 и CS10, то можно записать так:

ldi temp, (1 << CS11) I (1 << CS10)

или даже так:

ldi temp, (3 << CS10)

Подробно этот способ записи приведен в описании AVR‑ассемблера на сайте Atmel .

Подробности

В этой программе есть один тонкий момент, связанный с загрузкой счетных регистров таймера. При чтении и записи 16‑разрядных регистров Timer 1 их содержимое может измениться в промежутке между чтением или записью отдельных 8‑разрядных «половинок» (ведь, например, в данном случае таймер продолжает считать, пока идет обработка прерывания). Потому в 16‑разрядных таймерах AVR предусмотрен специальный механизм чтения и записи таких регистров. При записи первым загружается значение старшего байта, которое автоматически помещается в некий (недоступный для программиста) буферный регистр. Затем, когда поступает команда на запись младшего байта, оба значения объединяются, и запись производится одновременно в обе «половинки» 16‑разрядного регистра. Наоборот, при чтении первым должен быть прочитан младший байт, при этом значение старшего автоматически фиксируется помещением в тот же буферный регистр, и при следующей операции чтения старшего байта его значение извлекается оттуда. Таким образом и при чтении значения оба байта соответствуют одному и тому же моменту времени.

С Таймером 1 и Таймером 2 связаны три вектора прерывания:

Прерывание переполнения таймера - Timer Overflow Interrupt (INT00,2000H);

Прерывание переполнения Таймера 2 - Timer 2 Overflow Interrupt (INT12,2038H);

Прерывание фиксатора Таймера 2 - Timer 2 Capture Interrupt (INT11,2036H).

Регистр IOS1 содержит флажки, которые указывают какое событие вызвало прерывание. Обращение к битам регистра IOS1 по командам JBC или JBS обнуляет биты 0-5. По этой причине, мы рекомендуем чтобы Вы копировали содержимое регистра IOS1 в промежуточный регистр и затем выполняли команды проверки разрядов типа JBC или JBS на промежуточном регистре.

1.3.1. Прерывание переполнения таймера

И Таймер 1 и Таймер 2 могут вызвать прерывание переполнения Таймера (INT00). Установите INT_MASK.0 ,чтобы разрешить это прерывание. Установите или IOC1.2 (Таймер 1) или IOC1.3 (Таймер 2) чтобы выбрать источник прерывания. Когда происходит переполнение, устанавливается флажок состояния в регистре IOS1. Переполнение Таймера 1 устанавливает IOS1.5, а переполнение Таймера 2 устанавливает IOS1.4.

Input/Output Control Register 1

HWindow 0 (Write), HWindow 15 (Read)

Input/Output Status Register 1

Hwindow 0 (Write), HWindow 15 (Read)

1.3.2. Прерывание переполнения Таймера 2

Таймер 2 может генерировать прерывание переполнения Таймера 2 (INT12, адрес вектора - 2038H) вместо стандартного прерывания переполнения Таймера. Это прерывание разрешается установкой INT_MASK1.4. Переполнение Таймера 2 устанавливает IOS1.4. Таймер 2 может генерировать прерывание переполнения Таймера 2 или на границе 7FFFH/8000H или на границе 0FFFFH/0000H. Переполнение может происходить в любом направлении. IOC2.5 выбирает границу переполнения. Когда IOC2.5 установлен, Таймер 2 прерывается на границе 7FFFH/8000H. Иначе, он прерывается на границе 0FFFFH/0000H.

1.3.3. Прерывание фиксатора Таймера 2

Положительный переход на контакте T2CAPTURE (P2.7) заставляет значение Таймера 2 загружаться в регистр T2CAPTURE . Это событие генерирует прерывание фиксатора Таймера 2 (INT11), если установлен INT_MASK1.3 и T2CAPTURE утверждается в течение более двух времен состояний.

Timer 2 Capture Register

Hwindow 15(Read /Write)

1.4. Предосторожности при работе с Таймерами

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

Будьте осторожны при записи в регистры таймера TIMER1 и TIMER2:

Изменение значения TIMER1 после инициализизации HSI модуля может разрушить относительные ссылки между HSI событиями. Также, изменение значения соответствующего таймера (TIMER1 или TIMER2), после инициализизации HSO модуля, может заставить HSO пропускать запрограммированные события или выполнять их в неправильном порядке.

Конфигурируйте Таймер 2 для функционирования в нормальном режиме (не в быстром режиме приращения):

Так как для полного сканирования CAM, HSO требует восьми времен состояния, Таймер 2, когда он используется как датчик времени для HSO, должен функционировать в нормальном режиме приращения (не в быстром режиме приращения) .

Очистите бит FAST_T2_ENA(IOC2.0) для выбора нормального режима работы таймера.

Конфигурируйте Таймер 2 для счета только в одном направлении.

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

Очистите бит T2UD_ENA(IOC2.1), чтобы сконфигурировать Таймер 2 как суммирующий счетчик.

Используйте предостережение при сбросе Таймера 2.

Не сбрасывайте Tаймер 2 до того, как его значение достигнет самого наибольшего времени, запрограммированного в CAM. CAM задерживает ожидание события до соответствующего времени. Если запрограммированное значение Таймера 2 никогда не достигается, событие будет оставаться отложенным, пока устройство не сбросится или CAM не очистится.

Когда Таймер 2 сконфигурирован для сброса внешним контактом, события программы должны происходить когда Таймер 2 равен единице, а не нулю:

Когда Таймер 2 сконфигурирован, чтобы сбрасываться внешним контактом сброса (IOC0.3 =1), программные события не должны происходить, когда Таймер 2 равен нулю. Если HSI.0 или T2RST (P2.3) сбрасывают Таймер 2, событие может не произойти. Внешние контакты сбрасывают Таймер 2 асинхронно, и Таймер 2 может увеличиться до 1 до того, как HSO может сравнить и распознать CAM запись. Программируйте события так, чтобы они происходили, когда Таймер 2 равен 1, это гарантирует,что HSO имеет достаточное время, чтобы распознать команду, записанную в CAM .

ФРАГМЕНТ ПРОГРАММЫ ИЗУЧЕНИЯ ПРОГРАММИРОВАНИЯ ТАЙМЕРОВ

;**Исследование пpoгpаммных задеpжек с использованием Timer1*

ldb wsr,#15 ; Пеpеключиться в HWindow 15

ld timer1,#0c000h ; Загpузить значение счетчика

ldb wsr,#0 ; Пеpеключиться в HWindow 0

jbc ios1, 5, $ ; Ожидание пеpеполнения Timer

;***********************************************************

/* ISR_Blink Те же фрукты, только в другом ракурсе Мигание светодиодом с использованием механизма прерываний (переполнение таймера/счетчика 2) */ volatile long mks100; volatile long ms10; volatile int cntr; long tmillis,tms10=0; char flip; void setup() { mks100 = 0; // счетчик сотен микросекунд, переполнение счетчика примерно через 5 суток ms10 = 0; // счетчик десятков миллисекунд, переполнение счетчика примерно через 16 месяцев cntr = 0; flip = 0; // мигаем стандартным светодиодом. // На большинстве плат Arduino он подключен к 13-му выводу: pinMode(13, OUTPUT); Serial.begin(9600); // Включаем нужный нам режим таймера/счетчика - нормальный TCCR2A = 0; //нормальный режим (по умолчанию 1 - ШИМ с коррекцией фазы?) // Предделитель таймера/счетчика настраиваем на 16 - // это позволит "тикать" таймером каждую микросекунду // (в предположении, что сердце микроконтроллера стучит с // частотой 16.000.000 ударов в секунду) TCCR2B = 2; // 010 - fclk/8 (по умолчанию 100 - fclk/64) //TCCR2B = 7; // 111 - fclk/1024 (по умолчанию 100 - fclk/64) TCNT2=59;//55; TIMSK2 |= (1 << TOIE2); // разрешаем прерывание таймера/счетчика 2 по переполнению } ISR(TIMER2_OVF_vect) { // прежде всего взводим счетчик TCNT2=59;//55; // прошли очередные 100 мксек - увеличиваем счетчик сотен микросекунд mks100++; // if(mks100%100==0) ms10++; cntr++; // прошли очередные 10 мсек? - увеличиваем счетчик десятков миллисекунд if(cntr>99) { ms10++; cntr = 0; } } void loop() { if (ms10>tms10) { tmillis = millis(); tms10 = ms10; if (tms10%100==0) { if(flip) digitalWrite(13, HIGH); // set the LED on else digitalWrite(13, LOW); // set the LED off flip = !flip; } if (tms10%1000==0) { // выполнение каждые 10 сек Serial.print(tmillis,DEC); Serial.print(" milliseconnds, "); Serial.println(tms10,DEC); } } }

  • Categorized in
  • Tagged with

One comment

В этом уроке мы поговорим о прерываниях. Как понятно из названия, прерывание это событие, которое приостанавливает выполнение текущих задач и передает управление обработчику прерывания. Обработчик прерывания — это функция. Например: если вы написали скетч по управлению мотором или просто плавно зажигаете и гасите светодиод в цикле, то нажатие на кнопку может не обрабатываться, так как Arduino в данный момент занята другой частью кода. Если же использовать прерывание, то такой проблемы не возникнет, так как прерывания имеют более высокий приоритет.

В ардуино есть прерывания по таймеру и аппаратное прерывание. Далее я подробнее расскажу что это, как это использовать и зачем оно вам нужно.

В этом уроке используется:

Аппаратные прерывания

В Arduino имеется 4 вида аппаратных прерываний. Отличаются они сигналом на контакте прерывания.

  • Контакт прерывания притянут к земле. Ардуино будет выполнять обработчик прерывания пока на пине прерывания будет сигнал LOW.
  • Изменение сигнала на контакте прерывания. Ардуино будет выполнять обработчик прерывания каждый раз когда на пине прерывания будет изменяться сигнал.
  • Изменение сигнала на контакте прерывания от LOW к HIGH. Обработчик прерывания исполняется только при изменении низкого сигнала на высокий.
  • Изменение сигнала на контакте прерывания от HIGH к LOW. Обработчик прерывания исполняется только при изменении высокого сигнала на низкий.

Если прерывание ожидает нажатия кнопки, то это может стать проблемой из-за дребезга контактов. В мы уже говорили о дребезге контактов. Тогда мы использовали функцию но в прерываниях данная функция не доступна. Поэтому нам придется подавить дребезг контактов немного усложнив схему подключения кнопки к пину прерывания. Для этого понадобится резистор на 10 КОм, конденсатор на 10 микрофарад,
и инвертирующий триггер шмитта. Подключается все по следующей схеме:

В Arduino Uno есть два пина, поддерживающие прерывания. Это цифровые пины 2 (int 0) и 3 (int 1). Один из них мы и будем использовать в нашей схеме.

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


Схема кажется сложной и запутанной, но это не так. Мы подключаем кнопку прерывания к пину Arduino D2, используя аппаратное подавление дребезга контактов. К аналоговому пину A0 мы подключаем инфракрасный дальномер. И к пинам D9, D10 и D11 мы подключаем светодиоды через резисторы на 150 Ом. Мы выбрали именно эти контакты для светодиодов, потому что они могут выдавать ШИМ сигнал.Теперь рассмотрим скетч:

// Назначение прерывания int buttonInt = 0; // Переменные с пинами светодиодов int yellowLed = 11; int redLed = 10; int greenLed = 9; int nullLed = 6; volatile int selectedLed = greenLed; // Инфракрасный дальномер int distPin = 0; void setup () { // Устанавливаем режимы пинов pinMode(redLed, OUTPUT); pinMode(greenLed, OUTPUT); pinMode(yellowLed, OUTPUT); pinMode(nullLed, OUTPUT); // Устанавливаем прерывание attachInterrupt(buttonInt, swap, RISING); } // Обработчик прерывания void swap() { if(selectedLed == greenLed) selectedLed = redLed; else if(selectedLed == redLed) selectedLed = yellowLed; else if(selectedLed == yellowLed) selectedLed = nullLed; else selectedLed = greenLed; } void loop () { // Получаем данные с дальномера int dist = analogRead(distPin); int brightness = map(dist, 0, 1023, 0, 255); // Управляем яркостью analogWrite(selectedLed, brightness); }

// Назначение прерывания

int buttonInt = 0 ;

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

int yellowLed = 11 ;

int redLed = 10 ;

int greenLed = 9 ;

int nullLed = 6 ;

volatile int selectedLed = greenLed ;

// Инфракрасный дальномер

int distPin = 0 ;

void setup () {

// Устанавливаем режимы пинов

pinMode (redLed , OUTPUT ) ;

pinMode (greenLed , OUTPUT ) ;

pinMode (yellowLed , OUTPUT ) ;

pinMode (nullLed , OUTPUT ) ;

// Устанавливаем прерывание

attachInterrupt (buttonInt , swap , RISING ) ;

// Обработчик прерывания

void swap () {

if (selectedLed == greenLed )

selectedLed = redLed ;

else if (selectedLed == redLed )

selectedLed = yellowLed ;

else if (selectedLed == yellowLed )

Когда у меня возникло желание вести разработку под Arduino, я столкнулся с несколькими проблемами:
  • Выбор модели из списка доступных
  • Попытки понять, чего мне понадобится кроме самой платформы
  • Установка и настройка среды разработки
  • Поиск и разбор тестовых примеров
  • «Разборки» с экраном
  • «Разборки» с процессором

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

Выбор платформы

Перед началом программирования под железяку требуется в начале ее купить. И тут я столкнулся с первой проблемой: оказалось, что разных *дуин довольно много. Тут есть и широкая линейка Arduino и примерно такая же широкая Freeduino и другие аналоги. Как оказалось, большой разницы, что именно брать, нет. То есть одни из этих устройств чуть быстрее, другие чуть медленнее, одни дешевле, другие - дороже, но основные принципы работы практически не отличаются. Отличия появляются практически только при работе с регистрами процессора и то я далее объясню, как по возможности избежать проблем.

Я выбрал платформу Arduino Leonardo как самую доступную по цене и имеющуюся на тот момент в Интернет магазине, в котором я всё и заказывал. Отличается она от остальной линейки тем, что у нее на борту установлен только один контроллер, который занимается и работой с USB-портом и выполнением тех самых задач, которые мы на наше устройство повесим. У этого есть свои плюсы и минусы, но напороться на них при первоначальном изучении не получится, поэтому забудем о них пока. Оказалось, что она подключается к компьютеру через micro-USB, а не USB-B, как вроде бы большинство других. Это меня несколько удивило, но и обрадовало, потому что я, как владелец современного устройства на Android"е без этого кабеля вообще из дома не выхожу.
Да, питается почти любая *дуино-совместимая железяка несколькими способами, в том числе, от того же кабеля, через который программируется. Также один светодиод почти у всех плат размещен прямо на плате контроллера, что позволяет начать работу с устройством сразу после покупки, даже не имея в руках вообще ничего, кроме совместимого кабеля.

Спектр задач

Я думаю, что прежде, чем взяться за как таковое написание чего-то под железяку, интересно понять, что на ней можно реализовать. С Ардуино реализовать получится почти что угодно. Системы автоматизации, идеи для «умного дома», контроллеры управления чем-нибудь полезным, «мозги» роботов… Вариантов просто уйма. И сильно помогает в этом направлении довольно широкий набор модулей расширения, чрезвычайно просто подключаемых к плате контроллера. Список их довольно длинный и многообещающий, и ищутся они в Интернете по слову shield. Из всех этих устройств я для себя посчитал самым полезным LCD экран с базовым набором кнопок, без которого по моему скромному мнению заниматься какими бы то ни было тренировочными проектами совершенно неинтересно. Экран брался отсюда , еще там есть его описание, а также с приведенной страницы ведут ссылки на официальный сайт производителя.

Постановка задачи

Я как-то привык при получении в свои руки нового инструмента сразу ставить себе какую-нибудь в меру сложную и абсолютно ненужную задачу, браво ее решать, после чего откладывать исходник в сторонку и только потом браться за что-нибудь по настоящему сложное и полезное. Сейчас у меня под рукой был очень похожий на составную часть мины из какого-нибудь голливудского кинофильма экран со всеми необходимыми кнопками, с которым надо было научиться работать, а также очень хотелось освоить работу с прерываниями (иначе в чем смысл использования контроллера?) поэтому первым же, что пришло в голову, оказалось написать часы. А поскольку размер экрана позволял, то еще и с датой.

Первые шаги

Вот мне наконец приехали все купленные компоненты и я их собрал. Разъем экрана подключился к плате контроллера как родной, плата была подключена к компьютеру… И тут мне сильно помогла вот эта статья. Повторять то же самое я не буду.

Скрытый текст

Единственное скажу, что вспомнив молодость (а точнее первый «проект», собранный во время изучения радиоэлектроники во Дворце пионеров - мультивибратор с двумя светодиодами), я нашел 2 светодиода и поправил приведенный в статье пример и начал мигать ими:).

«Вторые шаги»

Следующим закономерным вопросом для меня стало «как работать с LCD экраном?». Официальная страница устройства любезно предоставила мне ссылки на архив, в котором оказалось 2 библиотеки с замечательными примерами. Только не сказала, что с этим всем делать. Оказалось, что содержимое нужно просто распаковать в папку libraries среды разработки.

После этого можно открывать пример GuessTheNumber.pde и заливать в плату аналогично примеру с мигающим светодиодом. Однако лично у меня после прошивки экран остался равномерно светящимся и без единой буквы. После недолгих поисков проблемы оказалось, что надо было просто подкрутить отверткой единственный имеющийся на плате экрана потенциометр чтобы задать нормальное значение контрастности.

Использованного в примере набора команд в принципе достаточно для простой работы с экраном, но если захочется чего-то большего, то можно открыть исходный текст библиотек LCDKeypad и LiquidCrystal и посмотреть что там есть еще.

Архитектура программы

Основная задача часов - считать время. И делать это они должны точно. Естественно, что без использования механизма прерываний никто не сможет гарантировать, что время считается с достаточной точностью. Поэтому вычисление времени точно нужно оставить им. Всё остальное можно вынести в тело основной программы. А этого самого «остального» у нас довольно много - вся работа с интерфейсом. Можно было бы поступить иначе, создать стек событий, создаваемый в том числе и механизмом обработки прерываний, а обрабатываемый внутри основного приложения, это позволило бы например заниматься обновлением экрана не чаще, чем раз в пол секунды (или по нажатию кнопки) но я посчитал это лишним для такой простой задачи, поскольку кроме перерисовки экрана процессору всё равно заняться нечем. Поэтому всё свободное время программа перечитывает состояние кнопок и перерисовывает экран.
Проблемы, связанные с таким подходом
Периодические изменения экрана
Очень хотелось сделать мигающие двоеточия между часами, минутами и секундами, чтобы как в классических часах они пол секунды горели, а пол - нет. Но поскольку экран перерисовывается всё время, надо было как-то определять в какую половину секунды их рисовать, а в какую - нет. Самым простым оказалось сделать 120 секунд в минуте и рисовать двоеточия каждую нечетную секунду.
Мелькания
При постоянной перерисовки экрана становятся заметны мелькания. Чтобы этого не возникало, имеет смысл не очищать экран, а рисовать новый текст поверх старого. Если сам текст при этом не меняется, то мелькания на экране не будет. Тогда функция перерисовки времени будет выглядеть вот так:
LCDKeypad lcd; void showTime(){ lcd.home(); if (hour<10) lcd.print("0"); // Случай разной длины приходится обрабатывать руками lcd.print(hour,DEC); // английские буквы и цифры ОНО пишет само, русские буквы нужно определять программисту if (second %2) lcd.print(" "); else lcd.print(":"); // вот они где используются, мои 120 секунд в минуте if (minute<10) lcd.print("0"); lcd.print(minute,DEC); if (second %2) lcd.print(" "); else lcd.print(":"); if (second<20) lcd.print("0"); lcd.print(second / 2,DEC); lcd.print(" "); lcd.setCursor(0,1); // переходим в координаты x=0, y=1 то есть в начало второй строки lcd.print(" "); lcd.print(day,DEC); lcd.print(months); // месяцы мне было приятнее нумеровать от 1 до 12, а массив с названиями от 0 до 11 lcd.print(year,DEC); }
Работа с кнопками
Похожая ситуация с кнопками. Нажатая кнопка числится нажатой при каждом прогоне программы, поэтому за одно нажатие может обработаться любое количество раз. Приходится заставлять программу ждать «отжимания» отдельно. Начнем основную программу так:
int lb=0; // переменная хранит старое значение кнопки void loop(){ // main program int cb,rb; // определим 2 переменные, для реально нажатой кнопки и для той, которую будет считать нажатой программа cb=rb=lcd.button(); // в начале можно считать, что это одна и та же кнопка if (rb!=KEYPAD_NONE) showval=1; // переменная указывает, что пока нажата кнопка не надо мигать тому, что настраивается if (cb!=lb) lb=cb; // если состояние кнопки изменилось, запоминаем новое, else cb=KEYPAD_NONE; // иначе говорим программе, что все кнопки давно отпущены.

Работа с таймером

Собственно, чтобы вся работа с таймером состоит из двух важных компонентов:
  • Инициализации механизма прерываний от таймера в удобном для нас режиме
  • Собственно, обработки прерывания
Инициализация таймера
Для того, чтобы начать получать нужные нам прерывания, нужно настроить процессор таким образом, чтобы он начал их генерировать. Для этого нужно установить нужные нам регистры в нужные значения. Какие именно регистры и в какие именно значения нужно устанавливать, нужно смотреть в… даташите на процессор:(. Честно говоря, сильно надеялся, что эту информацию можно будет найти в документации на саму Arduino, но нет, это было бы слишком просто. Мало того, для разных процессоров серии номера битов могут отличаться. И я сам лично натолкнулся на то, что попытка установки битов в соответствие с даташитом на соседний процессор привели к плачевным результатам… Но тем не менее, всё не настолько печально, как может показаться, поскольку для этих битов есть еще и имена, они более-менее общие для разных процессоров. Поэтому использовать цифровые значения мы не будем, только имена.

Для начала вспомним, что в микроконтроллерах AVR таймеров несколько. Нулевой используется для вычисления значений delay() и тому подобных вещей, поэтому его мы использовать не будем. Соответственно, используем первый. Поэтому далее в обозначении регистров часто будет проскакивать единичка, для настройки скажем второго таймера нужно там же поставить двойку.

Вся инициализация таймера должна происходить в процедуре setup(). Состоит она из помещения значений в 4 регистра, TCCR1A, TCCR1B, TIMSK1, OCR1A. Первые 2 из них называются «регистрами A и B управления таймера-счетчика 1». Третий - «регистр маски прерываний таймера/счетчика 1», и последний - «регистр сравнения A счетчика 1».

Команды для установки битов принято использовать следующие (понятное дело, что вариантов много, но чаще всего используются именно эти):
BITE |= (1 << POSITION)
то есть вдвигаем «1» на POSITION бит справо налево и проводим логическое «или» между целевым и полученным байтами. При включении контроллера значения всех этих регистров содержат 0, поэтому о нулях мы просто забываем. Таким образом, после выполнения следующего кода

A=0; A |= (1 << 3)

Значение A станет 8.

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

  • Чтобы таймер перешел в режим работы CTC (то есть в режим счета со сбросом после совпадения, «Clear Timer on Compare match»), судя по даташиту это достигается установкой битов WGM12:0 = 2, что само по себе означает установить биты со второго по нулевой в значение «2», то есть, «010», команда TCCR1B |= (1 << WGM12) ;
  • Поскольку 16МГц (а именно такая частота у кварцевого резонатора на моей плате) это много, выбрать максимально возможный делитель, 1024 (то есть только каждый 1024-ый такт будет доходить до нашего счетчика), CS12:0=5
  • Сделать так, чтобы прерывание приходило при совпадении с регистром A, для данного счетчика TIMSK1 |= (1 << OCIE1A)
  • Указать при достижении какого именно значения вызывать обработку прерывания, это значение помещается в тот самый регистр A счетчика 1 (целиком его название OCR1A), прерывание по совпадении с которым мы включали предыдущим пунктом.

Как посчитать, до скольки нам нужно проводить вычисления? - Легко, если тактовая частота кварцевого резонатора 16МГц, то при достижении счетчиком значения 16000 прошла бы секунда, если бы коэффициент деления был 1. Так как он 1024, то мы получаем 16000000/1024=15625 в секунду. И всё бы хорошо, но нам нужно получать значения каждые пол секунды, а 15625 на 2 не делится. Значит мы до этого ошиблись и придется взять коэффициент деления поменьше. А следующий по уменьшению у нас 256, что дает 62500 тиков в секунду или 31250 за пол секунды. Счетчик у нас 16-тибитный, поэтому может досчитать до 65536. Иными словами, нам его хватает и на пол секунды и на секунду. Лезем в даташит, потом в исходник и исправляем на CS12:0=4 , а после этого OCR1A = 31249; (как я понял, один такт уходит то ли на на сброс, то ли еще куда, поэтому встречаются советы сбросить еще единичку с полученной цифры).

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

Собственно, сейчас оно состоит из зарезервированного слова ISR и указания конкретного прерывания, которое эта функция обрабатывает в скобках. А внутри у этой функции как видите нет ничего фантастического. Даже обязательное RETI как видите за нас автоматом вставляет компилятор.

ISR(TIMER1_COMPA_vect) { digitalWrite(LEDPIN, !digitalRead(LEDPIN)); // LEDPIN=13. Эта строка мигает светодиодом на плате. Удобно и прикольно:) second++; if ((second %2) && lastshowval) { // эта и следующие 7 строк нужны только для того, lastshowval = 0; // чтобы можно было добиться этого забавного эффекта, как на аппаратных часах, showval = 0; // когда в режиме настройки скажем минут, значение настраиваемого параметра мигает } if (!(second %2) && !lastshowval){ // только при отпущенных кнопках, а пока кнопки нажаты, оно просто горит. lastshowval = 1; showval = 1; } if (second>=120) { // опять мои 120 секунд в минуте. Ну а кому сейчас легко? second-=120; minute++; if (minute>=60){ minute-=60; hour++; if (hour>=24){ hour-=24; day++; if (daylarge(day,month,year) // возвращает true если значение дня // больше максимально возможного для этого месяца этого года.) { day=1; month++; if (month>12){ month = 1; year++; } } } } } }

Надеюсь, эта статья будет кому-нибудь полезна, потому что достаточно подробных инструкций на тему работы с прерываниями от таймера на русском языке довольно мало.