Help with Debouncing my Matrix Switch

Thread Starter

beeson76

Joined Apr 19, 2010
211
Here is my code:

Rich (BB code):
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//This Pendant has 10 buttons using a 3 Column 4 Row 
//Matrix Switch.   
//
//Whenever a switch is pushed, that switches name is displayed on an LCD Display.
//Here are the names of the Buttons:
//Switch 1		--	Column 1 Row 4
//Switch 2	--	Column 2 Row 4
//Switch 3	--	Column 3 Row 4
//Switch 4		--	Column 1 Row 3
//Switch 5		--	Column 2 Row 3
//Switch 6	--	Column 3 Row 3
//Swtich 7		--	Column 1 Row 2
//Switch 8		--	Column 2 Row 2
//Switch 9		--	Column 3 Row 2
//Swtich 10		--	Column 1 Row 1
//Switch 11		--	Column 2 Row 1
//Switch 12		--	Column 3 Row 1
//
//The PIC16F886 Microcontroller from Microchip is being used.
//2x16 LCD Display being used
//
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#include 
#include "lcd.h"

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////DEFINE STATEMENTS
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#define	COL_1			RB0
#define COL_2			RB1
#define	COL_3			RB7
#define	ROW_1			RB3
#define ROW_2			RB4
#define	ROW_3			RB5
#define	ROW_4			RB6
#define  DelayS(T)		{unsigned char i; for (i = 0; i < T * 10; i++) __delay_ms(100);}	//Delay Macro
#define  _XTAL_FREQ				4000000								//Needs to be set for __delay_ms

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////CONFIGURATION FUSES (BITS)
////Master Clear Reset enabled & Internal RC No Clock & Watchdog Timer Disable & Power Up Timer On & Brown Out Reset Disabled &
////Low Voltage Porgramming Disabled & Code Unprotect
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
__CONFIG (MCLREN & INTIO & WDTDIS & PWRTEN & BORDIS & LVPDIS & UNPROTECT);
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


main()
{
	
PORTA = 0x00;										//PORTA is cleared and set low
PORTB = 0xFF;										//(0b11111111 binary) (0xFF hex)--ROWS set to HIGH, COLUMNS set to HIGH  
													//PORTB7:B0 is set to high,high,high,high,high,high,high,high
													
PORTC = 0x00;										//PORTC is cleared and set low
TRISA = 0x00;										//Set PORTA to outputs for LCD--RA1, RA2, RA4 are Control lines for LCD

TRISB = 0x7C;										//(0b01111100 binary) (0x7C hex)--ROWS set to INPUT, COLUMNS set to OUTPUT
													//PORTB7:B0 is set to output,input,input,input,input,input,output,output,
													//ROWS set to INPUT

TRISC = 0x00;										//Set PORTC to outputs for LCD--RC0:RC3 are Data Lines for LCD


ANSEL = 0;											//Initialize A/D Ports off
ANSELH = 0;											//Initialize ........
CM1CON0 = 0;										//Initialize Comparator 1 off
CM2CON0 = 0;										//Initialize Comparator 2 off

OPTION = 0b01010101;								//OPTION REG
													//xbxxxxx101  1:64
													//xbxxxx0xxx  Prescaler set to Timer0
													//xbxxx1xxxx  (T0SE) set to Increment on high-to-low transition on T0CKI pin
													//xbxx0xxxxx  (T0CS) Internal instruction cycle clock
													//xbx1xxxxxx  (INTEDG) Interrupt on rising edge of INT pin
													//xb0xxxxxxx  (RBPU) PORTB pull-ups are enabled by individual PORT latch values
RBPU = 0;											//Don't think I need this, but to be safe...PORTB Weak Internal Pullups enabled
WPUB0 = 0;											//COL_1 Weak pullup is individually DISABLED--OUTPUT 
WPUB1 = 0;											//COL_2 Weak Pullup is inidivdually DISABLED--OUTPUT
WPUB2 = 0;											//Not being used...Weak Pullup is individually DISABLED
WPUB3 = 1;											//ROW_1 Weak Pullup is individually ENABLED--INPUT
WPUB4 = 1;											//ROW_2 Weak Pullup is individually ENABLED--INPUT
WPUB5 =	1; 											//ROW_3 Weak Pullup is individually ENABLED--INPUT
WPUB6 = 1;											//ROW_4 Weak Pullup is individually ENABLED--INPUT
WPUB7 = 0;											//COL_3 Weak Pullup is individually DISABLED--INPUT


lcd_init();											//LCD Display is Initialized...See LCD.C file and LCD.h

unsigned char debouncecount;



while (1)
	{
		lcd_clear;									//Clear LCD
		lcd_goto(0);								//Go to Line 1 Position 1
		lcd_puts("Matrix Switch");				//Display "Matrix Switch"
		lcd_goto(40);								//Go to Line 2 Position 1
		lcd_puts("Press Button");					//Display "Press Button"
		
		for (debouncecount = 0; debouncecount <= 10; debouncecount++);
			{
				__delay_ms(1);
				COL_1 = 0;									//COLUMN 1 is set LOW
				if (ROW_1 == 0)								//and If ROW 1 is LOW...
					{										//...
						lcd_clear();						//...
						lcd_goto(0);						//...
						lcd_puts("Switch 3");					//Display "Flat"
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(100);
						
					}
			
				
				if (ROW_2 == 0)								//if ROW 2 is LOW...
					{										//...
						lcd_clear();						//...		
						lcd_goto(0);						//...
						lcd_puts("Switch 6");					//Display "Switch 6"
						__delay_ms(150);			
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(100);
					}	
				if (ROW_3 == 0)								//If ROW 3 is LOW
					{										//...
						lcd_clear();						//...
						lcd_goto(0);						//...
						lcd_puts("Switch 9");				//Display "Switch 9"
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(100);
					}
				if (ROW_4 == 0)								//If ROW 4 is LOW
					{										//...
						lcd_clear();						//...
						lcd_goto(0);						//...
						lcd_puts("Switch 12");				//Display "Switch 12"
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(100);
					}
			
				COL_1 = 1;									//COLUMN 1 is set HIGH again
				COL_2 = 0;									//COLUMN 2 is set LOW
				if (ROW_1 == 0)								//and If ROW 1 is LOW...
					{										//...
						lcd_clear();						//...
						lcd_goto(0);						//...
						lcd_puts("Switch 2");					//Display "Switch 2"
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(100);
					}
				
				if (ROW_2 == 0)								//if ROW 2 is LOW...
					{										//...
						lcd_clear();						//...		
						lcd_goto(0);						//...
						lcd_puts("Switch 5");				//Display "Switch 5"
						__delay_ms(150);			
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(100);
					}
				if (ROW_3 == 0)								//If ROW 3 is LOW
					{										//...
						lcd_clear();						//...
						lcd_goto(0);						//...
						lcd_puts("Switch 8");					//Display "Switch 8"
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(100);
					}
				if (ROW_4 == 0)								//If ROW 4 is LOW
					{										//...
						lcd_clear();						//...
						lcd_goto(0);						//...
						lcd_puts("Switch 11");			//Display "Switch 11"
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(100);
					}
				COL_2 = 1;									//COLUMN 2 is set HIGH again
				COL_3 = 0;									//COLUMN 3 is set LOW
				if (ROW_1 == 0)								//and If ROW 1 is LOW...
					{										//...
						lcd_clear();						//...
						lcd_goto(0);						//...
						lcd_puts("Switch 1");				//Display "Switch 1"
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(100);
					}
				
				if (ROW_2 == 0)								//if ROW 2 is LOW...
					{										//...
						lcd_clear();						//...		
						lcd_goto(0);						//...
						lcd_puts("Switch 4");				//Display "Switch 4"
						__delay_ms(150);			
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(100);
					}
				if (ROW_3 == 0)								//If ROW 3 is LOW
					{										//...
						lcd_clear();						//...
						lcd_goto(0);						//...
						lcd_puts("Switch 8");				//Display "Switch 8"
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(100);
					}
				if (ROW_4 == 0)								//If ROW 4 is LOW
					{										//...
						lcd_clear();						//...
						lcd_goto(0);						//...
						lcd_puts("Switch 12");			//Display "Switch 12"
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(150);
						__delay_ms(100);
					}
				COL_3 = 1;									//COLUMN 3 is set HIGH again
				debouncecount = 0;
			}
	}
}
Does this look like I am debouncing the switch the correct way. Thanks for any help that you can give. I appreciate it a lot. Thanks again.
 

Bosparra

Joined Feb 17, 2010
79
You need to first read to check for a keypress. Once a keypress is detected, wait for about 50ms and then read the pin again to check if the key is still being pressed, if so, you have a valid keypress.

You will make life allot easier for yourself, if you break your code up into functions. Just don't go crazy with nested function calls, else you will run out of stack space quickly. For instance, create a function where the do the key debouncing and make it return the value of that was pressed. That way you get code reuse and you can actually make your application size smaller.

Enjoy.
 

Thread Starter

beeson76

Joined Apr 19, 2010
211
Thanks for the replies. So here is what I think I should do. Please tell me if this is correct. I will go step by step per reply, so it will be easier for me to keep straight.

In my define statements I will define each switch, such as:

Rich (BB code):
#define SWITCH10	COL_1 && ROW_1
#define SWITCH7		COL_1 && ROW_2
#define SWITCH4		COl_1 && ROW_3
#define SWITCH1		COL_1 && ROW_4
#define SWITCH11	COL_2 && ROW_1
#define SWITCH8		COL_2 && ROW_2
#define SWITCH5		COL_2 && ROW_3
#define SWITCH2		COL_2 && ROW_4
#define SWITCH12	COL_3 && ROW_1
#define SWITCH9		COL_3 && ROW_2
#define SWITCH6  	COL_3 && ROW_3
#define SWITCH3		COL_4 && ROW_4
Then I will assign SWITCH... to a variable named keypress.

Is that correct as far as the start.

Thanks.
 

thatoneguy

Joined Feb 19, 2009
6,359
Use a timer interrupt instead of code delay loops for debouncing, that way the processor isn't spending most of the time waiting to see if a switch is bouncing or not.

Debouncing
 

Thread Starter

beeson76

Joined Apr 19, 2010
211
Now its getting too complicated for me. I am trying to keep it simple at first to learn the basics and then go from there. I have decided that I would like to use 3 functions. A key_detect, key_read, and key_delay.

The key_press function is the function were I would like it to detect a switch being pressed.

The key_read function is where I would like to read what switch is pressed.

The key_delay is the debounce function of maybe 20 to 50 ms.

From here can someone direct me in the right direction on how to go about programming these.

Thanks for the help.
 

thatoneguy

Joined Feb 19, 2009
6,359
The debounce and read should be the same function. Usually sample the line once every 10mS 5 times (total 50mS), if 3-5 of 5 samples are high, respond to it. Create a variable and add 1 to it each time it is read high, then if variable > 3 at end of routine, it is high.

The ~10mS delay can be part of the "read" code and rest of program, checking all rows and columns, and adding to a variable every time one line is polled. Once all are polled, and a key is decided to be pressed, reset counters, return the key value. Otherwise, if 5 reads pass with no lines hitting 3-5 counts, simply reset the counters for the rows/columns that were used in debouncing, and wait for the next call of the key-read function. The key-read function would be continually called at roughly 10mS points throughout the code while it is working on other things such as updating the LCD, etc.
 

Thread Starter

beeson76

Joined Apr 19, 2010
211
Thanks Thatoneguy for the replies. Appreciate it very much. When you say sample the line once every 10ms, how do I do that. I must be having a brain block or something because I couldn't even begin to think how you would do that. Thanks again for the replies.

If I would would create a key_detect function, I would put it into a while statement such as

while (key_detect == 1)

That would get me into a while statement wouldnt it when the key_detect() function detects a key press. If not could you lead me in the right direction.

Thanks.
 

thatoneguy

Joined Feb 19, 2009
6,359
Use an interrupt on long timer event. 10mS is a long time, you could call the routine more often, or read it faster. It depends on what you have set up on the timer prescalers and the clock speed. If not needed for anything else, set the prescalers down to trigger every 128 clocks or so, and on the interrupt service, add to a number until it equals <whatever period you want to sample at>.

When the interrupt hits the needed time for sampling, set a flag. In the main loop of a program, have an if (flag) <key read/debounce routine here>, reading the lines, updating variable counts for each line, and clearing the flag at the end. The routine would then be run again when the timer has hit the interrupt enough times.
 

Thread Starter

beeson76

Joined Apr 19, 2010
211
Thanks again for the reply. I have been doing some reading to see if I can understand what you are telling me to do. Here is what I have come up with.
Would my program have in it somewhere the following lines. I know it is kind of obscure, but I was reading a book a while ago about Timer Overflows and I remembered this section. Here is what I have. Can you tell me if I am heading the right direction.

while (!name of variable);
pause(10);
while (!name of variable);

void msecbase (void)
{
OPTION = 0b00000001;
TMR = 0xD;
while (!T0IF);
T0IF = 0;
}
 

thatoneguy

Joined Feb 19, 2009
6,359
There should be a function that is automatically called on each interrupt, here is the sample from Boost C samples:

Rich (BB code):
/*
  Basic sample for BoostC compiler.
  Use the 'Led Block' plugin to see
  changing value on port B.
*/
    
#include <system.h>

// Set configuration word (sample only, ajust for your particular case)
#ifdef _PIC16
    #pragma DATA 0x2007, _HS_OSC & _WDT_OFF
#endif //_PIC16

void interrupt( void )
{
#ifdef _PIC16
    portb++;
#else
    latb++;
#endif

      clear_bit( intcon, T0IF );  //clear TMR0 overflow flag
}

void main()
{
    trisb = 0;        //configure port B

#ifdef _PIC16
    portb = 0;        //clear port B

    option_reg = 7;    //set prescaler
#else
    latb = 0;        //clear port B

    // configure Timer0
    set_bit( t0con, TMR0ON );    //enable timer
    clear_bit( t0con, T08BIT );    //set 16-bit mode
    clear_bit( t0con, T0CS );    // select internal clock
    clear_bit( t0con, PSA );     // select prescaler
    set_bit( t0con, T0PS0 );    // set 1:64 prescale ratio
    clear_bit( t0con, T0PS1 );  
    set_bit( t0con, T0PS2 );
#endif
    // enable interrupts
    set_bit( intcon, T0IE ); //enable TMR0 overflow bit    
    set_bit( intcon, GIE );


    while( 1 ); //endless loop
}
 

Thread Starter

beeson76

Joined Apr 19, 2010
211
I am having trouble figuring out how to get into my continuous loop with the while statement, but I saw that you put your while statement at the end of your program. I wonder if that would work for me too?
 

thatoneguy

Joined Feb 19, 2009
6,359
I am having trouble figuring out how to get into my continuous loop with the while statement, but I saw that you put your while statement at the end of your program. I wonder if that would work for me too?
Yes, it will loop through whatever code is while(1) {code here}, and still hit the interrupts to update things. In the while loop is where you check to see if something needs to be done and do it.

I like to keep as much processing out of the interrupt as possible, just set a bit flag when something is required. Then in the main while loop, test the different bit flags that may have been set during an interrupt, and act on them, clearing the bit.

It's sort of like adding a "software interrupt" on top of the hard timer or pin input interrupt. You want to be sure your interrupt code completes before the next interrupt may occur, or it will be missed.

The example above doesn't do this, as the interrupt only acts as a counter to flash the LEDs. Instead of incrementing PORTB, I would set a variable called increment to true, then in the while statement, have a line of
Rich (BB code):
while(1)
{
if(increment)
{
   PORTB++;
   increment=FALSE;
}//end if
}//end while
Add all of the if conditionals in the while{} loop, the interrupts will change the conditionals as needed, and you have no "downtime" doing a delay!
 

Thread Starter

beeson76

Joined Apr 19, 2010
211
Ah. Its making sense now. You are considering the while loop the interrupt. Im not familiar with terms as of yet. But after reading it several times, I am getting the picture. This is awesome!! Thanks. Ok so from that point of view, I will re-read the previous post and see if it becomes clearer. I appreciate very much the help your giving me. This is a learning experience for me, and I appreciate the patience.
 

thatoneguy

Joined Feb 19, 2009
6,359
Ah. Its making sense now. You are considering the while loop the interrupt. Im not familiar with terms as of yet. But after reading it several times, I am getting the picture. This is awesome!! Thanks. Ok so from that point of view, I will re-read the previous post and see if it becomes clearer. I appreciate very much the help your giving me. This is a learning experience for me, and I appreciate the patience.
That is essentially it. If you don't look at the interrupt routine, it would appear that very little code would actually run in the while loop. The reason it works is having global variables that are set in the interrupts, and "worked on" in the time between interrupts. This gives a lot more "realtime" feel to embedded applications, since a delay() function basically locks up the uC from a user point of view when interrupts aren't enabled.
 
Top