CCS C Software and Maintenance Offers
FAQFAQ   FAQForum Help   FAQOfficial CCS Support   SearchSearch  RegisterRegister 

ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

CCS does not monitor this forum on a regular basis.

Please do not post bug reports on this forum. Send them to CCS Technical Support

simultaneous button press logic?

 
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion
View previous topic :: View next topic  
Author Message
RoGuE_StreaK



Joined: 02 Feb 2010
Posts: 73

View user's profile Send private message

simultaneous button press logic?
PostPosted: Sat May 17, 2014 5:50 am     Reply with quote

PWCHD v4.120
PIC24FJ64 @ 20MHz crystal
Momentary buttons on B6 and C9, switching to ground
RGB LED on B11,B12,B13

I've got a timer-based debouncing system working, it works OK at it's current timing of 50ms with "deliberate" presses, recognising buttons both individually and together; I need a safety-interlock style "both buttons must be pressed", as well as individual buttons for menu nav etc.
But if I decrease the timing to say 20ms, it has major issues getting into the simultaneous button routine, and if it does ever get into it it can never get back out. Wondering if anyone can give some input on how to get around this in the quicker timings?

Code:
#include <24F_interrupt_pwm.h>
  #include <float.h>
  #include <math.h>
  #include <stdio.h>
  #include <stddef.h>
  #include <stdlib.h>
  #include <stdlibm.h>

volatile int16 timerCount = 0;
volatile int16 msCount = 0;//20ms interrupt count
volatile int1 flagDebounce = 0;
volatile int1 btn1On = 0;
volatile int1 btn2On = 0;
volatile int8 btn1Count = 0;
volatile int8 btn2Count = 0;
volatile int8 btn1RelCount = 0;
volatile int8 btn2RelCount = 0;

typedef enum{READY,FLAGGED,FLAGGEDLONG,SERVICED} BTN_STATE;
BTN_STATE btn1State = READY;
BTN_STATE btn2State = READY;

#INT_TIMER5
void TIMER5_isr(void)
{
   msCount++;
   if(!input(PIN_B6))   {btn1On = 1;}
   else                 {btn1On = 0;}
         
   if(!input(PIN_C9))   {btn2On = 1;}
   else                 {btn2On = 0;}
   flagDebounce = 1;
}

void deBounce(void)
{
   if(btn1On)
   {
      btn1Count <255 ? btn1Count++: null;
      if(btn1Count > 3 && btn1State==READY)
      {
         btn1State = FLAGGED;
      }else if((btn1Count > 40) && (btn1State==FLAGGED || btn1State==SERVICED))
      {
         btn1State = FLAGGEDLONG;
      }
   }else
   {
      btn1Count = 0;
      if(btn1State==SERVICED){
         if(btn1RelCount <10) {btn1RelCount++;}
         else {btn1State = READY;} //reset for next response, after 15 counts
      }
   }
   
   if(btn2On)
   {
      btn2Count <255 ? btn2Count++: null;
      if(btn2Count > 3 && btn2State==READY)
      {
         btn2State = FLAGGED;
      }else if((btn2Count > 40) && (btn2State==FLAGGED || btn2State==SERVICED))
      {
         btn2State = FLAGGEDLONG;
      }
   }else
   {
      btn2Count = 0;
      if(btn2State==SERVICED){
         if(btn2RelCount <10) {btn2RelCount++;}
         else {btn2State = READY;} //reset for next response, after 15 counts
      }
   }
   flagDebounce = 0;//reset
}

void main()
{
   setup_timer5(TMR_INTERNAL | TMR_DIV_BY_8, 62500);//div8 and 62500 equal 50ms interrupt

   set_pullup(true, pin_B6);
   set_pullup(true, pin_C9);
   enable_interrupts(INT_TIMER5);
   //enable_interrupts(GLOBAL);  //throws an error?

   // TODO: USER CODE!!
   while(1){
      if(flagDebounce){   deBounce();}
     
      if(btn1State==FLAGGED && btn2State==FLAGGED) //both pulled low
      {
         output_toggle(PIN_B12); //red
         btn1State = SERVICED;
         btn2State = SERVICED;
         btn1RelCount = 0;
         btn2RelCount = 0;
      }
      else if(btn1State==FLAGGED && btn2State==READY) //only C9
      {
         output_toggle(PIN_B11); //blue
         btn1State = SERVICED;
         btn1RelCount = 0;
      }
      else if(btn1State==READY && btn2State==FLAGGED) //only B6
      {
         output_toggle(PIN_B13); //green
         btn2State = SERVICED;
         btn2RelCount = 0;
      }

   }
}


Basically a button must have the state of READY (released and clear) before it can be recognised as pressed. When pressed, if seen for a few counts then FLAG it. If flagged, routine should perform, then set the state to SERVICED, then when the button is released for a few counts after being serviced it is set to READY.

I believe the issue with the faster timing is that the individual buttons go into their independant routines before the "both" routine is triggered, as one button will always register as having been pressed slightly before the other, so it will be flagged before the other. The slower timing works, but is a bit clunky and has to be very deliberate presses, which is kinda what I'm wanting but not quite to that degree. Anyone see an obvious deficiency? I've tried also letting the "both" routine respond to SERVICED, but that just sends it into a loop with no way out.

Somewhere along the line I'd also like to get a long-press working/available, for entering a settings menu etc.

I thought this might perhaps be an alternate debouncing method to share around, if I can actually get it to work cleanly Idea
Mike Walne



Joined: 19 Feb 2004
Posts: 1785
Location: Boston Spa UK

View user's profile Send private message

PostPosted: Sat May 17, 2014 7:38 am     Reply with quote

First questions:-

Have you determined the physical limitations of your buttons?

When you press both together:-

1) What is the spread in the closure times?
2) How much timing spread do you want to accommodate?

Once you know your switch's properties, you are in a position to decide what can be achieved.

Maybe you need to consider different algorithms before embarking on coding.
Say a routine which handles both buttons together.

Suppose you are polling the buttons every ms.
Create a variable called 'button_state' and a counter for how long buttons have been in same state.
No buttons pressed, button_state = 0.
Button_0 pressed, button_state = 1.
Button_1 pressed, button_state = 2.
Both buttons pressed, button_state = 3.
Simply wait for button_state to be stable to initiate actions.

Mike
temtronic



Joined: 01 Jul 2010
Posts: 9226
Location: Greensville,Ontario

View user's profile Send private message

PostPosted: Sat May 17, 2014 5:34 pm     Reply with quote

Mike's on the right track !!

WHAT kind of 'buttons' are you using ???

Unless you're using something 'hitech' like SS or magreed, you'll have to grab a scope,read the datasheet of the switches, do some tests....current draw( pullup resistor value) will affect 'speed', materials used(gold plated helps...).

Generic...off the shelf stuff is NOT very good quality.

hth
jay
RoGuE_StreaK



Joined: 02 Feb 2010
Posts: 73

View user's profile Send private message

PostPosted: Mon May 19, 2014 4:16 am     Reply with quote

Buttons are ultra-generic cheapy momentaries, I have no scope etc for testing, just going off generic statements around here that almost all buttons debounce within 10-20ms. I should have stated that the individual buttons actually work quite nicely by themselves using the 20ms timing, respond first time every time; it's the simultaneous logic that's the problem.

Flowing in a similar but different direction from Mike's button_state idea, I think the solution lies in reseting / ignoring the individual button counts when it is detected that both buttons are pressed; at the moment one button will always inherently reach a FLAGGED state before the other, I think I need to clear everything when the second button is detected and maybe count with a dedicated btnBothCount etc., with a btnBothState for processing when flagged. Will experiment and see what I come up with.
Mike Walne



Joined: 19 Feb 2004
Posts: 1785
Location: Boston Spa UK

View user's profile Send private message

PostPosted: Mon May 19, 2014 3:26 pm     Reply with quote

RoGuE_StreaK wrote:
Buttons are ultra-generic cheapy momentaries, I have no scope etc for testing, just going off generic statements around here that almost all buttons debounce within 10-20ms. I should have stated that the individual buttons actually work quite nicely by themselves using the 20ms timing, respond first time every time; it's the simultaneous logic that's the problem.

Flowing in a similar but different direction from Mike's button_state idea, I think the solution lies in reseting / ignoring the individual button counts when it is detected that both buttons are pressed; at the moment one button will always inherently reach a FLAGGED state before the other, I think I need to clear everything when the second button is detected and maybe count with a dedicated btnBothCount etc., with a btnBothState for processing when flagged. Will experiment and see what I come up with.

I'm suggesting you keep it simple.
Just count how long button_state has remained unchanged.
When button_state changes you reset the count.
No need to have separate processes for any number of buttons.

Mike
RoGuE_StreaK



Joined: 02 Feb 2010
Posts: 73

View user's profile Send private message

PostPosted: Tue May 27, 2014 3:45 am     Reply with quote

After finally getting around to digesting Mike's advice, here's what I've come up with, which seems to work well on a 20ms timer. I've still kept the READY/FLAGGED/SERVICED system (FLAGGEDLONG in place but not used) to ensure things aren't double-processed, but the counting is now a singular entity, with btnWhich taking the role of Mike's suggested BTN_STATE; NONE, ONE, TWO, or BOTH

Much simpler and quicker, and works perfectly (as far as I've tested)

Code:
#include <24F_interrupt_pwm.h>
  #include <float.h>
  #include <math.h>
  #include <stdio.h>
  #include <stddef.h>
  #include <stdlib.h>
  #include <stdlibm.h>

volatile int16 timerCount = 0;
volatile int16 msCount = 0;//20ms interrupt count
volatile int1 flagDebounce = 0;
volatile int1 btn1On = 0;
volatile int1 btn2On = 0;
volatile int8 btnCount = 0;
volatile int8 btnRelCount = 0;

typedef enum{READY,FLAGGED,FLAGGEDLONG,SERVICED} BTN_STATE;
BTN_STATE btnState = READY;

typedef enum{NONE,ONE,TWO,BOTH} BTN_WHICH;
BTN_WHICH btnWhich = NONE;

#INT_TIMER5
void TIMER5_isr(void)
{
   msCount++;
   if(!input(PIN_B6))   {btn1On = 1;}
   else                 {btn1On = 0;}
         
   if(!input(PIN_C9))   {btn2On = 1;}
   else                 {btn2On = 0;}
   flagDebounce = 1;
}

void deBounce(void)
{
   if(btn1On || btn2On)
   {
      btnCount <255 ? btnCount++: null;
      if(btnCount > 5 && btnState==READY)
      {
         btnState = FLAGGED;
      }/*else if((btnCount > 50) && (btnState==FLAGGED || btnState==SERVICED))  //currently not used, so commented out as it introduces a lockout case
      {
         btnState = FLAGGEDLONG;
      }*/
      if(btn1On && btn2On) {btnWhich = BOTH;}
      else if(btn1On && !btn2On) {btnWhich = ONE;}
      else if(!btn1On && btn2On) {btnWhich = TWO;}
   }else
   {
      btnCount = 0;
      btnWhich = NONE;
      if(btnState==SERVICED)
      {
         if(btnRelCount <15) {btnRelCount++;}
         else{btnState = READY;} //reset for next response, after 15 counts
      }
   }
   flagDebounce = 0;//reset
}

void main()
{
  setup_timer5(TMR_INTERNAL | TMR_DIV_BY_8, 25000);//div8 and 25000 equal 20ms interrupt

   set_pullup(true, pin_B6);
   set_pullup(true, pin_C9);
   enable_interrupts(INT_TIMER5);
   //enable_interrupts(GLOBAL);

   // TODO: USER CODE!!
   while(1){
      if(flagDebounce){   deBounce();}
     
      if(btnState==FLAGGED)
      {
         switch(btnWhich)
         {
            case BOTH: output_toggle(PIN_B12); break;//red
            case ONE: output_toggle(PIN_B11); break;//blue
            case TWO: output_toggle(PIN_B13); break;//green
         }
         btnState = SERVICED;
         btnRelCount = 0;
      }

   }
}
Display posts from previous:   
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion All times are GMT - 6 Hours
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2005 phpBB Group