Potentiometer readings jump

Hi!
This is my first post here and my first Arduino project, so thanks for having me.
I just completed the installment of a Arduino-based timer for my coffee grinder. There’s a potentiometer (which already was there) where I can choose a time between 3s and 10s which is then displayed on an OLED and used for the actual grinding duration.

The problem is that my readings from the potentiometer differ a lot. The values on my OLED always jump around about 0.03 - so thats no big deal for the coffee but there is always movement on the screen which is distracting.

I already tried to filter out movements which are below 2 of 1000 but it still flickers a lot. Does anyone have any suggestions on how to solve that?

Here’s the code (for better visibility also on: Arduino Create) :

#define PotiPin 0 //braun
#define ButtonPin 4 // blau
#define SiebButton 2  //lila
#define GrindPin 12 //grün
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Fonts/FreeSans18pt7b.h>
#include <Fonts/FreeSans12pt7b.h>


Adafruit_SSD1306 display(128, 64, &Wire, -1);


//poti an 0
// variables will change:
int ButtonState = 0;         // variable for reading the pushbutton status
int SiebState = 0;
int PotiMessung = 0;
int Poti = 0;
int delta = 2;
int PotitempMessung = 0;
float Dispzeit = 0;
int Grind = 0;

void setup() {
  Serial.begin(9600);
  pinMode(GrindPin, OUTPUT); // GRND_START_STOP AN PIN 13
  digitalWrite(GrindPin, HIGH );

  // initialize the pushbutton pin as an input:
  pinMode(ButtonPin, INPUT_PULLUP);
  pinMode(SiebButton, INPUT_PULLUP);
  pinMode(PotiPin, INPUT_PULLUP);
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  //display.invertDisplay(true);
  display.clearDisplay();
  display.setFont(&FreeSans18pt7b);
  display.setTextColor(WHITE);
  display.display();


}


void loop() {


  PotiMessung = analogRead(PotiPin);  // potentiometer an PIN A0, "poti" als ausgangsvariable

  /* 1000ms = 0,1s - die Werte 0 bis 1023 werden also umgemapped und müssen später für print und out umgerechnet werden */
  if (abs(PotitempMessung - PotiMessung) >= delta) {
    Poti = map(PotiMessung, 0, 1023, 300, 1000);
    Dispzeit = (float)Poti / 100;  //poti umrechnen in kommastelle für display
    Grind = Poti * 10; //poti umrechnen für delay
    //Serial.println(Dispzeit);
    display.clearDisplay();
    //    display.setTextSize(); // Draw 2X-scale text
    display.setCursor(32, 40);
    display.setFont(&FreeSans18pt7b);
    display.println(Dispzeit);
    display.display();
  }
  else {
    //Serial.println("delta zu klein");
  }


  //hier display einbauen
  delay(1);
  // read the state of the pushbutton value, buttonState ist für manuell, siebState für den Taster am Siebträger
  ButtonState = digitalRead(ButtonPin);
  SiebState = digitalRead(SiebButton);

  // check if the pushbutton is pressed. If it is, the buttonState is HIGH:
  if (SiebState == LOW) {
    // grind dat bitch on time
    digitalWrite(GrindPin, LOW); // signal anlegen, zum starten
    Serial.println("sieb");
    delay(100);                // halbe sekunde
    digitalWrite(GrindPin, HIGH);  // signal weg, tasterdruck simuliert
    delay(Grind); //HIER WIRD GEMAHLEN
    Serial.println("sieb2");
    digitalWrite(GrindPin, LOW); // signal anlegen, zum beenden
    delay(500);                // halbe sekunde
    digitalWrite(GrindPin, HIGH);  // signal weg
    delay(2000);                // 2 sekunden schonzeit
    

  }
  if (ButtonState == LOW) {

    digitalWrite(GrindPin, LOW); // signal anlegen, zum starten
    display.clearDisplay();
    display.setCursor(25, 30);
    display.setFont(&FreeSans12pt7b);
    display.println("SELBST");
    display.setCursor(25, 50);
    display.println("ZAPFEN");

    display.display();                  // halbe sekunde
    delay(300);
    digitalWrite(GrindPin, HIGH);  // signal weg, tasterdruck simuliert
    //delay(500);                // halbe sekunde
    ButtonState = digitalRead(ButtonPin);
    while (ButtonState == HIGH) {
      ButtonState = digitalRead(ButtonPin);
      // Do nothing, warten auf nächsten Tastendruck
    }
    digitalWrite(GrindPin, LOW); // signal anlegen, zum beenden
     display.clearDisplay();
    //    display.setTextSize(); // Draw 2X-scale text
    display.setCursor(32  ^, 40);
    display.setFont(&FreeSans18pt7b);
    display.println(Dispzeit);
    display.display();
    delay(500);                // halbe sekunde
    digitalWrite(GrindPin, HIGH);  // signal weg
    delay(2000);                // 2 sekunden schonzeit

  }
  PotitempMessung = PotiMessung;
  delay(10);
}

Thanks a lot
Best
Frank

Try a 0.1uF to 1uF cap, wiper to ground to act as a low pass filter.

And/or keep a running average over several readings.

I can't really follow your logic, but it looks like you are continuing to clear the display and then re-display everything again each time through the loop. There is certainly no need for that. Only display a new value if it is different from the last time through the loop and don't clear the display after the first time.

Another thing is to not change the display after the time selection has been made.

Paul

groundFungus:
Try a 0.1uF to 1uF cap, wiper to ground to act as a low pass filter.

And/or keep a running average over several readings.

I tried to code in an EMA, but setting the alpha too high resulted in the grinding time to always change a bit after finishing to turn the poti.

float EMA_a = 0.5;      //initialization of EMA alpha
int EMA_S = 0;

void setup() {
 Serial.begin(9600);
 pinMode(GrindPin, OUTPUT); // GRND_START_STOP AN PIN 13
 digitalWrite(GrindPin, HIGH );

 // initialize the pushbutton pin as an input:
 pinMode(ButtonPin, INPUT_PULLUP);
 pinMode(SiebButton, INPUT_PULLUP);
 pinMode(PotiPin, INPUT_PULLUP);
 display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
 //display.invertDisplay(true);
 display.clearDisplay();
 display.setFont(&FreeSans18pt7b);
 display.setTextColor(WHITE);
 display.display();
 EMA_S = analogRead(PotiPin);


}


void loop() {


 PotiMessung = analogRead(PotiPin);  // potentiometer an PIN A0, "poti" als ausgangsvariable

 /* 1000ms = 0,1s - die Werte 0 bis 1023 werden also umgemapped und müssen später für print und out umgerechnet werden */
 if (abs(PotitempMessung - PotiMessung) >= delta) {
   Poti = map(PotiMessung, 0, 1023, 300, 1000);
   EMA_S = (EMA_a * Poti) + ((1 - EMA_a) * EMA_S); //run the EMA
   Grind = Poti * 10; //poti umrechnen für delay

   Dispzeit = (float)EMA_S / 100;  //poti umrechnen in kommastelle für display
   if (abs(Dispzeit - DispzeitDelta) >= deltaZeit) {
     //Serial.println(Dispzeit);
     display.clearDisplay();
     //    display.setTextSize(); // Draw 2X-scale text
     display.setCursor(32, 40);
     display.setFont(&FreeSans18pt7b);
     display.println(Dispzeit);
     display.display();
   }
 }

Paul_KD7HB:
I can't really follow your logic, but it looks like you are continuing to clear the display and then re-display everything again each time through the loop. There is certainly no need for that.

Also added this in the new sketch but still the values jump around a bit.

Frank

The trick is to use an EMA together with hysteresis.

This is what I use for my potentiometers and it works great:
https://tttapa.github.io/Arduino-Helpers/Doxygen/d3/dbe/1_8FilteredAnalog_8ino-example.html

Pieter

Thanks Pieter!
I got a bit trouble using the Library in my project as I don't really get the documentation - would you mind pasting your example here?

Frank

The simplest example is as follows:

[color=#434f54]// Include the library[/color]
[color=#5e6d03]#include[/color] [color=#434f54]<[/color][b][color=#d35400]Arduino_Helpers[/color][/b][color=#434f54].[/color][color=#000000]h[/color][color=#434f54]>[/color]
 
[color=#5e6d03]#include[/color] [color=#434f54]<[/color][b][color=#d35400]AH[/color][/b][color=#434f54]/[/color][color=#000000]Hardware[/color][color=#434f54]/[/color][b][color=#d35400]FilteredAnalog[/color][/b][color=#434f54].[/color][color=#000000]hpp[/color][color=#434f54]>[/color]
[color=#5e6d03]#include[/color] [color=#434f54]<[/color][b][color=#d35400]AH[/color][/b][color=#434f54]/[/color][color=#000000]Timing[/color][color=#434f54]/[/color][color=#000000]MillisMicrosTimer[/color][color=#434f54].[/color][color=#000000]hpp[/color][color=#434f54]>[/color]
 
[color=#434f54]// Create a filtered analog object on pin A0:[/color]
[b][color=#d35400]FilteredAnalog[/color][/b][color=#434f54]<[/color][color=#434f54]>[/color] [color=#000000]analog[/color] [color=#434f54]=[/color] [color=#000000]A0[/color][color=#000000];[/color]
 
[color=#00979c]void[/color] [color=#5e6d03]setup[/color][color=#000000]([/color][color=#000000])[/color] [color=#000000]{[/color]
  [b][color=#d35400]Serial[/color][/b][color=#434f54].[/color][color=#d35400]begin[/color][color=#000000]([/color][color=#000000]115200[/color][color=#000000])[/color][color=#000000];[/color]
  [b][color=#d35400]FilteredAnalog[/color][/b][color=#434f54]<[/color][color=#434f54]>[/color][color=#434f54]:[/color][color=#434f54]:[/color][color=#d35400]setupADC[/color][color=#000000]([/color][color=#000000])[/color][color=#000000];[/color] [color=#434f54]// Select the correct ADC resolution[/color]
[color=#000000]}[/color]
 
[color=#00979c]void[/color] [color=#5e6d03]loop[/color][color=#000000]([/color][color=#000000])[/color] [color=#000000]{[/color]
  [color=#434f54]// Read the analog input every millisecond, and print if the value has changed[/color]
  [color=#00979c]static[/color] [b][color=#d35400]Timer[/color][/b][color=#434f54]<[/color][color=#d35400]millis[/color][color=#434f54]>[/color] [color=#000000]timer[/color] [color=#434f54]=[/color] [color=#000000]1[/color][color=#000000];[/color] [color=#434f54]// ms[/color]
  [color=#5e6d03]if[/color] [color=#000000]([/color][color=#000000]timer[/color] [color=#434f54]&&[/color] [color=#000000]analog[/color][color=#434f54].[/color][color=#d35400]update[/color][color=#000000]([/color][color=#000000])[/color][color=#000000])[/color]
    [b][color=#d35400]Serial[/color][/b][color=#434f54].[/color][color=#d35400]println[/color][color=#000000]([/color][color=#000000]analog[/color][color=#434f54].[/color][color=#d35400]getValue[/color][color=#000000]([/color][color=#000000])[/color][color=#000000])[/color][color=#000000];[/color]
 
  [color=#434f54]// Explanation:[/color]
  [color=#434f54]//[/color]
  [color=#434f54]// analog.update() reads the analog input, applies the filters,[/color]
  [color=#434f54]// saves the value, and returns true if the value has changed.[/color]
  [color=#434f54]// You can then retreive the new value using analog.getValue().[/color]
  [color=#434f54]//[/color]
  [color=#434f54]// Timer is just a "Blink Without Delay" wrapper, it returns true[/color]
  [color=#434f54]// every time the specified amount of time has passed.[/color]
[color=#000000]}[/color]

You don’t have to use the timer, you can just call analog.update() all the time.

Or do you mean you’re having trouble installing the library for your project?

void setup() {
  Serial.begin(115200);
  FilteredAnalog<>::setupADC(); // Select the correct ADC resolution
}

Do I have to edit any values here?
Wherever I try to use analog.getValue I get an output of 0.

Based on the sketch I posted above I just changed;
PotiMessung = analogRead(PotiPin);
into
analog.update()
PotiMessung = analog.getValue;

void loop() {

analog.update()
PotiMessung = analog.getValue; 
   // potentiometer an PIN A0, "poti" als ausgangsvariable

  /* 1000ms = 0,1s - die Werte 0 bis 1023 werden also umgemapped und müssen später für print und out umgerechnet werden */
  if (abs(PotitempMessung - PotiMessung) >= delta) {
    Poti = map(PotiMessung, 0, 1023, 300, 1000);
    Dispzeit = (float)Poti / 100;  //poti umrechnen in kommastelle für display
    Grind = Poti * 10; //poti umrechnen für delay
    //Serial.println(Dispzeit);
    display.clearDisplay();
    //    display.setTextSize(); // Draw 2X-scale text
    display.setCursor(32, 40);
    display.setFont(&FreeSans18pt7b);
    display.println(Dispzeit);
    display.display();
  }

and left the rest the same (besides including the libraries and basically pasting the demo sketch into my setup).

Is there anything else I need to change?

Frank

That's strange, could you post your full and exact code? The one you posted misses a semicolon and a pair of parentheses, so it doesn't compile.

Sorry for the late response, I shorted the whole coffee grinder and had to build a new board for the motor.
But now that that works again I could re-test the code and after some corrections it actually worked. So, thanks a lot for your help!

I added an additional filter for movements <=0.01 seconds because I sometimes had very rare changes there.

So thats the full code:

#define PotiPin 0 //braun
#define ButtonPin 4 // blau
#define SiebButton 2  //lila
#define GrindPin 12 //grün
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Fonts/FreeSans18pt7b.h>
#include <Fonts/FreeSans12pt7b.h>
#include <Fonts/FreeSans9pt7b.h>
#include <Arduino_Helpers.h>

#include <AH/Hardware/FilteredAnalog.hpp>
#include <AH/Timing/MillisMicrosTimer.hpp>


Adafruit_SSD1306 display(128, 64, &Wire, -1);


//poti an 0
// variables will change:
int ButtonState = 0;         // variable for reading the pushbutton status
int SiebState = 0;
int PotiMessung = 0;
int Poti = 0;
int delta = 0.01;
int PotitempMessung = 0;
float Dispzeit = 0;
int Grind = 0;
int Zeitdifferenz = 0;

FilteredAnalog<> simpleAnalog = A0;

void setup() {
 Serial.begin(9600);
 pinMode(GrindPin, OUTPUT); // GRND_START_STOP AN PIN 13
 digitalWrite(GrindPin, LOW );

 // initialize the pushbutton pin as an input:
 pinMode(ButtonPin, INPUT_PULLUP);
 pinMode(SiebButton, INPUT_PULLUP);
 pinMode(PotiPin, INPUT_PULLUP);
 display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
 //display.invertDisplay(true);
 display.clearDisplay();
 display.setFont(&FreeSans18pt7b);
 display.setTextColor(WHITE);
 display.display();

 FilteredAnalog<>::setupADC();


}


void loop() {

 simpleAnalog.update();
 PotiMessung = simpleAnalog.getValue();  // potentiometer an PIN A0, "poti" als ausgangsvariable

 /* 1000ms = 0,1s - die Werte 0 bis 1023 werden also umgemapped und müssen später für print und out umgerechnet werden */
 Zeitdifferenz = (PotiMessung - PotitempMessung);
 if (Zeitdifferenz > delta || Zeitdifferenz > delta) {

   Poti = map(simpleAnalog.getValue(), 0, 1023, 500, 1500);
   Dispzeit = (float)Poti / 100;  //poti umrechnen in kommastelle für display
   Grind = Poti * 10; //poti umrechnen für delay
   //Serial.println(Dispzeit);

   //seitenabstand
   if (Dispzeit >= 10) {
   display.clearDisplay();
     //    display.setTextSize(); // Draw 2X-scale text
     display.setCursor(20, 40);
     display.setFont(&FreeSans18pt7b);
     display.println(Dispzeit);
     //   display.setCursor(20, 60);
     //   display.setFont(&FreeSans9pt7b);
     //display.println("sekunden");
     display.display();
   }
   else {
     display.clearDisplay();
     display.setCursor(32, 40);
     display.setFont(&FreeSans18pt7b);
     display.println(Dispzeit);
     display.display();


   }


   //hier display einbauen
   // delay(1);
   // read the state of the pushbutton value, buttonState ist für manuell, siebState für den Taster am Siebträger
   ButtonState = digitalRead(ButtonPin);
   SiebState = digitalRead(SiebButton);

   // check if the pushbutton is pressed. If it is, the buttonState is HIGH:
   if (SiebState == LOW) {
     // grind dat bitch on time
     digitalWrite(GrindPin, HIGH); // signal anlegen, zum starten
     Serial.println("sieb");
     // delay(100);                // halbe sekunde
     //  digitalWrite(GrindPin, HIGH);  // signal weg, tasterdruck simuliert
     delay(Grind); //HIER WIRD GEMAHLEN
     Serial.println("sieb2");
     digitalWrite(GrindPin, LOW); // signal anlegen, zum beenden
     delay(500);                // halbe sekunde
     //  digitalWrite(GrindPin, HIGH);  // signal weg
     delay(2000);                // 2 sekunden schonzeit


   }
   if (ButtonState == LOW) {

     digitalWrite(GrindPin, HIGH); // signal anlegen, zum starten
     display.clearDisplay();
     display.setCursor(18, 30);
     display.setFont(&FreeSans12pt7b);
     display.println("SELBST");
     display.setCursor(18, 50);
     display.println("ZAPFEN");

     display.display();                  // halbe sekunde
     delay(300);
     //  digitalWrite(GrindPin, HIGH);  // signal weg, tasterdruck simuliert
     //delay(500);                // halbe sekunde
     ButtonState = digitalRead(ButtonPin);
     while (ButtonState == HIGH) {
       ButtonState = digitalRead(ButtonPin);
       // Do nothing, warten auf nächsten Tastendruck
     }

     digitalWrite(GrindPin, LOW); // signal anlegen, zum beenden
     
     if (Dispzeit >= 10) {
     display.clearDisplay();
       display.setCursor(20, 40);
       display.setFont(&FreeSans18pt7b);
       display.println(Dispzeit);
       display.display();
     }
     else {
       display.clearDisplay();
       display.setCursor(32, 40);
       display.setFont(&FreeSans18pt7b);
       display.println(Dispzeit);
       display.display();


     }
    
     delay(1000);                // 2 sekunden schonzeit

   }

   delay(1);
 }
}