Гистерезис и диапазон масштабирования

adc mathematics filter

Здесь я спросил о том, как добавить гистерезис к показаниям АЦП. Полученный ответ был очень хорошим, и он работал из коробки.

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

Если бы нижняя рейка была нулевой, достаточно было бы изменить сдвиги с умножением/делением на 1024/max. Но я не могу понять, как обращаться с нижней направляющей, когда min > 0.

Моя попытка:

int Menu::resample(int adc, int *value, byte min, byte max)
{    
    // проверки работоспособности опущены: min < макс и макс < 128

    double m = 1015.0 / (max - min); // 1015 = 1023 - 8 (1 младший бит вывода с низким разрешением)
                                     // иначе он достигает 126, даже если max = 127
    int q = min << 3;

    int lowerTest = (q + *value * m) - ADC_HYSTERESIS;
    int higherTest = (q + (*value + 1) * m) + ADC_HYSTERESIS;

    int lowerOutput = min + (adc + ADC_HYSTERESIS) / m;
    int higherOutput = min + (adc - ADC_HYSTERESIS) / m;

    if (adc < lowerTest) *value = lowerOutput;
    else if (adc >= higherTest) *value = higherOutput;

    if (*value > max) *value = max;
    if (*value < min) *value = min;
    return *value;
}

Что-то не так в моей математике, так как сравнение всегда подпадает под первый if (т.е. *value = lowerOutput).

Можете ли вы помочь мне исправить этот код, чтобы я мог иметь гистерезис на любом масштабированном выходе?

Примечание: вместо использования статической переменной для значения, как в ответе, я использую struct:

slider.value = resample(adc, &slider.lastValue, slider.min, slider.max);

где:

typedef struct
{
    int value;
    int lastValue;
    int min;
    int max;
} slider_t;

slider_t slider;

, 👍2


1 ответ


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

2

Во-первых, несколько комментариев об этой первой попытке.

Нет смысла хранить в slider_t как lastValue, так и value. Как это используется, оба поля всегда обновляются одним и тем же значение: lastValue обновляется указателем внутри resample(), а value обновляется путем присвоения ему значения, возвращаемого resample(). я предложил бы удалить поле lastValue и либо:

  1. предоставление value по значению (а не по указателю) в resample(), или

  2. предоставление ссылки на slider_t в качестве единственного параметра.

Первый подход лучше, если вы хотите избежать resample() и slider_t тесно связан.

Далее идет сочетание типов int и byte. Если вы хотите сэкономить памяти и улучшить согласованность, я бы использовал byte для всех файлов с низким разрешением. значения, находящиеся в области вывода resample(). На самом деле, я бы используйте uint8_t, то же самое, только более стандартное.

Тогда есть несколько вещей, которые я не понимаю в этом коде:

  • Почему 1015? Мне кажется, что это число должно зависеть от ширина выходного диапазона.

  • Почему min << 3? Опять же, это деление на 8, а правильное фактор, вероятно, должен зависеть от выходного диапазона.

Теперь для решения я бы попробовал следующий подход:

  • Вычислите минимальное и максимальное возможные выходные значения в соответствии с предоставленный ввод с учетом гистерезиса.

  • Ограничьте вывод, чтобы он оставался в пределах этой скобки.

В коде:

uint8_t Menu::resample(int adc, uint8_t value, uint8_t min, uint8_t max)
{
    value = constrain(value, min, max);  // на всякий случай...
    uint8_t lower = map(adc - ADC_HYSTERESIS, 0, 1024, min, max+1);
    uint8_t upper = map(adc + ADC_HYSTERESIS, 0, 1024, min, max+1);
    return constrain(value, lower, upper);
}

Некоторые моменты, на которые стоит обратить внимание:

  1. Не знаю, было ли это задумано изначально, но Arduino функция map(x, in_min, in_max, out_min, out_max) работает плохо при сопоставлении интервала [in_min, in_max] с [out_min, out_max]. Однако он хорошо справляется с отображением [in_min, in_max) до [out_min, out_max) (обратите внимание на полуоткрытые интервалы). Вот почему четырьмя последними параметрами вызова map() являются (0, 1024, мин, макс+1), а не (0, 1023, мин, макс).

  2. Первый вызов constrain() можно удалить, если вызывающий ответственность за выполнение этого предварительного условия.

  3. Если предварительное условие допустимо (или, как здесь, принудительно), оно также удерживать для возвращаемого значения при условии, что adc находится в пределах [0, 1024].

Этот метод будет вызываться следующим образом:

slider.value = resample(adc, slider.value, slider.min, slider.max);
,

Невероятно, как довольно сложную задачу можно решить с помощью нескольких строк кода. Я впечатлен. Спасибо!, @Mark