Точность таймера Arduino

Я использую ATMega32u4 для генерации нескольких импульсов частотой 2,5 кГц. Я использую Таймер 3, который является 16-битным таймером и использует предварительный делитель 1, поэтому у меня есть разрешение 0,0625 мкс. Я хочу генерировать импульсы с довольно высокой точностью и согласованностью. У меня есть мой предыдущий пост здесь, который дал мне точность. Но когда я смотрю на импульс с помощью осциллографа, он имеет некоторое дрожание. Видео Jitter на YouTube.

Моя конечная цель — создать импульс на нескольких разных контактах в этой конфигурации.

Я предполагаю, что причина джиттера заключается в том, что в фоновом режиме происходят другие прерывания, которые могут смещаться, когда импульс прекращается и начинается на несколько микросекунд. Просматривая таблицу данных, я нашел это, которое, кажется, показывает приоритет прерывания.

Похоже, что мой TIMER3 COMPA и COMPB находятся очень низко в списке приоритетов, есть ли способ изменить этот список приоритетов? Или есть что-то еще, что вызывает дрожание?

Я разместил свой код ниже:

#define pin2 12
#define pin3 6
#define pin4 8

#define REFRESH 6400

int PULSEIndex;

class Pulse {
  public:
    int pulseLength;
    void Begin(byte GPIO_PIN) {
      pinMode(GPIO_PIN, OUTPUT);
    }
    void writeMicro(double pulse) {
      pulseLength = (pulse * 16);
    }
};

Pulse PULSE1;
Pulse PULSE2;
Pulse PULSE3;
Pulse PULSE4;

void setup() {
  PULSE1.Begin(pin1);
  PULSE2.Begin(pin2);
  PULSE3.Begin(pin3);
  PULSE4.Begin(pin4);

  // Сброс регистра управления таймера 3 в 0
  TCCR3A = 0;
  TCCR3B = 0;

  // Установить режим CTC
  TCCR3B &= ~(1 << WGM33); //0
  TCCR3B |= (1 << WGM32);  //1

  // Установить прескалер в 1
  TCCR3B &= ~(1 << CS32); //0
  TCCR3B &= ~(1 << CS31); //0
  TCCR3B |= (1 << CS30);  //1

  //Сброс таймера 3 и установка значения для сравнения
  TCNT3 = 0;

  // Включение таймера 3 для сравнения прерываний A и B
  TIMSK3 |= (1 << OCIE3B);
  TIMSK3 |= (1 << OCIE3A);

  //Включить глобальные прерывания
  sei();

  //Установить частоту обновления
  OCR3A = REFRESH;
  OCR3B = 1000;
}

void loop() {
  PULSE1.writeMicro(40);
  PULSE2.writeMicro(40);
  PULSE3.writeMicro(40);
  PULSE4.writeMicro(40);
  delay(2);
}

ISR(TIMER3_COMPA_vect) {
  digitalWrite(pin1, HIGH);
  OCR3B = PULSE1.pulseLength;
}

ISR(TIMER3_COMPB_vect) {

  if (PULSEIndex == 1) {
    digitalWrite(pin1, LOW);
    digitalWrite(pin2, HIGH);
    OCR3B += PULSE2.pulseLength;
  }

  if (PULSEIndex == 2) {
    digitalWrite(pin2, LOW);
    digitalWrite(pin3, HIGH);
    OCR3B += PULSE3.pulseLength;
  }

  if (PULSEIndex == 3) {
    digitalWrite(pin3, LOW);
    digitalWrite(pin4, HIGH);
    OCR3B += PULSE4.pulseLength;
  }

  if (PULSEIndex == 4) {
    digitalWrite(pin4, LOW);
  }

  PULSEIndex++;
  if (PULSEIndex > 4) PULSEIndex = 1;
} ```

, 👍2

Обсуждение

Сначала попытайтесь получить доступ к контактам напрямую. digitalWrite очень медленный, и, по крайней мере, для первого импульса это не digitalWrite(pin4, LOW);, так что джиттер составляет несколько микросекунд. Просто так. Возможно рассмотреть MCU с DMA, @KIIV

Я не уверен на 100%, но я думаю, что приоритеты прерываний установлены аппаратно., @the busybee


1 ответ


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

2

Я предполагаю, что причина дрожания заключается в том, что в фоновом режиме происходят другие прерывания

Это кажется разумным предположением. Так как вы не делаете сериал связь, прерывание Таймера 0, вероятно, является единственным на заднем фоне. Вы можете отключить его с помощью:

TIMSK0 = 0;

но тогда все функции синхронизации Arduino перестанут работать. Вы будете необходимо #include <util/delay.h> и заменить delay() на _delay_ms(). Обратите внимание, что эта функция avr-libc работает только с постоянные задержки времени компиляции. С другой стороны, он циклически точен. и принимает задержки с плавающей запятой.

Есть ли способ изменить этот список приоритетов?

Нет, и даже если бы это было возможно, это было бы не так полезный. Прерывания в Arduino не являются прерываемыми: если прерывание с низким приоритетом выполняется, когда срабатывает прерывание с высоким приоритетом, прерывание с высоким приоритетом должно ждать, пока текущее прерывание не будет завершено. прекратить. Приоритеты имеют значение только тогда, когда процессор собирается обслуживать запрос на прерывание, но должен решить, какой из нескольких ожидающих запросов, он будет обслуживаться первым.

Теперь небольшое замечание по этому поводу:

void loop() {
  PULSE1.writeMicro(40);
  PULSE2.writeMicro(40);
  PULSE3.writeMicro(40);
  PULSE4.writeMicro(40);
  delay(2);
}

Нет смысла переписывать эти значения снова и снова. Если вы запланируйте изменить код, чтобы сделать эти ширины непостоянными, затем вы должны позаботиться о:

  1. создайте свойства pulseLength (или все объекты Pulse) изменчивый
  2. в writeMicro() отключите прерывания при изменении Длина импульса.
,