Yet another switch debounce problem ...

Thread Starter

cmartinez

Joined Jan 17, 2007
7,763
What makes my question different from the average question, is that the intended application is for reading what's proved to be a rather noisy mechanical encoder, and not a manual switch. In fact, said encoder is comprised by a couple of standard magnetic reed switches. And no, I cannot change said component and adapting to its behavior is a must within the scope of my project. The reeds in question can work at up to 200 Hz and switch a 3.3V signal. In reality, I doubt they'll ever reach a frequency of 100 Hz, but I'm just being protective here.

As an aside, I found this very immersive (and entertaining) series of articles written by Max Maxfield to be quite enlightening in what proved to be a far more sophisticated subject than I at first thought:


At this point in time, I've decided that a hardware solution would be best for my application. This because my project is based on the PIC16LF1825 Microchip MCU, which has interrupt-on-change pin monitoring capabilities. And since the only debounce algorithm that I know of works by monitoring the historical values of the state of the switch, said algorithm would require a polling technique rather than an interrupt driven one. And my project's architecture relies heavily on monitoring the encoder's state through interrupts, not polling. Of course, I could be very wrong in my assessment and therefore I invite anyone with greater knowledge than me to please explain an alternate technique to properly implement an interrupt-on-change driven debounce algorithm.

Anyway, among the hardware options, I found:
  1. Custom dedicated logic gate driven circuits
  2. RC (and a diode) filters
  3. Specialized IC's

Options #1 and #3 usually require sourcing additional power for that specific application. And since my project is battery-powered, I'm trying to avoid said scenario. Of course, I found circuits out there that draw no more than 20 or 40 µA and look rather tempting. So I'm keeping an open mind in this regard.
Option #3 is probably the worst of all, because all of the chips I found have a fixed 20ms or more sampling period in them, and my circuit has to work at a frequency of up to 200 Hz (5 ms period)

That leaves me with using the primitive, but perhaps most effective RC filter. Except that I'm going to have to be very careful with its components' values. My main concern is that the reed switch is placed at an up to 5m distance from the main circuit, and I'd like to feed it a 3.3V signal, so inductive kickback and voltage drop could be a problem

And finally just so you know all there is to know about my circuit, I could use a 12V signal for the reed switch if I wanted to (that's the system's main battery voltage). And then connect it to my MCU using a voltage divider plus the RC filter. But the restriction would be that no more than a few µA should be drawn in this scenario, and I don't know if there's a significant advantage over using 3.3V instead. Also, this is not for an industrial application, so ambient EMI noise would be rather small.

This is the circuit that I'm currently considering:

1659017762144.png


My MCU already has Schmitt-trigger buffer at its input, so there's no need to add the component shown in the above image. And also, the MCU already has in internal pull-up resistor that can be enabled/disabled on demand, except that it is placed at the gate's input, and not at the switch's output as shown.
 

olphart

Joined Sep 22, 2012
113
Just an idea... reeds typically have bounce on make, but it settles out. IF the settle out span is <10mS (100Hz), each Int could look at a timer / counter to ignore further "makes" inside of that time. Knowing max pulse rate would be the upper bound for the span. Good Hunting <<<)))
 

Thread Starter

cmartinez

Joined Jan 17, 2007
7,763
Are you measuring speed and direction, or just speed?
I'm counting events. And yes, prior to olphart's suggestion it occurred to me that maybe I rejected the software solution too early... I understand now that the key lies in checking for stability inside the interrupt routine. I'm going to give some thought to the technique and get back here with a description and some code, for future reader's sake.
 

Ian0

Joined Aug 7, 2020
5,819
I'm counting events. And yes, prior to olphart's suggestion it occurred to me that maybe I rejected the software solution too early... I understand now that the key lies in checking for stability inside the interrupt routine. I'm going to give some thought to the technique and get back here with a description and some code, for future reader's sake.
I’ll have to think about this a bit more, but I wonder if there is a solution involving both switches and a set-reset latch to give a single output.
 

Thread Starter

cmartinez

Joined Jan 17, 2007
7,763
Ok, here's the code of what I intend to do. I haven't tested it yet, but the general idea is to use an Xor instruction to compare the current reading vs the previous one. If the readings were the same, the result will always be zero, if they were different then the result is non-zero. And when the latter happens, the sampling counter is reset back to its original value (in this case, 4) so as to start sampling anew. It is only when four identical readings in a row are made that the pin's state is considered to be stable and the sampling loop is exited.

Keep in mind that this code is running within the interrupt servicing routine.

Code:
          ;------------------------- START OF SWITCH DEBOUNCING CODE -------------------------
          
          ;at 32.768 kHz each instruction takes 0.0001220703125 to execute, so if
          ;we want to test for a 3ms stability, we need a cycle of at least 25 instructions
          ;while watching that pins ENC_A_PIN and ENC_B_PIN of PORTA have become stable
           
          ;first, seed ENC_SAMP_A with the current reading
          MovLf low  ENC_SAMP_A, FSR0L ;ENC_SAMP_A and ENC_SAMP_B lie in a different bank from
          MovLf high ENC_SAMP_A, FSR0H ;bank 0, that's why we're using an indirect pointer
          
          banksel PORTA                ;bank 0
          movf PORTA, w                ;load w with PORTA's current state
          andlw nENCODER_FILTER        ;filter only the two pertinent bits
          movwi 0[FSR0]                ;store the filtered reading in ENC_SAMP_A

          ;in the following loop, FSR1L will be used as a downcounter because its original value
          ;will be restored immediately after exiting this interrupt servicing routine, this because
          ;of the automatic context saving feature of the PIC16LF1825
          call Load_FSR1L_Downcounter  ;specify the number of equal samples in a row necessary to
Stability_Loop:                         ;qualify as a stable reading
            movf PORTA, w              ;load w with PORTA's current state
            andlw nENCODER_FILTER      ;filter only the two pertinent bits
            movwi 1[FSR0]              ;store the filtered value in ENC_SAMP_B (FSR0 is NOT incremented)
            xorwf INDF0, w             ;if this reading is different from the previous one, w will
            btfss STATUS, Z            ;result in a non-zero value
             call Load_FSR1L_Downcounter ;reset the downcounter if the two readings didn't match
            moviw 1[FSR0]              ;copy the current sample (ENC_SAMP_B) into
            movwi 0[FSR0]              ;ENC_SAMP_A for the next iteration
           decfsz FSR1L, f             ;decrement the downcounter
          goto Stability_Loop

          moviw 0[FSR0]                ;load w with the stable reading to continue with the analysis
          
          ;--------------------------- END OF SWITCH DEBOUNCE LOGIC --------------------------
 
Last edited:

boostbuck

Joined Oct 5, 2017
222
Your routine appears to contain an error for fast-operating switches. It will work just fine for new switches, but as they age and the bounce lengthens, your routine will lengthen to compensate. The error occurs if the bounce period exceeds acceptable limits and the code crosses a timing boundary with the switch transition report.

This may not be a problem with reed switches - I'm not so familiar with those.

I have taken an upper limit for acceptable bounce time and if transitions continue the switch is flagged as a problem for replacement before it interferes with operation.
 

Thread Starter

cmartinez

Joined Jan 17, 2007
7,763
Many thanks for taking the time of going through my code. Yes, I had considered what you've just mentioned. On the one hand, it's practically impossible for the routine to get locked in an infinite loop because the switch would have to be caught in a state of permanent chatter, which is not physically possible in my system. But on the other side, it's also important to draw a limit as of how long the switch is allowed to bounce before it becomes a malfunction that needs to be replaced. I guess that can be fixed by adding a secondary counter that draws that limit and flags the problem when it's encountered.

Also, I was more or less smugly proud of my "clever" use of an xor instruction to compare and find differences between the switch's states when I realized that a simple subtraction would have done the same ... oh well...
 

boostbuck

Joined Oct 5, 2017
222
thanks ... it's kind of sad for me to see that there are almost no routines out there written in assembly
You can at least pick the bones out of the samples.

I have very strong opinions about languages and the design of them, and I have always enjoyed assembler for it's coupling to the mechanics of the processor. But you can't fight the world and expect to win. C is it, so get used to it.
 

Ian0

Joined Aug 7, 2020
5,819
You can at least pick the bones out of the samples.

I have very strong opinions about languages and the design of them, and I have always enjoyed assembler for it's coupling to the mechanics of the processor. But you can't fight the world and expect to win. C is it, so get used to it.
C is great if you are in the business of making and selling microcontrollers. It requires the customer to buy one with faster speed and larger memory - result: greater revenue. It gives a competitive edge to those who can write assembler, as they can get the job done more cheaply.
 

Thread Starter

cmartinez

Joined Jan 17, 2007
7,763
It worked! ... my filtering worked beautifully and now I can perform the next step and take over the world...

1659102083445.png

Anyway, I also added a small routine to test if no change had happened and therefore what caused the interrupt was a glitch or some quick transient. And that improved things quite noticeably.

I've updated the code in my previous post to correct for a couple of syntax and misplacement errors.
 
Last edited:
Top