Можно ли сгенерировать точный тактовый импульс 15 кГц с помощью ардуино?

Я хочу сгенерировать импульс 15 кГц с помощью Arduino, используя Timer1, но проблема в том, что если нам нужны часы 15000 Гц, нам нужно инициализировать таймер с 1/15000 секунд или 66,66 микросекунд, но мы можем передавать только целые числа без какой-либо десятичной точности в функции Timer1.initialize(66); которая генерирует частоту 15155 Гц.

Итак, можно ли каким - либо другим способом генерировать точные 15000 Гц или частоты между 15000 Гц- 15155 Гц?

#include <TimerOne.h>

void setup() {
  pinMode(9,OUTPUT);
  Timer1.initialize(66);  // Частота, 100 мкс = 10 кГц
  Timer1.pwm(9,255);
}
 
void loop() {}

, 👍7

Обсуждение

Нет, вы не можете, по крайней мере, со стандартным Arduino Uno и никакими другими компонентами. Проблема в том, что Arduino Uno работает на частоте 16 МГц, которая неравномерно делится на 15 000 Гц. Вы можете получить приближение только с помощью внутренних таймеров (которые все основаны на основных часах или какой-то их части)., @StarCat

любой внешний компонент, который может решить мою проблему? или любой другой микроконтроллер, в котором я могу хотя бы инициализировать свой таймер на 66,6 микросекунды, @astrick

Используя серию PIC32MX1xx или 2xx, работающую на частоте 40 МГц, можно генерировать 14999.992675785 Гц, используя опорный тактовый выход, то есть системные часы, деленные на (2*(1333+(171/512)))., @Majenko

Если вы можете изменить xtal на 12MHz (загрузчик для этого xtal тоже), то это легко: D, @KIIV

[LTC6903] (https://www.analog.com/media/en/technical-documentation/data-sheets/69034fe.pdf ) может генерировать 14998,217 Гц - это достаточно близко для вас?, @Majenko

@StarCat хотя технически и правильно, но это вводит в заблуждение, так как мне будет все равно, если это будет только очень маленький кусочек. Деление 16 МГц на 1067 даст вам 14995 Гц. Что составляет всего 0,03% скидки. Кристалл 16 МГц на UNO, вероятно, имеет более высокую погрешность, чем эта., @Gerben

@Gerben, я понял, что ОП был заинтересован в близком (r) приближении после того, как я уже напечатал свой комментарий. Это не должно было вводить в заблуждение., @StarCat

Нет такого понятия, как "точно", только "в пределах допуска", которое вы на самом деле не указали (если только вы не имеете в виду 15077,5 ± 77,5)., @chrylis -cautiouslyoptimistic-

Нет, прочтите спецификацию той детали, которую вы используете. Нет такой вещи, как ТОЧНЫЕ часы. Даже высококлассные микропроцессоры, использующие PLL, имеют тактовое и фазовое дрожание., @TomServo

На практике разные осцилляторы будут иметь разные ошибки, и почти все осцилляторы зависят от температуры, поэтому вы, очевидно, никогда не получите идеальные 15 кГц с атомными часами, даже если у вас есть генератор 15 МГц. Но после принятия этого во внимание все сводится к компромиссу: хотите ли вы, чтобы в среднем 15000 тиков в секунду, или вам нужно, чтобы импульсы были равномерно распределены? С генератором 16 МГц вы можете относительно точно регулировать интервал, чтобы средняя частота была близка к 15 кГц., @Groo

@chrylis-cautiouslyoptimistic-: Я считаю, что имеется в виду "так же точно, как исходные часы"., @Ben Voigt


6 ответов


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

20

Вы можете приблизиться к этому, если запрограммируете таймер 1 напрямую (не через библиотеку) и запустите его с прескалером, установленным в 1. В идеале вы хотите, чтобы период таймера в тактах был следующим:

F_CPU / 15 кГц = 16 000 кГц / 15 кГц ≈ 1066,67 циклов процессора

Если вы округлите это до ближайшего целого числа, то получите

F_CPU / 1,067 = 16,000 кГц / 1,067 ≈ 14,9953 кГц

Это примерно на 0,03% слишком медленно, что вполне укладывается в допуск керамического резонатора, синхронизирующего Uno.

Вот моя попытка сделать это. Я проверил это.

constexpr float PWM_FREQUENCY = 15e3;  // 15 кГц
constexpr uint16_t PERIOD = round(F_CPU / PWM_FREQUENCY);

void setup() {
    // Настройка таймера 1 для ШИМ 15 кГц на выводе 9 = PB1 = OC1A.
    DDRB  |= _BV(PB1);    // установить вывод в качестве вывода
    TCCR1B = 0;           // таймер остановки
    TCCR1A = _BV(COM1A1)  // неинвертирующий PWN на OC1A
           | _BV(WGM11);  // режим 14: быстрый ШИМ, TOP = ICR1
    TCCR1B = _BV(WGM12)   // то же самое
           | _BV(WGM13)   // то же самое
           | _BV(CS10);   // часы @ F_CPU
    ICR1   = PERIOD - 1;  // период
    OCR1A  = PERIOD / 4;  // рабочий цикл
}

void loop(){}
,

Какова точность керамического резонатора? 0,5%?, @Peter Mortensen

@PeterMortensen: Типичная толерантность - 0,5%. Допуск - это наихудшая частотная погрешность. Типичная погрешность больше похожа на 0,1%., @Edgar Bonet

@PeterMortensen: Если вы калибруете ошибку частоты, вы можете настроить "ПЕРИОД", чтобы компенсировать ее. Затем у вас есть разрешение около 0,1% в откалиброванной частоте., @Edgar Bonet


-1

Да, ты можешь. Что-то вроде...

Задержка 66,6 мкс означает одну задержку 66 мкс, а затем две задержки 67 мкс. Вы можете сохранить замкнутый цикл, который вычисляет micros (), деленный на 66 и по модулю на 2 для каждой итерации. Это даст вам чередование 1 и 0. Сохраните предыдущее значение, чтобы проверить это значение на предмет изменения и digitalWrite HIGH или LOW.

На данный момент у меня нет средств для тестирования кода, но в (полу-псевдо) коде это будет выглядеть примерно так:

void loop()
{
  int freq=15000;
  float interval = ((float)1000000/(float)freq)/2;//33.333333
  byte state=0;
  byte previousState=0;
  while (1)
  {
    unsigned long currentMicros=micros();
    previousState=state;
    state = (unsigned long)((float)currentMicros / interval) % 2;
    if (state==1 && previousState==0) digitalWrite(9,HIGH);
    if (state==0 && previousState==1) digitalWrite(9,LOW);
  }
}

Этот метод гарантирует, что через одну секунду пройдет ровно 15000 периодов. Однако сроки отдельных периодов могут немного отличаться.

,

Обратите внимание, что это обеспечит выход, который в среднем очень близок к 15 кГц, но будет чередоваться между 1 циклом 15,15 кГц и 2 циклами 14,92 кГц. Это может быть или не быть приемлемым в зависимости от варианта использования., @jwh20

Я предполагаю, что прослушивание тона, производимого этим методом, будет звучать ужасно., @Hacky

Я исправил очевидные ошибки (пропущенные скобки после "micros", пропущенный фактор 2 в "interval`, переменная "state", затеняющая предыдущее объявление) и протестировал его на Uno. Мало того, что джиттер огромен, так еще и средняя частота неправильная: около 8,5 кГц. Вычисления с плавающей запятой делают код настолько медленным, что он пропускает импульсы., @Edgar Bonet

Я бы предположил, что частота будет 7,5 кГц, но ваш фактор 2 должен был это исправить. Я предполагаю, что digitalWrite() выполняет несколько тактов. Очень плохо, потому что теоретически это могло бы сработать., @Hacky

digitalWrite() - это не так уж плохо, проблема в использовании поплавков. Я запрограммировал что-то похожее на вашу логику (синхронизация с помощью micros() и digitalWrite()) с помощью целочисленной арифметики. На выходе действительно есть некоторое дрожание, видимое в области видимости, но оно далеко не так плохо, как эта версия с плавающей точкой. А так как он не пропускает импульсов, то имеет среднюю частоту 15 кГц., @Edgar Bonet

@EdgarBonet это потрясающее объяснение, поэтому в микроконтроллерах мы должны избегать вычислений с плавающей запятой, если это возможно, верно? кроме того, что вызывает дрожание в вашем случае, несмотря на использование целочисленной арифметики?, @user0193

@JhonnyS: Re “_avoid floating point_”: если вам нужно быть быстрым, да. Если у вас много времени или микроконтроллер с FPU, то с плавающей запятой все в порядке. Re “_ what causes jitter in your case_”: Я думаю, что это в основном разрешение micros (), которое составляет 4 мкс., @Edgar Bonet

Re * "В данный момент у меня нет средств протестировать код" *: Можете ли вы обновить свой ответ, когда это произойдет?, @Peter Mortensen

Эдгар Бонет исправил опечатки и обновил мой код. Его опасения см. во втором комментарии., @Hacky


10

Поскольку библиотека timer1 принимает только целые числа для параметра µs, вы получаете ошибку. Вы можете пропустить использование библиотеки и настроить таймер напрямую. Или вы можете взглянуть на исходный код библиотекии увидеть, что вы можете обойти ограничение, которое она имеет, только вычисляя более точное значение для регистра ICR1.

Посмотрите на cycles = ((F_CPU/100000 * микросекунды) / 20);. Если бы вы вставили в него свои 66.66666мкс вместо 66 мкс, то получили бы 533 вместо 528.

Это значение затем используется для установки частоты ШИМ в строке 213. Таким образом, вам нужно будет перезаписать регистр ICR1 более точным значением.

Ваш окончательный код должен выглядеть следующим образом:

pinMode(9,OUTPUT);
Timer1.initialize(66);
ICR1 = 533;
Timer1.pwm(9,255);

PS возможно, вам придется настроить это значение 533, чтобы получить еще более точную результирующую частоту.

,

Не могли бы вы описать роль регистрации ICR1 здесь? Мне нужно знать, как он работает с timer1., @astrick

Таймер один продолжает увеличивать его счетчик (TCNT1) до тех пор, пока он не достигнет ICR1. Увеличение ICR1 на 5 займет немного больше времени, чтобы сосчитать это, и, таким образом, даст вам немного более низкую частоту, чем 15155 Гц, которую вы получали. Надеюсь, это объясняет все немного лучше. Если нет, не стесняйтесь, дайте мне знать., @Gerben


2

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

SAMD21G, работающий на частоте 48 МГц (как видно на некоторых ардуино), может сделать 15 кГц несколькими способами.

Обратите внимание, что это предполагает, что часы ровно 48 МГц и стабильны, и оба предположения вполне могут быть ошибочными, к сожалению. Это помогает, если часы получены из кристалла; не все Arduino на базе SAMD21G делают это, если таковые имеются. Это возможное отсутствие тактовой точности и стабильности относится ко всем микроконтроллерам.

SAMD21G действительно имеет дробный PLL, и вы, конечно, можете настроить КАЖДЫЙ регистр, так что некоторая тонкая настройка возможна, если у вас есть оборудование для измерения фактической выходной частоты.

Если вы решите использовать один из ардуино на базе SAMD21G: для них существует библиотека PWM (я ее хорошо знаю, как уже писал), которая позволит вам установить делитель тактовых импульсов, прескалер и разрешение, что должно облегчить получение (номинального) 15 кГц. В разделе "дополнительные услуги"есть таблица частот.

,

"Это помогает, если часы получены из кристалла" вы хотите сказать, что не все Arduino имеют кристаллический closk?, @user0193

Есть также некоторые, которые используют керамический резонатор, а некоторые используют внутренний RC-генератор MCU., @ocrdu

@JhonnyS: [Arduino Uno] (http://arduino.cc/en/Main/ArduinoBoardUno ) (и его многочисленные клоны) использует 0,5% керамический резонатор (на печатной плате есть кристалл, но он *** используется только *** для USB-части). Официальные страницы об Arduino Uno вводят в заблуждение (по недомыслию, умышленному или нет). Технически они не лгут, но это очень близко. См. * [Есть ли у Arduino Uno два кристалла?](https://arduinoprosto.ru/q/30964 )*. Из вопроса: *"большая серебристая" штука на плате (красная) - это знаменитый кристалл 16 МГц Arduino. Вот во что я верил до самого последнего времени.*, @Peter Mortensen

@PeterMortensen спасибо за ссылку! Теперь в этом столько смысла! Итак, три - это два источника тактовых импульсов в Arduino! Мне просто интересно, являются ли они синхронными по своей природе., @user0193


6

Лучший способ сделать выше среднего 15 кГц (или любую другую частоту) - это использовать схему фазового аккумулятора. Тестов IF нет; при каждом тике прерывания вы добавляете шаг к аккумулятору и выводите состояние его MSB. Это может дать невероятное разрешение, и это, вероятно, лучшее, что вы можете сделать. Но хотя средняя частота будет мертвой, дрожание может быть ужасным. Я использую этот метод в своем скетче DaqPort с открытым исходным кодом https://www.daqarta.com/dw_rraa.htm#00ff, который используется DaquinOscope https://www.daqarta.com/dw_rroo.htm и Arduino_Oscillators https://www.daqarta.com/dw_rrss.htm мини-приложения, работающие в Daqarta. Существует также макрос Jitter_Tbl для вычисления джиттера для произвольных частот дискретизации и выходных частот. Хотя этот метод фазового аккумулятора здесь не имеет значения, он также может переходить на исключительно низкие частоты, в диапазон микрогерц, и джиттер там чрезвычайно мал.

,

3

Я могу получить в пределах 5 Гц от 15 000, используя тактовую частоту 16 МГц на Nano, Uno или 2560. Вот код...

// RTM_TimerCalc 1.20
// Таймер-1 Mode_14_16Bit_Fast_TOP_is_ICR

TCCR1B = 0x18; // 0001 1000, Отключить часы таймера 
TCCR1A = 0xA2; // 1010 0010

ICR1 = 1067-1;
OCR1A = (int) (ICR1 * 0.25);
OCR1B = (int) (ICR1 * 0.50);
TCNT1=0x0;

// Раскомментируйте следующие строки для контактов UNO-NANO Timer-1 
// pinMode(9, ВЫХОД); // OC1a
// pinMode(10, ВЫХОД); // OC1b

// Раскомментируйте следующие строки для 2560 контактов Timer-1 
// pinMode(11, ВЫХОД); // OC1a
// pinMode(12, ВЫХОД); // OC1b

TCCR1B |= 1; // Prescale=1, Включить таймер

Вот результаты ошибок... СПРОСИТЕ: 15 000 Гц CALC: 14,995.3139643861 Гц СМЕЩЕНИЕ: -4.6860356139 Гц

хтх

,