Учебный курс AVR. Таймер - счетчик Т0. Режим CTC. Ч2

12/10/2013 - 12:37 Павел Бобков

Введение

Следующий режим работы таймера Т0 - это режим CTC (Clear Timer on Compare) или "сброс при совпадении". Таймер в этом режиме работает следующим образом. 

При инициализации таймера мы очищаем счетный регистр TCNT0, а в регистр сравнения OCR0 загружаем число. Затем устанавливаем режим СТС и требуемый коэффициент предделителя. 

Таймер начинает работу. По каждому импульсу тактового сигнала счетный регистр увеличивает свое значение на единицу. Когда значение счетного регистра совпадет с регистром сравнения, установится флаг OCF0, счетный регистр сбросится и счет продолжится с нуля. Если прерывания по совпадению разрешены, то после установки флага OCF0 запустится подпрограмма обработки. С регистром сравнения при этом ничего не произойдет, он свое значение не изменит. Диаграмма ниже поясняет работу таймера Т0 в режиме СТС.

Таймер Т0 в режиме СТС

Практический пример. Нужно генерировать меандр с частотой 70 кГц на выводе PB1. Микроконтроллер ATmega16, тактовая частота 16 МГц. 


Самый простой способ генерировать меандр, состоит в инвертировании состояния вывода микроконтроллера в прерывании таймера. 

Меандр меняет свое состояние два раза за период. Сначала переключается в 1, затем сбрасывается в 0. Поэтому, чтобы генерировать сигнал с частотой 70 кГц, прерывания нужно вызывать в два раза чаще, то есть с частотой 140 кГц. 

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

T = 1/F = 1/140000 = 7.143 мкс 

Какую тактовую частоту задать таймеру, чтобы он мог отмерять интервал в 7.143 мкс? Если бы один такт таймера составлял ~0.1 мкс, это бы подошло. 71 такт даст 7.1 мкс. 

Подберем коэффициент предделителя. У нас пять вариантов 1, 8, 64, 256, 1024. При 1 мы получим тактовый сигнал с периодом 0.0625 мкс.

Tt0 = 1/(Fcpu/k) = 1/(16000000/1) = 0.0625 мкс

При таком периоде для формирования интервала в 7.143 мкс нам понадобятся 7.143/0.0625 = 114 тактов. Значит такой коэффициент подойдет. И точность хорошая и разрядности счетного регистра хватает. 

114 - это количество тактов, которые должен отсчитать таймер Т0. В регистр сравнения же нужно загрузить на один такт меньше, то есть 113. Счет ведь начинается с нулевого такта. 

Как видите, для режима CTC рассчитать значение регистра сравнения еще проще, чем для режима Normal:

-вычисляем период одного такта таймера Tt0 = k/Fcpu,
- вычисляем требуемое количество тактов для заданного интервала n = t/Tto - 1

Для оценки точности получаемого сигнала, можно проделать обратную процедуру. 

F = 1/((OCR0 + 1) * Tt0) = 1/((OCR0 + 1) * (1/(Fcpu/k)) = Fcpu/((OCR0 + 1) * k) 
F = 16000000/114 = 140351 Гц 

Весь код для нашей задачи будет выглядеть примерно так. (Код приведен для IAR`a. Для других компиляторов нужно изменить заголовочные файлы и обработчик прерывания.)


#include <ioavr.h>
#include <stdint.h>
#include <intrinsics.h>

#define TEST_PIN 1

int main( void )
{
   /*инициализация таймера*/
   TCCR0 = 0;
   TCCR0 = (0<<COM01)|(0<<COM00)|(1<<WGM01)|(0<<WGM00);
   TCNT0 = 0;
   OCR0 = 113;
   TIFR = (1<<OCIE0);
   TIMSK = (1<<OCIE0);
   TCCR0 |= (0<<CS02)|(0<<CS01)|(1<<CS00);

   /*настройка вывода*/
   DDRC |= (1<<TEST_PIN);

   __enable_interrupt();
   while(1);
   return 0;
}

#pragma vector = TIMER0_COMP_vect
__interrupt void TIM0_Ctc(void)
{
   /*инверсия логического уровня*/
   PORTC ^= (1<<TEST_PIN);
}

Управление выводом OC0

Управление выводом OC0 осуществляется в режиме СТС точно так же, как и в режиме Normal. Вывод OC0 меняет состояние при совпадении значения счетного регистра и регистра сравнения. Варианты управления задаются разрядами COM01 и COM00 регистра TCCR0.


Рассмотренную выше программу можно переделать для генерации меандра на выводе OC0. При этом прерывание можно не использовать. 



#include <ioavr.h>
#include <stdint.h>
#include <intrinsics.h>

int main( void )
{
   /*инициализация таймера Т0*/
   TCCR0 = 0;
   TCCR0 = (0<<COM01)|(1<<COM00)|(1<<WGM01)|(0<<WGM00);
   TCNT0 = 0;
   OCR0 = 113;
   TIMSK = 0;
   TCCR0 |= (0<<CS02)|(0<<CS01)|(1<<CS00);

   /*настройка выводов*/
   DDRB |= (1<<PB3);

   while(1);
   return 0;
}

Заключение

Благодаря автоматическому сбросу счетного регистра, таймер в режиме СТС точнее отмеряет временные интервалы. Единственная загвоздка - в некоторых микроконтроллерах таймеры-счетчики Т0 не имеют такого режима.

Comments   

# Andrew 2014-08-12 06:03
Добрый день, объясните пожалуйста, почему в этом месте
Code:
/*инициализация таймера Т0*/
...
TCCR0 = (0<<COM01)|(1<<COM00)|(1<<WGM01)|(0<<WGM00);
...
/code]
вы выставили биты в регистре через присвоение (=),
а в этом месте

TCCR0 |= (0<<CS02)|(0<<CS01)|(1<<CS00);

вы выставили биты в этом же регистре, но уже через (|=) ?
почему не так
Code: TCCR0 = (0<<CS02)|(0<<CS01)|(1<<CS00); ?
# sergey_root 2015-03-12 09:36
Если заменить |= на =, то ранее установленные флаги (COM00 и WGM01) сбросятся (т.е. установятся в 0 ), а так останутся и дополнится флагом CS00. Подробнее читайте про оператор "побитовое ИЛИ"
# Дрон 2015-09-02 16:03
как можно на Attiny13A включить шим на большую частоту чем при счетном регистре на 256. Блин не знаю как объяснить. Есть частота мк 9.6 МГц и тогда шим получится на 37500 Гц (длина всего периуда 1/37500) как сделать этот периуд короче то есть увеличить частоту не применяя программный шим? хотел запилить режим CTC но как из него шим сделать не могу догнать. (эх регистры регистры)
# Дрон 2015-09-02 16:06
Если программный делать в прерываниях по совпадению с B регистром а А сбрасывать то длина обоих прерываний получится около 156 инструкций это без кода который управляет ногой это долго.
# Дрон 2015-09-02 18:19
;-) разобрался. теперь могу шим на 240000 гц дергать)))
# Имя 2016-02-07 03:43
6 часов ночи, сижу не могу таймер запустить, а тут такая картинка. Добрые Вы, спать пойду.
# Alexis4479 2016-05-23 11:39
а я не понял: "как"нету такого режима?"

я читал в даташыте. там все есть.
# Alexis4479 2016-05-24 07:07
Code:/*инициализация таймера CTC ATmega8*/ TCCR2 = 0b00001101 ; // foc0 WGM00 com01 com00 wgm01 cs02 cs01 cs00 ; TCNT2 = 0;
OCR2=250; TIFR |= (1<<1) ; // TOV0);
TIMSK |= (1<<OCIE2); //TOIE0);
//TCCR0 |= (1<<CS02)|(0<<CS01)|(1<<CS00); /*инициализация остальной периферии*/
DDRD |= (1<<PD0); sei();
# releyshic 2016-07-14 10:46
1 Что такое TEST_PIN?
И вообще не понимаю что здесь происходит
PORTC ^= (1
# Alex4479 2016-08-29 12:50
там выше строка #define TEST_PIN 1
что значит, что вчесто TEST_PIN будет 1

У вас недостаточно прав для комментирования.