Прерывание сравнения Timer2 не работает должным образом

Чтобы разобраться с этим в первую очередь: я уже посмотрел на "Timer2 does not works as it should" и "Timer2 “Clear OC2B on Compare Match” not working as expected in CTC mode", но не нашел ответы там особенно полезными.

Теперь перейдем к моей проблеме:

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

#include "TimerTwo.h"

TimerTwo Timer2;

ISR(TIMER2_COMPA_vect) { //ISR при сравнении совпадений A
  Timer2.isrCallback();
}

void TimerTwo::initialize() {
  cli();
  TCCR2A = 0;// установить весь регистр TCCR2A в 0
  TCCR2B = 0;// то же самое для TCCR2B
  TCNT2  = 0;//инициализируем значение счетчика до 0

  // установить регистр сравнения для приращения 2 кГц
  OCR2A = 124;// = (16*10^6) / (2000*64) - 1 (должно быть < 256)

  // включить режим CTC
  TCCR2A |= _BV(WGM21);

  // Установить бит CS22 для 64-кратного предварительного делителя
  TCCR2B |=  _BV(CS22);

  // включить прерывание сравнения таймера
  TIMSK2 |= _BV(OCIE2A);
  sei();
}

void TimerTwo::attachInterrupt(void (*isr)()) {
  TIMSK2 |= _BV(OCIE2A);
  isrCallback = isr;
}

void TimerTwo::detachInterrupt() {
  TIMSK2 &= ~_BV(OCIE2A);
  isrCallback = nullptr;
}

и

#ifndef TIMERTWO_H
#define TIMERTWO_H

#include <Arduino.h>

class TimerTwo
{
  public: //методы
    void initialize();
    void attachInterrupt(void (*isr)());
    void detachInterrupt();
    void (*isrCallback)();
};

extern TimerTwo Timer2;

#endif

Вдохновением послужила библиотека TimerOne.

Однако, когда я измеряю период на выходном выводе (который я подключил внутри какого-то другого кода), я получаю только период 1 мс. Даже если я устанавливаю ORC2A на 63, этот факт не меняется.

У меня есть конструктор, который вызывается внутри моего setup(), поэтому регистры не должны перезаписываться при подключении.

Я также посмотрел техническое описание ATmega328p и проверил, что все регистры установлены правильно.

Заранее спасибо за помощь. Если вам понадобится дополнительная информация, дайте мне знать.

, 👍1

Обсуждение

Что в isrCallback после initialize? Вы должны включить прерывание timer compare A только в методе attachInterrupt. А в attachInterrupt его нужно сначала установить, а потом включить., @KIIV

Таймер может работать только так быстро, как ISR, который он запускает. Если ваш ISR работает 1 мс, вы не получите скорость быстрее 1 мс — независимо от того, какой период вы установите (ниже 1 мс)., @Majenko

Это не займет 1 мс, так как я уже запускал его на более высоких скоростях, просто не с этим таймером., @Lithimlin

@KIIV даже после активации только в attachIntterup он все равно не работает., @Lithimlin

Попробуйте удвоить OCR2A и посмотрите, получите ли вы интервал в 2 мс или нет. Какую плату Arduino вы используете? Например, Pro Mini 3.3V работает на частоте 8 МГц, так что половина скорости от 16 МГц, которые предполагает ваш код., @Gerben

Возвращаясь к моему комментарию о моем ISR: судя по всему, это ДЕЙСТВИТЕЛЬНО занимает около 1 мс, но это не имеет никакого отношения к проблеме. Я не знаю, как или почему мой логический анализатор показал мне, что период может быть 30 мкс, но поскольку я не смог воспроизвести этот результат, это, вероятно, был баг или сбой. Однако описанная здесь проблема не обязательно связана с этим, поскольку простая мигалка страдала от тех же проблем., @Lithimlin


1 ответ


Лучший ответ:

1

Так что вы правы, CTC ведет себя странно. Я тоже замечал это несколько лет назад на другой плате, но я всегда подозревал асинхронный режим (я добавил 32k RTC Xtal на Arduino Mega).

Однако режим Fast PWM с OCR2A в качестве значения TOP, похоже, работает:

class TimerTwo
{
  public: //методы
    void initialize();
    void attachInterrupt(void (*isr)());
    void detachInterrupt();
    void (*isrCallback)() = nullptr;
};

TimerTwo Timer2;

ISR(TIMER2_COMPA_vect) { //ISR при сравнении совпадений A
  Timer2.isrCallback();
}

void TimerTwo::initialize() {
  cli();

  OCR2A = 124;                        // = (16*10^6) / (2000*64) - 1 (должно быть < 256)
  TCCR2A = _BV(WGM21) | _BV(WGM20);   // включаем быстрый режим ШИМ с верхним в OCR2A
  TCCR2B = _BV(WGM22) | _BV(CS22);    // Установить бит CS22 для 64-кратного предварительного делителя
  TCNT2  = 0;                         // инициализируем значение счетчика на 0

  sei();
}

void TimerTwo::attachInterrupt(void (*isr)()) {
  isrCallback = isr;
  TIMSK2 |= _BV(OCIE2A);
}

void TimerTwo::detachInterrupt() {
  TIMSK2 &= ~_BV(OCIE2A);
  isrCallback = nullptr;
}

volatile uint32_t counter = 0;
void isrHandler() {
  ++counter;
}

void setup() {
  Serial.begin(115200);
  Timer2.initialize();
  Timer2.attachInterrupt(&isrHandler);
}

void loop() {
    static uint32_t counter_old = 0;
    Serial.println(counter - counter_old);
    counter_old = counter;
    delay(500);
}

EDIT: Хорошо, после некоторого изучения этой проблемы я обнаружил, что настройка режима CTC может привести к повреждению OCR2A (я полагаю?). Кажется, это работает, если изменить порядок настроек следующим образом:

  TCCR2B = 0;          // остановить таймер и сбросить WGM22
  TCCR2A = _BV(WGM21); // включаем режим CTC с верхним в OCR2A
  OCR2A  = 124;        // = (16*10^6) / (2000*64) - 1 (должно быть < 256)
  TCNT2  = 0;          //инициализируем значение счетчика до 0
  TCCR2B = _BV(CS22);  // Установить бит CS22 для 64 предварительного делителя (для запуска таймера)
,

Спасибо, это действительно помогло. Однако я обнаружил еще одну вещь: код в моем ISR, по-видимому, выполняется дольше 1 мс. Так что мне придется быстро найти решение этой проблемы., @Lithimlin