1. 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.
    Dismiss Notice

Dedicated PIC Debouncer

Discussion in 'Microcontrollers' started by Mogs, Aug 19, 2017.

  1. Mogs

    Mogs New Member

    Joined:
    Aug 19, 2017
    Messages:
    13
    Likes:
    0
    Location:
    whidbey island
    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.
     
  2. jpanhalt

    jpanhalt Well-Known Member Most Helpful Member

    Joined:
    Jun 21, 2006
    Messages:
    5,886
    Likes:
    500
    Location:
    Cleveland, OH, USA
    ONLINE
    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 (https://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: Aug 19, 2017
    • Like Like x 1
  3. Mogs

    Mogs New Member

    Joined:
    Aug 19, 2017
    Messages:
    13
    Likes:
    0
    Location:
    whidbey island
    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: Aug 19, 2017
  4. dave

    Dave New Member

    Joined:
    Jan 12, 1997
    Messages:
    -
    Likes:
    0


     
  5. jpanhalt

    jpanhalt Well-Known Member Most Helpful Member

    Joined:
    Jun 21, 2006
    Messages:
    5,886
    Likes:
    500
    Location:
    Cleveland, OH, USA
    ONLINE

    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
     
  6. Mogs

    Mogs New Member

    Joined:
    Aug 19, 2017
    Messages:
    13
    Likes:
    0
    Location:
    whidbey island
    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: Aug 19, 2017
  7. jpanhalt

    jpanhalt Well-Known Member Most Helpful Member

    Joined:
    Jun 21, 2006
    Messages:
    5,886
    Likes:
    500
    Location:
    Cleveland, OH, USA
    ONLINE
    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.
     
  8. Mogs

    Mogs New Member

    Joined:
    Aug 19, 2017
    Messages:
    13
    Likes:
    0
    Location:
    whidbey island
    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: Aug 19, 2017
  9. Mike - K8LH

    Mike - K8LH Well-Known Member

    Joined:
    Jan 22, 2005
    Messages:
    3,637
    Likes:
    109
    Location:
    Michigan, USA
    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
     
  10. Pommie

    Pommie Well-Known Member Most Helpful Member

    Joined:
    Mar 18, 2005
    Messages:
    10,008
    Likes:
    316
    Location:
    Brisbane Australia
    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.
     
  11. Nigel Goodwin

    Nigel Goodwin Super Moderator Most Helpful Member

    Joined:
    Nov 17, 2003
    Messages:
    39,204
    Likes:
    640
    Location:
    Derbyshire, UK
    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.
     
  12. Mogs

    Mogs New Member

    Joined:
    Aug 19, 2017
    Messages:
    13
    Likes:
    0
    Location:
    whidbey island
    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.
     
  13. Mogs

    Mogs New Member

    Joined:
    Aug 19, 2017
    Messages:
    13
    Likes:
    0
    Location:
    whidbey island
    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.
     
  14. JonSea

    JonSea Well-Known Member

    Joined:
    Oct 1, 2012
    Messages:
    1,118
    Likes:
    90
    Location:
    Seattle, WA
    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.
     
  15. Mogs

    Mogs New Member

    Joined:
    Aug 19, 2017
    Messages:
    13
    Likes:
    0
    Location:
    whidbey island
    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: Aug 22, 2017
  16. Mogs

    Mogs New Member

    Joined:
    Aug 19, 2017
    Messages:
    13
    Likes:
    0
    Location:
    whidbey island
    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: Aug 28, 2017
  17. KeepItSimpleStupid

    KeepItSimpleStupid Well-Known Member Most Helpful Member

    Joined:
    Oct 30, 2010
    Messages:
    9,908
    Likes:
    1,094
    ONLINE
  18. Mogs

    Mogs New Member

    Joined:
    Aug 19, 2017
    Messages:
    13
    Likes:
    0
    Location:
    whidbey island
    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.
     
  19. KeepItSimpleStupid

    KeepItSimpleStupid Well-Known Member Most Helpful Member

    Joined:
    Oct 30, 2010
    Messages:
    9,908
    Likes:
    1,094
    ONLINE
    USDigital has rotary encoder chips.
     
  20. Mogs

    Mogs New Member

    Joined:
    Aug 19, 2017
    Messages:
    13
    Likes:
    0
    Location:
    whidbey island
    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-

    Code (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: Aug 27, 2017
  21. KeepItSimpleStupid

    KeepItSimpleStupid Well-Known Member Most Helpful Member

    Joined:
    Oct 30, 2010
    Messages:
    9,908
    Likes:
    1,094
    ONLINE
    Edit your post to include CODE tags: [code=c][/code]

    e.g.
    Code (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]
    }
     
     

Share This Page