1 Гц и 32 Гц от 32,768 кГц на ATmega328P на 8 МГц и DS3231 32K

У меня на входе ATmega328P сигнал 32,768 кГц. Мне нужно получить 2 сигнала из этого сигнала: 1 Гц и 32 Гц. Насколько хорошо это будет работать? Как это сделать с помощью таймера/счетчика ATmega328P? Какой выигрыш даст Таймер/Счетчик по сравнению с моим решением?

void setup() {
   pinMode(S32768Hz_PIN, INPUT_PULLUP); // сигнал 32,768 кГц
   attachInterrupt(digitalPinToInterrupt(S32768Hz_PIN), s32768Isr, FALLING); // Пин 3
}

volatile bool s32768IsrWasCalled = false;  // Флаг ISR 32768 Гц
void s32768Isr() {
   s32768IsrWasCalled = true;
}
uint16_t s32Counter = 0;
uint16_t s1Counter = 0;

void loop() {
  if (s32768IsrWasCalled) {
      s32768IsrWasCalled = false;
      s32Counter++;
      if (s32Counter == 1024) {
        s32Counter = 0;
        // 32 Гц
      }

      s1Counter++;
      if (s1Counter == 32768) {
        s1Counter = 0;
        // 1 Гц
      }
  }
}

Обновление от 24.10.2022 Я написал свой первый код установки таймера 1. Будет ли это выполняться 32 раза в секунду бесконечно?

// ATmega328P 3,3 В 8 МГц, Таймер 1 и усилитель; DS3231 32768 Гц
void setupTimer1() {
    // Отключаем все прерывания
    cli();
    // Сбросить биты
    TCCR1A = 0;
    TCCR1B = 0;
    // Разрешить прерывание по переполнению Таймера 1 (TOIE1)
    TIMSK1 = (1 << TOIE1);
    // Режим ШИМ. Режим № 7 (быстрый ШИМ, верх фиксирован на 10 битах = 1024)
    TCCR1A |= _BV(WGM10) | _BV(WGM11);
    TCCR1B |= _BV(WGM12);
    // Внешняя синхронизация на выводе T1 по заднему фронту (вывод DS3231 32768 Гц)
    TCCR1B |= (1 << CS11) | (1 << CS12);
    // Включает прерывания
    sei();
    // pinMode(T1_pin, INPUT_PULLUP); ?
}

void loop() {
    // Получается 1 Гц из 32 Гц?
    // Здесь не будет гонок данных?
    if((pulse_counter % 32) == 0) {
        callback1Hz();
    }
    
    if((pulse_counter % 2) == 0) {
        callback16Hz();
    }
}

volatile uint8_t pulse_counter;
// Переполнение таймера 1 (32 Гц)
ISR(TIMER1_OVF_vect) {
    pulse_counter++;
}

Спасибо!

, 👍-2

Обсуждение

Насколько хорошо это будет работать? ... разве вы не должны быть тем, кто определяет это? ... используйте аппаратный делитель, для этого есть подходящие микросхемы, @jsotola

Почему бы тебе просто не измерить? У вас есть прицел?, @PMF

О, и подсказка: не используйте флаг в ISR, вместо этого увеличивайте счетчик там., @PMF

Вы можете [отредактировать](https://arduino.stackexchange.com/posts/91054/edit) свой вопрос, чтобы включить детали и разъяснения, о которых вас спрашивают в комментариях., @timemage

Какая точность вам нужна для 1 Гц и 32 Гц? просто логический уровень?, @Tony Stewart Sunnyskyguy EE75

Я не могу использовать внешний аппаратный разделитель. Разделить 1 сигнал на 2 сигнала я могу только на ATmega328P. Я использую выход DS3231 32K с точностью ±2ppm., @Andre

@EdgarBonet Я написал свой первый код настройки Таймера 1. Обновление от 24.10.2022. Будет ли это работать 32 раза в секунду бесконечно?, @Andre

Да, похоже, это должно сработать. Время проверить это., @Edgar Bonet

@EdgarBonet Так что получите 1 Гц из 32 Гц? Здесь не будет гонок данных? if((pulse_counter % 32) == 0) { } Я обновил свой пример в заголовке., @Andre

Нет гонки данных, так как вы читаете однобайтовую переменную. Но вы не выполняете обнаружение краев., @Edgar Bonet


1 ответ


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

2

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

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

  1. он срабатывает, когда уже есть прерывание (или другое критическое раздел) обрабатывается, и это прерывание (возможно, в сочетании с какое-либо другое ожидающее прерывание) требуется более 30,5 мкс для завершить

  2. вы добавляете больше кода в loop(), и эта функция в конечном итоге занимает больше времени чем 30,5 мкс.

Первое условие должно быть очевидным, но я предполагаю, что риск довольно низким, так как только очень плохо написанный ISR займет так много времени. полный. Однако остерегайтесь библиотечного кода, так как он может преподнести сюрпризы.

Второе условие – больший риск, хотя, возможно, и менее очевидный. проблема в том, что этот loop() предполагает код внутри if (s32768IsrWasCalled) будет вызываться ровно один раз за прерывать. Это может быть не так: если программа станет больше, вы можете пропускать прерывания и ошибаться в частотах.

Простой способ избежать проблемы номер 2 – подсчитать количество импульсов в пределах ISR, как предлагает PMF в комментарии. Я бы использовал один счетчик для this, и сделайте его неподписанным, чтобы гарантировать, что он обертывается надежно:

volatile uint16_t pulse_counter;
void s32768Isr() {
    pulse_counter++;
}

void loop() {
    int signal_32Hz = (pulse_counter >> 9) & 1;
    int signal_1Hz = (pulse_counter >> 14) & 1;
}

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

Однако для этого задания лучше использовать аппаратный таймер. Я бы предложил использовать Таймер 1 в режиме «счетчик» (внешние часы источник на контакте T1). Используя режим 7 (быстрый ШИМ, 10 бит), вы получаете Сигнал 32 Гц полностью генерируется аппаратно. И тогда ты сможешь используйте прерывание переполнения таймера для генерации 1 Гц. Прерывание ставка теперь в 1024 раза ниже. Я позволю вам проверить техническое описание подробности.

,

Блестящий ответ, как всегда :), @VE7JRO

Спасибо, хороший код и вариант!, @Andre

@EdgarBonet Какое условие будет верным для signal_1Hz? if (signal_32Hz == 1) не работает раз в секунду., @Andre

@Andre: «signal_1Hz» — это сигнал частотой 1 Гц с рабочим циклом 50%: это «0» в течение половины секунды, затем «1» в течение половины секунды. Если вы хотите, чтобы условие было «истинным» только один раз в секунду (в отличие от истинного в течение целых полсекунды), вам придется выполнить _обнаружение фронта_ для этого сигнала. В качестве альтернативы выполните _обнаружение изменений_ на pulse_counter&(1U<<15)., @Edgar Bonet