Неточная частота и время сигнала ШИМ

У меня есть Teensy 3.2, и для его программирования я использую Arduino IDE. Я пытаюсь сгенерировать 8 импульсов частотой 40 кГц и это нужно повторять каждую секунду. Я написал 3 разных кода, и ни один из них не работает так, как я хотел. Скважность импульсов не всегда постоянна (т.е. задний фронт часто смещается по оси времени), что указывает на неточность задержки между командами analogWrite. Кажется, существует проблема с задержками, которые не являются постоянными и меняются в пределах 8 импульсов и между 8 импульсами. Эта неточность различна для трех разных кодов, но существует во всех из них. Изменение скорости процессора между 16 МГц и 120 МГц не приводит к изменению проблемы (за исключением кода 3, где NOP зависит от в теме). Есть ли способ сделать циклы более последовательными на частоте 40 кГц? Нужно ли использовать внешний таймер или генератор? На двух изображениях ниже показаны две проблемы. На первом изображении показаны различия во временных задержках и выходной частоте. Красные пунктирные линии показаны для обозначения расхождения в одном и том же импульсе при последовательных итерациях кода. На втором изображении показана повторяющаяся проблема, когда прямоугольный сигнал на самом деле не является прямоугольным и, похоже, не вызван проблемами программного обеспечения.

Изображение 1:

Изображение 2:

Написанные мной коды можно найти ниже:

Код 1:

const uint8_t pin1 = 5;
const uint8_t pin2 = 6;

void setup() {
  analogWriteFrequency(pin1, 40000);
  analogWriteFrequency(pin2, 40000);
}

void loop() {
  analogWrite(pin1,128);   //для рабочего цикла 50%
  delayMicroseconds(188);  //Для генерации 8 импульсов на заданной частоте требуется 187,5 мкс
  analogWrite(pin1,0);
  delay(1000);
}

Код 2:

const uint8_t pin1 = 5;
const uint8_t pin2 = 6;

void setup() {
  pinMode(pin1, OUTPUT);
  pinMode(pin2, OUTPUT);
}

void loop() {
  PWMSignal();
  delay(1000);
}

void PWMSignal(){
  for(int i =0; i<=8; i++){
      analogWrite(pin1, 255);
      analogWrite(pin2, 0);
      delayMicroseconds(10);
      analogWrite(pin1, 0);
      analogWrite(pin2, 255);
      delayMicroseconds(10);
      analogWrite(pin2, 0);
  }
}

Код 3:

const uint8_t pin1 = 5;
const uint8_t pin2 = 6;

// Переменная N определяет длину задержки.
// Выполнение NOP соответствует задержке 62,5 нс
// когда частота процессора равна 16 МГц.
const int N = 50;
#define NOP __asm__ __volatile__ ("nop\n\t")

void setup() {
  pinMode(pin1, OUTPUT);
  pinMode(pin2, OUTPUT);
}

void loop() {
  PWMSignal();
  delay(1000);
}

void PWMSignal(){
  for(int i =0; i<=8; i++){
      analogWrite(pin1, 255);
      analogWrite(pin2, 0);
      delayNOP();
      analogWrite(pin1, 0);
      analogWrite(pin2, 255);
      delayNOP();
      analogWrite(pin2, 0);
  }
}

void delayNOP(){
  for(int j =0; j<=N; j++){
    NOP;
  }
}

, 👍0

Обсуждение

Попробуйте отключить прерывания вокруг всплесков импульсов., @Majenko

Возможно, использование [IntervalTimer](https://www.pjrc.com/teensy/td_timing_IntervalTimer.html) Teensy 3.x для ручного переключения вывода (с прямой записью порта, а не digitalWrite()) даст больше надежные результаты. Подсказка: GPIOx_PTOR переключит вывод за одну операцию. «x» — это номер порта, и напишите в него (1<<p), где p — это номер контакта., @Majenko


2 ответа


0

Один из вариантов — использовать IntervalTimer Teensy:

// Настраиваем переменные и объекты:
IntervalTimer myTimer;
volatile int count = 0;   

// ... позже запустим его:
count = 0;
myTimer.begin(togglePin, 12);

// ... и:
void togglePin() {
    GPIOB_PTOR = 1<<8; // Примечание: найдите правильный регистр и бит для выбранного вами контакта.
    count++;
    // 16 переключений — это 8 импульсов.
    if (count == 16) {
        myTimer.end();
    }
}

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

,

1

на первой картинке кажется, что два импульса имеют разные рабочие циклы, поэтому они кажутся смещенными. Похоже, что часть «вкл.» остается включенной меньше времени, чем «выкл.», поэтому ваш рабочий цикл, вероятно, < 50, но ваша частота все еще может быть 40 кГц.

Я думаю, что наиболее точный способ получить 40 кГц только с помощью teensy — использовать прерывания таймера, как предложил Majenko. Ознакомьтесь с документацией по нему здесь, это довольно просто: вы устанавливаете таймер на срабатывание каждые 12,5 мкс, а в ISR просто переключаете цифровой вывод и ведете подсчет, сколько раз вы его переключали. Если вашему teensy нужно запускать другие прерывания, вам следует изменить приоритет с помощью myTimer.priority(number); чтобы, возможно, дать прерыванию 40 кГц более высокий приоритет. Одно замечание: не используйте digitalWrite в ISR, так как он медленный, попробуйте digitalWriteFast или используйте язык ассемблера для его переключения, как указал Majenko.

В качестве альтернативы вы можете использовать таймер 555, который легко настроить, и я почти уверен, что он использовался для генерации 40 кГц при 50% рабочем цикле, хотя может быть немного сложно найти правильные значения конденсатора/резистора, если они слишком малы/нестандартны. Затем вы подключите выход таймера 555 к тому, к чему вы его подключаете, а также к цифровому входному контакту на микроконтроллере. Затем вам придется установить внешнее прерывание на этом контакте, так что всякий раз, когда 555 становится высоким, прерывание на контакте срабатывает, и вы можете отслеживать, сколько импульсов было сгенерировано.

В качестве альтернативы вы можете использовать цифровой синтезатор сигналов. Я использовал ad9837 для одного из своих проектов. Он может генерировать что-то вроде 0-3 МГц с внешним тактовым сигналом 5 МГц с разрешением 0,02, что довольно хорошо. У меня есть проект altium для него, если вы не хотите покупать модуль, и адаптированная версия библиотеки adafruit miniGen, которая работает с teensy3.5 ссылка. Затем вам нужно будет сделать тот же цикл обратной связи, что и 555, чтобы отслеживать, сколько импульсов было произведено (или ваш способ ожидания X микросекунд). Модуль на самом деле довольно маломощный, поэтому вы можете питать его от цифрового вывода и включать/выключать этот вывод каждую секунду.

Думаю, сначала вам стоит попробовать метод прерывания, так как он самый простой. Кроме того, я понимаю, что вам нужно 8 импульсов каждую секунду, но, возможно, в целях отладки заставьте импульс 40 кГц идти постоянно и измените свой осциллограф для измерения частоты, чтобы вы могли видеть, насколько вы далеки от 40 кГц. Затем, как только вы узнаете, что вы действительно получаете 40 кГц, запустите его каждую секунду.

,