2

I've got a 50k potentiometer connected to an Arduino Uno. The user should adjust the hue of an Adafruit NeoPixelStrip.

I've got the following code inside the loop function:

int hue = analogRead(poti1);
int mappedHue = map(hue, 0, 1023, 0, 65535);

neoPixelStrip.fill(neoPixelStrip.gamma32(neoPixelStrip.ColorHSV(mappedHue, 255, 255)));
neoPixelStrip.show();

If I turn the potentiometer knob, the colors are updating correctly and reflect the correct hue. But if I let go of the knob, the LEDs are flickering a little (changing their hue values ever so slightly). If I print the potentiometer values to the console, they are changing a little. If I turn the potentiometer all the way to the left, the values range from 0 ... 13 or something. This change affects the hue of the pixels.

How do I ignore those value variations? I want the user to constantly change the color, so I have to read the value constantly.

Any ideas?

2
  • Have you got AREF output enabled and the pot wired between AREF and ground, not VCC and ground. That will help with noise. Also, for another alternative, look at rotary encoders.
    – Rich
    Oct 21, 2019 at 2:29
  • Not quite sure what AREF is. Oct 22, 2019 at 19:28

2 Answers 2

3

You have two issues going on - noise pickup, and a potentiometer with imprecise stops at one or both ends.

The pot is the easiest to fix: What are the lowest low reading and highest high reading you can reliably get when you turn it to its stops? Use those in place of '0' and '1023' in your map() function.

Improving the noise is only slightly more involved. An exponential average is an easy filter to write and quite effective at quieting noisy data, as long the signal to noise ratio is fairly low. The basic idea is you add only a fraction of each new value and keep (1 - that fraction) of the old average; so if your chosen fraction is 0.1, you only consider 10% each new datum, keeping 90% of the current average.

avg = f*avg + (1-f)*new_value

The output of this averaging filter can be slow to respond to changes if your fraction is too small, but less effective at quieting noise if your fraction is too large.

I usually use .25, or 25% of each new point and 75% of the current average, when I'm just trying to clean up minor noise, which is what you probably have.

Here is my 25% averaging filter:

int16_t xpavg(int16_t newdat, int16_t avg){

   // (3 * avg + 1 * newdat) / 4
   return( (((avg<<2) - avg + newdat) + 2) >> 2 );
}

As you can see, I do the arithmetic in Fixed Point - just like integer arithmetic, but with an assumed binary point 2 places to the left during the computation. And I choose a fraction that represents "nicely" in binary - 1/4 in this case - to keep the arithmetic faster (since multiplication and division can be replaced by shifting).

The function just multiplies the current average by 4 and subtracts the old average (effectively multiplying by 3), adds the new value, and divides the sum by 4. (That extra "+2" rounds the fractional bits just before we shift them away). The end result is:
(3*avg + newData)/4 (== 3/4*avg + 1/4*newData).

If you were to only sample the pot every second or two, you could probably observe the delay as the filter falls behind while you turn the pot and catches up when you stop. If you're sampling at 10x/sec or less, you shouldn't be able to notice.

Update:

You need to add the function xpavg() to your code, and call it each time you read a new pot value (that is "newdat"), passing it the old average as well. It returns a new average pot value, now less affected by noise. You use that new average value instead of the raw number you just read from the pot, in your hue calculation. As Duncan offered, you can - and I do - initialize the average with a raw value from the pot. You could also start it at zero but it will take a few samples to converge on the current actual pot reading. The downside of using one raw sample to start it is that if that one value is terribly noisy, i.e. way different from the pot reading, it will still take a few samples to converge.

unsigned int AvgPot = analogRead(poti1);        // initialize pot average

// Exponential averaging functions
int16_t xpavg(int16_t newdat, int16_t avg)
{
   // (3 * avg + 1 * newdat) / 4
   return( (((avg<<2) - avg + newdat) + 2) >> 2 );
}

void setup()
{
   ;    // initializations go here
}

void loop()
{
   int hue = analogRead(poti1);
   hue = xpavg(poti1, PotAvg);
   int mappedHue = map(hue, 0, 1023, 0, 65535);

   neoPixelStrip.fill(neoPixelStrip.gamma32(neoPixelStrip.ColorHSV(mappedHue, 255, 255)));
   neoPixelStrip.show();
}
8
  • Sorry but i don't understand all of your post. It's a little too advanced for me. I don't know how I am going to implement this into my code. Where is that variable new_value coming from? Or what is it representing? And f? And what exactly do I have to pass as the parameter newdat? I don't know how to use your function in my code... Oct 20, 2019 at 18:16
  • The idea is that you want to avoid "jitter" in the readings from your pot. An algorithm like an exponential average from this answer smooths out input values over time. You'd create a global int16_t variable (let's call it average) to hold the "rolling average" value for your pot.
    – Duncan C
    Oct 20, 2019 at 20:15
  • Each time through your loop() function you'd read a new "raw" value from the pot, then call the xpavg() function, passing in the new pot value and the old average value. You'd then store the returned average value in your average var, and use that as the pot value. As the pot value changes, the average value will change more slowly, and will tend to ignore rapid "jittery" changes in the input.
    – Duncan C
    Oct 20, 2019 at 20:16
  • But what is the initial value of average? Oct 20, 2019 at 20:22
  • 2
    The initial value doesn't matter that much. As new values come in, the average will converge on the actual average reading. I suggest setting it to a value halfway between your max and your min value to start. Or you could initialize it with your very first analog reading.
    – Duncan C
    Oct 20, 2019 at 23:50
2

There are many ways to remove noise, below are some ways:

  • Only change the value when it differs enough. Assume the range is 0-1023. Than only change the value if the input value differs more than a constant value (like 5 or 10, experiment with this to find a good value for your pot meter).
  • Another way is to use the average of the last x readings, You can also combine this with the previous approach. There might be a small delay because of the averaging.

There are many more solutions, from reasonably easy to very complicated, search for removing noise in sensor data.

Your Answer

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

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