Генерация стабильной частоты

Я пытаюсь найти лучший способ генерировать стабильную частоту с помощью моего 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 управлялся внешним источником тактового сигнала.

, 👍4

Обсуждение

Вы пробовали использовать выходы 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


3 ответа


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

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


1

Я знаю, что эта ветка может быть мертва, но если это случится с кем-то, вышеупомянутый метод отлично сработает. Вам просто нужно отключить timer0 (power_timer0_disable();), и тогда ваш сигнал 40 кГц не будет колебаться (по частоте).

В любом случае, да, старайтесь избегать digitalWrite, если вам нужна скорость.

,

1

ШИМ — это решение.

Ответ Рассела правильный. Мой вклад здесь — это упрощение и передача моих наблюдений. Недавно я выполнил QA по PWM и могу подтвердить, что "битовые удары" и прерывания не будут стабильными. Я заметил, что цифровая запись занимает примерно 1 микросекунду, поэтому переключение вывода составляет 2 мкс. Это не повлияло на мой проект, поэтому дальнейшие тесты функции digitalWrite() не проводились. Запись изменений в макрос порта в целом намного быстрее, чем использование digitalWrite (), однако это все равно не будет иметь присущей стабильности, обеспечиваемой настройкой PWM и его выходным выводом. Как ни странно, аналоговое чтение() составляет примерно 100 мкс.

Я настроил таймер с предварительным масштабированием, а затем применил счетчик к OCRxA. С помощью моего предварительного делителя и битов WGM оказалось, что регистры OCR были 1:1 для микросекунд. OCRxB идеально установил ширину импульса, которую я искал.

Наблюдая за точкой на о'скопе, она могла отличаться от точного целого числа в OCRxA, но не дрейфовала. Ошибка, которую я наблюдал, составляла десятки микросекунд, а значения в OCRxB были между 6300 и 63000. Примеры кода находятся в вышеупомянутой ссылке на мой QA. Изучив свое собственное решение, я узнал, что эта ошибка, вероятно, находится в пределах допуска кристалла, обеспечивающего частоту ЦП.

,