Выработка прерывания по совпадению таймера ардуино. Прерывание таймера по переполнению

С учетом всего сказанного напишем программу, переключающую светодиод. В данном случае она будет это делать по событию переполнения таймера‑счетчика 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

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

Прерывание по таймеру

В данном разделе будет описано на использование программного таймера 2 для периодических прерываний. Исходная идея состояла в использовании этого таймера для генерации частоты биений в звуковых проектах Arduino. Чтобы выводить тон или частоту нам нужно переключать порт ввода-вывода на согласованной частоте. Это можно делать с использованием циклов задержки. Это просто, но означает, что наш процессор будет занят, ничего не выполняя, но ожидая точного времени переключения вывода. С использованием прерывания по таймеру мы можем заняться другими делами, а вывод пусть переключает ISR, когда таймер подаст сигнал, что время пришло.

Необходимо только установить таймер, чтобы подавал сигнал с прерыванием в нужное время. Вместо прокрутки бесполезного цикла для задержки по времени, главная программа может делать что-то другое, например, контролировать датчик движения или управлять электроприводом. Что бы ни требовалось проекту, больше нам не нужно процессорное время для получения задержек. Далее будет описано ISR в общем только то, что касается прерываний таймера 2. Более подробно об использовании прерываний в процессорах AVR можно прочитать в руководстве пользователя avr-libc(англ). На данном этапе не требуется полного понимания, но, в конечном счете, вы можете захотеть получить возможность ускорить использование прерываний, раз это важный инструмент для приложений на микроконтроллерах.

Таймеры на Arduino

Arduino пользуется всеми тремя таймерами ATMega168:

1) Таймер 0 (Системное время, ШИМ 5 and 6)

Используется для хранения счетчика времени работы программы. Функция millis() возвращает число миллисекунд с момента запуск программы, используя ISR глобального приращения таймера 0. Таймер 0 также используется для реализации ШИМ на выводах 5 и 6.

2) Таймер 1 (ШИМ 9 и 10)

Используется для реализации ШИМ для цифровых выводах 9 и 10.

3)Таймер 2 (ШИМ 3 и 11)

Используется для управления выходами ШИМ для цифровых выводов 3 и 11.

Хотя все таймеры используются, только Таймер 0 имеет назначенную таймеру ISR. Это означает, что можно захватить Таймер 1 и/или Таймер2 под свои нужды. Однако в результате нельзя будет использовать ШИМ на некоторых портах ввода-вывода. Если планируется использовать ШИМ, необходимо помнить об этом.

Загрузка микроконтроллера прерываниями

Чтобы дать представление об эффекте, предположим, что таймер ISR запускался бы каждые 20 мкс. Процессор, работающий на 16 МГц, может выполнить около 1 машинной команды каждые 63 мс или около 320 машинных команд для каждого цикла прерывания (20 мкс). Предположим также, что исполнение каждой строки программы на С может занять много машинных команд. Каждая инструкция, используемая в ISR, отнимает время, доступное для исполнения любой другой программы. Если бы ISR использовала около 150 машинных циклов, было бы потрачено половина доступного процессорного времени. При активных прерываниях главная программа откладывалась бы около? времени, занимаемого ей в других случаях. 150 машинных команд - не очень большая программа на С, поэтому необходимо быть внимательны.

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

Измерение загрузки прерываниями

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

Таймер не был установлен в режим, когда он перезагружается автоматически. Это значит, что ISR должна перезагрузить таймер для следующего интервала счета. Было бы точнее иметь автоматически перезагружаемый таймер, но, используя этот режим, можно измерить время, проводимое в ISR, и соответственно исправить время, загружаемое в таймер. Ключ в том, что при помощи этой коррекции при разумной точности, также получаем и число, показывающее, сколько времени проводим в ISR.

Метод заключается в том, что таймер хранит время, даже если он переполнен и прерван. В конце ISR можно захватить текущее значение счетчика таймера. Это значение представляет то время, которое он отнял у разработчика до следующей точки программы. Это суммарное время, затраченное на переход в процедуру прерывания и выполнение программы в ISR. Небольшая ошибка будет оттого, что не подсчитывается время, затраченное на команду перезагрузки таймера, но эту ошибку можно исправить эмпирически. Фактически именно поэтому используется в формуле подсчета загружаемого значения 257 вместо 256. Было обнаружено опытным путем, что это дает лучший результат. Лишний такт компенсирует команду перезагрузки таймера.

Использование прерываний в Arduino

Часто при работе с проектами на микроконтроллерах требуется запускать фоновую функцию через равные промежутки времени. Это часто реализуется установкой аппаратного таймера для выработки прерывания. Это прерывание запускает программу обработки прерываний (Interrupt Service Routine, ISR) для управления периодическим прерыванием. В настоящей статье я описываю установку 8-битного таймера 2 для выработки прерываний на микроконтроллере ATMega168 Arduino. Я пройдусь по этапам, требуемым для установки программы обработки прерываний и внутри нее самой. Arduino подразумевает процессор ATMega168. Этот микроконтроллер имеет несколько систем ввода-вывода, которые доступны каждому пользователю Arduino, поскольку библиотека Arduino облегчает их использование. К примеру, цифровой ввод-вывод, ШИМ, аналого-цифровые входы и последовательный порт. ATMega168 также имеет три внутренних аппаратных таймера. Хотя библиотека Arduino позволяет использовать некоторые свойства таймеров, нельзя напрямую использовать таймер для выработки периодических прерываний.

таймер память цоколевка stepper

/* 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

С счетчиком итераций главного цикла мы разобрались и выяснили, что для точных временных отсчетов он не годится совершенно — выдержка плавает, да и считать ее сложно. Что делать?

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

И такой счетчик есть, даже не один — это периферийные таймеры. В AVR их может быть несколько штук да еще с разной разрядностью. В ATmega16 три, в ATmega128 четыре. А в новых МК серии AVR может даже еще больше, не узнавал.

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

Что умееют таймеры

  • Тикать с разной скоростью, подсчитывая время
  • Считать входящие извне импульсы (режим счетчика)
  • Тикать от внешнего кварца на 32768гц
  • Генерировать несколько видов ШИМ сигнала
  • Выдавать прерывания (по полудесятку разных событий) и устанавливать флаги

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

Источник тиков таймера
Таймер/Счетчик (далее буду звать его Т/С) считает либо тактовые импульсы от встроенного тактового генератора, либо со счетного входа.

Погляди внимательно на распиновку ног ATmega16, видишь там ножки T1 и T0?

Так вот это и есть счетные входы Timer 0 и Timer 1. При соответствующей настройке Т/С будет считать либо передний (перепад с 0-1), либо задний (перепад 1-0) фронт импульсов, пришедших на эти входы.

Главное, чтобы частота входящих импульсов не превышала тактовую частоту процессора, иначе он не успеет обработать импульсы.

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

Зачем это вообще надо? Да хотя бы организовать часы реального времени. Повесил на них часовой кварц на 32768 Гц да считай время — за секунду произойдет 128 переполнений (т.к. Т/С2 восьми разрядный). Так что одно переполнение это 1/128 секунды. Причем на время обработки прерывания по переполнению таймер не останавливается, он также продолжает считать. Так что часы сделать плевое дело!

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

То есть еще до попадания в счетный регистр частота импульсов будет делиться. Делить можно на 8, 32, 64, 128, 256, 1024. Так что если повесишь на Т/С2 часовой кварц, да пропустишь через предделитель на 128, то таймер у тебя будет тикать со скоростью один тик в секунду.

Удобно! Также удобно юзать предделитель когда надо просто получить большой интервал, а единственный источник тиков это тактовый генератор процессора на 8Мгц, считать эти мегагерцы задолбаешься, а вот если пропустить через предделитель, на 1024 то все уже куда радужней.

Но тут есть одна особенность, дело в том, что если мы запустим Т/С с каким нибудь зверским предделителем, например на 1024, то первый тик на счетный регистр придет не обязательно через 1024 импульса.

Это зависит от того в каком состоянии находился предделитель, а вдруг он к моменту нашего включения уже досчитал почти до 1024? Значит тик будет сразу же. Предделитель работает все время, вне зависимости от того включен таймер или нет.

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

Например первый таймер работает на выводе 1:64, а второй на выводе 1:1024 предделителя. У второго почти дотикало в предделителе до 1024 и вот вот должен быть тик таймера, но тут ты взял и сбросил предделитель, чтобы запустить первый таймер точно с нуля. Что произойдет? Правильно, у второго делилка тут же скинется в 0 (предделитель то единый, регистр у него один) и второму таймеру придется ждать еще 1024 такта, чтобы получить таки вожделенный импульс!

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

Для сброса предделителей достаточно записать бит PSR10 в регистре SFIOR. Бит PSR10 будет сброшен автоматически на следующем такте.

Счетный регистр
Весь результат мучений, описанных выше, накапливается в счетном регистре TCNTх, где вместо х номер таймера. он может быть как восьмиразрядным, так и шестнадцати разрядным, в таком случае он состоит из двух регистров TCNTxH и TCNTxL — старший и младший байты соответственно.

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

А дело все в чем - таймер считает независимо от процессора, поэтому мы можем положить вначале один байт, он начнет считаться, потом второй, и начнется пересчет уже с учетом второго байта.

Чувствуете лажу? Вот! Таймер точное устройство, поэтому грузить его счетные регистры надо одновременно! Но как? А инженеры из Atmel решили проблему просто:
Запись в старший регистр (TCNTxH) ведется вначале в регистр TEMP. Этот регистр чисто служебный, и нам никак недоступен.

Что в итоге получается: Записываем старший байт в регистр TEMP (для нас это один хрен TCNTxH), а затем записываем младший байт. В этот момент, в реальный TCNTxH, заносится ранее записанное нами значение. То есть два байта, старший и младший, записываются одновременно! Менять порядок нельзя! Только так

Выглядит это так:

CLI ; Запрещаем прерывания, в обязательном порядке! OUT TCNT1H,R16 ; Старшей байт записался вначале в TEMP OUT TCNT1L,R17 ; А теперь записалось и в старший и младший! SEI ; Разрешаем прерывания

Зачем запрещать прерывания? Да чтобы после записи первого байта, прога случайно не умчалась не прерывание, а там кто нибудь наш таймер не изнасиловал. Тогда в его регистрах будет не то что мы послали тут (или в прерывании), а черти что. Вот и попробуй потом такую багу отловить! А ведь она может вылезти в самый неподходящий момент, да хрен поймаешь, ведь прерывание это почти случайная величина. Так что такие моменты надо просекать сразу же.

Читается все также, только в обратном порядке. Сначала младший байт (при этом старший пихается в TEMP), потом старший. Это гарантирует то, что мы считаем именно тот байт который был на данный момент в счетном регистре, а не тот который у нас натикал пока мы выковыривали его побайтно из счетного регистра.

Контрольные регистры
Всех функций таймеров я расписывать не буду, а то получится неподьемный трактат, лучше расскажу о основной — счетной, а всякие ШИМ и прочие генераторы будут в другой статье. Так что наберитесь терпения, ну или грызите даташит, тоже полезно.

Итак, главным регистром является TCCRx
Для Т/С0 и Т/С2 это TCCR0 и TCCR2 соответственно, а для Т/С1 это TCCR1B

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

У разных таймеров немного по разному, поэтому опишу биты CS02..CS00 только для таймера 0

  • 000 — таймер остановлен
  • 001 — предделитель равен 1, то есть выключен. таймер считает тактовые импульсы
  • 010 — предделитель равен 8, тактовая частота делится на 8
  • 011 — предделитель равен 64, тактовая частота делится на 64
  • 100 — предделитель равен 256, тактовая частота делится на 256
  • 101 — предделитель равен 1024, тактовая частота делится на 1024
  • 110 — тактовые импульсы идут от ножки Т0 на переходе с 1 на 0
  • 111 — тактовые импульсы идут от ножки Т0 на переходе с 0 на 1

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

За прерывания от таймеров отвечают регистры TIMSК, TIFR. А у более крутых AVR, таких как ATMega128, есть еще ETIFR и ETIMSK — своего рода продолжение, так как таймеров там поболее будет.

TIMSK это регистр масок. То есть биты, находящиеся в нем, локально разрешают прерывания. Если бит установлен, значит конкретное прерывание разрешено. Если бит в нуле, значит данное прерывание накрывается тазиком. По дефолту все биты в нуле.

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

  • TOIE0 — разрешение на прерывание по переполнению таймера 0
  • TOIE1 — разрешение на прерывание по переполнению таймера 1
  • TOIE2 — разрешение на прерывание по переполнению таймера 2

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

Регистр TIFR это непосредственно флаговый регистр. Когда какое то прерывание срабатывает, то выскакивает там флаг, что у нас есть прерывание. Этот флаг сбрасывается аппаратно когда программа уходит по вектору. Если прерывания запрещены, то флаг так и будет стоять до тех пор пока прерывания не разрешат и программа не уйдет на прерывание.

Чтобы этого не произошло флаг можно сбросить вручную. Для этого в регистре TIFR в него нужно записать 1!

А теперь похимичим
Ну перекроим программу на работу с таймером. Введем программный таймер. Шарманка так и останется, пускай тикает. А мы добавим вторую переменную, тоже на четыре байта:

ORG $010 RETI ; (TIMER1 OVF) Timer/Counter1 Overflow .ORG $012 RJMP Timer0_OV ; (TIMER0 OVF) Timer/Counter0 Overflow .ORG $014 RETI ; (SPI,STC) Serial Transfer Complete

Добавим обработчик прерывания по переполнению таймера 0, в секцию Interrupt. Так как наш тикающий макрос активно работает с регистрами и портит флаги, то надо это дело все сохранить в стеке сначала:

Кстати, давайте создадим еще один макрос, пихающий в стек флаговый регистр SREG и второй — достающий его оттуда.

1 2 3 4 5 6 7 8 9 10 11 12 .MACRO PUSHF PUSH R16 IN R16,SREG PUSH R16 .ENDM .MACRO POPF POP R16 OUT SREG,R16 POP R16 .ENDM

MACRO PUSHF PUSH R16 IN R16,SREG PUSH R16 .ENDM .MACRO POPF POP R16 OUT SREG,R16 POP R16 .ENDM

Как побочный эффект он еще сохраняет и R16, помним об этом:)

1 2 3 4 5 6 7 8 9 10 11 12 13 Timer0_OV: PUSHF PUSH R17 PUSH R18 PUSH R19 INCM TCNT POP R19 POP R18 POP R17 POPF RETI

Timer0_OV: PUSHF PUSH R17 PUSH R18 PUSH R19 INCM TCNT POP R19 POP R18 POP R17 POPF RETI

Теперь инициализация таймера. Добавь ее в секцию инита локальной периферии (Internal Hardware Init).

; Internal Hardware Init ====================================== SETB DDRD,4,R16 ; DDRD.4 = 1 SETB DDRD,5,R16 ; DDRD.5 = 1 SETB DDRD,7,R16 ; DDRD.7 = 1 SETB PORTD,6,R16 ; Вывод PD6 на вход с подтягом CLRB DDRD,6,R16 ; Чтобы считать кнопку SETB TIMSK,TOIE0,R16 ; Разрешаем прерывание таймера OUTI TCCR0,1<

Осталось переписать наш блок сравнения и пересчитать число. Теперь все просто, один тик один такт. Без всяких заморочек с разной длиной кода. Для одной секунды на 8Мгц должно быть сделано 8 миллионов тиков. В хексах это 7A 12 00 с учетом, что младший байт у нас TCNT0, то на наш счетчик остается 7А 12 ну и еще старшие два байта 00 00, их можно не проверять. Маскировать не нужно, таймер мы потом переустановим все равно.

Одна только проблема — младший байт, тот что в таймере. Он тикает каждый такт и проверить его на соответствие будет почти невозможно. Т.к. малейшее несовпадение и условие сравнение выпадет в NoMatch, а подгадать так, чтобы проверка его значения совпала именно с этим тактом… Проще иголку из стога сена вытащить с первой попытки наугад.

Так что точность и в этом случае ограничена — надо успеть проверить значение до того как оно уйдет из диапазона. В данном случае диапазон будет, для простоты, 255 — величина младшего байта, того, что в таймере.

Тогда наша секунда обеспечивается с точностью 8000 000 плюс минус 256 тактов. Не велика погрешность, всего 0,003%.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 ; Main ========================================================= Main: SBIS PIND,6 ; Если кнопка нажата - переход RJMP BT_Push SETB PORTD,5 ; Зажгем LED2 CLRB PORTD,4 ; Погасим LED1 Next: LDS R16,TCNT ; Грузим числа в регистры LDS R17,TCNT+1 CPI R16,0x12 ; Сравниванем побайтно. Первый байт BRCS NoMatch ; Если меньше -- значит не натикало. CPI R17,0x7A ; Второй байт BRCS NoMatch ; Если меньше -- значит не натикало. ; Если совпало то делаем экшн Match: INVB PORTD,7,R16,R17 ; Инвертировали LED3 ; Теперь надо обнулить счетчик, иначе за эту же итерацию главного цикла; мы сюда попадем еще не один раз -- таймер то не успеет натикать 255 значений, ; чтобы число в первых двух байтах счетчика изменилось и условие сработает. ; Конечно, можно обойти это доп флажком, но проще сбросить счетчик:) CLR R16 ; Нам нужен ноль CLI ; Доступ к многобайтной переменной; одновременно из прерывания и фона; нужен атомарный доступ. Запрет прерываний OUTU TCNT0,R16 ; Ноль в счетный регистр таймера STS TCNT,R16 ; Ноль в первый байт счетчика в RAM STS TCNT+1,R16 ; Ноль в второй байт счетчика в RAM STS TCNT+2,R16 ; Ноль в третий байт счетчика в RAM STS TCNT+3,R16 ; Ноль в первый байт счетчика в RAM SEI ; Разрешаем прерывания снова. ; Не совпало - не делаем:) NoMatch: NOP INCM CCNT ; Счетчик циклов по тикает; Пускай, хоть и не используется. JMP Main BT_Push: SETB PORTD,4 ; Зажгем LED1 CLRB PORTD,5 ; Погасим LED2 RJMP Next ; End Main =====================================================

; Main ========================================================= Main: SBIS PIND,6 ; Если кнопка нажата - переход RJMP BT_Push SETB PORTD,5 ; Зажгем LED2 CLRB PORTD,4 ; Погасим LED1 Next: LDS R16,TCNT ; Грузим числа в регистры LDS R17,TCNT+1 CPI R16,0x12 ; Сравниванем побайтно. Первый байт BRCS NoMatch ; Если меньше -- значит не натикало. CPI R17,0x7A ; Второй байт BRCS NoMatch ; Если меньше -- значит не натикало. ; Если совпало то делаем экшн Match: INVB PORTD,7,R16,R17 ; Инвертировали LED3 ; Теперь надо обнулить счетчик, иначе за эту же итерацию главного цикла; мы сюда попадем еще не один раз -- таймер то не успеет натикать 255 значений, ; чтобы число в первых двух байтах счетчика изменилось и условие сработает. ; Конечно, можно обойти это доп флажком, но проще сбросить счетчик:) CLR R16 ; Нам нужен ноль CLI ; Доступ к многобайтной переменной; одновременно из прерывания и фона; нужен атомарный доступ. Запрет прерываний OUTU TCNT0,R16 ; Ноль в счетный регистр таймера STS TCNT,R16 ; Ноль в первый байт счетчика в RAM STS TCNT+1,R16 ; Ноль в второй байт счетчика в RAM STS TCNT+2,R16 ; Ноль в третий байт счетчика в RAM STS TCNT+3,R16 ; Ноль в первый байт счетчика в RAM SEI ; Разрешаем прерывания снова. ; Не совпало - не делаем:) NoMatch: NOP INCM CCNT ; Счетчик циклов по тикает; Пускай, хоть и не используется. JMP Main BT_Push: SETB PORTD,4 ; Зажгем LED1 CLRB PORTD,5 ; Погасим LED2 RJMP Next ; End Main =====================================================

Вот как это выглядит в работе

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

Можно еще немного оптимизировать процесс проверки. Сделать его более быстрым.

Надо только сделать счет не на повышение, а на понижение. Т.е. загружаем в переменную число и начинаем его декрементировать в прерывании. И там же, в обработчике, проверяем его на ноль. Если ноль, то выставляем в памяти флажок. А наша фоновая программа этот флажок ловит и запускает экшн, попутно переустанавливая выдержку.

А что если надо точней? Ну тут вариант только один — заюзать обработку события прям в обработчике прерывания, а значение в TCNT:TCNT0 каждый раз подстраивать так, чтобы прерывание происходило точно в нужное время.