Как произвести количество прокатки в минуту?
У меня есть счетчик, прикрепленный к 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 мс.
@7E10FC9A, 👍1
Обсуждение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
Вам нужен буфер первого входа и первого выхода (FIFO), чтобы самые старые данные выпадали, освобождая место для самых новых. Обычно это делается с помощью кругового буфера - массива, начальная / конечная точка которого изменяется по мере добавления данных. (Для сравнения, упрощенный способ-это массив, содержимое которого перемещается вниз (к i = 0) каждый раз, когда вам нужно добавить данные. Но перемещение данных происходит медленно, поэтому запоминание того, где находится самая старая запись, замена ее самой новой и обновление запоминаемого местоположения самых старых данных дает огромное улучшение перморманса.
Во - вторых, вам нужно определить, насколько отзывчивым должен быть ваш счетчик-сколько обновлений в минуту вам понадобится. Это количество элементов, которые вам нужны в массиве.
Оценка количества сразу прошедших минут так же проста, как суммирование массива, что можно сделать, не заботясь о том, где находится начальная/конечная точка. Сумма от 0 до N-1; результат будет тот же.
- Как использовать SPI на Arduino?
- Как решить проблему «avrdude: stk500_recv(): programmer is not responding»?
- Как создать несколько запущенных потоков?
- Как подключиться к Arduino с помощью WiFi?
- avrdude ser_open() can't set com-state
- Как узнать частоту дискретизации?
- Что такое Serial.begin(9600)?
- Я закирпичил свой Arduino Uno? Проблемы с загрузкой скетчей на плату
настройте буфер FIFO ... ISR помещает десятичное число 60 в буфер ... каждую секунду отдельная программная процедура уменьшает каждое из значений в буфере на единицу, подсчитывает ненулевые значения и возвращает счетчик, @jsotola
Переписывая код моего ответа, вы ввели пару ошибок: 1. Обновляя
t0
сt0 = millis();
, вы делаете крошечные ошибки синхронизации кумулятивными. Если вы обновите его с помощьюt0 += 1000;
, они не будут кумулятивными: они создают только небольшое количество дрожания, которое несколько усредняется последующим средним значением. 2. Дважды прочитавCNT
с включенными прерываниями, вы создаете условие гонки. ЕслиCNT
обновляется ISR между двумя считываниями, вы пропускаете один импульс., @Edgar Bonet