1

I have a potentiometer connected to an ADC input of an Arduino Leonardo. The ADC resolution is 10-bit (0..1024), while I need just 7-bit (0..127).

I use a simple filter to reduce the noise and then a map to cut the unused bits:

#define ADC_CH                18
#define ADC_FILTER            0.90

uint16_t value;

uint16_t adc = analogRead(ADC_CH);
value = (uint16_t) ((float) ADC_FILTER * adc + (1. - ADC_FILTER) * value);

I show value on an LCD. It's very stable, but it may happen that if the adc readings are in the nearby of two values, there is nothing that could prevent a change in the final value, even if the noise is of just few LSBs.

Is there a simple way to implement an hysteresis for each low-res value?

In this way the change between N and N+1 happens on a different ADC reading than between N and N-1, improving the noise rejection without increasing the filter delay (that would only increase the change interval).

1 Answer 1

4

When you reduce the resolution from 10 bits to 7 bits, every possible output corresponds to an interval of inputs. If you add some hysteresis, then those intervals should overlap. Below is a representation of the input intervals corresponding to outputs 0, 1,… 6, for an hysteresis width of 4:

input: 0    5   10   15   20   25   30   35   40   45   50   55   60
       └────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴
       ├─────0────┤
           ├───────1──────┤
                   ├───────2──────┤
                           ├───────3──────┤
                                   ├───────4──────┤
                                           ├───────5──────┤
                                                   ├───────6──────┤

To implement this in code, you have to remember the last output, and change it only if the input lies outside the interval assigned to this output. Here is an example implementation:

const int hysteresis = 4;  // tune to taste

/* Remove 3 bits of resolution while adding some hysteresis. */
int resample(int adc)
{
    static int value;  // last output
    if (adc < (value<<3) - hysteresis) {
        value = (adc + hysteresis) >> 3;
    } else if (adc >= ((value+1)<<3) + hysteresis) {
        value = (adc - hysteresis) >> 3;
    }
    return value;
}

Note that a static local variable keeps its value between successive calls to the function.

3
  • I'm not supposed to do this but anyway: excellent answer! Nice graphic, well explained. :)
    – Nick Gammon
    Commented May 9, 2023 at 6:04
  • Your code works perfectly and it fits exactly the example in my question. Anyway, is there a more general approach? For example, what if I want to map the ADC input to [min, max] (always < 1024) ?
    – Mark
    Commented May 12, 2023 at 18:13
  • 1
    @Mark: Then you would replace the shifts (which are basically optimized multiplications and divisions) by arbitrary map()s. You may then want to do some tests in order to see how the rounding errors introduced by the map interfere with the hysteresis. Commented May 12, 2023 at 18:42

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.