Использование 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, однако я пытался использовать это в библиотеке, которую писал, и пытался разделить функциональность... это может оказаться невозможным .

, 👍3

Обсуждение

Вы можете немного оптимизировать свой код, заменив if (stateA) ... на PORTD ^= _BV(PIND4); (исключительно или этого бита) и удалив эти переменные состояния volatile., @jfpoilpret

Я не вижу смысла, зачем вам нужны 2 прерывания на одной частоте, вы можете делать свои вещи в одном и том же ISR., @jfpoilpret

Потому что некоторые вещи находятся в библиотеке, и я хочу, чтобы они были отделены от другой логики., @Adam Haile


4 ответа


1

Ваш код делает не то, что вы думаете. Таймер всегда работает на частоте 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


1

Я думаю, что ваш код неправильно установил 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


1

На самом деле, еще раз прочитав техническое описание 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


1

Можно использовать прерывания сравнения 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;
  }
}
,