Potentiometer 'Debouncing' Revisited

ForestCat

Member
Thought this might be of use to some of you struggling w/ jumpy pots/MIDI cc values, etc. The twist is adding/subtracting a small % of the total raw value IN ADDITION to the usual fixed value. I noticed the fixed values just weren't enough at raw > 900, but increasing the fixed values then really screws up the pot 'feel' at low values. Haven't seen this approach before, but it doesn't mean it isn't out there ;-)

You'll need to tune the values, but these work perfectly for me w/ a teensy 2.0, 10k pot, .1uf to ground on the wiper

View attachment nn_MIDI-PotTest-01c.ino
 
While making the threshold scale might sort out stability it seems it limits access to CC values near 127.

If you max the pot so voltage = Vcc then the voltage reading should be 1023.
As you turn it slowly into the voltage divider range the value the second OR clause becomes effective when the current value falls to 997 or lower. (2% margin = 20; plus constant 5 means you have to be 26 lower before it will send an new MIDI event.)

When that gets bit-shifted to MIDI values it will be 124 so the intervening values could never be generated by lowering from above.

Even in the middle it looks like you'd skip half the available CC values when you try to make a slow adjustment.

Or am I getting this wrong?

Code:
// *************   function to convert pot to MIDI cc. Pass it the pin number, the cc to transmit, and the MIDI channel ***************
void pot_to_cc(byte pinNum, byte PotCC, byte Ch ){ 
    int PotCurVal = analogRead(pinNum) ; // get the raw value
    int margin = PotPrevVal * .02; //  get 2% of the raw value.  Tune for lowest non-jitter value.
    /*
     * Next we add (or subtract...) the 'standard' fixed value to the previous reading. (PotPrevVal needs to be declared outside the function so it persists.)
     * Here's the twist: Since the jitter seems to be worse at high raw vals, we also add/subtract the 2% of total raw. Insignificantat on low 
     * raw vals, but enough to remove the jitter at raw >900 without wrecking linearity or adding 'lag', or slowing down the loop, etc.
     */
    if (PotCurVal > PotPrevVal + (4 + margin) || PotCurVal < PotPrevVal - (5 + margin)) { // a 'real' change in value? Tune the two numeric values for best results
          MIDI1.sendControlChange(PotCC,PotCurVal >> 3,Ch);  // scale to 0-127 and send the cc
      PotPrevVal = PotCurVal; // store valid raw val  for next comparison
      //Serial.print("PotCurVal-"); // uncomment to debug
      //Serial.println(PotCurVal); // uncomment to debug
    }    
}
 
Also... if you put a cap between the wiper and the ground don't you end up with a low-pass where the cutoff frequency increases as you rotate the pot CW since the R in the RC circuit gets bigger and bigger.

Couldn't this explain the higher noise observed on the upper end? The rising cutoff is letting more noise through?

You could put a static low-pass in series with the wiper but I'm wondering if a discrete low-pass filter in the code couldn't be used in conjunction with 'change band' hysteresis.


Just run the signal from the pin through the discrete low-pass before testing it
y := α * x + (1-α) * y[i-1]

...and then use a 'change-band' technique to update the CC value when the filtered signal crosses the change threshold that corresponds to the 3 bits of headroom the ADC provides.

The cutoff for this filter would not depend on the pot position and it could be fine-tuned in code.
 
I'll caveat the following w/ the disclaimer that I'm no expert on this, lol.

A couple of things, in no order. All of the solutions I found online for pot jitter where MIDI is concerned (i.e. you absolutely CAN NOT have the mcu transmitting cc's when no one is moving a control...) involve adding some kind of hysteresis or dead-band. I THINK (again, not sure) that if you use that approach, the tradeoff will always be A) Resolution, B) Worse with slower control movement (Paul, chime in any time, lol). In my (and I suspect other's) case, the tradeoff of not being able to hit 127 every time, or not being able to 'zero in' on an exact setting every time is an annoyance, but not a dealbreaker, since I'm generally using Teensy/Arduino-based pots (as opposed to switches) 'expressively' in pedals (think wah-wah pedal, etc), rather than for fine adjustments, which are better suited to my Lemur or Max-based touchscreens.

re: the wiper cap to ground, it is the usual 'professional' recommendation in the threads where guys (like me) are whining about their inability to get a pot on an mcu gpio pin to "settle down"

Of the various solutions I tried, this gave me the best result. Not nearly perfect, but closer than I was getting w/ dead-band, cap to ground, or a combo of the two.
If you can test and post a solution that doesn't involve any of the tradeoffs of the other approaches, you'll make a lot of friends, starting w/ me, lol :) :)
 
This is a very interesting thread as the pot smoothing algorithm that is commonly used works, but introduces lag. :)

I'll be nosing through this at a later date for my untztrument.
 
This is a very interesting thread as the pot smoothing algorithm that is commonly used works, but introduces lag...



But the lag should be negligible as long as the loop isn't using a delay (which you normally only use to get around this problem anyway).

Can I ask what algorithm you mean when you say 'commonly used'?

on a different note...

I was wondering if a counter variable could track code cycles since the last MIDI event and prevent a new message until a 'time' threshold is passed.

That is if you need to limit the rate that messages can be generated at but want/need to avoid code 'delays' (for a digital filter to run for example).

But I'm hopeful the digital filter with and three-bit threshold for CC events will be stable enough with no limits on how fast the MIDI can fire.
 
Last edited:
use ElapsedMillis or ElapsedMicros: td_timing_elaspedMillis.html

Very AWESOME tool - even if you use it when you don't need to it is fun and harmless.

<edit> ... Harmless compared to (mis)using delay()

Here's a snip of my latest use -

Code:
elapsedMillis fftTime;
uint32_t fftCount=0;

void setup() {
  // ...
  Serial.print("\n\n  starting ....");
  fftTime = 0;
}

void loop() {
  // ...
  fftCount++;
  if ( fftTime >= 1000 ) {
    // Do something once a second
    // ... print the fftCount per second ...
    fftCount=0;
    fftTime = 0;
  }
  // ...
}
 
Last edited:
But the lag should be negligible as long as the loop isn't using a delay (which you normally only use to get around this problem anyway).

Can I ask what algorithm you mean when you say 'commonly used'?

https://www.arduino.cc/en/Tutorial/Smoothing

This one. It works but it actually introduces lag (this was noticed on an LC). Of course on could reduce the iterations and speed up the measurement intervals, I'm sure a reduction to 3 readings over a sensible period of time would serve the same purpose.
 
Thanks for the elapsedMillis/Micros thing. I guess I should read the documentation other than the MIDI section.

It got me thinking about time and frequency calculations inside this kind of loop – which isn’t really made for DSP filtering.

I’ve also since noticed Paul’s ‘To Do’ comment on digital filtering on the MIDI page. I wonder what he is planning.

So I think I should try some actual work on this... and stop hijacking this thread for now. :)
 
Thought this might be of use to some of you struggling w/ jumpy pots/MIDI cc values, etc. The twist is adding/subtracting a small % of the total raw value IN ADDITION to the usual fixed value. I noticed the fixed values just weren't enough at raw > 900, but increasing the fixed values then really screws up the pot 'feel' at low values. Haven't seen this approach before, but it doesn't mean it isn't out there ;-)

You'll need to tune the values, but these work perfectly for me w/ a teensy 2.0, 10k pot, .1uf to ground on the wiper

View attachment 7229

did you connect ground to agnd and the 'voltage' to aref?

I'm struggling with a teensy 3.2 based midi-controller with neopixels, digital ins and analog inputs.
The analog input is unstable, and plugging in an expression pedal distorts the leds (some leds turn blue all of a sudden)
 
In response to oddson and others wanting to filter a potentiometer. From the hardware side of things... to avoid having the potentiometer position affect the low-pass filter response, there needs to be another resistor that defines the filter and acts as a buffer. In short it needs to be much more than the variable resistance that precedes it.
pot_filter.PNG
For this application with a typical potentiometer of 10k, roughly 5x more or around 50k works well enough. I've attached a circuit, and a plot of the time response of a 'noisy' signal added to an increase and decrease of the potentiometer value. The frequency response plot shows that the cutoff frequency Fc remains constant at ~700Hz. (The four lines are for different values of the potentiometer.) You can see that large noise pulses with sharp edges are knocked down pretty well without noticeably delaying the response.

filtered_out_time_response.PNG
filtered_output.PNG
 
Last edited:
@David Smith thankyou for this. My experience is that there is only so much you can do in software without introducing lag or reducing resolution (although I haven't tried a proper digital filter yet for pots). I have come to the (personal) conclusion that a good hardware solution should probably be the starting point ...
 
Last edited:
@David Smith thankyou for this. My experience is that there is only so much you can do in software without introducing lag or reducing resolution (although I haven't tried a proper digital filter yet for pots). I have come to the (personal) conclusion that a good hardware solution should probably be the starting point ...

I kind of semi-agree. If the hardware solution tackles the source of the problem and inhibits the noise at its point of origin, yes, I'd favor the hardware solution. If the idea is to remove noise from the signal I'd prefer a software solution, because it is much more versatile and opens up possibilities such as adaptive and nonlinear filtering and per-input calibration of filter parameters.
 
Hardware debounce often reduces but doesn't eliminate the false detections.
There are several good ways to do this in software (firmware).
My favorite is:
Most MCU apps have a recurring timer interrupt, say 100 or 1,000 Hz.
In the ISR for that, you read the switch bit(s) from GPIO. If a switch bit is true: increment a one byte counter (but not past 255). IF bit is false, decrement if not 0.
If count is > x, switch is pressed. If less than y, switch is released.
 
David,
Thanks for this detailed example. I'm just learning LTSpice and this showed me many techniques I hadn't realized.
 
@stevech there are lots of killer good ways to debounce switches .... I use a similar method to the one you describe, using a very fast timer .... I have 4 bits, I shift them left one bit, read in the current GPIO bit. Then I check if the 4 bits = 0000 or = 1111. Depending on the previous state and active low/high, the check will give me the debounced leading edge, or the debounced trailing edge.

Analog pots are a bit more difficult, I find.
 
In response to oddson and others wanting to filter a potentiometer. From the hardware side of things... to avoid having the potentiometer position affect the low-pass filter response, there needs to be another resistor ...
Thank you... that was my point... There are a lot of places on the web that suggest putting a cap between the wiper and ground... I think it's guitarists used to tone controls. :p
 
@David Smith thankyou for this. My experience is that there is only so much you can do in software without introducing lag or reducing resolution (although I haven't tried a proper digital filter yet for pots). I have come to the (personal) conclusion that a good hardware solution should probably be the starting point ...
Hmm... I believe the lag is theoretically identical. I'm leaning towards software filtering (if/when needed) but I have a way to go before I can properly code a DSP loop for Teensy.

EDIT - It occurs to me this theoretical equivalence only applies to the appropriate IIR filter but I don't think it alters my point and may even favor post-acquisition filtering.
 
Last edited:
Sorry if this was already said, I just want to share the way I do it. I don't smooth values, so there's no lag. What I do is set a timer that every time you move the pot the timer starts and you read everything for X milliseconds. After the timer reaches X milliseconds it stops reading. So all the jitter just happens when you are moving the pot, this way you hardly can notice that there's a jitter.

Code:
/////////////////////////////////////////////
// POTENTIOMETERS
void potentiometers() {
  
    for (int i = 0; i < NPots; i++) { // Loops through all the potentiometers
    
    potCState[i] = analogRead(potPin[i]); // Reads the pot and stores it in the potCState variable
    midiCState[i] = map(potCState[i], 0, 1023, 0, 127); // Maps the reading of the potCState to a value usable in midi


    potVar = abs(potCState[i] - potPState[i]); // Calculates the absolute value between the difference between the current and previous state of the pot

    if (potVar > varThreshold) { // Opens the gate if the potentiometer variation is greater than the threshold
      PTime[i] = millis(); // Stores the previous time
    }

    timer[i] = millis() - PTime[i]; // Resets the timer 11000 - 11000 = 0ms

    if (timer[i] < TIMEOUT) { // If the timer is less than the maximum allowed time it means that the potentiometer is still moving
      potMoving = true;
    }
    else {
      potMoving = false;
    }

    if (potMoving == true) { // If the potentiometer is still moving, send the change control
      if (midiPState[i] != midiCState[i]) {
        //Serial.println(midiCState);
        MIDI.sendControlChange(cc+i, midiCState[i], midiCh);
        potPState[i] = potCState[i]; // Stores the current reading of the potentiometer to compare with the next
        midiPState[i] = midiCState[i];
      }
    }
  }
  
}

Here's the whole code for the midi controller (not using Teensy in this one):

Code:
#include <MIDI.h>

MIDI_CREATE_DEFAULT_INSTANCE();

/////////////////////////////////////////////
// buttons
const int NButtons = 12; //*
const int buttonPin[NButtons] = {10, 11, 12, 13, 6, 7, 8, 9, 2, 3, 4, 5};     //* the number of the pushbutton pins in the desired not order
int buttonCState[NButtons] = {0};         // stores the button current value
int buttonPState[NButtons] = {0};        // stores the button previous value
byte pin13index = 3; // put the index of the pin 13 in the buttonPin[] if you are using it, if not, comment lines 68-70

/////////////////////////////////////////////
// debounce
unsigned long lastDebounceTime[NButtons] = {0};  // the last time the output pin was toggled
unsigned long debounceDelay = 5;    //* the debounce time; increase if the output flickers

/////////////////////////////////////////////
// potentiometers

const int NPots = 6; //*
int potPin[NPots] = {A0,A1,A2,A3,A4,A5}; //* Pin where the potentiometer is
int potCState[NPots] = {0}; // Current state of the pot
int potPState[NPots] = {0}; // Previous state of the pot
int potVar = 0; // Difference between the current and previous state of the pot

int midiCState[NPots] = {0}; // Current state of the midi value
int midiPState[NPots] = {0}; // Previous state of the midi value

int TIMEOUT = 300; //* Amount of time the potentiometer will be read after it exceeds the varThreshold
int varThreshold = 6; //* Threshold for the potentiometer signal variation
boolean potMoving = true; // If the potentiometer is moving
unsigned long PTime[NPots] = {0}; // Previously stored time
unsigned long timer[NPots] = {0}; // Stores the time that has elapsed since the timer was reset

/////////////////////////////////////////////

byte midiCh = 1; //* MIDI channel to be used
byte note = 36; //* Lowest note to be used
byte cc = 1; //* Lowest MIDI CC to be used

void setup() {

  Serial.begin(115200);

    for (int i = 0; i < NButtons; i++) {
    pinMode(buttonPin[i], INPUT_PULLUP);
  }
  pinMode(buttonPin[3], INPUT); //pin 13

}

void loop() {

buttons();
potentiometers();

}

/////////////////////////////////////////////
// BUTTONS
void buttons() {

    for (int i = 0; i < NButtons; i++) {

    buttonCState[i] = digitalRead(buttonPin[i]);

    // Comment this if you are not using pin 13...
    if(i == pin13index) {
      buttonCState[i] = !buttonCState[i]; //inverts pin 13 because it has a pull down resistor instead of a pull up
    }
    // ...until here

    if ((millis() - lastDebounceTime[i]) > debounceDelay) {

      if (buttonPState[i] != buttonCState[i]) {
        lastDebounceTime[i] = millis();

        if (buttonCState[i] == LOW) {
          MIDI.sendNoteOn(note + i, 127, midiCh);
          //          Serial.print("button on  >> ");
          //          Serial.println(i);
        }
        else {
          MIDI.sendNoteOn(note + i, 0, midiCh);
          //          Serial.print("button off >> ");
          //          Serial.println(i);
        }
        buttonPState[i] = buttonCState[i];
      }
    }
  }
}

/////////////////////////////////////////////
// POTENTIOMETERS
void potentiometers() {
  
    for (int i = 0; i < NPots; i++) { // Loops through all the potentiometers
    
    potCState[i] = analogRead(potPin[i]); // Reads the pot and stores it in the potCState variable
    midiCState[i] = map(potCState[i], 0, 1023, 0, 127); // Maps the reading of the potCState to a value usable in midi


    potVar = abs(potCState[i] - potPState[i]); // Calculates the absolute value between the difference between the current and previous state of the pot

    if (potVar > varThreshold) { // Opens the gate if the potentiometer variation is greater than the threshold
      PTime[i] = millis(); // Stores the previous time
    }

    timer[i] = millis() - PTime[i]; // Resets the timer 11000 - 11000 = 0ms

    if (timer[i] < TIMEOUT) { // If the timer is less than the maximum allowed time it means that the potentiometer is still moving
      potMoving = true;
    }
    else {
      potMoving = false;
    }

    if (potMoving == true) { // If the potentiometer is still moving, send the change control
      if (midiPState[i] != midiCState[i]) {
        //Serial.println(midiCState);
        MIDI.sendControlChange(cc+i, midiCState[i], midiCh);
        potPState[i] = potCState[i]; // Stores the current reading of the potentiometer to compare with the next
        midiPState[i] = midiCState[i];
      }
    }
  }
  
}
 
Back
Top