2

Here I asked about how to add an hysteresis on ADC readings. The answer received was very good and it worked out of the box.

Still I'm having troubles trying to generalize the resample function in order to map the input values to a different range of output values, where the lower rail is not zero.

If the lower rail was zero, it would be enough to change the shifts with a multiplication/division for 1024 / max. But I cannot understand how to handle the lower rail when min > 0.

My attempt:

int Menu::resample(int adc, int *value, byte min, byte max)
{    
    // sanity checks omitted: min < max and max < 128

    double m = 1015.0 / (max - min); // 1015 = 1023 - 8 (1 LSB of low-res output)
                                     // otherwise it reaches 126 even if 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;
}

There is something wrong in my math, since the comparison always falls under the first if (i.e. *value = lowerOutput).

May you help me to fix this code so I can have an hysteresis on any scaled output?

Note: instead of using a static variable for value as in the answer, I use a struct:

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

where:

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

slider_t slider;

1 Answer 1

2

First, a couple of comments about this first attempt.

There is no point in storing in slider_t both lastValue and value. The way this is used, both fields are always updated with the same value: lastValue is updated by pointer inside resample(), while value is updated by assigning it the value returned by resample(). I would suggest removing the field lastValue and either:

  1. providing value by value (rather than by pointer) to resample(), or

  2. providing a reference to a slider_t as the only parameter.

The first approach is better if you want to avoid resample() and slider_t being tightly coupled.

Next, there is a mix between int and byte types. If you want to save memory, and improve consistency, I would use byte for all low-res values living in the output domain of resample(). Actually, I would use uint8_t, which is the same, only more standard.

Then, there are a few things I do not understand in this code:

  • Why 1015? It seems to me this number should be dependent on the width of output range.

  • Why min << 3? Again, this is a division by 8, while the proper factor should be probably be output-range dependent.

Now, for the solution, I would try this approach:

  • Compute the minimum and maximum possible output values consistent with the provided input, accounting for the hysteresis.

  • Constrain the output to remain within this bracket.

In code:

uint8_t Menu::resample(int adc, uint8_t value, uint8_t min, uint8_t max)
{
    value = constrain(value, min, max);  // just in case...
    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);
}

Some points worth noticing:

  1. I do not know whether this was originally intended, but the Arduino function map(x, in_min, in_max, out_min, out_max) does a poor job at mapping the interval [in_min, in_max] to [out_min, out_max]. It does, however, do a good job at mapping [in_min, in_max) to [out_min, out_max) (mind the semi-open intervals). This is why the four last parameters of the call to map() are (0, 1024, min, max+1), rather than (0, 1023, min, max).

  2. The first call to constrain() can be removed if the caller bears the responsibility of guaranteeing this precondition.

  3. If the precondition is valid (or, like here, enforced), it will also hold for the returned value, provided adc is within [0, 1024).

This method would be called like this:

slider.value = resample(adc, slider.value, slider.min, slider.max);
1
  • It's unbelievable how a quite complex task can be solved with few lines of code. I'm impressed. Thank you!
    – Mark
    Commented May 16, 2023 at 8:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.