Как произвести количество прокатки в минуту?

У меня есть счетчик, прикрепленный к ISR:

void ISR_impulse() {
  CNT++;
}

и я хочу рассчитать количество прокатки в минуту с произвольной частотой обновления.

Однако я могу только заставить его обновляться каждую минуту:

void loop() {    
  if (t - t0 > 60000) {
    CPM = CNT - CNT0;
    t0 = t;
    CNT0 = CNT;      
  }

  t = millis();
  delay(100);
}

Требования:

  • Скользящий счетчик с окном в 1 минуту и произвольной частотой обновления, например, в 1 секунду ?
  • У процессора есть и другие задачи, которые могут занять значительное количество времени.
  • SAMD21 имеет измерение периода импульса в TC, но я понятия не имею, как его настроить.

Частичное решение Эдгара Бонета:

  static uint8_t buf[60];
  static uint8_t idx;
  static uint32_t t0;
  static uint16_t CNT0;
  static uint8_t cpm;

  if (millis() - t0 >= 1000) {
    t0 = millis();
    uint16_t cps = CNT - CNT0;
    CNT0 = CNT;
    cpm += cps - buf[idx];
    buf[idx] = cps;
    if (++idx >= 60) idx = 0;
  }

Однако это работает только в том случае, если код цикла заканчивается менее чем за 1 мс.

, 👍1

Обсуждение

настройте буфер FIFO ... ISR помещает десятичное число 60 в буфер ... каждую секунду отдельная программная процедура уменьшает каждое из значений в буфере на единицу, подсчитывает ненулевые значения и возвращает счетчик, @jsotola

Переписывая код моего ответа, вы ввели пару ошибок: 1. Обновляя t0 с t0 = millis();, вы делаете крошечные ошибки синхронизации кумулятивными. Если вы обновите его с помощью t0 += 1000;, они не будут кумулятивными: они создают только небольшое количество дрожания, которое несколько усредняется последующим средним значением. 2. Дважды прочитав CNT с включенными прерываниями, вы создаете условие гонки. Если CNT обновляется ISR между двумя считываниями, вы пропускаете один импульс., @Edgar Bonet


2 ответа


2

Если вы хотите обновлять каждую секунду среднее значение в одну минуту, у вас нет выбора, кроме как хранить в памяти подсчеты, которые вы получили в предыдущий раз 60 секунд. Вы храните их в кольцевом буфере, так что каждое новое чтение заменяет самое старое. Затем, каждую секунду:

  • вы добавляете к счетчику CPM количество отсчетов, которые вы получили в эту секунду
  • вы вычитаете количество отсчетов, которые вы получили на секунду, которая только что вышла из раздвижного окна.

Я бы реализовал эту логику следующим образом:

const uint32_t ONE_SECOND = 1000;

uint16_t counts_per_minute;
uint16_t previous_counts[60];
size_t counts_pos;

void loop() {
    static uint32_t previous_second;
    if (millis() - previous_second >= ONE_SECOND) {
        previous_second += ONE_SECOND;
        noInterrupts();
        uint16_t CNT_copy = CNT;
        interrupts();
        static uint16_t CNT_previous;
        uint16_t second_count = CNT_copy - CNT_previous;
        CNT_previous = CNT_copy;
        counts_per_minute += second_count - previous_counts[counts_pos];
        previous_counts[counts_pos] = second_count;
        if (++counts_pos >= 60) counts_pos = 0;
    }
}

Обратите внимание, что:

  • Переменная timing обновляется с помощью previous_second += ONE_SECOND вместо previous_second = now, чтобы избежать накопления небольших ошибок синхронизации.
  • Счетчик, который обновляется в ISR, должен быть прочитан только один раз, при этом прерывания отключены. В противном случае прерывание может изменить его, пока вы находитесь в середине чтения.
,

Значит, это скользящая средняя с количеством периодов обновления в качестве элементов ? Разве noInterrupts() не приведет к тому, что счетчик пропустит некоторые отсчеты ?, @7E10FC9A

Было бы идеально, если бы вы могли добавить код для учетной записи millis (). < ситуация 60000., @7E10FC9A

@7E10FC9A: Вы не можете осмысленно вычислить одноминутное скользящее среднее менее чем за одну минуту., @Edgar Bonet

@7E10FC9A: noInterrupts() не заставит вас пропустить прерывание. Если запрос на прерывание срабатывает при отключенных прерываниях, он ставится на удержание и обслуживается сразу после вызова функции interrupts (). Только если в течение этого периода срабатывают два экземпляра одного и того же запроса, вы пропускаете прерывание. Но поскольку они отключены менее чем на полмикросекунды, это может произойти только при частоте прерываний _way_ выше той, с которой может справиться Arduino., @Edgar Bonet

Код не работает, если у процессора есть другие задачи. Интервал дискретизации не гарантируется равным 1000 мс., @7E10FC9A

@7E10FC9A: Если вы программируете в _non blocking fashion_, ваш loop() всегда должен выполняться очень быстро. Если это займет больше миллисекунды, вы, скорее всего, делаете что-то не так. При очень быстром "цикле ()" неточности будут крошечными и, скорее всего, неуместными. Если ваши требования к точности действительно настолько высоки, что доля миллисекунды имеет значение, то запрограммируйте таймер на запуск прерывания каждую миллисекунду и обновите counts_per_minute в ISR., @Edgar Bonet

MCU имеет только один поток, поэтому трудно сделать код цикла неблокирующим. Допустим, что бы вы сделали, если бы были другие задачи, требующие процессорного времени >>1 мс случайным образом?, @7E10FC9A

@7E10FC9A: В таком случае я бы использовал прерывание таймера. Я хочу сказать, что на самом деле редко требуется 1 мс процессорного времени одновременно. Если loop() занимает более 1 мс, то более чем вероятно, что большинство этих циклов процессора задерживаются или заняты ожиданием какого-то события (т. Е. “Блокировки”). Затем вы можете сделать его неблокирующим, используя конечный автомат и/или технику, показанную в учебнике Blink Without Delay., @Edgar Bonet

Q не говорит, но является ли uint16_t достаточно большим для счета/сек? // Если прерывание CNT является точным, то любые ошибки, вызванные синхронизацией выборок, уравновешиваются соответствующими ошибками в следующую секунду. Если цикл займет больше секунды, он может отстать и пересчитать минуту, но если не считать этого, дрожание должно позаботиться о себе само., @Dave X


0

Вам нужен буфер первого входа и первого выхода (FIFO), чтобы самые старые данные выпадали, освобождая место для самых новых. Обычно это делается с помощью кругового буфера - массива, начальная / конечная точка которого изменяется по мере добавления данных. (Для сравнения, упрощенный способ-это массив, содержимое которого перемещается вниз (к i = 0) каждый раз, когда вам нужно добавить данные. Но перемещение данных происходит медленно, поэтому запоминание того, где находится самая старая запись, замена ее самой новой и обновление запоминаемого местоположения самых старых данных дает огромное улучшение перморманса.

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

Оценка количества сразу прошедших минут так же проста, как суммирование массива, что можно сделать, не заботясь о том, где находится начальная/конечная точка. Сумма от 0 до N-1; результат будет тот же.

,