Arduino таймер на прерываниях

Arduino.ru

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

Таймеры на Arduino

Я связался с Дэвидом Меллисом (David Mellis) из команды разработчиков Arduino и узнал, что Arduino пользуется всеми тремя таймерами ATMega168.

  • Tаймер 0 (Системное время, ШИМ 5 and 6)
    Используется для хранения счетчика времени работы программы. Функция millis() возвращает число миллисекунд с момента запуска программы, используя ISR глобального приращения таймера 0. Таймер 0 также используется для реализации ШИМ на выводах 5 и 6.
  • Tаймер 1 (ШИМ 9 и 10)
    Используется для реализации ШИМ для цифровых выводах 9 и 10.
  • Tаймер 2 (ШИМ 3 и 11)
    Используется для управления выходами ШИМ для цифровых выводов 3 и 11.

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

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

Дэвид Меллис дал мне эту ссылку для изучения того, как библиотечные программы используют таймеры. Она хорошо документирована и я уверен, что она сослужит мне хорошую службу в будущем. В будущих экспериментах я планирую управлять ШИМ-модуляцией напрямую, чтобы достичь намного большей частоты, чем нормальная. Высокочастотный ШИМ может здорово пригодиться для функции аудио-ЦАП.
Ссылки, относящиеся к прерываниям в Arduino.

Здесь приведены несколько ссылок (англ.), относящихся к прерываниям на Arduino. Я просмотрел некоторые из них, пока забавлялся с прерываниями. Я должен признать, что мои познания – заслуга этих авторов.

Тексты программ

Программы, приведенные в этой статье, доступны в zip-архиве.

Установка Таймера 2

Приведенная ниже программа показывает созданную мной функцию установки Таймера 2. Эта функция подключает прерывание по переполнению Таймера 2, устанавливает предварительно заданный масштаб для таймера подсчитывает загружаемое значение таймера, дающее желаемую частоту прерываний по времени. Эта программа основана на программе, найденной мной по вышеуказанным ссылкам. Я приобрел опыт, читая спецификации AVR, однако гораздо проще повторно использовать программу, если вы можете её найти. Рекомендую вам искать информацию по таймерам в спецификациях, только если по-другому никак не представить, как это трудно для понимания.

Сначала определяется тактовая частота таймера. Показано, что тактовая частота установлена 2 МГц, так как мы используем деление на 8 (делитель частоты) опорной частоты 16 МГц. Это жестко запрограммировано в функции. Это определение улучшает внешний вид программы и может быть полезно в некоторых приложениях. Наконец, оно напоминает мне, как я настроил таймер.

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

Далее подсчитывается значение, перезагружаемое в таймер. Это очень простой подсчет, но требует операций с плавающей точкой. К счастью, нам нужно сделать это только один раз, поскольку операции с плавающей точкой очень дорого обходятся в пересчете на машинное время. Примем, что таймер будет установлен на 2 МГц при каждом счете. Загружаемое значение – это число отсчетов, которое мы хотим произвести при 2 МГц между прерываниями. Вы можете заметить, что я использовал 257 вместо 256 для числа отсчетов в этом выражении. Я знаю, что верное значение – 256, но я получил лучшие результаты с 257. Далее в этой в этой статье я разъясню, почему.

Следующий участок секретного кода устанавливает таймер в режим 0 и выбирает делитель частоты /8. Режим 0 – это базовый режим таймера, а делитель /8 показывает, как мы получаем счетчик, считающий с частотой 2 МГц или 0,5 мкс на отсчет.

Далее подключается прерывание по переполнению. После выполнения этого кода микроконтроллер будет вызывать ISR каждый раз, когда счетчик прокрутится от 0xFF до 0×00. Это случится, когда счетчик просчитает от нашего загруженного значения через FF и назад до 00.

Наконец, мы загружаем значение счетчика в таймер и возвращаем это загруженное значение, чтобы ISR могла использовать его позже.

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

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

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

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

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

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

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

Источник

Arduino и прерывания таймера

Привет, Хабр! Представляю вашему вниманию перевод статьи «Timer interrupts» автора

Предисловие

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

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

В этой статье обсуждаются таймеры AVR и Arduino и то, как их использовать в Arduino проектах и схемах пользователя.

Что такое таймер?

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

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

Итак, предположим, что имеется устройство, которое должно что-то делать, например мигать светодиодом каждые 5 секунд. Если не использовать таймеры, а писать обычный код, то надо установить переменную в момент зажигания светодиода и постоянно проверять не наступил ли момент ее переключения. С прерыванием по таймеру вам достаточно настроить прерывание, и затем запустить таймер. Светодиод будет мигать точно вовремя, независимо от действий основной программы.

Как работает таймер?

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

Вы можете проверять этот флаг вручную или можете сделать таймерный переключатель — вызывать прерывание автоматически в момент установки флага. Подобно всяким другим прерываниям вы можете назначить служебную подпрограмму прерывания (Interrupt Service Routine или ISR), чтобы выполнить заданный код, когда таймер переполнится. ISR сама сбросит флаг переполнения, поэтому использование прерываний обычно лучший выбор из-за простоты и скорости.

Чтобы увеличивать значения счетчика через точные интервалы времени, таймер надо подключить к тактовому источнику. Тактовый источник генерирует постоянно повторяющийся сигнал. Каждый раз, когда таймер обнаруживает этот сигнал, он увеличивает значение счетчика на единицу. Поскольку таймер работает от тактового источника, наименьшей измеряемой единицей времени является период такта. Если вы подключите тактовый сигнал частотой 1 МГц, то разрешение таймера (или период таймера) будет:

T = 1 / f (f это тактовая частота)
T = 1 / 1 МГц = 1 / 10^6 Гц
T = (1 ∗ 10^-6) с

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

Типы таймеров

В стандартных платах Arduino на 8 битном AVR чипе имеется сразу несколько таймеров. У чипов Atmega168 и Atmega328 есть три таймера Timer0, Timer1 и Timer2. Они также имеют сторожевой таймер, который можно использовать для защиты от сбоев или как механизм программного сброса. Вот некоторые особенности каждого таймера.

Timer0:
Timer0 является 8 битным таймером, это означает, что его счетный регистр может хранить числа вплоть до 255 (т. е. байт без знака). Timer0 используется стандартными временными функциями Arduino такими как delay() и millis(), так что лучше не запутывать его если вас заботят последствия.

Timer1:
Timer1 это 16 битный таймер с максимальным значением счета 65535 (целое без знака). Этот таймер использует библиотека Arduino Servo, учитывайте это если применяете его в своих проектах.

Timer2:
Timer2 — 8 битный и очень похож на Timer0. Он используется в Arduino функции tone().

Timer3, Timer4, Timer5:
Чипы ATmega1280 и ATmega2560 (установлены в вариантах Arduino Mega) имеют три добавочных таймера. Все они 16 битные и работают аналогично Timer1.

Конфигурация регистров

Для того чтобы использовать эти таймеры в AVR есть регистры настроек. Таймеры содержат множество таких регистров. Два из них — регистры управления таймера/счетчика содержат установочные переменные и называются TCCRxA и TCCRxB, где x — номер таймера (TCCR1A и TCCR1B, и т. п.). Каждый регистр содержит 8 бит и каждый бит хранит конфигурационную переменную. Вот сведения из даташита Atmega328:

TCCR1A
Бит 7 6 5 4 3 2 1 0
0x80 COM1A1 COM1A0 COM1B1 COM1B0 WGM11 WGM10
ReadWrite RW RW RW RW R R RW RW
Начальное значение 0 0 0 0 0 0 0 0
TCCR1B
Бит 7 6 5 4 3 2 1 0
0x81 ICNC1 ICES1 WGM13 WGM12 CS12 CS11 CS10
ReadWrite RW RW R RW RW RW RW RW
Начальное значение 0 0 0 0 0 0 0 0

Наиболее важными являются три последние бита в TCCR1B: CS12, CS11 и CS10. Они определяют тактовую частоту таймера. Выбирая их в разных комбинациях вы можете приказать таймеру действовать на различных скоростях. Вот таблица из даташита, описывающая действие битов выбора:

CS12 CS11 CS10 Действие
0 0 0 Нет тактового источника (Timer/Counter остановлен)
0 0 1 clk_io/1 (нет деления)
0 1 0 clk_io/8 (делитель частоты)
0 1 1 clk_io/64 (делитель частоты)
1 0 0 clk_io/256 (делитель частоты)
1 0 1 clk_io/1024 (делитель частоты)
1 1 0 Внешний тактовый источник на выводе T1. Тактирование по спаду
1 1 1 Внешний тактовый источник на выводе T1. Тактирование по фронту

По умолчанию все эти биты установлены на ноль.

Допустим вы хотите, чтобы Timer1 работал на тактовой частоте с одним отсчетом на период. Когда он переполнится, вы хотите вызвать подпрограмму прерывания, которая переключает светодиод, подсоединенный к ножке 13, в состояние включено или выключено. Для этого примера запишем Arduino код, но будем использовать процедуры и функции библиотеки avr-libc всегда, когда это не делает вещи слишком сложными. Сторонники чистого AVR могут адаптировать код по своему усмотрению.

Сначала инициализируем таймер:

Регистр TIMSK1 это регистр маски прерываний Таймера/Счетчика1. Он контролирует прерывания, которые таймер может вызвать. Установка бита TOIE1 приказывает таймеру вызвать прерывание когда таймер переполняется. Подробнее об этом позже.

Когда вы устанавливаете бит CS10, таймер начинает считать и, как только возникает прерывание по переполнению, вызывается ISR(TIMER1_OVF_vect). Это происходит всегда когда таймер переполняется.

Дальше определим функцию прерывания ISR:

Сейчас мы можем определить цикл loop() и переключать светодиод независимо от того, что происходит в главной программе. Чтобы выключить таймер, установите TCCR1B=0 в любое время.

Как часто будет мигать светодиод?

Timer1 установлен на прерывание по переполнению и давайте предположим, что вы используете Atmega328 с тактовой частотой 16 МГц. Поскольку таймер 16-битный, он может считать до максимального значения (2^16 – 1), или 65535. При 16 МГц цикл выполняется 1/(16 ∗ 10^6) секунды или 6.25e-8 с. Это означает что 65535 отсчетов произойдут за (65535 ∗ 6.25e-8 с) и ISR будет вызываться примерно через 0,0041 с. И так раз за разом, каждую четырехтысячную секунды. Это слишком быстро, чтобы увидеть мерцание.

Если мы подадим на светодиод очень быстрый ШИМ сигнал с 50% заполнением, то свечение будет казаться непрерывным, но менее ярким чем обычно. Подобный эксперимент показывает удивительную мощь микроконтроллеров — даже недорогой 8-битный чип может обрабатывать информацию намного быстрей чем мы способны обнаружить.

Делитель таймера и режим CTC

Чтобы управлять периодом, вы можете использовать делитель, который позволяет поделить тактовый сигнал на различные степени двойки и увеличить период таймера. Например, вы бы хотели мигания светодиода с интервалом одна секунда. В регистре TCCR1B есть три бита CS устанавливающие наиболее подходящее разрешение. Если установить биты CS10 и CS12 используя:

то частота тактового источника поделится на 1024. Это дает разрешение таймера 1/(16 ∗ 10^6 / 1024) или 6.4e-5 с. Теперь таймер будет переполняться каждые (65535 ∗ 6.4e-5с) или за 4,194с. Это слишком долго.

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

Чтобы использовать режим CTC надо понять, сколько циклов вам нужно, чтобы получить интервал в одну секунду. Предположим, что коэффициент деления по-прежнему равен 1024.

Расчет будет следующий:

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

Функция настройки setup() будет такая:

Также нужно заменить прерывание по переполнению на прерывание по совпадению:

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

Вот полный стартовый пример который вы можете использовать как основу для собственных проектов:

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

Поскольку переменная будет модифицироваться внутри ISR она должна быть декларирована как volatile. Поэтому, при описании переменных в начале программы вам надо написать:

Послесловие переводчика

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

Источник

Adblock
detector