Многократное превышение порога активирует несколько светодиодов — но только один за раз
Я рассчитываю вектор модуля ускорения с акселерометра, находящегося внутри толстого полиуретанового коврика. При ударе по коврику, в зависимости от полученного значения, на короткое время загорается только один из двух светодиодов, а затем и несколько. Однако, поскольку нижний(ие) порог(и) неизбежно всегда преодолеваются первым, при более сильных ударах загораются оба светодиода, а не только тот, который соответствует наибольшему превышенному порогу. Надеюсь, изображение объясняет проблему. Мне нужно предусмотреть возможность того, что при пересечении нижнего порога сразу же может быть преодолен более высокий, возможно, даже ещё более высокий. Как мне изменить/дополнить код?

mAV = sqrt(ax * ax + ay * ay + az * az) - valueShift;
if (mAV < 0)
{
mAV = 0;
}
mAVEMA = (alphaEMA * mAV) + ((1 - alphaEMA) * mAVEMA);
Serial.print("mAVEMA:"); Serial.println(mAVEMA);
if (mAVEMA >= thresholdC && previousmAVEMA < thresholdC)
{
digitalWrite(pinLEDR, HIGH);
timeLEDROn = timeNow;
}
if (mAVEMA >= thresholdB && previousmAVEMA < thresholdB)
{
digitalWrite(pinLEDY, HIGH);
timeLEDYOn = timeNow;
}
previousmAVEMA = mAVEMA;
if (digitalRead(pinLEDY) == HIGH && timeNow - timeLEDYOn >= timeLEDOn)
{
digitalWrite(pinLEDY, LOW);
}
if (digitalRead(pinLEDR) == HIGH && timeNow - timeLEDROn >= timeLEDOn)
{
digitalWrite(pinLEDR, LOW);
}
Редактировать: Приближаемся к желаемому поведению https://wokwi.com/projects/406459026144282625
const int pinAccel = A0; // simulate acceleration magnitude vector
const int pinsLED[] = {11, 12, 13}; // yellow, orange, red
const int threshold0 = 300;
const int threshold1 = 600;
const int threshold2 = 900;
const unsigned long timeSearch = 200;
const unsigned long timeLEDsOn = 500;
int mAVEMA = 0;
int active = -1;
unsigned long timersLED[] = {0, 0, 0};
void setup()
{
Serial.begin(9600);
for (byte i = 0; i < 3; i++)
{
pinMode(pinsLED[i], OUTPUT);
}
}
void loop()
{
unsigned long timeNow = millis();
mAVEMA = analogRead(pinAccel);
int threshold = checkThresholds(mAVEMA);
// only on rising signal
if (threshold == 0)
{
unsigned long timerStart = millis();
active = 0;
timersLED[active] = millis();
while (millis() - timerStart < timeSearch) // timeSearch user adjustable?
{
mAVEMA = analogRead(pinAccel);
threshold = checkThresholds(mAVEMA);
if (threshold == 1)
{
active = 1;
timersLED[active] = millis();
}
if (threshold == 2) // reset to 0 once one of the three LEDs was lit
{
active = 2;
timersLED[active] = millis();
}
}
digitalWrite(pinsLED[active], HIGH);
}
if (timeNow - timersLED[active] >= timeLEDsOn)
{
digitalWrite(pinsLED[active], LOW);
}
delay(10); // Only for serial monitor/plotter
}
int checkThresholds(int accel) // later floats for real accelerometer!
{
if (mAVEMA >= threshold2) return 2;
if (mAVEMA >= threshold1) return 1;
if (mAVEMA >= threshold0) return 0;
return -1;
}

@Systembolaget, 👍1
Обсуждение2 ответа
Лучший ответ:
Я думаю, что решение Крисла (позволить светодиоду меняться при пересечении более высокой точки) пороги) является самым простым и, вероятно, самым эффективным для вашего Проблема. Если вы действительно хотите, чтобы при каждом ударе загорался только один светодиод, то Вам придётся добавить задержку. Я бы реализовал это с помощью конечного числа. машина следующим образом:
Система может находиться в одном из трех состояний:
ХОЛОСТОЙ ХОД: ускорение ниже первого порога, и все светодиоды горят выкл
ОЖИДАНИЕ: порог только что преодолен, и Arduino ждет некоторое время, чтобы преодолеть и более высокий порог
ГОРИТ: один светодиод горит и горит в течение фиксированного времени
В состоянии ОЖИДАНИЯ, если действительно превышен более высокий порог, Таймер сбрасывается для ожидания следующего порога. Если таймер истекает, загорается светодиод, и мы переходим в состояние LIT.
Диаграмма состояний:

Для программирования я бы использовал эту вспомогательную функцию:
/*
* The punch "level" is:
* (0, 1 or 2) if the acceleration reaches the threshold (A, B or C)
* -1 otherwise
*/
int get_level(float accel)
{
if (accel >= threshold_C) return 2;
if (accel >= threshold_B) return 1;
if (accel >= threshold_A) return 0;
return -1;
}
А вот код конечного автомата:
enum { IDLE, WAITING, LIT } state;
int punch_level = -1;
uint32_t timer_start;
void loop() {
float accel = get_acceleration();
int level = get_level(accel);
int32_t now = millis();
switch (state) {
case IDLE:
if (level >= 0) { // a threshold has been crossed
punch_level = level;
state = WAITING;
timer_start = now;
}
break;
case WAITING:
if (level > punch_level) { // crossed a higher threshold
punch_level = level;
timer_start = now;
}
if (now - timer_start >= wait_timeout) {
digitalWrite(led_pins[punch_level], HIGH);
state = LIT;
timer_start = now;
}
break;
case LIT:
if (now - timer_start >= light_timeout) {
digitalWrite(led_pins[punch_level], LOW);
state = IDLE;
}
break;
}
}
Спасибо, я начал с конечного автомата, но потерпел неудачу. Изучу ваше предложение., @Systembolaget
Спасибо ещё раз. Я разместил ваше предложение в WOKWI (https://wokwi.com/projects/406583203546521601), чтобы попробовать и понять логику, но оно не работает. Не могли бы вы ещё раз взглянуть?, @Systembolaget
Благодаря вашей идее вспомогательной функции я приблизился к желаемому поведению системы (см. правку в исходном вопросе). Всё ещё требуется тестирование только нарастающего фронта., @Systembolaget
@Systembolaget digitalWrite(led_pins[level], HIGH); Полагаю, вместо level здесь имеется в виду punch_level, поскольку он сохраняет максимальное значение level, зафиксированное в сеансе. То же самое происходит и при выключении светодиода. Кстати, очень элегантное решение., @6v6gt
@6v6gt Да, вы правы. Светодиоды всё равно загораются при падении сигнала. У меня тоже возникла эта проблема при попытке редактирования. Ничто не должно реагировать на падение сигнала. Полагаю, мне нужно дополнительно реализовать эту штуку "старое значение новое значение" для нарастающего сигнала., @Systembolaget
@Systembolaget: Судя по моим тестам, ваша симуляция Вокви работает отлично. На спадающих фронтах нет подсветки. С одной оговоркой: удар должен длиться меньше light_timeout (300 мс), что не кажется неразумным, но сложно реализовать с помощью мыши и симулированного котла., @Edgar Bonet
В качестве клуджа добавьте задержку (3000) после выключения светодиода. Лучше было бы добавить новое состояние, например, «БЛОКИРОВКА». Изменено. Сообщение пересеклось с Эдгаром., @6v6gt
Удары, которые я наблюдал, наносились менее чем за 300 миллисекунд, а провести комбинацию из трёх ударов за секунду может только тренированный человек. Теперь это работает отлично, график помог мне понять, что должно/не должно происходить и когда. Спасибо за решение! Я всё ещё хочу найти свой собственный подход ;), @Systembolaget
При дискретизации аналогового сигнала сначала будет превышен нижний порог. Этого нельзя изменить. Но может быть достаточно включить только текущий светодиод, выключив все остальные, как показано ниже:
if(mAVEMA >= thresholdC){
digitalWrite(pin_led_c, HIGH);
digitalWrite(pin_led_b, LOW);
} else if (mAVEMA >= thresholdB) {
digitalWrite(pin_led_c, LOW);
digitalWrite(pin_led_b, HIGH);
}
Или, если вы хотите обобщить данные для большего количества пороговых значений, вы также можете отключить все светодиоды, затем проверить пороговые значения и включить соответствующий светодиод:
// в глобальной области видимости
int pin_led[] = {4,5,6}; // определение всех выводов светодиода по порядку
int N_LEDs = sizeof(pin_led)/sizeof(pin_led[0]) // получение количества светодиодов
int threshold[] = {100, 200, 500}; // определение всех порогов по порядку
// в вашей функции измерения или в цикле loop()
for(int i=0;i<N_LEDs;i++) digitalWrite(pin_led[i], LOW); //выключение всех светодиодов
for(int i=N_LEDs-1;i>0;i--){ //цикл в обратном порядке по пороговым значениям
if(mAVEMA >= threshold[i]){ //проверка от самого высокого до самого низкого порога
digitalWrite(pin_led[i], HIGH);
break; // прерывание, так как достигнут порог
}
}
Обратите внимание, что при этом светодиод нижнего порога кратковременно загорится в течение времени, пока сигнал находится между двумя порогами. При достижении верхнего порога светодиод нижнего порога снова погаснет.
Если вы этого не хотите, вам нужно изменить способ обработки измерений. Поскольку вы не можете заглянуть в будущее, вам нужно заменить простые пороговые измерения алгоритмом обнаружения пиков. Реализация этого зависит от ваших требований, хотя это всегда будет означать, что светодиоды будут загораться только вскоре после пика (иначе вы не сможете определить, пик ли это). Например, вы можете проверить, опускается ли значение ниже текущего максимального значения (за определённый промежуток времени) в течение определённого минимального времени, то есть проверить максимум через 500 мс и проверить, упало ли значение хотя бы на 100 пунктов за 100 мс после достижения максимума.
Спасибо, значит, это так: временная метка, когда пересекается самый низкий порогA -> ожидание возможных пересечений более высоких порогов, пока не будет обнаружен пик сигнала -> только затем включение светодиода для самого высокого пересечения порога. Тренированный боксер может нанести джеб, левый хук и правый кросс за одну секунду, а время воздействия составляет до 200 миллисекунд при медленном ударе, так что это должно быть моим пороговым окном выборки «подождем и посмотрим», верно?, @Systembolaget
Отредактировал исходный вопрос, думаю, с частичным решением. Но теперь нужно дождаться возможно более высоких порогов, прежде чем сигнал достигнет пика. Код редактирования должен работать в WOKWI., @Systembolaget
- Как объявить массив переменного размера (глобально)
- Программирование Arduino с использованием Python, а не C/C ++
- Загрузка Arduino Nano дает ошибку: avrdude: stk500_recv(): programmer is not responding
- Как справиться с rollover millis()?
- Является ли использование malloc() и free() действительно плохой идеей для Arduino?
- Можно ли сделать несколько функций loop() с помощью Arduino Uno?
- Печать string and integer LCD
- устаревшее преобразование из строковой константы в 'char*'
вставить слой разделения между обнаружением сигнала и зажиганием светодиодов... при обнаружении сигнала установить переменную-флаг, и ничего больше... зажечь светодиоды в ответ на значение переменных-флагов, а не в ответ на сигнал, @jsotola
@jsotola Разделил, см. правку, но безрезультатно. Мне кажется, нужно засечь время пересечения нарастающим сигналом нижнего порога, затем дождаться пересечения более высокого порога, а затем дождаться пересечения ещё одного более высокого порога, а затем установить соответствующий вывод светодиода в состояние HIGH. Но как ждать и как долго?, @Systembolaget
вы просто слепо включаете светодиоды... подумайте, как светодиоды должны себя вести, @jsotola
Ну, я так и сделал, как показано на пояснительном графике; только когда сигнал нарастает: порог A -> светодиод A, порог B -> только светодиод B, порог C -> только светодиод C и так далее. Проблема в том, что когда, например, порог C пересекается, порог A и порог B неизбежно пересекаются заранее., @Systembolaget
почему вы зажигаете
pinLEDY, когдаcrossedCравноtrue? ... у вас нет логики, которая бы это предотвращала ... вы уже знаете состояние всех переменных флагов, когда приходит время зажигать светодиоды, @jsotolaЭти светодиоды предназначены только для визуальной индикации воздействия на этот коврик или вы позже попытаетесь извлечь периоды свечения светодиодов в расчет? Если рассматривать простой случай и ваш пример, я бы, вероятно, дождался, пока передний фронт сигнала пересечёт нижний порог, и запустил таймер на 30 мс. Продолжайте измерять максимальное значение, пока таймер работает, а затем, по истечении времени таймера, зажгите соответствующий светодиод на X мс. Начало индикации светодиода будет немного отставать от пикового значения на несколько мс., @6v6gt
@6v6gt Только для наглядной демонстрации джеба, левого хука и правого кросса. Это забавное упражнение, ни в коем случае не научное. Я обновил вышеприведённое редактирование, добавив вариант, который работает для одного, самого низкого, порога. Теперь дело за временем ожидания, за таймером, как вы и сказали. Также мне нужно взглянуть на реализацию Эдгара Бонета ниже., @Systembolaget