9

I have a pair of potentiometers, on the Arduino, the output seems to drift, by one or 2 digits, back and forth. I would prefer they were read as one fixed value, without variation when their knobs are not turning. You can see in the image, the first and second values are shifting back and forth slightly. How should I address this?

I'm thinking of making a standard deviation function, but then I thought that might be unnecessarily complex.

wandering pots

My code is that the 2 sensor values are setting points in an SVG polyline. Variance means extra points will get plotted, while the device waits for valid input.

How should I deal with the variance in potentiometer readings to avoid outputting noise?

Here's my source:

/**Fucntion Defs**/
void svgHeader();
void svgFooter();
void polyLineBegin();
void polyLineEnd();

int Reset = 4;     //programmatically reset the arduino
int potX = A0;
int potY = A1;

int sensorValX = 0;
int sensorValY = 0;
int oldX = 0;
int oldY = 0;

int del = 2;       //a button on pin 2 for deleting drawn content
int delbutton = 0;
void setup() {
  Serial.begin(9600);
  pinMode(del, INPUT);
  svgHeader();
  polyLineBegin();
  delay(1000);
}

void loop() {
  // close the svg and start over
  if (digitalRead(del) == LOW ) {
    delbutton = 1;
    polyLineEnd();
    svgFooter();
    delay(5000);
    digitalWrite(Reset, LOW);
    digitalWrite(Reset, HIGH);
    svgHeader();
    polyLineBegin();
  } else {
    delbutton = 0;
    //Serial.println(delbutton);

  }

  //dont draw if its not changing
  sensorValX = analogRead(potX);
  sensorValY = analogRead(potY);
  if (sensorValX == oldX && sensorValY == oldY) {
    ; //dont do shi.
  } else {
    Serial.print(sensorValX);
    Serial.print(",");
    Serial.print(sensorValY);
    Serial.print(",");
    oldX = sensorValX;
    oldY = sensorValY;
    // Serial.println(delbutton);
    delay(1000);
  }
}
/**Functions**/

void polyLineBegin() {
  Serial.println ("<polyline points=\""); //x,y points go here

}

void polyLineEnd() {
  Serial.println ("0, 0\" stroke=\"red\" fill=\"transparent\" stroke-width=\"5\"/>");  //a dirty hack to avoid dealing with 1 last trailing comma.

}

void svgHeader() {
  Serial.println("<svg width=\"1023\" height=\"1023\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\">");
};

void svgFooter() {
  Serial.println ("</svg>");
};
4
  • 10
    That's quantisation noise and is normal. You can't get rid of it, only deal with it. You could take an average, or you could discard some resolution, or you could write your code to work in a range of values not one single value.
    – Majenko
    Jul 31 at 22:30
  • 6
    Thats not drifting, just electrical noise in the analog input. You cannot really avoid that. But since your code isn't time critical, why not measuring multiple times, a erafing the values and then using the result. That would drastically reduce the noise in the used values
    – chrisl
    Jul 31 at 22:34
  • 2
    so Ill have to write some code to deal with it. I'm currently looking at calculating variance, standard deviation, normalization, and some other math terms I'm not exactly familiar with. I think I can make a simple statement to ignore values +/-1 of the read values, but im not sure what to do if the variance where any more than that.
    – j0h
    Jul 31 at 22:59
  • 1
    Before you do a analogRead(potX) ... do a "calibration" of your Vcc supply (through internal reference) if it is used as "reference" voltage with some "mean". This can help.
    – Antonio51
    Aug 1 at 18:54
10

ok, once I got the idea I would need to do it in code, it became simpler to solve. In looking around the terms of quantization, the term thresh-holding came up, and I realized how to solve the problem.

I created a threshold value, and compared the difference between old and new variables.

int threshold =3;
int xVariance = abs(sensorValX-oldX);
int yVariance = abs(sensorValY-oldY);
if (xVariance >=threshold && yVariance >= threshold ||xVariance >=threshold || yVariance >=threshold){
  Serial.print(sensorValX);
  Serial.print(",");
  Serial.print(sensorValY);
  Serial.print(",");
 oldX=sensorValX;
 oldY=sensorValY; 
  
}else{
; //dont do shi.
 // Serial.println(delbutton);
delay(1000);
    } 

now, when I turn either potentiometer, I get a response, and output I desire, but not while the device is just sitting there.

4
  • 3
    Note, that you can leave out the xVariance >=threshold && yVariance >= threshold part in your if statement. It is redundant. The OR operator || will also evaluate to true, if both conditions are met.
    – chrisl
    Aug 1 at 8:34
  • 8
    This approach introduces hysteresis, which is indeed a common and effective approach against this kind of problems. Aug 1 at 15:31
  • @chrisl, is it redundant? in the event one or both potentiometers are +/- threshold value? one value may change, while the other is constant, or both may change.
    – j0h
    Aug 1 at 21:35
  • 1
    Yes, it is redundant. The OR operator || also evaluates to true, if both operants are true. You can refer to the truth table of that operator. What you were thinking of is called exclusive OR, or shortly XOR.
    – chrisl
    Aug 1 at 21:58
6

Remove the analog component from your device.

Use a rotary encoder.

It will sense the knob rotation digitally.

One huge advantage is that you can easily select the sensitivity of the input. One click can advance the counter by .001 just as easily as advancing by a 1000.

https://duckduckgo.com/?q=rotary+encoder&iar=images&iax=images&ia=images

enter image description here

3
  • you're not wrong, once I get this project functionally operable, its going to a children's museum, and you cant have knobs with limits, they break when you get 1000 kids a week cramming them all the way to one side or the other. but I still have some work to do, and I don't have the encoders yet.
    – j0h
    Aug 1 at 0:49
  • 4
    @j0h then you may be interested in this ... uses a stepping motor as an encoder ... it makes a tough encoder ... create.arduino.cc/projecthub/andrewf1/…
    – jsotola
    Aug 1 at 1:17
  • Right on! @jsotola I saw some things like this while working in a paper mill, but forgot all about it. I'm definitely going to look into this more, as steeper motors are harder to break than tiny little knobs.
    – j0h
    Aug 1 at 15:58
5

Some methods for "dealing" with the noise:

  1. Take multiple samples. Calculate the average ("mean"). Discard the decimal. This is "oversampling" along with resolution reduction (but reduction of the now increased oversampled resolution).
  2. Take multiple samples. Find the most common value ("mode").
  3. Divide the value by 2 (or 4 if needed). This reduces the range of values and the sampled resolution but discards the noise portion.

The problem you are suffering from is "quantisation noise". This is because an analog signal and a digital signal are not completely compatible. The ADC converts the analog signal into a digital value by successively comparing it to smaller and smaller voltages in steps ("quanta"). There is a limit to how small the steps can get (the resolution), but an analog voltage doesn't adhere to those steps, it will always be somewhere between the final two steps. Sometimes it will be detected as the lower step, sometimes the upper step. By taking multiple samples and averaging them you are estimating how close to one step or the other the value is and "refining" the result. This is "oversampling" and is used to increase the resolution of the ADC at the cost of sample speed.

With those extra samples you can either say "It's this much closer to the upper step" (take the "mean"), or say "It's more likely to be nearer the upper step" (take the "mode") to try and "fix" the value to one of the steps.

1
  • I appreciate the new words, and concepts, I will look into these methods further. I was concerned that calculating standard deviation on the Arduino may reduce performance, by being computationally intensive.
    – j0h
    Aug 1 at 16:07
2

I think you should handle the trailing comma within the Arduino by using leading commas instead, but omit the first leading comma using a flag to indicate the first coordinate:

bool firstCoordinate;

void loop()
{
    . . .
    if (xVariance >= threshold || yVariance >= threshold)
    {
        if (!firstCoordinate)
        {
            Serial.print(",");
            firstCoordinate = false;
        }
        Serial.print(sensorValX);
        Serial.print(",");
        Serial.print(sensorValY);
        oldX = sensorValX;
        oldY = sensorValY;
    }
    . . .
}

void polyLineBegin()
{
    Serial.println("<polyline points=\""); //x,y points go here
    firstCoordinate = true;
}


void polyLineEnd()
{
    // Clean line ending.
    Serial.println("\" stroke=\"red\" fill=\"transparent\" stroke-width=\"5\"/>");
}

Alternatively, the first coordinate could be added to the line when it begins, then there is no need for a flag:

void polyLineBegin()
{
    Serial.println("<polyline points=\""); //x,y points go here
    Serial.println(sensorValX);
    Serial.println(",");
    Serial.println(sensorValY);
}

void loop()
{
    . . .
    if (xVariance >= threshold || yVariance >= threshold)
    {
        Serial.print(",");
        Serial.print(sensorValX);
        Serial.print(",");
        Serial.print(sensorValY);
        oldX = sensorValX;
        oldY = sensorValY;
    }
    . . .
}

The thresholding technique is great, but, as an aside, I recently discovered this excellent statistics library by Majenko while working on a non-blocking averaging function for Arduino and looking at converting it into a class. I found that that library already does what I want.

1
  • thanks @tim, this is valid, constructive criticism, related to the code. It might not address the question, but it does represent meaningful feedback.
    – j0h
    Aug 1 at 16:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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