Close

Arduino analogRead Averaging (and Alliteration!)

A project log for Twister™: a play on MIDI controllers

Music is play, let's put game controls into the performance! Atari paddle knobs & arcade buttons make a great show of MIDI knob twiddling!

t-b-trzepaczT. B. Trzepacz 05/15/2016 at 10:110 Comments

So, even with my very expensive precision potentiometers, using "analogRead" on the Arduino has the values varying as much as +/- 4, reducing our 10 bits of resolution down to not quite 8 on a good day. What to do?

Well, when I had similar problems reading resistive touchscreen on the Teensy 3, I had some success eliminating noise by taking the average of 4 consecutive reads. So I tried that here:

#define NUM_KNOBS (8)
int knobValues[NUM_KNOBS];


  for(int i=0; i< NUM_KNOBS;i++)
  {
    int value = analogRead(i) + analogRead(i) + analogRead(i) + analogRead(i);
    value>>=2;
    int delta = abs( value - knobValues[i] ); 
    if ( delta > 2 )
    {
      knobValues[i]=value;
      Serial.print(i, DEC);
      Serial.print(" ");
      Serial.println(knobValues[i],DEC);    
    }
  }
I unrolled the loop of analogRead, and used a right shift to do a divide by 4 to save cycles. As long as you are averaging powers of 2, that is the best way to do it.
I managed to increase the accuracy by about +/-2 this way, which gives us maybe a single bit of resolution back?

I upped the averaging to 8 values ( which has to be getting kinda slow by now) and got it stable to +/- 1!
For grins, I upped the averaging to 16 values, but it didn't increase the accuracy enough to be useful, and 8 seemed to still have some stability problems, so I backed down to 4 again.

I worry that, since this thing is supposed to also do a bunch of other MIDI stuff, that I might be slowing down everything else, so I might have to back off on the averaging eventually, but for now I'll leave it at 4.

Another problem is that the knobs don't seem to cover the full voltage range. Some don't go all the way to zero. Some don't return analogRead values higher than 1015 (out of 1023).

While one could determine the limits of each pot and subtract and then scale the values accordingly, that isn't really feasible for a production unit. Instead, the best bet is to find the maximum deviation at each end, and clamp that off and then scale across the range (or cut off some bits if that is more convenient....)
The highest minimum value for a pot seems to be 9. The lowest maximum value for a pot seems to be 1012.

So to spread the values across the range we

  1. subtract the highest minimum value ( 9 )
  2. multiply by the desired maximum value ( 1023 )
  3. divide by the range of values (highest min - lowest max) ( 1012 - 9 = 1003)

But this will give us some odd values if the value is less than 9 or greater than 1023. That is easily resolved with some if tests.
Multiplying by 1023 is silly when 1024 is just a left shift by 10. But we want to make sure the output value won't exceed 1023. Now we are going to clamp ANYWAY, but it would be nice to squeeze out as much range as we can, so we should determine how high our divisor needs to be to get 1023 from an input of 1012 instead of getting 1024 at that value.

Hmmm. Can't really do integer math with values less than 0. Well, let's try calling it 1 and see what we get.

Well, that's great if we have rounding, but in integer we don't, really. So clamping it is!

Note that we are multiplying a 10 bit number by 10 bits, which means we need 20 bits for the result! This will not fit in the 16 bit math that is default on Arduino, so we have to explicitly define our work variable as 32 bits using the "int32_t" type.

#define HIGHEST_MIN_VALUE (9)
#define LOWEST_MAX_VALUE (1012)
#define VALUE_RANGE (LOWEST_MAX_VALUE-HIGHEST_MIN_VALUE)
#define MAX_RANGE (1023)

      int32_t outval = value - HIGHEST_MIN_VALUE; //note: 32 bit math!
      if ( outval < 0 ) outval=0;
      outval<<=10;
      outval/= VALUE_RANGE;
      if ( outval > MAX_RANGE ) outval = MAX_RANGE;
This means that there is a little bit of play in the values at the beginning and end of the range, but at least we can reach 0 and 1023 reliably now. If we wanted to get fancy, we could store a min and max value for each knob and expand the range on the fly, but why waste the cycles when this is good enough?

This also means that it is not possible to get every single value. It is going to skip some. That might be a problem for some applications.
In those cases, the best thing is to just drop the low 2 or 3 bits and treat it as a 0-255 or 0-127 knob. Which is the MIDI CC spec, after all...
Oh noes! Our hopes of a more accurate controller have been dashed!
Well, not entirely. 0-255 is still 2x the resolution of standard MIDI. For situations where we don't need to dial in every bit, we can still be a little more accurate. Strangely enough, dividing by 3 seems to give us the best range we can get without skipping any values. Dividing is a no-no if one is counting cycles, but if you don't care, that will give you a range from 0 to 341, which is probably as good as you are going to get out of an Arduino.

Discussions