Генерация стабильной частоты
Я пытаюсь найти лучший способ генерировать стабильную частоту с помощью моего Uno R3
Я использую прерывания для генерации частоты приблизительно 40 кГц, которая управляет некоторыми ИС/МОП-транзисторами, чтобы эффективно генерировать переменный ток частотой 40 кГц, питающий преобразователь.
Мне нужно получить частоту от второго преобразователя и рассчитать любые сдвиги частоты.
Проблема в том, что генерируемая частота немного отклоняется от 40 кГц. Есть ли способ зафиксировать его на частоте 40 кГц с помощью программного обеспечения? Я получаю выход примерно 40 кГц (измерено с помощью программного обеспечения PicoScope) с этим кодом:
#define LEDPIN 13 void setup() { pinMode(LEDPIN, OUTPUT); // инициализируем Таймер1 cli(); // отключаем глобальные прерывания TCCR1A = 0; // установить весь регистр TCCR1A в 0 TCCR1B = 0; // то же самое для TCCR1B // установить сравнение регистра совпадения с желаемым счетчиком таймера: OCR1A = 24; // включить режим CTC: TCCR1B |= (1 << WGM12); // Установите CS11 для 8-битного прескалера: TCCR1B |= (1 << CS11); // включить прерывание сравнения таймера: TIMSK1 |= (1 << OCIE1A); // разрешить глобальные прерывания: sei(); } void loop() { // основная программа } ISR(TIMER1_COMPA_vect) { digitalWrite(LEDPIN, !digitalRead(LEDPIN)); }
Если нет способа заблокировать его, есть ли способ, по крайней мере, для Uno точно отображать/сохранять частоту сигнала, который он генерировал в заданное время?
Или это неправдоподобно, потому что Uno "думает", что генерирует 40 кГц?
Спасибо за любые подсказки или предложения.
EDIT: @RussellMcMahon указал мне правильное направление для этого, поэтому я принял его ответ. Тем не менее, я хотел поделиться своим окончательным кодом и своими неправильными представлениями, с которыми я столкнулся. Надеюсь, это поможет кому-то еще в будущем. Я, очевидно, ни в коем случае не эксперт, и я постараюсь не давать здесь вводящей в заблуждение информации.
Моя первоначальная цель состояла в том, чтобы создать как можно более чистый ШИМ с частотой 40 кГц и коэффициентом заполнения 50 %.
Проблемы с исходным кодом выше:
- Использование 8-битного предварительного масштабирования
- Использование прерывания
- Использование digitalWrite()
8-битный прескейлер
Проблема здесь заключалась в том, что счетчик тикал только один раз каждые 8 тактов. тактовая частота 16 МГц / 8 (предварительный масштабатор) = тактовая частота 2 МГц Когда я спросил об этом одного из своих профессоров, он сказал что-то вроде «цикл может быть пропущен из-за прерывания, и это приведет к ждать еще 7 циклов, чтобы «тикать»». На самом деле, изначально я использовал прерывания, и когда мы провели расчеты, казалось, что это может привести к колебаниям до 1 кГц на частоте 40 кГц.
Чтобы исправить это, я отключил предварительный масштабатор и запустил прямо на базовой тактовой частоте, чтобы увеличить разрешение.
Использование прерывания
По сути, прерывание может привести к тому, что тактовые циклы не будут подсчитываться, что еще больше усложняется использованием 8-битного предварительного масштабатора. Если один такт был пропущен, с тактовой частотой 2 МГц (после предварительного масштабирования) вы добавляете к счетчику 5E-7 секунд, изменяя выходную частоту.
Использование digitalWrite()
Функция digitalWrite довольно медленная. Я не гуру, но я читал это в нескольких местах, и, кажется, здесь есть хорошее сравнение методов: Колебание цифровых выводов Чтобы обойти эту проблему, было предложено переключить один бит, что я и намеревался сделать в мой окончательный код. (Стоит отметить, что теперь, когда я завершил этот проект, предыдущая ссылка также ссылается на добавление библиотеки для более быстрой цифровой записи/чтения/и т. д....)
Я просмотрел несколько вариантов и собрал код отовсюду и сделал пару рабочих примеров, но они не сделали это так, как я хотел. Я считаю, что частью проекта, на основе которого я создал эту версию, было объединение предыдущей версии, над которой я работал, и примера класса, который я нашел в Интернете.
Без дальнейших церемоний, я представляю код, на написание которого у меня ушло постыдное количество времени из многочисленных источников... Я действительно должен возиться с этим материалом чаще:
#include <avr/io.h>
#include <avr/interrupt.h>
void setup()
{
//Отключить прерывания
cli();
// Очистить регистры Timer1
TCCR1A = 0;
TCCR1B = 0;
// Установите OCR1A (TOP): 16 МГц/40 кГц/2 = 200 шагов.
// Делим 200 на 2 = 100, потому что осциллограммы сосредоточены вокруг OCR1A (из-за переключения)
OCR1A = 100;
// Настройка таймера/счетчика 1
// Выбрать P & F Правильный режим ШИМ и OCR1A в качестве ВЕРХНЕГО. Используйте биты 10 и 13 WGM.
// Биты WGM находятся в TCCR1A и TCCR1B. Любые предыдущие настройки сбрасываются.
// Включить COM1A0 для переключения OC1A при сравнении соответствия
TCCR1A = _BV(WGM10) | _BV(COM1A0);
// CS10 для включения базовых часов без предварительного масштабирования
TCCR1B = _BV(WGM13) | _BV(CS10);
// Установите OC1A (цифровой контакт 9 / порт B, контакт 1) на выход
DDRB |= _BV(1);
}
void loop()
{
}
Для справки, вот формула тактовой частоты. В этом случае мне пришлось снова разделить результат на 2, так как я переключаюсь.
Результирующий вывод проверен PicoScope:
В заключение вы заметите, что это не совсем 40 кГц. Исследования показали, что Arduino Uno использует менее точный керамический резонатор для тактирования ATMEGA328P (хотя для тактирования USB имеется встроенный кварцевый генератор с частотой 16 МГц). Для дальнейшего повышения точности генерируемого сигнала потребуется, чтобы Arduino управлялся внешним источником тактового сигнала.
@Ramrod, 👍4
Обсуждение3 ответа
Лучший ответ:
Вы можете использовать таймер/счетчик для вывода сигнала прямоугольной формы по вашему выбору без вмешательства процессора или использования прерываний.
В описании
"ATmega48A/PA/88A/PA/168A/PA/328/P"Atmel-8271I-AVR-ATmega-Datasheet_10/2014
стр. 142 там написано
- Регистр сравнения вывода с двойной буферизацией (OCR2A и OCR2B) постоянно сравнивается со значением таймера/счетчика. Результат сравнения может использоваться генератором сигналов для генерации ШИМ или переменной частоты на выводах сравнения выходов (OC2A и OC2B). См. «Блок сравнения выходов». ” на стр. 143 для получения подробной информации. Событие сравнения соответствия также устанавливает флаг сравнения (OCF2A или OCF2B), который может использоваться для создания запроса на прерывание сравнения вывода.
Прерывание МОЖЕТ быть сгенерировано и использовано, но необязательно.
Похоже, что соответствующий материал простирается до страницы 159.
Спасибо, Рассел, я боролся с этим, и это кажется правильным направлением. Я читал документацию и думаю, что у меня все получилось., @Ramrod
Я знаю, что эта ветка может быть мертва, но если это случится с кем-то, вышеупомянутый метод отлично сработает. Вам просто нужно отключить timer0 (power_timer0_disable();), и тогда ваш сигнал 40 кГц не будет колебаться (по частоте).
В любом случае, да, старайтесь избегать digitalWrite, если вам нужна скорость.
ШИМ — это решение.
Ответ Рассела правильный. Мой вклад здесь — это упрощение и передача моих наблюдений. Недавно я выполнил QA по PWM и могу подтвердить, что "битовые удары" и прерывания не будут стабильными. Я заметил, что цифровая запись занимает примерно 1 микросекунду, поэтому переключение вывода составляет 2 мкс. Это не повлияло на мой проект, поэтому дальнейшие тесты функции digitalWrite()
не проводились. Запись изменений в макрос порта в целом намного быстрее, чем использование digitalWrite ()
, однако это все равно не будет иметь присущей стабильности, обеспечиваемой настройкой PWM и его выходным выводом. Как ни странно, аналоговое чтение() составляет примерно 100 мкс.
Я настроил таймер с предварительным масштабированием, а затем применил счетчик к OCRxA
. С помощью моего предварительного делителя и битов WGM
оказалось, что регистры OCR
были 1:1 для микросекунд. OCRxB
идеально установил ширину импульса, которую я искал.
Наблюдая за точкой на о'скопе, она могла отличаться от точного целого числа в OCRxA
, но не дрейфовала. Ошибка, которую я наблюдал, составляла десятки микросекунд, а значения в OCRxB
были между 6300 и 63000. Примеры кода находятся в вышеупомянутой ссылке на мой QA. Изучив свое собственное решение, я узнал, что эта ошибка, вероятно, находится в пределах допуска кристалла, обеспечивающего частоту ЦП.
- Изменчивая переменная не обновляется с таймера ISR
- ATmega328P - проблема с использованием таймера 2 для генерации тона
- Интервальный таймер на Arduino: Сомнения по поводу библиотеки TimerOne
- Точность синхронизации Arduino nano
- максимальная частота ШИМ на основе прерываний при 500 Гц
- Не удается получить OC1B (контакт 10) для вывода
- Заставить TCNT оставаться ниже OCRxA на ATmega328P
- Можно ли отсоединить прерывание на определенное время
Вы пробовали использовать выходы PWM? Их частоту можно изменить, см. http://playground.arduino.cc/Code/PwmFrequency., @DaveP
Предположительно процессор управляется кристаллом? | Не может ли таймер управлять выводом напрямую без прерываний? - или это то, что здесь происходит? (просматривая код, не очевидно (для меня), как он завершает функцию, которую вы реализуете.) Если это режим таймера счетчика, почему требуются прерывания - разве пин не переключается таймером без вызова прерывания. Если это так, IRQ может увеличить задержку задачи., @Russell McMahon
@DaveP, я не верю, что смогу уменьшить частоту до того, что мне нужно. С помощью digitalWrite, который я использую, в основном он создает ШИМ с нуля., @Ramrod
@RussellMcMahon Я пропустил фрагмент кода, когда вставлял его изначально - смотрите обновленный код. Я подумал о том, чтобы просто запустить метод переключения через цикл, но не будет ли это потенциально привязывать Arduino к чему-либо еще? Хотя, если я могу положиться на него для генерации точного сигнала, я полагаю, что мне не понадобится Arduino для чего-то еще., @Ramrod
Вы не можете заставить Arduino измерять сигнал, сгенерированный timer1. Часы для таймера такие же, как и для процессора, поэтому он всегда будет говорить, что он прав на деньги. Вам придется настроить его, используя некоторые внешние измерения. Кстати, я не понимаю, почему вы не используете опцию timer1 для переключения контакта, если только нет необходимости использовать контакт 13 вместо 9 или 10., @Gerben
Это использует режим генерации сигнала 9 (0b1001), фазу и частоту правильные, что дает вам 50% рабочий цикл за счет переключения на соответствие OCR1A, пока он считает до OCR1A, а затем обратно. Если вам нужно отрегулировать рабочий цикл от 50%, вы можете использовать регистр сравнения OCR1B для управления выходным контактом OC1B, или вы можете использовать режим 8 WGM для установки TOP с ICR1 и управления рабочим циклом на OC1A. с OCR1A. Вы также можете получить удвоенную максимальную частоту, используя быстрые схемы ШИМ только со счетом с режимами WGM 14 или 15 (0b1110 или 0b1111)., @Dave X
Извините за вопрос, но я не вижу функцию ISR TIMER1, которую вы использовали в своем последнем коде. Я только прочитал настройку таймера и порта для цифрового контакта 9., @Stavros Boletis