Процедура изменения частоты Нано или аналогичной

Мне нужно получать прерывания с одной из трех определенных скоростей: 1920, 2000 и 2400 в секунду с довольно высокой точностью (~10 ppm). Я спрашивал об этом по электронике, но я думаю, что здесь может быть более уместно.

Я не могу использовать стандартную частоту 16 МГц с внутренними таймерами, так как 16 МГц / 2400 (например) дает 66666.666... отсчетов.

Я бы использовал внешний стандарт, но, похоже, я не могу найти тот, который соответствует потребностям. Что-то маленькое и точное на 48 кГц / 96 кГц и т. Д. Было бы прекрасно.

В качестве альтернативы я мог бы поменять кристалл / резонатор на один, работающий на частоте 18,432 МГц, который равномерно делится на эти частоты и не должен облагаться налогом в 328 пенсов. Могу ли я даже сделать это на Nano или каком-нибудь клоне / сопоставимом с требуемыми 10 ppm?

-- лишнее удалено --

, 👍0

Обсуждение

Конечно, ты можешь поменять кристалл местами. Максимум-20 МГц, так что 18.все, что находится в пределах возможностей. Вам придется настроить параметры платы, чтобы они соответствовали, конечно (F_CPU)., @Majenko

Ууу, 10 промилле, довольно сложная задача! Учитывали ли вы начальную точность, влияние температуры и напряжения/тока, дрейф, старение? Возможно, вам понадобится что-то получше простого кристалла, например [кварцевый генератор с духовым управлением](https://en.wikipedia.org/wiki/Crystal_oven)., @the busybee

О, и, конечно, дрожание из-за разного количества часов для инструкций, приводящее к разной задержке прерывания., @the busybee

@thebusybee - Я знаю, что 10 ppm-это слишком много. Если бы я мог найти TXCO по разумной цене с частотой 48 или 96 кГц в небольшом пакете, я бы точно это сделал. Что касается дрожания, небольшая величина приемлема в обмен на более длительную (~30 мс) точность., @Jim Mack

Вы могли бы использовать TXCO более высокой частоты и разделить его. -- Все это, похоже, требует более аппаратного решения, возможно, CPLD или FPGA. Зачем вам нужна такая точность?, @the busybee


1 ответ


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

3

Вы могли бы использовать внутренний таймер.

Вы используете прерывания, и вы, вероятно, знаете, что обработка прерываний неизбежно вызывает некоторое дрожание. Таким образом, если вы не возражаете против дополнительного одиночного цикла дрожания процессора, вы можете перенастроить таймер для каждого прерывания, чтобы контролировать средний период прерывания.

Вот как я бы это сделал, если бы таймер 1 работал в обычном режиме (режим CTC потребовал бы дополнительной операции):

// Период прерывания в единицах 1/2^16 циклов процессора.
// Для 2,4 кГц это 16000/2,4*65536 = 436906666.666...
uint32_t interrupt_period = 436906667;

ISR(TIMER1_COMPA_vect) {
    // Обновите таймер для следующего прерывания.
    static uint32_t accumulator;
    accumulator += interrupt_period;
    OCR1A = accumulator >> 16;

    // Периодическая задача находится здесь...
}

void setup() {
    // Настройка таймера 1.
    TCCR1A = 0;            // нормальный режим
    TCCR1B = _BV(CS10);    // clock @ F_CPU
    TIMSK1 = _BV(OCIE1A);  // включить прерывание COMPA
}

void loop(){}

Здесь аккумулятор является 32-разрядным расширением выходного регистра сравнения таймера. Верхние 16 бит копируются в фактический таймер регистр, в то время как младшие 16 бит гарантируют, что накопленная ошибка никогда не превысит одного цикла.

Этот способ обновления таймера должен занимать всего около 2 мкс на прерывание. Это операция с постоянным временем, поэтому она не добавляет дополнительных дрожание для вашей задачи, кроме одного цикла дрожания, вызванного округлением до следующего края тактовой частоты процессора.

Однако есть несколько предостережений:

  1. Это может работать только на Arduino с тактовой частотой кристалла (например, микро). Те, которые отключены от керамического резонатора (Uno...), будут слишком нестабильны по частоте, чтобы соответствовать вашим требованиям 10 ppm.
  2. Даже если кристалл более чем достаточно стабилен, чтобы соответствовать вашим требованиям, он вряд ли будет достаточно точным. Затем вам нужно будет откалибровать его, например, путем вывода ШИМ-сигнала (analogWrite()) в измеритель частоты. Затем отрегулируйте переменную interrupt_period в соответствии с измеренной тактовой частотой.

ОБНОВЛЕНИЕ: Я добавил конфигурацию таймера в код, и вот объяснение того, как работает этот код.

Думайте о таймере как о цифровом будильнике. Он измеряет время в единицах циклов процессора вместо часов и минут. Это перекатывается через каждую 216 циклов вместо каждых 1440 минут. Но в остальном это обычный будильник. У него есть два будильника под названием “COMPA” и “COMPB”, но вам нужен только один. Для выполнения периодической задачи вы включаете будильник и каждый раз, когда он звонит, вы увеличиваете время будильника на необходимый период. То есть вы устанавливаете:

новое время будильника = предыдущее время будильника + период выполнения задачи

Обратите внимание, что сложение должно быть рассчитано по модулю периода переключения ваших часов : 1440 минут для обычного будильника, 216 циклов для таймера. К счастью, арифметика для чисел без знака делает это автоматически.

Теперь предположим, что период выполнения задачи составляет 43 минуты и 35 секунд, но сигнал тревоги может быть установлен только с разрешением в одну минуту. Что вы можете сделать, так это вычислить “идеальное” время будильника на бумаге (это переменная аккумулятора) и сократить его до разрешения часов при установке будильника. Если вы начнете в 00:00:00, вы вычислите следующее идеальное время 00:43:35 и установите будильник на 00:43. Когда он звонит, вы добавляете период к ранее вычисленному времени, вы получаете 01:27:10, и вы устанавливаете будильник на 01:27. И т. Д. Вот список времени срабатывания будильника, рассчитанного и фактически установленного на часах:

computed  set
───────────────
00:00:00  00:00
00:43:35  00:43
01:27:10  01:27
02:10:45  02:10
...

Обратите внимание, что, несмотря на то, что ограниченное разрешение настройки будильника создает некоторое дрожание, средний период выполнения задачи по-прежнему точно 43 минуты и 35 секунд.

Код, который я написал выше, делает точно то же самое. “Идеальное” время срабатывания будильника вычисляется как дробное число циклов процессора, записанное в двоичной фиксированной точке, с 16 битами на каждой стороне точки основания. В шестнадцатеричном коде это 1A0A.Циклы процессора AAAB. Вычисленное время срабатывания кратно этому периоду, в то время как время, фактически записанное в OCR1A, равно целому числу:

computed   set
───────────────
1A0A.AAAB  1A0A
3415.5556  3415
4E20.0001  4E20
682A.AAAC  682A
...
,

Большое спасибо за это, но, извините, я не понимаю, как это работает. Я так понимаю, есть какая-то начальная настройка счетчика, которую вы не показываете? Похоже, что мы выводим дико переменные значения в OCR1A: 1A0A, 3415, 4E20, 682A и т. Д. Что происходит?, @Jim Mack

@JimMack: Смотрите расширенный ответ., @Edgar Bonet

Очень милый. Я знал, что каким-то образом я должен реализовать алгоритм Бресенхэма, чтобы усреднить ошибку, но я не видел способа сделать это. Я задам еще один вопрос, основанный на этом, чтобы попытаться решить еще одну проблему., @Jim Mack