Continue to Site

Welcome to our site!

Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

  • Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

Dedicated PIC Debouncer

Status
Not open for further replies.

Mogs

New Member
As a hobby only, I'm developing a sous vide (hot water bath) controller with an ATMega328p (yep, Arduino) micro-controller, that uses a rotary encoder for user input. Debouncing an encoder is very finicky to say the least, since it needs to be solid, but quick enough to avoid being over-run by a quick turn of the knob. Since, in my case, the 328p already has its hands full with many other time critical tasks, it would be very nice to off-load this particular chore to hardware. My idea is that a PIC 16F628A @ < $1, would be easier and even cheaper than using the typical hex Schmitt trigger and 18 resistors and capacitors that would only provide six channels of hardware debounce.

I can envision using port A of the 16F628A for up to 8 bits of Schmitt triggered input, and port B for up to 8 bits of debounced output and, because of its internal oscillator, wouldn't require any other discrete components. The debounced outputs on port B would then be available to the 328p for either polled, or interrupt input at its discretion.

Here's what I would like to implement-

The initialization process of the 16F628 would setup a one byte memory register for port B output bits. It would be initialized by reading port A into the accumulator, and writing that to the port B memory register, as well as writing that value to port B, itself. Eight more two-byte memory registers would also be setup, each acting as a 16-bit shift register for each of the eight bits corresponding to the input pins of port A.

The main loop would then start with another read of port A into the accumulator.

Bit 0 of the accumulator would be bit rotated right, into the carry bit. That carry bit would be right bit rotated into the Most Significant bit of the first byte of the 'Bit 0 serial register'. Since the carry bit now contains the LSbit of the first byte, it is shifted into the second byte with a right bit rotation of that second byte. The entire register is then checked for identical bits (all ones or all zeros), and if so, the corresponding Port B output register bit 0 is updated.

Next, the accumulator would be bit rotated right, into the carry bit again [corresponding to the second bit of port A this time]. Then that carry bit would be bit rotated into the MSbit of the first byte of the 'Bit 1 serial register'. Since the carry bit now contains the LSbit of the first byte, it is shifted into the second byte with a right bit rotation of that second byte. The entire register is then checked for identical bits (all ones or all zeros), and if so, the corresponding Port B output register bit 1 is updated.

… repeated for the corresponding next six bits ...

And finally, port B itself is updated with the port B output register byte, and the program then jumps back to the start of the main loop.

This effectively shuffles all bounce spikes along through their 'shift' registers, but does not change the actual port B output bit until that bit's entire two-byte memory register agrees that the input bit is currently solid, hasn't changed for awhile, and that it is opposite to its previous state.

Since the data sheet says that all 35 of the 14-bit instructions execute in a single-cycle of 200 ns @ 20 MHz, except for branches, then I figure that at 4 MHz, this approximately 40 instruction main loop should only take roughly 50 uSecs per iteration, or just barely a millisecond to complete a full 16 bit shift register cycle. A third byte could be added to each shift register to stretch this routine into a handful of milliseconds. Masking some of the shift register bits would, of course, allow any specific length of register to be used, and a sprinkling of NOPs could be used to fine tune the routine timing.

If I am off the beaten track, here, would appreciate a nudge, and any input on design or implementation considerations. I'm just here to learn new stuff, not optimize a production line.
 
Well, you may not need to debounce. But if you do, there are dedicated chips for doing that . Usual delay is 40 ms. Here's a link to an often cited study on debouncing: http://www.ganssle.com/debouncing.pdf I recently did a PCB using the MC14490 to debounce 6 switches (http://www.onsemi.com/pub/Collateral/MC14490-D.PDF ). If I were to do it again, I would use a dedicated MCU that was cheaper.

Second, you can do it in software. Many people use 10 ms from a change in state to checking and being sure that is a permanent state. When I have an interrupt that is relatively long and called by push button switches, I use the interrupt flags as a "debounced" state rather than reading the port.

John
 
Last edited:
Thanks for your input, jpanhalt. I explained why I wanted to do it in hardware, above. Dedicated debouncers are fairly difficult to obtain and are very expensive, starting at 4 or 5 bucks in singles. And, doing it with Schmitt triggers and discretes ain't elegant, grin.

Rotary encoders are particularly noisier than push buttons, due to their sliding nature, and you just can't wait around for tens of milliseconds, or you'll over-run the debounce delay with quick user input. I am not that inexperienced with these issues, and I was hoping for a discussion on the advantages in this situation of shift register monitoring in avoiding false positives as well as providing much quicker indications of those transitions than blind and fixed period debounce delays.

PS edit- Yes, that Ganssle article you linked is fantastic. It is a considerable part of the reason that I have veered off on this tangent about twerking a 16F628A into a quick and accurate debouncer.
 
Last edited:
Hi Mogs,

The point I tried to make is that an 8-pin or bigger MCU is cheaper and simpler than using the dedicated chip like I did. The only reasons I used that chip was because I bought it years ago and needed something I could easily plug in. If you are only debouncing a rotary encoder, I might use a 10Fxxx chip and glue it to the encoder for a hobbyist solution.

More recently, I used optical and magnetic rotary encoders without debounce.

John
 
Cool, never messed with magnetic, but I know from experience how clean optical encoders are.

And, that's the idea of a 16F628A... an octal debouncer with no externals chugging away on an endless (but very efficient) assembler loop... for less than a buck!

[...not to be used for pleasure, profit, or with batteries...]
 
Last edited:
Exactly. You might even consider one of the enhanced MCU's (e.g., 16F1xxx) -- just to get familiar with it. There have been threads here with code on how to debounce with them. Maybe one of those authors will chime in. I think the main reason dedicated hardware debouncers are so expensive is that their need has been eclipsed by programmable chips.
 
Thank you, so much, John. A few years ago (okay, okay, maybe more than a few years ago), I developed a very complicated implementation of an 18F2520 controller through one of those old MikroElectronika's development boards, to implement a high-level language (Basic, if you must know, with assembly subroutines) compiled version of an environmental controller. It had four independent timers, controlled by temp, humidity, light level, and CO2 inputs, and four 5 amp 120Vac outputs. [Yes, you guessed it, for pot or mushroom growers.] I still have about 90 pcbs if anyone wants one. The main reason I still have 90 pcbs is that my menu implementation became so complicated, that I couldn't even remember how to navigate it after a week, much less a typical user. One of the problems was that the four tactile pushbuttons were so damn hard to debounce, that after a month of gathering dust, they became very erratic (not to mention myself, somewhat both erratic and dust gathering).

Anyway, my Chinese order of parts for this current project just started to arrive an hour ago (the pt100 temp sensors), so I will soon play with this 16F628A idea of debouncing and get back to you and this forum with, hopefully, some modest experience. I look forward to the possibility that a pushbutton rotary encoder, coupled with a color graphical lcd display might possibly redeem my previous user interface (16 x 2 lcd, 4 button) debacle.

Gary
 
Last edited:
Gary, what type of rotary encoders are you using? Is it the type with detents? Those usually produce all four phase changes between detents as you rotate it and they're relatively easy to debounce.

Regards, Mike
 
There is no need to debounce rotary encoders as each bounce increments and then decrements the counter. If you want to do it in hardware then an up/down counter with one input to clock and the other to up/down will work perfectly (CD4029 comes to mind).

Mike.
 
As a hobby only, I'm developing a sous vide (hot water bath) controller with an ATMega328p (yep, Arduino) micro-controller, that uses a rotary encoder for user input. Debouncing an encoder is very finicky to say the least, since it needs to be solid, but quick enough to avoid being over-run by a quick turn of the knob. Since, in my case, the 328p already has its hands full with many other time critical tasks, it would be very nice to off-load this particular chore to hardware.

I suspect you're overthinking this?, an Arduino should have no problem whatsoever running all the tasks, and connecting a rotary encoder is trivial - and an easy to use library is available.

A hot water bath controller seems a pretty simple project, so I've no idea what 'many other time critical tasks' you might be talking about?, and an interrupt driver encoder routine wouldn't impact them particularly anyway.

I can only imagine you're massively over estimating the processing power required for your application?, which I suspect will only be a tiny percentage of the Arduino's capabilities.
 
Hi Nigel. Thanks for the input. Yes, you are completely right that I am over-thinking this. However, that is my intention.

If I wanted a quick and dirty solution, I actually already have it. When I first stumbled across sous vide, I found an old temp controller that I built years ago, for what purpose I can not now recall, and I simply plugged in an old crock pot into that. It does the job, with the caveat that it has temperature hysteresis of about 4 degrees F, the crock pot has an upper and lower temperature layer differential of about 4 F (due to no active water circulation) and the internal dimensions are only about 7" dia by about 4" high. But it is serving its purpose of demonstrating to me the advantages of water bath cooking.

So, to improve things, I decided to have a larger, insulated bath container, with a much improved temp controller. So I got into platinum RTDs, and the entire self-heating, 2/3/4 wire rigmarole. Several hi-res ADC interface/signal preconditioning chips later, I'm good. I also obtained a solar circulator 12Vdc water pump that is whisper quiet and can stand 100C water temps. I decided that it would be nice to have the interior lights come on automatically in the presence of a user, since an insulated container is obviously dark inside, so I ordered a few varieties of microwave Doppler motion sensors and some illumination LEDs. It would be nice to have access to settings and status in my computer room, so I am implementing a WeMos D1 version of Arduino so that WiFi is available. Water level sensing is important to prevent heating the water when the levels are below either the pump input or minimum water depth for the heater.

The bottom line, Nigel, is that I am not trying to do this sensibly or efficiently. Sous vide is just something that caught my fancy, and gave me a reason to learn new shyt. I am learning Arduino C/C++, 16F628A assembly, microwave doppler technology, platinum thin-film tech, rotary encoder quadrature decoding/debouncing, Pickit debugging, MASM simulation, all at the same time that I am trying to figure out what my wife wants for dinner. I am not concerned that I am over-estimating my quandary.
 
Mike - K8LH and Pommie-
For some reason this forum doesn't allow me to respond to your post. It probably is because I noticed Nigel's post first, and responded to him, and his was most recent... anyway- thank you both so much, and I really mean it. The devices are (2) EC11's [$.50 each] and I will try no debounce, promise.
 
The problem with crock pots for sous vide is the heat elements are on the sides.

Using a turkey fryer with the heating element on the bottom provides excellent results using natural convection. No circulator pump is required.
 
Hi JonSea. Yes, the Sous Vide Supreme bath chamber [or whatever it's called, at $500) does what you are talking about without active circulation, and with the additional advantage of insulation around the bath. I'm sure an electric turkey fryer would have been a much better (and larger) home-brewed solution than my stupid old crock pot.

For me, this started as a quick and dirty sous vide experiment with what parts I had on hand, and has already evolved into a very expensive project. Just now, I received my Arduino order, (2) Unos, a Due, a WeMos1, a plethora of shields, and, of course, the encoders. I could have just thrown a hundred bucks at a rock solid Anova immersion circulator, but I am doing this for the experience, not because of my cheap nature. Appreciate your input. Now... you have me thinking... a bottom heater from an electric turkey fryer... instead of the immersion heaters and pump that I just bought... nah, I'm on a retirement fixed income, grin... I gotta go with what I got now... I think...
 
Last edited:
Just as an aside, I received one of those (10) Pt100 packages widely available on Ebay sites that are displayed as "WZP PT100" a day or two ago. They are worthless. After thorough testing, the ten units showed a +/- 4 ohm variance from 0 to 100 degrees C. That translates to no better than +/- 10 degrees C. Stay away, please. I then carefully filed away the SS tube on one of them and discovered inside a resistor that is jacketed in a fiberglass mesh sheath, stuck into the SS tube with just a daub of epoxy at the open end to waterproof it. It is obviously platinum, because it is roughly 100 ohms at 0 C, and a few ohms below 138 at 100 C. The fact that they all were several ohms below 138.5 at 100 C, though, indicates that they are sub-optimal alloys of platinum. Also, they are sitting there in a mostly air-filled tube, jacketed in fiberglass mesh, and that means that they are very well insulated from the their liquid environment, and exhibit a full minute or more of response time.

Okay, okay, what did I expect for 69 cents each? Well, glad you asked. I thought that they were B Class Pt100s that I could manipulate in software to be much more accurate. No. Not happening, sorry.

Almost any platinum temp sensor can call itself a pt100. This one does. But they are gaming on the fact that PT100s have been long accepted as the defacto standard of temperature sensors. However, true PT100s (or PT1000s for that matter) usually connotes at least an accuracy of +/- .25 degrees C (class B), and they usually have a response of about a quarter of a second in circulated liquid. I have ordered some true Class A (+/- .15 degree) sensors, but I will have to encapsulate them myself, and figure out if the leads are solderable (AgPd). But at least I am able to decide on a 2, 3, or 4 wire interface. And depend on a modicum of accuracy.

WZP (What's Ze Problem?) makes some decent Pt100s, but these particular ones are crap. They are not accurately trimmed thin film sensors (much less wire-wound). They are instead, a cheap platinum resistor stuck in a tube to trade on the good name of a PT100. You can recognize these frauds by the mis-spelling in the description- "resister", and also by the fact that they are 4 x 30 mm. Either a DS18B20 one-wire sensor, or an LM34 (Fahrenheit), or LM35 (Celsius) two wire sensor, make much more sense at a buck or two each than these turds.

Just be assured that anything less than a buck (even from China) is not of dollar-tree quality. But with $2, and a tube from those cheapos, you can roll your own.
 
Last edited:
Hi. Do you mean the Elm 401 encoder debouncer? Yep, but I couldn't find it cheaper than $8.

USdigital would be great if I could pretend to still be a manufacturer, and convince them to send me a sample optical encoder. Naw, not very good at lying.

Thanks, Kiss, appreciate the references.
 
I have had my hands on these cheap mechanical encoders for a few days now, and man, they bounce like a super ball in a marble quarry. No amount of debouncing delays, whether polled or in timed interrupts seemed to calm them down sufficiently. So I started throwing capacitors and resistors at them like the Bourn's datasheet specified, varying the values and arrangements endlessly until the glitches seemed less irritating, but still persisted. Embarrassingly, I don't have any Schmidt triggers on hand to try, and it is difficult for me to acquire electronics in a timely fashion, but I assume (as in my original post), that the hysteresis would completely (or at least mostly) clear up this bouncing problem. And that is also why I originally thought that a cheap PIC would be a low cost, low pin count solution, offering both hysteresis and a dedicated shift register. Without that, however, as you lengthen an encoder debounce delay, whether in software or hardware, you get over-run well before the fixed delay is long enough to properly debounce an encoder. A push button is much easier, since you can wait around for 50 mS or so, which seems virtually instantaneous to most users, and few can poke a button faster than 20 times a second, so you also don't get over run.

Anyway, I found a non-delay software routine that is completely rock solid. I am sure most of you guys know about the quadrature valid transition look up table method, as I have run across it many times in various places on the net. It offers rejection of invalid transitions, while decoding direction and providing a counter to keep track of the absolute value of a variable that you allow a user to modify. I was hoping that the rejection of half of the possible encoder transitions (both invalid and non-change transitions) would help ease my bounce problem. So I tried one of these routines as the only function in the main loop, meaning constant polling- Wow. I mean after I was heavily schooled in the arcane art of C language Arduino, I discovered what a gem this method really is.

It is so solid that no matter what I did, I could not get it to glitch even once. I then jerked all the resistors and caps out of my breadboard and connected the encoder directly to the Arduino pins (with internal pullups, of course). Still rock solid. So then I was thinking, yeah, this is cool in a dedicated polling loop, but what about freeing up the main loop by using the same method in an ISR? After some more heavy damage to my head in the Arduino school of interrupts, I found true bliss. Rock solid debouncing, decoding, and counting all in a pin change interrupt that is so quick that it catches every single bounce and valid transition (a minimum of four valid transitions between each detent), all without using up a single external interrupt. And most of the time, there is no user input, so there isn't even the quick overhead of a pin change interrupt to steal processor time.

If you try the code below, here is a test... Turn the knob pointer straight up. Push the knob to zero the counter, and then watch the serial monitor as you spin it to a value exceeding 500, then spin it rapidly back down to exactly zero. The knob is magically in a perfectly vertical position, again. Even spinning rapidly over 1000 detents (500 up/ 500 down) the code did not miss a single beat. And that means that the code caught all 4000 valid transitions, at 4 transitions per detent, plus every single bounce pulse that occurred during the forty five seconds or so of knob twisting. Now, the button can stand at least a cap across its terminals, but it is used mostly for menu selection and to confirm a new setting, so a fixed bounce delay in software is easy to implement.

I know you are probably laughing at my naivety, Nigel, but I have less confidence than you do in 8 bit micro-controller efficiency. So, let me enjoy this brief respite from the learning curve still looming over me, grin. If anyone is considering code for an encoder, I highly recommend trying this code out-

C:
/*
rotary encoder pin change interrupt service routine
for an Arduino 328p
code based on Oleg Mazurov's
circuits@home article, April 08, 2011
pin change interrupt on Port B, pins D8, D9, and D10
look up table in flash to determine validity/direction
tracking four transitions per detent
*/                                                                                                                                                                                                                          

volatile int16_t counter = 0;  //allows this variable to be changed inside the ISR,
                             //yet be available in the main loop
void setup()
{
  #define ENC_CTL DDRB    //encoder port control
  #define ENC_WR PORTB  //encoder port write
  #define ENC_RD PINB      //encoder port read
  #define ENC_A 0                //encoder channel A (Arduino pin D8, PCINT0)
  #define ENC_B 1                 //endoder channel B (Arduino pin D9, PCINT1)
  #define BUT_1 2                 //encoder button (Arduino pin D10, PCINT2)

  ENC_CTL &= ~(( 1<<ENC_A )|( 1<<ENC_B )|(1<<BUT_1));       //inputs
  ENC_WR  |=  (( 1<<ENC_A )|( 1<<ENC_B )|(1<<BUT_1));         //activate pullups
  PCMSK0  |=  (( 1<<PCINT0 )|( 1<<PCINT1 )|( 1<<PCINT2));      //enable the 3 pins as interrupt sources
  PCICR   |=  ( 1<<PCIE0 );                  // enable pin change interrupts on port b

  Serial.begin(115200);
  Serial.println("Start");
}

void loop()
{
  Serial.println(counter);
  delay(1000);
}

ISR(PCINT0_vect)
{
  static uint8_t old_AB = 3;                         //lookup table index
  static int8_t encval = 0;                             //encoder step value
  static const int8_t enc_states [] PROGMEM =
  {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0};         //encoder lookup table
  old_AB <<=2;                                             //remember previous state
  old_AB |= ( ENC_RD & 0x03 );             //add in the current state
                //this is the heart of the program- use the lookup table to
               //decide whether this transition is valid CW (+1), valid CCW (-1),
               //invalid (+0), or no change (+0)...
  encval += pgm_read_byte(&(enc_states[( old_AB & 0x0f )]));
 
  if( encval > 3 ) {                //if four steps forward-
    counter++;                      //tell main program that the counter incremented
    encval = 0;}                    //reset step value
  else if( encval < -3 ) {     //if four steps backwards-
    counter--;                       //tell main program that the counter decremented
    encval = 0;}                   //reset step value
 
  if (!(digitalRead(10))){   //if encoder button [Arduino pin D10] pressed-
    counter = 0;                   //tell main program that the counter is re-zeroed
    encval = 0;}                   //reset step value
}
 
Last edited:
Edit your post to include CODE tags: [code=c][/code]

e.g.
C:
if (!(digitalRead(10))){ [I]//if encoder button [Arduino pin D10] pressed-[/I]
counter = 0; [I] //tell main program that the counter is re-zeroed[/I]
encval = 0;} [I]//reset step value[/I]
}
 
Status
Not open for further replies.

Latest threads

New Articles From Microcontroller Tips

Back
Top