faster, leaner input polling using PCINT0 flag

I have the urge to redesign my USB foot switch keyboard, toekeys. Eight switches turned out to be too many for me so I’m reducing to six with only 4 in frequent use. Now I’m back to thinking about debouncing switches without lag. My switches are SPDT and with my new reduced switch count I can connect both sides of every switch to an IO pin on the Arduino Micro. Not that there was really anything wrong with my previous strobed array design. I just have the urge…

Long story short I have settled on what may be a novel combination of SPDT switches and interrupt flags for very fast switch response with relatively low processing overheads. I figured it might be worth sharing.

The advantages as I see them:

  • No lockout or delay required for debouncing thanks to the break-before-make nature of SPDT switches. Nothing novel there.
  • No interrupts and no delay()s, in the switch handling code anyway.
  • No polling of IO pins unless a change is detected by the interrupt hardware. Could be a nice improvement in available cycles for other tasks. This is where it started getting interesting for me.
  • The interrupt is flagged by the very first make contact and subsequent bounces are made irrelevant by dynamically changing which side of the switch is monitored. This I haven’t seen done before. The result is the fastest possible time between key press and resulting action short of maybe completing the resulting action inside an interrupt handler.
  • The individual switch states are assessed by checking the break side, which is never bouncing immediately after a make.
/*  unbounce 02 
 *  button array input using SPDT switches and PCINT0 flag
 *  ** not using interrupts, just the flag
 *  for arduino micro (atmega32U4)
 *  
 *  switch 0       NO -> PB0     switch 1       NO -> PB1
 *           GND--\                       GND--\
 *                 NC -> PB4                    NC -> PB5
 *
 *
 *  switch 2       NO -> PB2     switch 3       NO -> PB3
 *           GND--\                       GND--\
 *                 NC -> PB6                    NC -> PB7
 *  
 *  note each switch should be wired with one side on the high nibble
 *  and the other side on the matching bit in the low nibble
 */


void setup() {
  DDRB = 0xFF;  // using internal pullups
  delayMicroseconds(50);  // how long should I wait for pullups to do their thing?
  // begin initializing PCINT0 mask
  int notReady = 1;
  do {
    byte input = PINB;
    if (input ^ nibbleSwap(input) == 0xFF){  // check for a valid state from all switches
      // configure PCINT0 to watch only for switch make (LOW) events based on current switch positions
      PCMSK0 = input;
      notReady = 0;
    }
  }  while (notReady);
}

void loop() {
  int actionQueue = 0;
  // PCIFR is only set by switch make events
  if (PCIFR) {
    byte switchStatus = processSwitchArray();
    processBitwiseSettings(switchStatus);
    addToActionQueue();
    }
  if (actionQueue){
    processActionQueue();
  }
}

byte processSwitchArray() {
  // record changes on break side, which is not bouncing if make just happened
  byte chng = ~PCMSK0 & PINB; 
  // attempt to resolve overlapping bounce and simultaneous event scenarios
  if (bitsSetCount(chng) > 1) {
    chng &= nibbleSwap(PCMSK0 & ~PINB);
    // chng now only flags the switch(es) with with both make and break verified
  }
  // update the make side change record to match the break side reading
  chng |= nibbleSwap(chng);
  // flip make and break sides - set interrupt to flag a change on opposite make
  PCMSK0 ^= chng; 
  if (chng) {
    // if the make event couldn't be confirmed, leave the interrupt flag up and try again next loop
    // otherwise clear the interrupt flag
    PCIFR = 0x01;    // write 1 to clear flag (yeah I checked the datasheet twice on that one)
  }
  // return combined change record and current switch status (1 == "on")
  return (chng & 0xF0 | ~(PCMSK0 & 0x0F)); 
}

void processBitwiseSettings(byte stat) {
  // nothing here yet
  // will contain bitwise transforms to apply per switch settings
  // polarity, toggle, level or edge mode
}

void addToActionQueue(){
  //nothing here yet  
}

void processActionQueue() {
  //nothing here yet   
}

inline byte nibbleSwap(byte b) {
  return (b >> 4 | b << 4);
}

inline byte bitsSetCount(byte v) {
  byte c;
  for (c = 0; v; c++) {
  v &= v - 1;
  }
  return (c);
}

I only dabble and expect I have probably overlooked some things so please feel free to point out errors or ask questions.

Update: My current best effort at a simple-as-possible demonstration:

// unbounce04 interrupt flag polling demonstration
// using 1 SPDT switch and an Arduino Micro

void setup() {
  pinMode(13, OUTPUT);      // demonstration LED
  pinMode(8, INPUT_PULLUP); // normally open contact of switch
  pinMode(9, INPUT_PULLUP); // normally closed contact
  PCMSK0 = 0x10; // initialize interrupt mask
}                // to match resting state of the switch pins

void loop() {
  ////// these 3 lines are the core of this demonstration //////
  if (PCIFR) { //only branch if an input change was sensed
    PCMSK0 ^= 0x30; // toggle the interrupt mask
    PCIFR = 0x01;   // reset the interrupt flag
    // end of essential switch processing

    /* The code inside this branch runs once 
       whenever the switch changes from off to on
       or vice versa.

       PCMSK0 bit 5 is the current on state.
       This value is accessible globally.          */
       
    byte switchState = PCMSK0>>5;  // optionally make a nice
                                   // boolean state variable
                                   
    digitalWrite(13, switchState); // example controlling an LED
    
    if (PCMSK0>>5) {
      // do this stuff once when the switch is switched on   
    }
    else {
      // do this stuff once when the switch is switched off
    }
  }

  if (PCMSK0>>5) {
    // do this stuff every loop whilever the switch is on
  }
}

Congratulations, you have invented an RS flip flop in software :slight_smile:

Thank you! Your sarcasm is sooo appreciated :slight_smile:

But actually you wouldn't debounce with an RS flip-flop since the clock would partially defeat the beauty of an RS debounce. You can't truly duplicate an RS latch debounce in software due to its inherently asynchronous nature. You can of course closely emulate its behaviour by polling both sides of the switch frequently and doing a little bitwise/boolean math each time it's polled. To quote myself from the original post: "Nothing novel there."

The novel part I think is dynamically configuring the (asynchronous) pin change interrupt to only flag switch make events.

This way results in a leaner main loop and faster switch response than a typical "RS flip flop in software". See this:

  if (PCIFR) {

Input polling adds just a single, 1 clock instruction to the main loop unless a switch has actually changed. I could be wrong. Like I said, I dabble.

I have updated the topic title to better reflect my point for posting. Though debouncing is a part of it I'm more interested in discussing dynamic fiddling of interrupt masks and flags.

Interesting ... haven't yet got around to playing with the pin change interrupts, but when if I do, I'll be testing 4 noisy inputs to see what maximum performance could be achieved in testing various pulse frequencies. Hoping to be able to temporarily disable a pin change interrupt to ignore noise until a certain condition is met (elapsed time or other flag). Also considering toggling resistive feedback from another pin to boost hysteresis levels to prevent false interrupt triggering.

You can't truly duplicate an RS latch debounce in software due to its inherently asynchronous nature.

Form C contacts and toggle switches that break-before-make could be read and Set or Reset a status variable cleanly, without any debounce code or external filtering. I would just connect the common to GND, use INPUT_PULLUP for the 2 inputs, then just check for the first HIGH to LOW transition on both inputs. One would Set a status variable high, the other would Reset it low. Multiple transitions on any one input are inherently ignored. Both inputs could never transition at the same time (by design of Form C or SPDT).

matmanj:
Thank you! Your sarcasm is sooo appreciated :slight_smile:

It was not real sarcasm, I gave you a karma point for your creative (ab)use of existing hardware features. I also noticed that you found the algorithm for fast bit counting, which I used myself on the slow microprocessors many decades ago.
Keep on surprising us :slight_smile:

Nonetheless I doubt that the overall performance boost will be really high. When you process 4 switch changes at a time, you still have to decode and branch to the handling code for each switch. But time will not be the real gain, in detail when a mechanical switch can toggle only on a low frequency. OTOH code size reduction is of little importance, when compared to available flash memory on nowadays processors. And last not least every switch occupies 2 digital inputs, which may be missing for other signals.

delayMicroseconds(50); // how long should I wait for pullups to do their thing?

Two clocks. That is the depth of the pin synchronizer.

Unless there is significant capacitance.

You could always enable the pullup then wait for the pin to read high.

Use static instead of inline. Let the compiler decide if inlining is the right choice. It’s good at that sort of thing.

Some good points thank you guys.

Here’s a much simpler demonstration of the concept:

/*  unbounce 03 
 *  fast polling an SPDT switch using INT flags
 *  ** not using interrupts, just the flags
 *  for arduino micro (atmega32U4)
 *  
 *  switch connections:                
 *                 NO -> Arduino pin 2 (PD1 - INT1)
 *           GND--\
 *                 NC -> Arduino pin 3 (PD0 - INT0)
 */

void setup() {
  pinMode(2, INPUT_PULLUP);
  pinMode(3, INPUT_PULLUP);
  EICRA |= 0x0A; 
}

void loop() {
  if (EIFR & 0x03) {
    byte switchStatus = EIFR & 0x02;
    EICRA = EICRA & 0xF0 | 0x02 << switchStatus;
    // do stuff that happens in response to the switch  
  }
  // do loop stuff here regardless of switch state
}

I think a similar setup could be applied to an SPST switch with debouncing done lockout style using a similar routine with a timer interrupt.

The main advantages I’m looking at here are less loop overheads and no delays caused if the contact happens to be bouncing when it’s polled.

matmanj:
You can't truly duplicate an RS latch debounce in software due to its inherently asynchronous nature. You can of course closely emulate its behaviour by polling both sides of the switch frequently and doing a little bitwise/boolean math each time it's polled. To quote myself from the original post: "Nothing novel there."

Bull. What part of the RS logic depends on it being synchronous? None. At the very worst, the only possible change in behaviour is an infinitesimal delay.

Bull? Is anything you quoted technically incorrect? I suppose it depends on your interpretation of "truly duplicate". I freely concede that a software solution like described by dlloyd performs near enough to make no difference in nearly all applications.

The worst case difference would be the switch makes contact but then repeatedly being in a bounce state every time it's polled. Real life worst case - occasionally it takes a couple of poll loops before the make is detected.

matmanj:
Bull? Is anything you quoted technically incorrect? I suppose it depends on your interpretation of "truly duplicate". I freely concede that a software solution like described by dlloyd performs near enough to make no difference in nearly all applications.

Yes, it is technically incorrect to say that a correct software implementation of an RS debounce circuit functions differently in any way from a hardware implementation, except for the response time.
You said you can't "truly duplicate" it. Well, it's up to you to explain why.

Yes, dlloyd got it right. What he proposes in the final paragraph "truly duplicates" an RS debounced input, except for the response time (it might be a few microseconds slower than a hardware RS on an AVR).

Simplified test code (untested):

bool switchStatus()
{
static bool state;
if (pinR == LOW) state = false;
if (pinS == LOW) state = true;
return state;
}

A good but incomplete example. Most input polling code needs to provide an edge condition, something included in my equivalent polling code:

if (PCIFR) {

A lot of stuff has been written here and I may easily have missed something. Also I have no idea how fast toes can tap switches. But it seems to me all that is necessary is something like

if (millis() - lastReadMillis >= 50) {
   lastReadMillis += 50);
   sw1State = digitalRead(sw1Pin);
   sw2State = digitalRead(sw2Pin);
   etc
}

...R

matmanj:
A good but incomplete example. Most input polling code needs to provide an edge condition, something included in my equivalent polling code:

if (PCIFR) {

No, it is complete and fully functional. What do you mean "edge condition"?

Robin2:
A lot of stuff has been written here and I may easily have missed something. Also I have no idea how fast toes can tap switches. But it seems to me all that is necessary is something like

if (millis() - lastReadMillis >= 50) {

lastReadMillis += 50);
  sw1State = digitalRead(sw1Pin);
  sw2State = digitalRead(sw2Pin);
  etc
}




...R

There is absolutely no reason to use millis() or any other timing here. That's the beauty of it. :slight_smile:

My function differs in only one respect from an actual RS. If both inputs are low on a hardware RS, the state is held at whatever state was set by the last combination of one high and one low. But my function favours one input instead because the tests are performed consecutively.

However, this has no bearing whatsoever on the issue at hand, because the switch contacts are never both low. I only mention this to beat someone to it. :slight_smile:

Edge condition meaning an indication that the switch just changed from one state to another as opposed to a level condition. Usually done by comparing the digitalRead() result with your static state variable. It's not much extra but it is extra.

Is there something wrong with my code? I realize it's different from from the usual input polling methods - that's why I posted. Am I wrong to claim that it has some (arguably insignificant) advantages over other methods?

matmanj:
Edge condition meaning an indication that the switch just changed from one state to another as opposed to a level condition. Usually done by comparing the digitalRead() result with your static state variable. It's not much extra but it is extra.

Is there something wrong with my code? I realize it's different from from the usual input polling methods - that's why I posted. Am I wrong to claim that it has some (arguably insignificant) advantages over other methods?

You might be confusing change of state with debounce. They are different things, that solve different problems.

What is wrong with your code, is that is extremely complicated and long, for the job that it has to do. I can't be bothered reading it. I'm going from your requirements description and circuit diagram and beginning from first principles.

I believe my few lines provides exactly the same functionality as yours, but is at least an order of magnitude faster and incalculably easier to understand.

Note that it is not intended to provide state change detection (which would however, be extremely easy to implement). It shouldn't be part of this discussion because it is separate from the task of debouncing. Or at least can be.

To make it driven by pin change interrupts is just a change in the implementation, but I think it would follow similar logic, and would not be as complicated as what you have done. It is getting late here, but I will attempt to illustrate that also, when I have time.

A quick and incomplete outline using external interrupts:

volatile bool state;

bool switchStatus()
{
  bool st;
  nointerrupts();
  st = state;
  interrupts();
  return st;
}

void pinR_ISR
{
state = false;
}

void pinS_ISR
{
state = true;
}

I left out the attachInterrupts...configured for FALLING

By the way, I don't see at this time what is so evil about polling a switch, in the context of an app. So I'm not at all convinced that interrupts are necessary.