Использование TIMER0_COMPB_vect
Я пытаюсь понять, как в полной мере использовать вектор COMPB ISR Timer0 на ATMega328 (стандартное ядро Arduino). Я понимаю, что это заставит delay() и millis() не работать, но меня это устраивает. Я настроил тестовый код, который должен выводить сигнал 244 Гц на D4 и 488 Гц на D5 (обратите внимание, он работает на частоте 8 МГц). Но D5 (COMPB) — это всегда то же самое, что и D4 (COMPA). Какие-нибудь мысли? Код ниже:
#include <avr/interrupt.h>
volatile bool stateA = false;
ISR(TIMER0_COMPA_vect)
{
if (stateA)
PORTD |= _BV(PIND4);
else
PORTD &= ~(_BV(PIND4));
stateA = !stateA;
}
volatile bool stateB = false;
ISR(TIMER0_COMPB_vect)
{
if (stateB)
PORTD |= _BV(PIND5);
else
PORTD &= ~(_BV(PIND5));
stateB = !stateB;
}
void setup()
{
DDRD |= _BV(PIND4) | _BV(PIND5);
// устанавливаем прерывание timer0 на 2 кГц
TCCR0A = 0;// устанавливаем весь регистр TCCR2A в 0
TCCR0B = 0;// то же самое для TCCR2B
TCNT0 = 0;//инициализировать значение счетчика до 0
// установить регистр соответствия для сравнения с шагом 2 кГц
OCR0A = 255;//244 Гц
OCR0B = 127;//488 Гц
// включаем режим CTC
TCCR0A |= (1 << WGM01);
// Установите биты CS01 и CS00 для 64 прескалера
TCCR0B |= (1 << CS01) | (1 << CS00);
// включить прерывание сравнения таймера
TIMSK0 |= (1 << OCIE0B) | (1 << OCIE0A);
interrupts();
}
Обновление: Подумав об этом, я понял, что мне не нужны две отдельные частоты (хотя я просто хотел знать, как это сделать, так что это хорошо), но мне просто нужно два отдельных прерывания, использующих только Timer0 (1 и 2 уже используются где-то еще). Они могут быть одной частоты, скорее всего. Вероятно, я мог бы поместить всю необходимую мне функциональность в один и тот же TIMER0_COMPA_vect, однако я пытался использовать это в библиотеке, которую писал, и пытался разделить функциональность... это может оказаться невозможным .
@Adam Haile, 👍3
Обсуждение4 ответа
Ваш код делает не то, что вы думаете. Таймер всегда работает на частоте 244 Гц, а OCR0B соответствует только дважды за период, в результате чего он имеет одну и ту же частоту, но другую фазу. Что вам нужно сделать, так это установить OC0B на совпадение сравнения и очистить его в ISR переполнения. OCR0A можно просто включить при совпадении.
TCCR0A = _BV(COM0A0) | _BV(COM0B1) | _BV(COM0B0) | _BV(WGM01);
TIMSK0 |= _BV(OCIE0A);
...
ISR(TIMER0_COMPA_vect)
{
PORTB &= ~_BV(PD5);
}
Кроме того, OC0A — это D6, а не D4.
Ваш код, похоже, не соответствует тому, что вы говорите... не могли бы вы немного пояснить?, @Adam Haile
CTC сбрасывает таймер, когда он соответствует OCR0A. Мы устанавливаем для COM0A[1:0] значение 0b01, чтобы заставить OC0A переключаться. Для OC0B мы устанавливаем его в середине периода таймера (COM0B[1:0]=0b11, OCR0B ~= OCR0A/2), а затем очищаем его в конце (через ISR). Это дает ему вдвое большую частоту, чем OC0A., @Ignacio Vazquez-Abrams
Извините, но какой-то код был бы действительно полезен, я все еще немного потерян., @Adam Haile
Код, который я дал, интегрируется непосредственно в код, который у вас уже есть, просто замените назначение на TCCR0A, а также весь ISR., @Ignacio Vazquez-Abrams
Так же как и разрешение прерывания переполнения, которое я только что добавил (поскольку у вас не будет библиотек Arduino, которые сделают это за вас)., @Ignacio Vazquez-Abrams
А-а-а, поскольку я не думал, мне не приходило в голову, что совпадение на OCR0A произойдет одновременно с переполнением, поэтому я изменил используемый ISR. Теперь он должен скомпилироваться с библиотеками Arduino., @Ignacio Vazquez-Abrams
Итак... Я еще немного подумал о своем коде, и мне *не нужно*, чтобы они работали на двух разных частотах. Мне просто нужно разделить прерывания. Делает ли это что-то проще?, @Adam Haile
@AdamHaile: вам даже не нужны два прерывания, вам просто нужно, чтобы OC0B переключался в два раза чаще, чем OC0A. Мой код использует преимущества аппаратного таймера, чтобы делать именно это, а не делать все это в прерываниях., @Ignacio Vazquez-Abrams
Я думаю, что ваш код неправильно установил TIMSK0
.
Вместо:
TIMSK0 |= (1 << OCIE0B) | (1 << OCIE0A);
Вы должны использовать:
TIMSK0 = (1 << OCIE0B) | (1 << OCIE0A);
Почему? Поскольку TIMSK0
уже инициализирован библиотеками Arduino до вызова вашего setup()
.
Если вы посмотрите на hardware/arduino/cores/arduino/wiring.c
и выполните поиск функции init()
, вы увидите, что она содержит следующие строки:
// включить прерывание по переполнению таймера 0
#if defined(TIMSK) && defined(TOIE0)
sbi(TIMSK, TOIE0);
#elif defined(TIMSK0) && defined(TOIE0)
sbi(TIMSK0, TOIE0);
#else
#error Timer 0 overflow interrupt not set correctly
#endif
В этих строках бит TOIE0
в TIMSK0
установлен, но ваш setup()
не очищает его.
Хотя на самом деле это не должно мешать. Переполнение таймера 0 предназначено только для функций синхронизации Arduino., @Ignacio Vazquez-Abrams
Я так и думал, но я думаю, что все же предпочтительнее убедиться, что вы правильно установили все регистры Timer0. Функции таймера немного сложны, и вас может укусить какое-то странное поведение, которого вы не ожидали, если вас это не волнует., @jfpoilpret
См. обновление выше, @Adam Haile
На самом деле, еще раз прочитав техническое описание ATmega328P, я не уверен, что вы можете использовать как OCR0A
, так и OCR0B
в обычном режиме таймера.
В частности, поскольку вы используете режим CTC, я думаю, что TCNT0
будет очищаться каждый раз, когда его значение достигает OCR0A
, то есть 127.
Поскольку вам нужны 244 Гц и 488 Гц (ровно вдвое больше 244 Гц), я думаю, вы можете работать только с одним из OCR0A
или OCRB0
и выполнять работу только в одном ISR:
#include <avr/interrupt.h>
volatile bool setBothPins = false;
ISR(TIMER0_COMPA_vect)
{
if (setBothPins)
// D4 изменяется только один раз (244 Гц) на каждые 2 изменения D5 (488 Гц)
PORTD ^= _BV(PIND4);
PORTD ^= _BV(PIND5);
setBothPins = !setBothPins;
}
void setup()
{
DDRD |= _BV(PIND4) | _BV(PIND5);
// установить прерывание timer0 на 2 кГц
TCCR0A = 0;// устанавливаем весь регистр TCCR2A в 0
TCCR0B = 0;// то же самое для TCCR2B
TCNT0 = 0;//инициализировать значение счетчика до 0
// установить регистр соответствия для сравнения с шагом 2 кГц
OCR0A = 127;//488 Гц
// включаем режим CTC
TCCR0A |= (1 << WGM01);
// Установите биты CS01 и CS00 для 64 прескалера
TCCR0B |= (1 << CS01) | (1 << CS00);
// включить прерывание сравнения таймера
TIMSK0 = (1 << OCIE0A);
interrupts();
}
Только что проверил на своем UNO, вроде работает нормально (с двойными частотами, так как UNO работает на 16МГц)., @jfpoilpret
См. обновление выше, @Adam Haile
Можно использовать прерывания сравнения Timer0, не затрагивая функции millis()
и micros()
. Просто установите прерывания сравнения на 1/3 и 2/3 периода переполнения. Таким образом, они не будут конфликтовать друг с другом, потому что между каждым из трех прерываний будет некоторое время (переполнение, сравнение A и сравнение B). Вы не можете изменить частоту, потому что это изменит обновление millis()
и micros()
. Вместо этого вы можете использовать программные post-масштабаторы внутри ISR, что-то вроде этого:
volatile bool isr_t0a_flag = false;
volatile uint32_t isr_t0a_delta_time = 0;
#define TIMER_0_COMPARE_A_POST_SCALER 50
volatile bool isr_t0b_flag = false;
volatile uint32_t isr_t0b_delta_time = 0;
#define TIMER_0_COMPARE_B_POST_SCALER 100
void setup()
{
Serial.begin(115200);
Serial.print("\n\n");
// Узнать, чем был инициализирован Timer0.
Serial.println(TCCR0A); // 3 = режим Fast PWM с фиксированным временем ожидания 255.
Serial.println(TCCR0B); // 3 = 64 предделителя.
Serial.println(OCR0A); // 0
Serial.println(OCR0B); // 0
Serial.println(TIMSK0); // 1 = прерывание по переполнению таймера разрешено для millis()/micros().
Serial.print("\n\n");
cli(); // Отключить прерывания.
//
// Установить Timer0 для сравнения прерываний
//
// Тактовая частота = 16 МГц
// Пределитель = 64
// Время ожидания = 255
//
// Частота прерывания переполнения = 16 МГц / 64 / (255 + 1) = 977 Гц (1024 мкс)
// Частота прерывания переполнения = 8 МГц/64/(255 + 1) = 488 Гц (2048 мкс)
//
OCR0A = 85; // Установите фазовый сдвиг на 1/3 для сравнения A.
OCR0B = 171; // Установите фазовый сдвиг на 2/3 для сравнения B.
TIMSK0 |= (1 << OCIE0A); // Разрешить прерывание сравнения A.
TIMSK0 |= (1 << OCIE0B); // Разрешить прерывание сравнения B.
sei(); // Включить прерывания.
// Проверка.
Serial.println(TCCR0A); // 3 = режим Fast PWM с фиксированным временем ожидания 255.
Serial.println(TCCR0B); // 3 = 64 предделителя.
Serial.println(OCR0A); // 85 = сдвиг фазы на 1/3 для сравнения A.
Serial.println(OCR0B); // 171 = фазовый сдвиг 2/3 для сравнения B.
Serial.println(TIMSK0); // 7 = Все 3 прерывания разрешены.
Serial.print("\n\n");
}
void loop()
{
uint32_t t0a_delta_time = 0;
uint32_t t0b_delta_time = 0;
/////////////////////////////
// НАЧАЛО КРИТИЧЕСКОЙ СЕКЦИИ
cli(); // Отключить прерывания.
bool t0a_flag = isr_t0a_flag;
if (t0a_flag)
{
t0a_delta_time = isr_t0a_delta_time;
isr_t0a_flag = false;
}
bool t0b_flag = isr_t0b_flag;
if (t0b_flag)
{
t0b_delta_time = isr_t0b_delta_time;
isr_t0b_flag = false;
}
sei(); // Включить прерывания.
// КОНЕЦ КРИТИЧЕСКОЙ СЕКЦИИ
/////////////////////////////
if (t0a_flag)
{
Serial.print("T0a ");
Serial.println(t0a_delta_time);
}
if (t0b_flag)
{
Serial.print("T0b ");
Serial.println(t0b_delta_time);
}
}
ISR(TIMER0_COMPA_vect)
{
static byte count = 0;
count++;
if (count >= TIMER_0_COMPARE_A_POST_SCALER)
{
count = 0;
static uint32_t previous_time = 0;
uint32_t time = micros();
uint32_t delta_time = time - previous_time;
previous_time = time;
isr_t0a_delta_time = delta_time;
isr_t0a_flag = true;
}
}
ISR(TIMER0_COMPB_vect)
{
static byte count = 0;
count++;
if (count >= TIMER_0_COMPARE_B_POST_SCALER)
{
count = 0;
static uint32_t previous_time = 0;
uint32_t time = micros();
uint32_t delta_time = time - previous_time;
previous_time = time;
isr_t0b_delta_time = delta_time;
isr_t0b_flag = true;
}
}
- Использование millis() и micros() внутри процедуры прерывания
- Arduino непрерывно считывает значение АЦП с помощью прерывания
- 4-битный счетчик вверх и вниз
- Включить и отключить отдельные прерывания
- ATtiny85 AC Phase Control для регулировки яркости лампочки
- Присоедините функцию Arduino ISR к члену класса
- Быстрее TimerOne с Teensy 4.0 (600 МГц)
- Прерывание переполнения Timer0 не работает
Вы можете немного оптимизировать свой код, заменив
if (stateA) ...
наPORTD ^= _BV(PIND4);
(исключительно или этого бита) и удалив эти переменные состоянияvolatile
., @jfpoilpretЯ не вижу смысла, зачем вам нужны 2 прерывания на одной частоте, вы можете делать свои вещи в одном и том же ISR., @jfpoilpret
Потому что некоторые вещи находятся в библиотеке, и я хочу, чтобы они были отделены от другой логики., @Adam Haile