![](templates/subSilver/images/CCSLogo.jpg) |
![CCS C Software and Maintenance Offers](templates/subSilver/images/forumAd6.jpg) |
View previous topic :: View next topic |
Author |
Message |
RoGuE_StreaK
Joined: 02 Feb 2010 Posts: 73
|
simultaneous button press logic? |
Posted: Sat May 17, 2014 5:50 am |
|
|
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](images/smiles/icon_idea.gif) |
|
![](templates/subSilver/images/spacer.gif) |
Mike Walne
Joined: 19 Feb 2004 Posts: 1785 Location: Boston Spa UK
|
|
Posted: Sat May 17, 2014 7:38 am |
|
|
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 |
|
![](templates/subSilver/images/spacer.gif) |
temtronic
Joined: 01 Jul 2010 Posts: 9173 Location: Greensville,Ontario
|
|
Posted: Sat May 17, 2014 5:34 pm |
|
|
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 |
|
![](templates/subSilver/images/spacer.gif) |
RoGuE_StreaK
Joined: 02 Feb 2010 Posts: 73
|
|
Posted: Mon May 19, 2014 4:16 am |
|
|
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. |
|
![](templates/subSilver/images/spacer.gif) |
Mike Walne
Joined: 19 Feb 2004 Posts: 1785 Location: Boston Spa UK
|
|
Posted: Mon May 19, 2014 3:26 pm |
|
|
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 |
|
![](templates/subSilver/images/spacer.gif) |
RoGuE_StreaK
Joined: 02 Feb 2010 Posts: 73
|
|
Posted: Tue May 27, 2014 3:45 am |
|
|
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;
}
}
} |
|
|
![](templates/subSilver/images/spacer.gif) |
|
|
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
|