В платах 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 МГц
Здравствуйте! У меня есть код сирены:
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
——————
Это из-за использования сиреной и сервой одного таймера. Как изменить таймер, чтобы не было ошибки? Спасибо.