Как реализовать счетчик импульсов для двух тактов на Nano Every?

Для синхронизации часов я хочу реализовать счётчик импульсов на Arduino Nano Every. Есть два тактовых сигнала с частотой 40–100 кГц, Nano работает на частоте 20 МГц. Найквист говорит, что нам нужно как минимум вдвое больше частоты дискретизации, а у нас она составляет 200. Поэтому я предполагаю, что можно запрограммировать надёжный счётчик импульсов.

В настоящее время я использую функцию attachInterrupt(digitalPinToInterrupt(pin), isr, RISING) для подсчёта нарастающих фронтов на A0 и A1 и периодического отображения их с помощью функции Serial.print(). Если тактовый сигнал подан на один вывод, подсчитывается нужное количество фронтов. Но если я подключу оба вывода, каждый из них будет учитывать только 50–70% нарастающих фронтов. На осциллографе сигнал выглядит нормально.

volatile unsigned long risingCount0 = 0;
volatile unsigned long risingCount1 = 0;
void isr0() { risingCount0++; }
void isr1() { risingCount1++; }

void setup() {
  Serial.begin(9600);
  pinMode(A0, INPUT_PULLUP);
  pinMode(A1, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(A0), isr0, RISING);
  attachInterrupt(digitalPinToInterrupt(A1), isr1, RISING);
}

void loop() {
  static unsigned long lastPrintTime = 0;
  int waitSec = 5;
  if (millis() - lastPrintTime >= waitSec * 1000) {
    lastPrintTime = millis();

    unsigned long c0, c1;
    noInterrupts();
    c0 = risingCount0;
    c1 = risingCount1;
    interrupts();

    Serial.print(c0);
    Serial.print(" ");
    Serial.println(c1);
  }
}

Этот человек говорит, что у него работают выводы 2–5, но при использовании цифровых выводов 2 и 3 регистрируется только 10 или 100 результатов в секунду. Это меня озадачивает, поскольку в документации также говорится, что цифровые выводы подходят для прерываний. Одновременные прерывания тоже должны сохраняться.

Чтение Как работают прерывания на Arduino Uno и подобных платах?, Я вижу, что, например, millis() отключает прерывания на короткое время, поэтому предполагаю, что что-то препятствует их возникновению. Уменьшение количества последовательных выводов также каким-то образом смягчает проблему, но всё равно не решает её. Кроме того, прерывания должны обрабатываться одно за другим, а обработчики прерываний, записывающие одну переменную, должны занимать всего несколько микросекунд.

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

Любые предложения по библиотекам, улучшению существующего кода или вообще по другим подходам приветствуются. Я добавил тег project-critique, как указано на странице помощи.

, 👍1

Обсуждение

У Atmega4809 есть несколько полезных функций: счётчики (TCx) и система событий. TCB может подсчитывать события, и вы можете настраивать события на любых выводах (но обычно только на двух выводах одного порта, поскольку разные каналы «подключены» к разным портам или парам портов)., @KIIV

Также обратите внимание, что ядро Arduino в Nano Every работает только на частоте 16 МГц, если мне не изменяет память. Но в MegaCoreX есть настройка нужной частоты., @KIIV

@KIIV В документации написано 20 МГц. Может, помнишь 16 МГц от nano — «не все»? Спасибо за подсказку TCx. Я надеялся, что смогу обойтись без обучения счётчиков на Arduino, но раз уж так, то пусть будет так., @Mo_

Прерывает ли нарастающий фронт сигнал отмены, если вы не обработали его вовремя?, @Questor

@Questor Не совсем понимаю, что вы подразумеваете под деактивацией. Но вот что я понял из вопроса по ссылке о прерываниях: есть фрагмент, сигнализирующий о прерывании. То есть у меня есть целый период (как минимум 10 мкс) на обработку прерываний. Если бы другое прерывание возникло до того, как я его обработал, я бы заметил только установку флага, но не появление двух флангов., @Mo_

@Mo_ Это ответило на мой вопрос. Давно я не пользовался Arduino... Ты смотрел на ассемблерный код, который выдаёт твой компилятор (-S)? Это может дать тебе лучшее представление о том, сколько времени занимает выполнение чего-либо, чем C., @Questor

@Mo_ Он «может» работать на частоте 20 МГц, но ядро Arduino по умолчанию использует только 16. См.: https://github.com/arduino/ArduinoCore-megaavr/blob/master/boards.txt#L80 — из технического описания 0x1 == 16 МГц, @KIIV


1 ответ


4

Тот факт, что он работает с одним тактовым сигналом, предполагает, что ваш Arduino может Просто перегружен. Можно попробовать снизить тактовую частоту: если Если подсчеты верны, то это ваша проблема.

Два тактовых сигнала с частотой 100 кГц означают, что ваш Arduino в среднем имеет 5 мкс (т.е. 100 тактов ЦП) для обработки одного фронта тактовой частоты. Это может показаться… много, но не так уж и много. Увеличение однобайтовой переменной из Для ISR может потребоваться до 9 циклов:

  • 2 цикла для сохранения регистра ЦП в стеке
  • 2 цикла для загрузки переменной из SRAM
  • 1 цикл для увеличения значения
  • 2 цикла для сохранения результата в SRAM
  • 2 цикла для восстановления резистора ЦП из стека

Для 4-байтовой переменной (unsigned long) это уже 36 циклов. Но то вы не пишете ISR. Фактически ISR предоставляется Ядро Arduino. Оно должно найти указатель на функцию, который вы указали как настоящий обработчик прерываний, убедитесь, что он не nullptr, и вызовите его. Предположительно, ему придется сохранить в стеке гораздо больше, чем 4 регистра: все регистры, которые используются при вызове в соответствии с соглашением о вызовах. Все Это не очень эффективно. Возможно, вам захочется дизассемблировать двоичный файл, чтобы посмотрите, что он делает.

Я не думаю, что вы сможете сделать свой код значительно быстрее, используя Ядро Arduino. Если у вас есть время изучить техническое описание ATmega4809, вы можете захотеть написать свои обработчики прерываний как настоящие Вместо того, чтобы полагаться на attachInterrupt(), можно даже использовать аппаратный счетчик, но, если я правильно понял техническое описание, только Таймер/счетчик типа А подходит для этой работы, и у вас есть один пример этого.

,

Если необходимо, я могу изучить его. Однако это всего лишь вспомогательный метод, я хочу, чтобы он был понятен даже тем, кто не знаком с Arduino. Поэтому сначала поищу более простое решение. Спасибо, что описали накладные расходы. Как думаете, цикл с большим объёмом будет работать эффективнее?, @Mo_

@Mo_ Этого не будет. Возможные решения: аппаратный подсчёт краёв. Или более быстрый процессор., @Questor

@Mo_ Для проверки теории можно переключать пины во время выполнения обработчиков прерываний (ISR). Обязательно используйте библиотеку digitalWriteFast, которая компилирует функции с постоянными номерами пинов в одну машинную инструкцию. Затем воспользуйтесь осциллографом., @the busybee

@Mo_ Если вы можете ограничить диапазон счетчиков, использование меньшего типа данных, например unsigned char, может сократить количество циклов, затрачиваемых в ISR., @the busybee

Посмотрел на таймеры и обработчики прерываний. Просто возьму более быстрый процессор или куплю готовое оборудование. Было бы интересно реализовать это на Arduino, но, к сожалению, у ATM нет на это времени., @Mo_

@Mo_: Re “_Как вы думаете, цикл с использованием функции busy будет работать лучше_”: цикл с использованием функции busy, вызывающий digitalRead(), может быть не лучше, поскольку эта функция довольно медленная. С [digitalReadFast()](https://github.com/ArminJo/digitalWriteFast) цикл с использованием функции busy, скорее всего, будет работать нормально... пока вы не вызовете Serial.print(), и этот метод не начнёт выполнять (медленное) преобразование двоичного кода в десятичный., @Edgar Bonet