Генерируемая частота не соответствует ожидаемой

Этот код устанавливает на выводе ВЫСОКИЙ уровень на 1 микросекунду, а затем НИЗКИЙ на 1 микросекунду. Ожидаемая частота должна составлять около 500 кГц. При измерении выходной частоты частота составляет около 96,4 кГц. Почему?

int del = 1;

void setup() {
   // put your setup code here, to run once:
pinMode(3, OUTPUT);
}

void loop() {
  // put your main code here, to run repeatedly:

digitalWrite(3, HIGH);
delayMicroseconds(del);
digitalWrite(3, LOW);
delayMicroseconds(del);

}

, 👍2

Обсуждение

В вашем коде указано 1 микросекунда, но delayMicroсекунды на самом деле намного медленнее. Кроме того, «цикл» имеет некоторые накладные расходы. Попробуйте поместить код внутри while(true){ }`. Последние версии Arduino IDE стали лучше выполнять код быстрее, но если вам нужна точность синхронизации, лучше использовать низкоуровневые таймеры/счетчики AVR., @MichaelT


2 ответа


5
Выполнение

digitalWrite() также занимает несколько микросекунд. Также существуют некоторые накладные расходы, связанные с функцией delayMicroсекунды().

Если вам нужна ровно 500 кГц, можно рассмотреть возможность использования таймера. О том, как его настроить в микроконтроллерах, можно прочитать техническая таблица.

К сожалению, у меня нет доступа к Arduino Leonardo, но вот пример кода для ATmega328P. Имена регистров будут другими, но вы сможете адаптировать код под свой Arduino.

// частота 500 кГц

void setup() {
  cli();                 //Отключаем прерывание во время установки
  TCCR2A = 0;
  TCCR2B = 0;

  TCCR2B |= (1 << CS20); //Включаем таймер без прескалера
  OCR2A = 15;            //Прерываем TIMER2_COMPA после достижения этого значения счетчика
  TIMSK2 |= (1 << 1);    //Включаем прерывание TIMER2_COMPA

  DDRD |= (1 << PORTD3); //Устанавливаем цифровой контакт 3 в качестве выходного контакта
  sei();                 //Снова разрешаем прерывания
}

ISR(TIMER2_COMPA_vect) { //Если счетчик достиг точки, когда выходной сигнал должен быть низким
  TCNT2 = 0;
  PIND |= (1 << PORTD3); //Переключение цифрового контакта 3
}

void loop() {
  //здесь ваш циклический код
}

Объяснение: TIMER2 — это 8-битный таймер, что означает, что он всегда будет считать от 0 до 255, независимо от того, что делает процессор.

Обычно она рассчитывается на тактовой частоте Arduino, например 16 МГц.

Когда счетчик достигает 15, немедленно выполняется специальный код, называемый «Процедурой обслуживания прерываний». В этом коде мы сбрасываем таймер и меняем состояние вашего выходного контакта, в результате чего на этом выводе появляется прямоугольный сигнал частотой 500 кГц.

Мы считаем до 15, так как между "0" 16 шагов. где начинается счетчик и 15, где мы его сбрасываем.

16 000 000 Гц / 1 (без прескалера) / 16 (счетчик) / 2 = 500 кГц

Дивизор 2 обусловлен тем, что нам нужно изменить состояние контакта дважды для каждого полного квадрата выходного сигнала.

,

Иногда пишешь 500кГц, потом пишешь 500Гц. Согласно расчетам, код рассчитан на 500 Гц. Поскольку ОП запрашивал 500 кГц, прескалер можно установить на 1, а значение сравнения (при котором таймер запускает прерывание) на 16. В моих расчетах это дает ровно 500 кГц. Я прав?, @chrisl

Но, как вы написали, digitalWrite() занимает слишком много времени, поэтому правильным решением будет манипулирование битами в регистрах порта: https://www.arduino.cc/en/Reference/PortManipulation, @chrisl

Да, конечно, мне очень жаль. Вот что происходит, когда вы перерабатываете код после долгого рабочего дня. Я исправлю это. И ты тоже прав, digitalRead и -Write слишком медленные для 500 кГц, реализую через регистры., @towe

Вы можете использовать один из аппаратных выходов времени, например OC2A или OCRB, и полностью избежать накладных расходов ISR/digitalWrite/bitbanging., @Dave X

Сейчас все исправлено, еще раз извините. Мой прицел недостаточно быстр для 500 кГц, но он работает так, как задумано, для 50 кГц. «Стандартные» выходы хороши, но тогда вы ограничены двумя контактами на таймер. Это уже необходимо на частоте 500 кГц?, @towe

Я думаю, вы ответили на вопрос: 1) почему: из-за накладных расходов, 2) решения проблемы 500 кГц. Но если накладные расходы ISR по-прежнему остаются проблемой на этой или более высоких частотах, ему, возможно, придется настроить порог, меньший, чем рассчитано, или перейти к аппаратному переключению на выводе OCxx., @Dave X

На https://www.arduino.cc/en/Tutorial/SecretsOfArduinoPWM есть раздел «Режим быстрого ШИМ с верхом OCRA» для этого варианта использования., @Dave X


0
Согласно документации IDE,

delayMicroсекунды() имеет минимальное значение 3-4 мкс. Это не поможет 1.

loop() также добавляет некоторые механизмы задержки, выполняющие фоновую работу.

Вы увидите лучшие результаты с помощью while() :

loop(){
  while (1){
  digitalWrite(3, HIGH);
  delayMicroseconds(del);
  digitalWrite(3, LOW);
  delayMicroseconds(del);
  }
}

с прямым манипулированием портом вместо цифровой записи.

Также будет наблюдаться дрожание фонового измерения времени millis(). Если вам нужен более плавный сигнал, лучше также отключить прерывания.

,