Таймер T1 (Arduino)

В платах Arduino UNO Arduino NANO в 8 битном AVR Atmega168 и Atmega328 есть три таймера Timer0, Timer1 и Timer2.

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

Timer1 16 битный таймер, его счетный регистр может хранить числа от 0 до 65535.

Timer2 8 битный таймер,  его счетный регистр может хранить числа от 0 до 255. Timer2 используется в Arduino в функции tone().

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

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

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

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

  • TCNT1 — счетный регистр таймера/счетчика T1 (16 бит)
  • OCR1A — регистр сравнения A (16 бит)
  • OCR1B — регистр сравнения B (16 бит)
  • TIMSK1 — регистр маски прерываний для таймера/счетчика T1
  • TIFR1 — регистр флагов прерываний для таймера/счетчика T1
  • TCCR1A — регистр управления A
  • TCCR1B — регистр управления B
  • TCCR1C — регистр управления C
  • ICR1 — регистр захвата (16 бит)

В рассматриваемых далее примерах нам важны только эти регистры: OCR1A, TCCR1A, TCCR1B и TIMSK1. 

Регистр TCCR1A:

7 6 5 4 3 2 1 0
COM1A1 COM1A0 COM1B1 COM1B0 WGM11 WGM10

Регистр TCCR1B:

7 6 5 4 3 2 1 0
ICNC1 ICES1 WGM13 WGM12 CS12 CS11 CS10

Регистр TIMSK1:

7 6 5 4 3 2 1 0
ICIE1 OCIE1B OCIE1A TOIE1

Регистр OCR1A (регистр сравнения 16 бит): содержимое этого регистра постоянно сравнивается с содержимым счётного регистра TCNT1. В случае совпадения выполняются действия, определённые регистром TCCR1A.

Биты CS12, CS11 и CS10 в регистре TCCR1B определяют тактовую частоту таймера, при помощи этих битов можно задать коэффициент делителя.

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. Тактирование по фронту

Далее рассмотрим пример скетча, который позволяет мигать светодиодом расположенном на плате Arduino (D13) с периодом 2 секунды:

void setup(){
    pinMode(13, OUTPUT);
 
    // инициализация Timer1
    cli();  // отключить глобальные прерывания
    TCCR1A = 0;   // установить регистр в 0
    TCCR1B = 0;   // установить регистр в 0
    // Таймер переполняться каждые 65535 отсчетов при коэффициенте деления 1024 или за 4,194с
 
    OCR1A = 15624; // установка регистра совпадения (1 секунда)
 
    TCCR1B |= (1 << WGM12);  // включить CTC режим > сброс таймера по совпадению
 
    TCCR1B |= (1 << CS10);   // Установить биты CS10 CS12 на коэффициент деления 1024
    TCCR1B |= (1 << CS12);
 
  // F (ПРЕРЫВАНИЙ) = 16000000 / (1024 *(1 + 15624) = 1 s
 
    TIMSK1 |= (1 << OCIE1A);  // включить прерывание по совпадению таймера 
    sei(); // включить глобальные прерывания
}
 
void loop(){}
 
ISR(TIMER1_COMPA_vect)// Вызывается ISR(TIMER1_OVF_vect), это происходит всегда когда таймер переполняется
{
    digitalWrite(13, !digitalRead(13));
}

Таймер переполняться каждые 65535 отсчетов, при частоте 16 МГц это каждые 0,0041 секунды, но у нас биты CS10 CS11 CS12 в регистре TCCR1B определяют коэффициент деления тактовой частоты в 1024 раза, соответственно прерывания будут в 1024 раза медленней за 4,194 секунды. Но так же в регистре сравнения OCR1A указано число 15624, а бит OCIE1A в регистре TIMSK1 разрешает включить прерывание при совпадении счетного регистра TCNT1 со значением регистра сравнения OCR1A, что дает нам период срабатывания прерывания в 1 секунду. Бит WGM12 в регистре TCCR1B активирует режим СТС (сброс при совпадении).

Далее активируется функция прерывания ISR в которой происходит обращение к выходу D13.

ISR(TIMER1_COMPA_vect)

TIMER1_COMPA_vect  — прерывание по сравнению, канал A таймера/счетчика 1

T(ПРЕРЫВАНИЙ) = 1/(16000000 / (1024 * (1 + 15624))) = 1 s

F (ПРЕРЫВАНИЙ)= 16000000 / (1024 * (1 + 15624)) = 1 Hz

Для определения числа регистра сравнения OCR1A используйте следующую формулу:

OCR1A = 16000000 /  делитель * период срабатывания — 1

Например для периода срабатывания в 0.2 с расчет будет таким:

OCR1A = 16000000 / 1024 * 0.2 — 1 = 3124

Так же вместо маски бита можно указывать сразу значение регистра:

void setup(){
    pinMode(13, OUTPUT);
    cli();  
    TCCR1A = 0b00000000;  // 0x00
    TCCR1B = 0b00001100;  // 0x0C 
    OCR1A = 3124; 
    TIMSK1 = 0b00000010;  // 0x02
    sei(); 
}
 
void loop(){}
 
ISR(TIMER1_COMPA_vect){digitalWrite(13, !digitalRead(13));}

Прерывание при переполнении счетного регистра TCNT1 

void setup(){
    pinMode(13, OUTPUT);
    cli();  
    TCCR1A = 0;
    TCCR1B = 0;
    TCCR1B |= (1 << CS11) | (1 << CS10); // делитель 64
    // F = 16000000 / 64 / 65535 = 3.81 Hz
    TIMSK1 |= (1 << TOIE1);
    sei(); 
}
 
void loop(){}
 
ISR (TIMER1_OVF_vect){digitalWrite(13, !digitalRead(13));}

Как и в предыдущем примере при помощи регистра TCCR1B устанавливаем делитель, а бит TOIE1 в регистре TIMSK1 взывает прерывание когда таймер переполняется.

Расчет частоты прерывания достаточно простой, при тактовой частоте 16 МГц получаем следующую формулу:

F = 16000000 / делитель /  65535

В итоге при переполнении счетного регистра активируется функция ISR (TIMER1_OVF_vect) , которая в свою очередь выполняет размещенный в ней фрагмент кода.

У таймера есть два выхода OC1A OC1B, что соответствует PB1 PB2 (D9 D10), эти выходы используются для получения ШИМ сигнала. В следующем примере выход OC1A будет менять свое логическое состояние при выполнении прерывания, при подключении светодиода к выходу D9 (через резистор 220 Ом), он будет на 1 секунду загораться, а потом на 1 секунду гаснуть (генерация меандра).

void setup(){
   // см. пример - http://rcl-radio.ru/?p=80812
    DDRB = 0b00000010;       // установить пин PB1 (D9) как выход | выход OC1A
 
    cli();                   // отключить глобальные прерывания
    TCCR1A = 0;              // установить регистр TCCR1A в 0
    TCCR1B = 0;              // установить регистр TCCR1B в 0
 
    TCCR1A |= (1 << COM1A0); // COM1A1 COM1A0  = bit:01 - изменение состояния вывода OC1A на противоположное при совпадении с A
 
    TCCR1B |= (1 << WGM12);  // включить CTC режим > сброс таймера по совпадению
    TCCR1B |= (1 << CS10);   // Установить биты CS10 CS12 на коэффициент деления 1024  
    TCCR1B |= (1 << CS12);   
 
    OCR1A = 15624;           // установка регистра совпадения (1 секунда)
 
  // F(ЧАСТОТА МЕАНДРА) = 8000000 / (1024 *(1 + 15624) = 1 s
 
    sei();                   // включить глобальные прерывания
}
 
void loop(){ }

Регистр TCCR1A:

7 6 5 4 3 2 1 0
COM1A1 COM1A0 COM1B1 COM1B0 WGM11 WGM10

Как видно из кода был задействован регистр TCCR1A, изменен бит COM1A0.

Биты COM1A1 (7) и COM1A0 (6) влияют на то, какой сигнал появится на выводе OC1A  при совпадении с A (совпадение значения счетного регистра TCNT1 со значением регистра сравнения OCR1A).

Биты COM1B1 (5) и COM1B0 (4) влияют на то, какой сигнал появится на выводе OC1B  при совпадении с B (совпадение значения счетного регистра TCNT1 со значением регистра сравнения OCR1B).

Биты COM1A1 (7) COM1A0 (6) и COM1B1 (5) COM1B0 (4) имеют следующее назначение:

  • 00 — вывод OC1A или OC1B не функционирует
  • 01 — изменение состояния вывода OC1A или OC1B на противоположное при совпадении с A или B
  • 10 — сброс вывода OC1A или OC1B в 0 при совпадении с A или B
  • 11 — установка вывода OC1A или OC1B в 1 при совпадении с A или B

В примере используется только выход OC1A, остальная часть кода аналогична предыдущим примерам.

Как ранее было отмечено у таймера есть два выхода OC1A OC1B, что соответствует PB1 PB2 (D9 D10), эти выходы используются для получения ШИМ сигнала. В стандартной функции analogWrite() максимальная частота 488,28 Гц при коэффициенте заполнения от 0 до 255. Используя регистры таймера можно получить максимальную частоту до 62,500 кГц при ШИМ 8 бит, 31,250 кГц при ШИМ 9 бит и 15,625 кГц при ШИМ 10 бит.

Битность ШИМ это сколько ступеней может быть при регулировке ШИМ от 0 до 100%, для 8 бит это от 0 до 255, 9 бит это от 0 до 511 и 10 бит от 0 до 1023. Чем выше битность ШИМ, тем плавнее может быть его регулировка (изменение скважности).

void setup(){
    DDRB = 0b00000010;       // установить пин PB1 (D9) как выход | выход OC1A
 
    cli();                   // отключить глобальные прерывания
    TCCR1A = 0;              // установить регистр TCCR1A в 0
    TCCR1B = 0;              // установить регистр TCCR1B в 0
 
   /* COM1A1 COM1A0 = bit:11 - установка вывода OC1A в 1 при совпадении с A, 
      установка  вывода OC1A в 0 если регистр TCNT1 принимает значение 0x00  (инверсный режим) */
   TCCR1A |= (1<<COM1A1);
   TCCR1A |= (1<<COM1A0);
 
/* PWM11  PWM10 
    0      1    8 bit 255
    1      0    9 bit 511    
    1      1   10 bit 1023 
   PWM13  PWM12
    0      1    PWM    
*/
   TCCR1B |= (1<<WGM12);
   TCCR1A |= (1<<WGM11);
   TCCR1A |= (1<<WGM10);
 
   TCCR1B |= (1<<CS12); //  Установить биты CS10 CS12 на коэффициент деления 1024 
// TCCR1B |= (1<<CS10);   
   TCCR1B |= (1<<CS10); 
 
   ICR1=1023; // регистр захвата (16 бит) 
   // максимальное значение ICR1 1023-10bit, 511-9bit,255-8bit
   OCR1A=511; // 0 = 100% | 511 = 50%| 1023 = 0%
 
  // F = 16000000 / 1024 (10 bit) / 1024 (делитель) = 15,26 Гц
  // F = 16000000 / 512 (9 bit) / 1024 (делитель) = 30,52 Гц
  // F = 16000000 / 256 (8 bit) / 1024 (делитель) = 61,04 Гц
 
    sei();                   // включить глобальные прерывания
}
 
void loop(){ }

Для пояснения скетча снова обратимся к регистрам TCCR1A и TCCR1B

Регистр TCCR1A:

7 6 5 4 3 2 1 0
COM1A1 COM1A0 COM1B1 COM1B0 WGM11 WGM10

Регистр TCCR1B:

7 6 5 4 3 2 1 0
ICNC1 ICES1 WGM13 WGM12 CS12 CS11 CS10

Биты WGM12 WGM11 WGM10 активируют PWM, от битов WGM11 WGM10 зависит разрядность PWM(ШИМ):

PWM11 PWM10 разрядность
0 1 8 bit
1 0 9 bit
1 1 10 bit

Биты CS12 CS11 CS10 определяют коэффициент деления тактовой частоты.

Регистры COM1A1 COM1A0 устанавливают вывод OC1A в 1 при совпадении с A,  и устанавливают вывод OC1A в 0 если регистр TCNT1 принимает значение 0x00 (инверсный режим).

Так же используем два 16-и битных регистра ICR1 — регистр захвата и OCR1A — регистр сравнения.

Регистр захвата ICR1 определяет максимальное разрешение ШИМ, для 10 бит это 1023, 9 бит это 511 и для  8 бит это 255. Регистр OCR1A меняет скважность, его значение не должно превышать значение регистра ICR1.

Расчет частоты ШИМ достаточно прост:

F = 16000000 / битность / делитель

Пример:

F = 16000000 / 1024 (10 bit) / 1024 (делитель) = 15,26 Гц

CS12 CS11 CS10 = bit:101

WGM12 WGM11 WGM10 = bit:111

ICR1 = 1023


F = 16000000 / 1024 (10 bit) / 1 (делитель) = 15625 Гц

CS12 CS11 CS10 = bit:001

WGM12 WGM11 WGM10 = bit:111

ICR1 = 1023


F = 16000000 / 512 (9 bit) / 1024 (делитель) = 30,52 Гц

CS12 CS11 CS10 = bit:101

WGM12 WGM11 WGM10 = bit:110

ICR1 = 511


F = 16000000 / 512 (9 bit) / 1 (делитель) = 31250 Гц

CS12 CS11 CS10 = bit:001

WGM12 WGM11 WGM10 = bit:110

ICR1 = 511


F = 16000000 / 256 (8 bit) / 1 (делитель) = 62500 Гц

CS12 CS11 CS10 = bit:001

WGM12 WGM11 WGM10 = bit:101

ICR1 = 255

В следующем примере показан пример создания простого частотомера, таймер настроен срабатывания прерывания при переполнении счетного регистра TCNT1. Тактовые импульсы подаются на вход Т1 (PD5 или D5), при переполнении счетного регистра TCNT1, переменная byte х увеличивается на единицу. Функция delay() задает время работы таймера (1000 мс).

volatile byte x;
unsigned long f;
 
void setup(){
    DDRD  = 0b00000000; // весь порт D как вход
    PORTD = 0b00100000; // подтягивающий резистор на PD5 (вход T0)
    Serial.begin(9600);
 
    cli();  
    TCCR1A = 0;
    TCCR1B = 0;
    TCCR1B = (1 << CS12)|(1 << CS11)|(1 << CS10); //Внешний тактовый источник на выводе T1. Тактирование по фронту
    TIMSK1 |= (1 << TOIE1); // бит TOIE1 в регистре TIMSK1 взывает прерывание когда таймер переполняется
    sei(); 
}
 
void loop(){
  x=0; // обнулить переменную х
  TCNT1 = 0; // обнулить счетный регистр
  sei(); // разрешить прерывания
  delay(1000); // ждем 1 секунду пока таймер считает импульсы
  cli(); // запретить прерывания
  f = x * 65535 + TCNT1; // подсчет частоты
  Serial.println(f);
  }
 
ISR (TIMER1_OVF_vect){x++;}// при переполнении увеличить переменную х на 1

На вход Т1 подана частота 1 МГц

Comments

  1. Здравствуйте! У меня есть код сирены:
    void setup() {
    //настройка сирены
    pinMode(7,OUTPUT); // speaker на 7 ноге
    tone(7,300); // цифровой пин, с какой частоты начинать гудеть
    //настройка мигалки
    TCCR1A=0; // Если закомментить, мигают по 1 разу
    OCR1A=624;// 40ms
    TCCR1B=(1<<WGM12)|(1<<CS12)|(1<<CS10); //div 1024 CTC mode
    TIMSK1=1<<OCIE1A;
    pinMode(A1,OUTPUT);// светодиод на A1 ноге
    pinMode(A2,OUTPUT); // светодиод на A2 ноге
    }

    void loop() { }

    ISR (TIMER1_COMPA_vect) {
    static byte n=1;
    static byte spik=40;
    static boolean count_direct=false;
    if (n<=6) PORTC^=(1<=18&&n<=23) PORTC^=(1<<2);//6 тиков моргать светодиодом на A2 ноге
    n==35? n=1:n++;
    if (count_direct==false){
    spik==60? count_direct=true : spik++; // 50-150 — скорость завывания сирены
    }
    if (count_direct==true){
    spik==40? count_direct=false : spik—;
    }
    OCR2A=spik;
    }
    Я вставляю в код моего приемника — ошибка:
    ——————
    Tone.cpp.o (symbol from plugin): In function `timer0_pin_port':
    (.text+0x0): multiple definition of `__vector_7'
    sketch\sirena-leds_4-GOOD.ino.cpp.o (symbol from plugin):(.text+0x0): first defined here
    collect2.exe: error: ld returned 1 exit status
    exit status 1
    ——————
    Это из-за использования сиреной и сервой одного таймера. Как изменить таймер, чтобы не было ошибки? Спасибо.

Добавить комментарий

Войти с помощью: