AVR130: настройка и использование таймеров AVR Печать
Добавил(а) microsin   

В этом переводе апноута AVR130 [1] рассматриваются следующие вопросы:

• Описание событий таймеров/счетчиков
• Оповещение о событиях таймера/счетчика
• Опции тактирования
• Пример кода для Timer0 (прерывание по переполнению)
• Пример кода для Timer1 (Input Capture, прерывание по захвату входного сигнала)
• Пример кода для Timer2 (асинхронная работа, прерывание по совпадению Compare Match)
• Базовые сведения о ШИМ (PWM)
• Пример генерации ШИМ на основе Timer2

В апноуте описано, как использовать различные таймеры AVR на примере микроконтроллера AT90S8535. Назначение документа - дать общий обзор таймеров, показать их возможности и разъяснить конфигурирование. Все это пояснено примерами кода, которые можно взять за основу для создания других приложений. Продвинутые читатели могут пропустить общий обзор, и сразу начать с секции "Настройка таймеров". Дополнительную информацию можно найти в даташитах на микроконтроллер и в апноутах, где используются таймеры.

[Общий обзор]

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

Почему существуют разные таймеры? Ответ очень прост: для создания подходящего таймера для разных задач нужны некоторые разные (аппаратные) ресурсы.

Обычно в микроконтроллерах серий 90S, megaAVR и tinyAVR имеется два 8-битных таймера и один 16-битный. Таймер, у которого 16-битное разрешение, обычно более гибок в настройке и обладает расширенным функционалом в сравнении с 8-битным таймером. Однако правило "чем больше тем лучше" не всегда справедливо для мира микроконтроллеров, когда нужно решить узкоспециализированную задачу. Для многих приложений достаточно 8-разрядной разрешающей способности. Использование разрядности выше означает увеличение затрат на вычисления, увеличение времени обработки, и этого нужно пытаться избегать путем оптимизации кода по скорости. Это также означает, что нужно применять более дорогой микроконтроллер. Гибкость таймеров AVR позволяет приспособить их для решения различных задач. Количество таймеров определяет количество различных конфигураций. Далее будут более подробно описаны эти опции конфигурации.

[События таймеров]

Таймер AVR может быть настроен на обработку некоторых событий. Флаги состояния в регистре TIMSK покажут, произошло ли какое-то событие. Микроконтроллер AT90S8535 может быть сконфигурирован для отслеживания до 3 событий на таймер. Ниже описаны эти события.

Переполнение (Timer Overflow). Переполнение таймера (timer overflow) означает, что счетчик таймера досчитал до своего предельного значения (255 для 8-битного таймера и 65535 для 16-битного) и сбросился в 0 при следующем тактовом цикле. Разрешение таймера (т. е. диапазон его счета) определяется разрядностью счетчика в таймере. В микроконтроллере AT90S8535 есть 2 таймера с 8-битным разрешением и 1 таймер с 16-битным разрешением. Максимальное значение, до которого может считать таймер, вычисляется по следующей формуле (здесь Res, стоящая в показателе степени, это разрешающая способность в битах, или 8 или 16):

MaxVal = 2Res – 1   (Формула 1)

Событие переполнения взведет флаг Timer Overflow (TOVx) в регистре Timer Interrupt Flag Register (TIFR).

Совпадение (Compare Match). В тех случаях, когда недостаточно отслеживать переполнение таймера, может использоваться прерывание по совпадению счетчика с неким заданным значением (compare match interrupt). Регистр Output Compare Register (OCRx) может быть загружен значением [0..MaxVal] которое будет проверяться на совпадение со счетчиком при каждом тактовом цикле таймера. Когда таймер достигнет значения сравнения, будет установлен соответствующий флаг Output Compare Flag (OCFx) в регистре TIFR. Таймер может быть сконфигурирован для очистки регистра счетчика в 0 при событии совпадения.

Связанные с функцией совпадения порты микроконтроллера могут быть сконфигурированы как выходы для автоматической установки, сброса и переключения при наступлении события совпадения. Эта функция очень полезна для генерирования сигналов прямоугольной формы различных частот. Эта функция предоставляет много возможностей для реализации DAC (преобразование цифровых данных в аналоговый сигнал). Режим PWM специально предназначен для наилучшей генерации сигналов синусоидальной и другой формы. Подробнее см. раздел "Базовые сведения о PWM", даташит [4].

Захват входа (Input Capture). У AVR есть входной порт, предназначенный для срабатывания от входных событий, т. е. происходит событие захвата входного события (input capture event). Изменение сигнала на этом входе приведет к тому, что значение из счетчика таймера будет прочитано и сохранено в регистре захвата Input Capture Register (ICRx). Одновременно с этим установится флаг Input Capture Flag (ICFx) в регистре TIFR. Функция захвата полезна для измерения длительности внешних импульсов.

[Как происходит оповещение о событиях]

Таймер работает независимо от выполнения кода программы. Для каждого события таймера есть соответствующий флаг состояния в регистре флагов прерывания (Timer Interrupt Flag Register, TIFR). Произошедшее событие таймера требует оповещения процессора, чтобы он предпринимал по событию соответствующие действия. Это как раз и делается установкой определенного флага, когда происходит определенное событие.

Есть три различных способа отслеживать события таймера и реагировать на них:

1. Постоянный опрос флагов статуса – флагов прерывания и по ним определять запуск соответствующего кода.
2. Остановка основного потока программы и выполнение обработчика прерывания Interrupt Service Routines (ISR).
3. Автоматическое измерение уровня выходного порта.

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

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

loop:
   in r16,TIFR       ; загрузка TIFR в регистр 16
   sbrs r16,TOV0     ; пропуск следующей инструкции, если установлен бит TOV0
   rjmp loop         ; если не было переполнения Timer0, то переход по метке loop
                     ; в этом месте должен начинаться код обработки события

Оповещение по прерываниям. Микроконтроллер AVR может быть сконфигурирован так, что будет запускаться обработчик прерывания, если произошло событие таймера (если установится соответствующий флаг прерывания в регистре TIFR). В этом случае главный поток программы немедленно (почти всегда немедленно) останавливается (прерывается, отсюда и пошел термин "прерывание"), и процессор переходит к выполнению кода обработчика прерывания (Interrupt Service Routine, сокращенно ISR). Достоинство по сравнению с опросом флагов состоит в том, что основной код освобождается от затрат процессорного времени на опрос флагов, и может вместо опроса заниматься другими вычислениями. В разделе "Настройка таймеров" даны несколько примеров, как это делается. Прерывания таймера разрешаются установкой соответствующих битов в регистре маски прерываний таймера Timer Timer Interrupt Mask Register (TIMSK). В следующем примере показано, как разрешить Output Compare Interrupt для Timer2:

   ldi r16, 1 << OCIE2
   out TIMSK,r16     ; Разрешить прерывание таймера output compare
   sei               ; Разрешить глобальные прерывания

Автоматическая обработка событий. Timer1 и Timer2 поддерживают реагирование на события прерывания таймера полностью аппаратно, без необходимости выполнения кода. Связанные с этой функцией выходные порты могут быть сконфигурированы для автоматической установки, сброса или переключения в противоположное состояние при возникновении события совпадения (compare match). Если сравнивать с двумя предыдущими методами, то после настройки это будет происходить параллельно с выполнением обычной программы, и не будет требовать трат процессорного времени на обработку событий.

Следующий пример кода показывает, как установить значение совпадения и разрешить переключение вывода порта. Обычно настройка действия выходного порта делается путем конфигурирования двух битов COMx0 и COMx1 в регистре TCCRx (вместо x подставляется номер таймера, 1 или 2). Конфигурирование Timer2 может выглядеть примерно так:

   ldi r16, (1 << COM20)|(1 << CS20)
   out TCCR2,r16     ; порт OC2 будет переключаться при событии compare match
                     ; частота тактирования таймера равна системной частоте ядра
   ldi r16,32
   out OCR2,r16      ; величина для сравнения (output compare value) будет равна 32

Для того, чтобы вывод порта OCx мог переключаться, он должен быть настроен как выход установкой соответствующего бита в регистре направления данных (data direction register, DDR). К функции OCx привязана определенная ножка определенного порта микроконтроллера.

[Тактирование]

Узел тактирования таймеров AVR состоит из предделителя (прескалера), подключенного к мультиплексору. Прескалер можно описать как ступенчатый делитель частоты. Он реализован как двоичный счетчик с несколькими выходными сигналами от разных ступеней счетчика. Для AT90S8535 это 10-разрядный счетчик, используемый для деления входной частоты на 4 разных коэффициента деления (в случае Timer2 это 6 коэффициентов), так что можно получить разные тактовые частоты. Мультиплексор используется для выбора нужной поделенной тактовой частоты в качестве входного сигнала для тактирования таймера. Альтернативно мультиплексор может быть использован для пропуска прескалера, и для конфигурирования внешнего вывода для использования его как входа для счетчика таймера.

Фактически имеется 2 разных прескалера, но 3 разных таймера, что можно увидеть в контексте выбора - на каком источнике тактов основана делимая величина частоты. Оба таймера Timer0 и Timer1 синхронны с системной тактовой частотой (тактовая частота ядра AVR) в качестве источника тактов. В этом случае нет ограничений, если оба счетчика используют один и тот же прескалер (при этом каждый таймер может быть сконфигурирован отдельно). Однако асинхронно тактируемый Timer2 нуждается в собственном прескалере, чтобы быть независимым от системной тактовой частоты.

На рис. 1 ниже показан прескалер и узел конфигурирования тактовой частоты таймера на мультиплексоре. Даташит на используемый микроконтроллер даст больше информации о всех прескалерах и мультиплексорах. Обзор возможных настроек тактирования приведен в таблице 1. В следующих секциях это будет рассмотрено подробнее.

Таблица 1. Обзор настроек тактирования.

TCCRx Для синхронных Timer0 и Timer1
PCK = CK
Для синхронно/асинхронного Timer2
PCK = f(AS2) 
Бит 2 Бит 1 Бит 0
CSx2 CSx1 CSx0 TCK0,1 TCK2
0 0 0 0 (таймер остановлен) 0 (таймер остановлен)
0 0 1 PCK (системная частота ядра AVR) PCK2 (системная частота / асинхронное тактирование)
0 1 0 PCK / 8 PCK2 / 8
0 1 1 PCK / 64 PCK2 / 32
1 0 0 PCK / 256 PCK2 / 64
1 0 1 PCK / 1024 PCK2 / 128
1 1 0 Внешний вывод, тактирование по спаду уровня PCK2 / 256
1 1 1 Внешний вывод, тактирование по нарастанию уровня PCK2 / 1024

Примечания:

1. Во время работы прескалер функционирует непрерывно. В случаях, когда таймер должен делать отсчеты очень точно, нужно обеспечить, чтобы прескалер начинал считать от нуля. В моделях микроконтроллеров, где не возможности сброса прескалера, момент перехода прескалера через переполнение можно детектировать в программном обеспечении, и соответственно инициализировать регистр таймера/счетчика TCNTx.

2. В новых устройствах, где имеется общий прескалер, выполнение сброса прескалера повлияет на все подключенные к нему таймеры.

AVR130-Prescaler

Рис. 1. Предделитель (прескалер).

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

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

Частота переполнения таймера является хорошим индикатором размера фрейма времени, в пределах которого может работать таймер. Формула ниже (вычисление частоты переполнения таймера TOVCK) показывает взаимосвязь между частотой переполнения таймера TOVCK, максимальным значением для счетчика таймера (MaxVal), системной тактовой частоты (CK) и коэффициента деления прескалера (PVal):

                 fCK        (PCKx/PVal)             PCKx
TOVCK = --------- = ------------- = -----------------
               MaxVal         MaxVal       (PVal * MaxVal)
  (Формула 2)

Предположим, что ядро AVR работает частоте fCPU = 3.69 МГц, разрядность таймера 8 битt (MaxVal = 256). Если выбрать коэффициент деления прескалера 64, то таймер будет тактироваться от частоты TCK = 3.69 MHz/64, так что будет происходить 225 переполнений таймера в секунду. Вот корректное математическое описание:

                 fCK        (3.69 МГц / 64)
TOVCK = --------- = ------------------ = ~225
               MaxVal             256

При 225 переполнениях таймера в секунду получается, что между переполнениями будет проходить 4.4 миллисекунды. С максимальным коэффициентом деления прескалера прерывание от переполнения таймера будет происходить каждую 71 мс, а с минимальным коэффициентом деления каждые 69 мкс. В большинстве случаев для определения настроек могут быть использованы разные методы. Требования приложения будут задавать частоту переполнений таймера. Базируясь на ней, и на имеющейся частоте тактов ядра вместе с разрядностью таймера настройки прескалера могут быть вычислены по следующей формуле:

                  PCKx 
PVal = -----------------
          (TOV * MaxVal)
  (Формула 3)

Реализация на ассемблере для Timer0 может выглядеть как в следующем примере кода. Эти строки установят значения прескалера в TCCR0 для коэффициента деления 1024 (см. таблицу 1).

   ldi r16, (1 << CS02)|(1 << CS00)
   out TCCR0,r16              ; частота тактирования таймера = system clock/1024

Тактирование от асинхронного источника. В отличие от двух других таймеров, которые не поддерживают этот вариант тактирования, Timer2 микроконтроллера AT90S8535 может асинхронно тактироваться от внешнего тактового сигнала. Для этой цели к выводам TOSC1 и TOSC2 может быть подключен кварцевый или керамический резонатор - это выводы, которые подсоединены к встроенному генератору.

Встроенный генератор на выводах TOSC1 и TOSC2 оптимизирован для часового резонатора на частоту 32.768 кГц. Эта частота хорошо подходит для реализации часов реального времени (Real Time Clocks, RTC)(1). Показано главное достоинство отдельной тактовой частоты - она не зависит от системной частоты ядра AVR. Это делает возможным работу микроконтроллера на высокой частоте, в то время как таймер тактируется от внешних тактов с частотой, оптимизированной под точный отсчет времени. Дополнительно можно переключать ядро в режимы пониженного энергопотребления, что не повлияет на работу асинхронно тактируемого таймера.

Примечание (1): внешняя тактовая частота должна быть в интервале 0 Гц .. 256 кГц, и с максимальным значением CK/4.

Асинхронное функционирование требует учета некоторых дополнительных условий. Поскольку тактирование Timer2 асинхронно, то события таймера синхронизируются с ядром AVR. Это требует, чтобы тактовая частота таймера была как минимум вчетверо меньше, чем системная частота ядра (т. е. внутренняя частота ядра должна быть как минимум в 4 раза выше частоты тактирования таймера). С другой стороны, нужно избегать конфликтов между синхронным и асинхронным доступом. Это осуществляется путем использования промежуточных регистров. Биты состояния сигнализируют, что происходит обновление конфигурационных регистров. См. подробности в описании Asynchronous Status Register (ASSR) в даташите на микроконтроллер.

Частота переполнения таймера TOVCK вычисляется по формуле 2, но вместо системной частоты в формулу подставляется частота генератора. Настройки TCCR2 даны в таблице 1. Входная частота прескалера PCK2 является функцией бита AS2 в регистре ASSR. Если этот бит очищен, то таймер работает в синхронном режиме от системной тактовой частоты. Если этот бит установлен, то для входной частоты прескалера используется асинхронный тактовый сигнал выводов TOSC1 и TOSC2.

Реализация на ассемблере для Timer2 может выглядеть как в примере кода ниже. Эти 2 строки установят настройку прескалера в TCCR2 на коэффициент деления 1024 (см. таблицу 1).

   ldi r16, (1 << CS22)|(1 << CS21)|(1 << CS20)
   out TCCR2,r16           ; частота тактирования таймера = system clock/1024

Внешнее тактирование. Внешнее тактирование поддерживается только таймерами Timer0 и Timer1. Этот режим позволяет использовать широкий диапазон внешних сигналов в качестве тактовых частот. Это синхронное тактирование, что означает, что ядро детектирует состояние вывода и тактирует таймер синхронно с системной тактовой частотой, если был детектирован внешний тактовый сигнал. По каждому фронту нарастания внутреннего такта ядра делается выборка внешнего тактового сигнала. Для ядра нужно как минимум 2 такта ядра для детектирования изменения на внешнем тактовом входе, так что максимальная тактовая частота внешних тактов должна быть не более чем CK/2. Для индикации внешнего события такта может использоваться либо фронт, либо спад внешнего тактового сигнала T0/T1. Этот выбор делается в регистре TCCRx путем установки битов CS00, CS01 и CS02 (см. таблицу 1).

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

   ldi r16, (1 << CS02)|(1 << CS01)|(1 << CS00)
   out TCCR0,r16           ; частота тактирования = внешний вывод T0,
                           ; по фронту нарастания

Примечание: важно настроить вывод T0 как вход через регистр направления данных (Data Direction Register) порта B (DDRB). Настройка регистра направления не перезаписывается настройкой таймера, потому что это также разрешено в AVR для реализации программно тактируемого таймера. T0 и T1 по умолчанию (после сброса или включения питания) настроены как входы.

Как остановить таймер. Остановка таймера, чтобы он прекратил счет, очень проста. Нулевое значение для настройки прескалера в регистре TCCRx остановит соответствующий таймер (см. таблицу 1). Однако помните, что прескалер все равно работает.

Реализация на ассемблере для Timer0 может выглядеть как в примере кода ниже.

   clr r16
   out TCCR0,r16           ; запись 0 в TCCR0 остановит Timer0

Примечание: другие регистры TCCRx могут содержать конфигурационные биты помимо битов выбора тактовой частоты (CSxx). Поэтому пример выше также сбросит и эти биты. В примере ниже показано, как этого можно избежать, что потребует дополнительной строки кода:

   in r16,TCCR0            ; загрузка текущего значения TCCR0
   andi r16, ~((1 << CS02)|(1 << CS01)|(1 << CS00))
                           ; очистка CS02,CS01,CS00
   out TCCR0,r16           ; запись нуля у биты CS02, CS01, и CS00 регистра TCCR0
                           ; остановит Timer0. Другие биты останутся нетронутыми.

[Настройка таймеров]

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

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

Общие регистры. Если используются общие регистры в обработчике прерывания (ISR) и основном коде программы, то эти регистры должны быть предварительно сохранены в начале ISR, и восстановлены по окончании ISR. Если в приложении используются не все 32 регистра, то можно избежать процедуры сохранения и восстановления регистров путем использования разных регистров в основном коде программы и в ISR.

Очень важно помнить, что нужно сохранить регистр состояния (Status Register, SREG), потому что ISR это не делает автоматически.

Примечание: все вышеперечисленное касается использования языка ассемблера. Если используется язык C, то компилятор C все это делает автоматически.

[8-bit Timer0]

8-разрядный таймер Timer0 является синхронным таймером. Это означает, что он тактируется от системной тактовой частоты (возможно поделенной прескалером), или от внешней тактовой частоты (возможно также поделенной прескалером), которая все равно синхронизируется с системной тактовой частотой (подробнее см. раздел "Тактирование"). Этот таймер самый простой из всех трех. Для его запуска нужно выполнить только несколько установок.

Здесь будет показано, как Timer0 можно использовать для генерации Timer Overflow Interrupt. В коде ISR для примера будут переключаться выводы порта B. Чтобы это было видно, можно использовать плату разработчика STK500. В ней порт B может быть подключен к светодиодам 10-жильным ленточным кабелем. Светодиоды (LED) будут мигать с частотой fLED, которую можно вычислить по формуле:

              fCK        (CK/ PVal)                CK
fLED = --------- = ------------- = ---------------------
           MaxVal      2 * MaxVal     2 * (PVal * MaxVal)

У нашего 8-разрядного Timer0 величина MaxVal = 256, и системная тактовая частот CK = 3.69 МГц, поделенная прескалером на величину PVal = 1024, что приведет к частоте мерцания LED fLED примерно 7 Гц. Настройка показана в следующем примере кода:

init_Ex1:
   ldi r16, (1 << CS02)|(1 << CS00)
   out TCCR0,r16           ; частота тактирования таймера = system clock / 1024
   ldi r16, 1 << TOV0
   out TIFR,r16            ; Очистка TOV0 (сброс ожидающего прерывания)
   ldi r16, 1 << TOIE0
   out TIMSK,r16           ; Разрешить прерывание Timer/Counter0 Overflow
   ser r16
   out DDRB,r16            ; Настройка выводов порта B как выходов
   ret

Соответствующий код на C для компилятора IAR:

void init_Ex1(void)
{
   TCCR0 = (1 << CS02)|(1 << CS00); //частота тактирования таймера = system clock / 1024
   TIFR = 1 << TOV0;                //Очистка TOV0 (сброс ожидающего прерывания)
   TIMSK = 1 << TOIE0;              //Разрешить прерывание Timer/Counter0 Overflow
   DDRB = 0xFF;                     //Настройка выводов порта B как выходов
}

На следующем шаге должен быть написан код для ISR (подпрограмма - обработчик прерывания). Этот код будет выполняться каждый раз, когда таймер переполнится. В качестве примера ISR будет переключать в противоположное состояние ножки порта B (поэтому светодиоды LED будут мерцать).

ISR_TOV0:
   push r16
   in r16,SREG
   push r16
   in r16,PORTB         ; Чтение порта B в регистр r16
   com r16              ; Инверсия битов в регистре r16
   out PORTB,r16        ; Запись r16 в порт B
   pop r16
   out SREG,r16
   pop r16
   reti

Соответствующий код на C для компилятора IAR:

void interrupt [TIMER0_OVF0_vect] ISR_TOV0 (void)
{
   PORTB = ~PORTB;      // Переключить выводы порта B
}

[16-bit Timer1]

16-разрядный Timer1 также является синхронным. Это означает, что он тактируется от системной тактовой частоты, поделенной прескалером системной тактовой частоты, или от внешних тактов (которые также могут быть поделены прескалером), которые синхронизируются с системной тактовой частотой. Чтобы гарантировать, что 16-разрядные регистры будут читаться и записываться одновременно (атомарной операцией), применяется специальная процедура с неявным задействованием промежуточного регистра (Temp). Во время выполнения этой процедуры доступ к регистрам должен выполняться в определенном, строго заданном порядке (подробнее см. AVR072 [2]). Корректный путь для доступа к регистрам показан в таблице 2.

Таблица 2. Доступ к 16-битным регистрам.

Операция 1 доступ 2 доступ
Чтение Младший байт Старший байт
Запись Старший байт Младший байт

В соответствии с этими правилами, операция чтения 16-битного регистра будет выглядеть так:

   in r16,TCNT1L
   in r17,TCNT1H

Операция записи в этот регистр происходит в обратном порядке:

   out TCNT1H,r17
   out TCNT1L,r16

Компилятор C автоматически обрабатывает операции 16-битного ввода/вывода в нужном порядке.

Здесь показан очень простой пример использования события захвата по входу с обработкой по прерыванию. Вывод порта PD6 является входом захвата (input capture pin, ICP). Если логический уровень на этом выводе поменяется, то время между соседними положительными или отрицательными перепадами будет измерено системой захвата Timer1. 8 старших бит значения таймера будут записаны в порт B. Порт B может быть подключен к светодиодам платы разработчика STK500, а порт D подключен к кнопкам (с использованием двух 10-жильных ленточных кабелей). Это сделает возможным наблюдение значения таймера светодиодами LED, и использовать кнопки для подачи измеряемого сигнала на вход ICP.

В этом примере максимальное измеренное время может быть примерно 1 секундой (TOVCK = 1). Используйте формулу 3 для подбора нужного коэффициента деления прескалера. Для системной тактовой частоты CK = 3.69 МГц значение коэффициента деления прескалера определяется так:

3.69 МГц
----------- = 56
    216 

Ближайшее к 56 выбираемое значение коэффициента деления PVal будет 64. Следующая подпрограмма инициализации показывает, как настроить такую систему:

init_Ex2:
   ldi r16, (1 << CS11)|(1 << CS10)
   out TCCR1B,r16          ; Частота тактирования таймера = system clock/64
   ldi r16, 1 << ICF1
   out TIFR,r16            ; Очистка ICF1 (сброс ожидающих прерываний)
   ldi r16, 1 << TICIE1
   out TIMSK,r16           ; Разрешить прерывание Timer/Counter1 Capture Event
   ser r16                 ; Установить все биты в регистре
   out DDRB,r16            ; Настройка выводов порта B как выходов
   cbi DDRD,PD6            ; настройка PD6/ICP как входа
   ret

Соответствующий код на C для компилятора IAR:

void init_Ex2(void)
{
   TCCR1B = (1 << CS11)|(1 << CS10);// Частота тактирования таймера = system clock/64
   TIFR = 1 << ICF1;                // Очистка ICF1 (сброс ожидающих прерываний)
   TIMSK = 1 << TICIE1;             // Разрешить прерывание Timer/Counter1 Capture Event
   DDRB = 0xFF;                     // Настройка выводов порта B как выходов
   DDRD &= ~(1 << PD6);             // настройка PD6/ICP как входа
}

На следующем шаге должен быть написан код для ISR (подпрограмма - обработчик прерывания). Этот код будет выполняться каждый раз, когда произойдет событие захвата (input capture event). Назначение этого примера - вывести старший байт Timer1 в порт B (светодиоды LED). Потом таймер сбрасывается для подготовки следующего измерения.

TIM1_CAPT:
   push r16
   in r16,SREG
   push r16
   in r16,ICR1L               ; Чтение младшего байта ICR, старший байт ICR
                              ; при этом будет сохранен неявно в Temp
   in r16,ICR1H               ; Чтение старшего байта ICR
   com r16                    ; Инвертирование бит, см. примечание 1
   out PORTB,r16              ; Запись ICR1H в порт PORTB
   clr r16
   out TCNT1H,r16             ; Запись в регистр Temp
   out TCNT1L,r16             ; Очистка 16-битного регистра TCNT1
   pop r16
   out SREG,r16
   pop r16
   reti

Соответствующий код на C для компилятора IAR:

void interrupt [TIMER1_CAPT1_vect] ISR_ICP1(void)
{
   // Чтение 16-битного значения из регистра Input Capture Register
   // и сдвиг его вправо 8 раз, чтобы получить старший байт
   PORTB = ~( ICR1>>8);    // инверсия (см. примечание 1) и вывод
                           // старшего байта в порт B
   TCNT1 = 0;              // Сброс 16-битного регистра счетчика Timer1
}

Примечания:

1. Инверсия битов нужна потому, что светодиоды в STK500 подключены к + питания, т. е. когда на выходе порта лог. 0, то светодиод горит, а когда лог. 1 - гаснет.
2. В этой реализации есть один недостаток: не обрабатывается переполнение таймера. Для того, чтобы этого избежать, можно использовать установку глобальной переменной в отдельном ISR для события переполнения таймера. Если эта переменная установлена, то в порт B должно быть записано значение 0xFF вместо значения из таймера.

[Асинхронный 8-bit Timer2]

Timer2 может использоваться в синхронном режиме, как Timer0 и Timer1. Дополнительно может использоваться асинхронный режим. Подробнее см. "Тактирование от асинхронного источника" в разделе "Тактирование".

Здесь показано, как использовать output compare interrupt для Timer2. Таймер будет сконфигурирован так, что событие compare match будет происходить каждую секунду. Эта возможность может использоваться для реализации часов реального времени RTC. Но в этом примере для упрощения будут просто переключаться в противоположное состояние ножки порта, так что подключенные к ним LED-ы будут мигать с частотой 0.5 Гц.

Как и в предыдущем примере, порт B подключен к светодиодам LED и порт D подключен к переключателям платы STK500. Дополнительно кварцевый резонатор на 32.768 кГц (часовой кварц) подключен к выводам TOSC1/PC6 и TOSC2/PC7 порта C.

Настройки таймера могут быть вычислены по формуле 2. Вместо максимальной величины таймера MaxVal используется значение OCR2. Частота для подачи на вход тактирования прескалера (PCKx) в этом случае будет тактовым сигналом от часового кварца (fOSCCK), в то время как TOVCK будет использоваться в качестве тактового сигнала для события изменения уровня вывода с периодом 1 секунда. Математическое описание этого соотношения показано в следующей формуле:

                       fOSCCK         32.768 кГц
1 = TOVCK = --------------- = --------------
                   PVal * OCR2    PVal * OCR2

Выбрано значение коэффициента деления прескалера 1024 и соответствующее значение 32 для OCR2, чтобы получить задержку 1 секуда между двумя событиями Timer compare match.

Следующая подпрограмма инициализации показывает, как настроить такую систему:

init_Ex3:
   ldi r16, 1 << AS2
   out ASSR, r16           ; Разрешить асинхронный режим,
                           ; очистить таймер для compare match,
   ldi r16, (1 << CTC2)|(1 << CS22)|(1 << CS21)|(1 << CS20)
   out TCCR2,r16           ; частота тактирования таймера = system clock / 1024
   ldi r16, 1 << OCF2
   out TIFR,r16            ; Очистка OCF2 (сброс ожидающего прерывания)
   ldi r16, 1 << OCIE2
   out TIMSK,r16           ; Разрешить прерывание Timer2 Output Compare Match Interrupt
   ldi r16,32
   out OCR2,r16            ; Установить значение сравнения 32
   ser r16
   out DDRB,r16            ; Настроить выводы порта B как выходы
loop:
   sbic ASSR, OCR2UB       ; Ожидание завершения обновления регистров
   rjmp loop
   ret

Соответствующий код на C для компилятора IAR:

void init_Ex3(void)
{
   ASSR= 1 << AS2;      // Разрешить асинхронный режим,
                        // очистить таймер для compare match,
                        // частота тактирования таймера = system clock / 1024
   TCCR2 = (1 << CTC2)|(1 << CS22)|(1 << CS21)|(1 << CS20);
   TIFR= 1 << OCF2;     // Очистка OCF2 (сброс ожидающего прерывания)
   TIMSK= 1 << OCIE2;   // Разрешить прерывание Timer2 Output Compare Match Interrupt
   OCR2= 32;            // Установить значение сравнения 32
   DDRB= 0xFF;          // Настроить выводы порта B как выходы
   while (ASSR & (1 << OCR2UB))
      ;                 // Ожидание завершения обновления регистров
}

На следующем шаге должен быть написан код для ISR (подпрограмма - обработчик прерывания). Этот код будет выполняться каждый раз, когда произойдет событие сравнения выхода (output compare event). Назначение этого примера - просто переключить в противоположное состояние биты порта B (они подключены к светодиодам LED для индикации события).

ISR_OCIE2:
   push r16
   in r16,SREG
   push r16
   in r16,PORTB         ; Чтение порта B в регистр r16
   com r16              ; Инверсия битов в регистре r16
   out PORTB,r16        ; Запись r16 в порт B
   pop r16
   out SREG,r16
   pop r16
   reti

Соответствующий код на C для компилятора IAR:

void interrupt [TIMER2_COMP_vect] ISR_OCIE2 (void)
{
   PORTB = ~PORTB;         // invert bits on Port B
}

[Базовые сведения о PWM]

PWM является аббревиатурой от Pulse Width Modulation (по-русски это Широтно-Импульсная Модуляция, или ШИМ). Это специальный режим который может быть сконфигурирован для таймеров Timer1 и Timer2. В этом режиме таймер работает как счетчик вверх и вниз. Это означает, что счетчик считает вверх до своего максимального значения, и затем считает вниз до нуля. Это контрастирует с обычным режимом счета, когда таймер переполняется и переходит в 0 после достижения своего максимального значения. Преимущество режима PWM в том, что изменение скважности ШИМ может быть реализовано с корректным сохранением фазы.

Если PWM сконфигурирован для переключения вывода Output Compare (OCx), то сигнал на этом выводе может выглядеть так, как показано на рис. 2.

AVR130-PWM-signal

Рис. 2. Выходной сигнал PWM.

VH: выходное напряжение лог. 1
VL: выходное напряжение лог. 0
VAV: средний уровень выходного напряжения (Average Output Voltage)
x: высокий уровень периода PWM
y: низкий уровень периода PWM

ФНЧ, подключенный к выходному выводу PWM, вместе с относительно высокой частотой выходного сигнала PWM приведет к постоянному среднему уровню на выходе вместо прямоугольных импульсов. Формула 4 показывает, как можно вычислить уровень этого среднего напряжения:

          (VH * x + VL * y)
VAV = -------------------
                (x + y)
  (Формула 4)

где

x = OCRx * 2

y = (MaxVal - OCRx) * 2

          (VH * OCRx + VL * (MaxVal - OCRx))
VAV = -----------------------------------------
                             MaxVal
  (Формула 5)

Фактически этот метод позволяет таймеру генерировать уровни напряжения между напряжением питания VCC и нулевым общим проводом GND - получится цифро-аналоговый преобразователь (DAC), реализованный на основе PWM. Подробнее, как это делается, описано в аптноутах AVR314 [4] и AVR335 [5].

Этот пример показывает, как генерировать напряжения между VCC и GND с помощью сигнала PWM (PD7/OC2). Чтобы увидеть результаты регулирования, порт D должен быть подключен к светодиодам с помощью 10-жильного плоского кабеля.

Порт D сконфигурирован так, что светодиоды, подключенные к младшей четверке битов порта будут включаться, а светодиоды, подключены к старшей четверке битов порта, выключаться. Только светодиод на выводе PD7 / OC2 не будет выключаться, у него будет меняться яркость, потому что скважность сигнала PWM будет меняться от 1/8 до 7/8 (OCR2 = 0xE0).

Примечание: соотношение скважность в этой конфигурации проинвертировано потому, что светодиоды на плате STK500 подключены к + питания.

init_Ex4:
   ldi r16, (1 << PWM2)|(1 << COM21)|(1 << CS20)
   out TCCR2,r16           ; Неинвертированная 8-разрядная PWM (Fck/510)
   ldi r16,0xE0
   out OCR2,r16            ; Установить значение сравнения (скважность PWM)
   ldi r16,0x8F
   out DDRD,r16            ; Настроить PD7/OC2 и младшую четверку разрядов
                           ; порта D как выходы
   ret

Соответствующий код на C для компилятора IAR:

void init_Ex4(void)
{
   // Разрешить неинверсный 8-битный режим PWM
   // частота тактирования таймера = system clock
   TCCR2 = (1 << COM21)+(1 << PWM2)+(1 << CS20);
   DDRD = (1 << PD7)|0x0F; // PD7 (OC2) и PD0..PD3 работают как выходы
   OCR2= 0xE0;             // установить значение сравнения (скважность PWM)
}

[Ссылки]

1. AVR130: Setup and Use the AVR® Timers site:atmel.com.
2. Доступ к 16-битным регистрам AVR.
3. AVR134: Real Time Clock (RTC) using the Asynchronous Timer site:atmel.com.
4. AVR314: DTMF Transmitter site:atmel.com.
5. AVR335: Digital Sound Recorder with AVR and Serial DataFlash site:atmel.com.
6. Таймеры-счетчики ATmega2560.