|
|
View previous topic :: View next topic |
Author |
Message |
Bill_Smith
Joined: 06 Feb 2004 Posts: 26 Location: Curitiba, Brazil
|
INT_EXT Interrupts On Both Edges |
Posted: Tue Jun 15, 2004 2:02 pm |
|
|
After reading through most of the messages posted on Debouncing and Interrupt handling, especially those from PCM Programmer and Neutone, I attempted to create a simple INT_EXT driven State Machine.
I have a Normally Open pushbutton connected to B0, processor is an 18F452, and I'm using CCS PCWH 3.202. For lack of a better way to view state transitions, I incorporated a 16 x 2 LCD panel. The State Machine starts in MODE 0 and increments up to MODE 5, then it goes back to MODE 0. While in each MODE (state), mode specific data are calculated and displayed.
After several presses of the pushbutton I can see the MODE incrementing and interrupt_count increasing. The value of interrupt_count clearly shows that multiple interrupts are occuring. Even though I have ext_int_edge(INT_EXT,H_TO_L) selected, I am getting interrupts when I release the pushbutton as well.
Since I based my interrupt handler on PCM Programmer's posting, I'll direct my questions to him.
1. The variable interrupt_count is incremented each time an edge event occurs on EXT. How can I use this value to know when transitions have stopped?
2. The counter's value once initialized is never reset to zero. Is this correct?
3. How can I prevent the pushbutton's release from generating interrupts?
Thank you for your advice.
The following code is provided to illustrate my problem:
Code: | #include <18F452.h>
#device *=16
#include <stdlib.h>
#include <math.h>
#use delay(clock=20000000)
#fuses NOWDT,HS,PUT,NOPROTECT,BROWNOUT,NOCPD,NOWRT,NOLVP
#zero_ram
//---------- Defines ---------------------------------
#define DISPLAY_COLS 16
#define LCD_D0 PIN_D4
#define LCD_D1 PIN_D5
#define LCD_D2 PIN_D6
#define LCD_D3 PIN_D7
#define LCD_EN PIN_D3
#define LCD_RS PIN_D2
#define LINE_1 0x00
#define LINE_2 0x40
#define CLEAR_DISP 0x01
#define EOF 0x00
#define COMMA ','
#define CR 13
#define SPACE ' '
#define PERIOD '.'
#define DEGREE 0xdf
#define DOLLAR '$'
#define PB1 PIN_B0
#bit INTF_BIT=0x0B.1
//---------- Variables -------------------------------
static char got_interrupt;
static char interrupt_count;
static char MODE;
//---------- Function Prototypes ---------------------
void set_mode(void);
void display_mode(BYTE md);
void display_data();
void lcd_init(void);
void LCD_SetPosition (unsigned int cX);
void LCD_PutChar (unsigned int cX);
void LCD_PutCmd (unsigned int cX);
void LCD_PulseEnable (void);
void LCD_SetData (unsigned int cX);
#INT_EXT
void EXT_isr(void)
{
interrupt_count++;
got_interrupt=TRUE;
disable_interrupts(INT_EXT);
}
void main(void)
{
output_float(PIN_B0);
port_b_pullups(TRUE);
delay_us(10);
interrupt_count=0;
got_interrupt=FALSE;
MODE=0;
ext_int_edge(INT_EXT,H_TO_L);
INTF_BIT=0;
enable_interrupts(INT_EXT);
enable_interrupts(GLOBAL);
lcd_init();
display_mode(0);
while(TRUE)
{
if(got_interrupt==TRUE){
delay_ms(100);
if(input(PB1)==0){
set_mode();
got_interrupt=FALSE;
INTF_BIT=0;
enable_interrupts(INT_EXT);
}
}
switch(MODE){
case 0: // MODE 0
display_data();
break;
case 1: // MODE 1
break;
case 2: // MODE 2
display_data();
break;
case 3: // MODE 3
display_data();
break;
case 4: // MODE 4
display_data();
break;
case 5: // MODE 5
break;
default:
break;
}
}
}
void set_mode(void){
switch(MODE){
case 0: // [In MODE 0]
MODE=1; // Change to [MODE 1]
display_mode(1);
break;
case 1: // [In MODE 1]
MODE=2; // Change to [MODE 2]
display_mode(2);
break;
case 2: // [In MODE 2]
MODE=3; // Change to [MODE 3]
display_mode(3);
break;
case 3: // [In MODE 3]
MODE=4; // Change to [MODE 4]
display_mode(4);
break;
case 4: // [In MODE 4]
MODE=5; // Change to [MODE 5]
display_mode(5);
break;
case 5: // [In MODE 5]
MODE=0; // Change to [MODE 0]
display_mode(0);
break;
default:
MODE=0; // Change to [MODE 0]
display_mode(0);
break;
}
}
void display_mode(BYTE md)
{
LCD_PutCmd (0x01);
LCD_SetPosition (LINE_1 + 0);
printf (LCD_PutChar,"MODE %d",md);
}
void display_data()
{
LCD_SetPosition (LINE_2 + 0);
printf (LCD_PutChar,"Int Count=%d",interrupt_count);
}
void lcd_init(void)
{
LCD_SetData (0x00);
delay_ms (200); /* wait enough time after Vdd rise */
output_low (LCD_RS);
LCD_SetData (0x03); /* init with specific nibbles to start 4-bit mode */
LCD_PulseEnable();
LCD_PulseEnable();
LCD_PulseEnable();
LCD_SetData (0x02); /* set 4-bit interface */
LCD_PulseEnable(); /* send dual nibbles hereafter, MSN first */
LCD_PutCmd (0x2C); /* function set (all lines, 5x7 characters) */
LCD_PutCmd (0x0C); /* display ON, cursor off, no blink */
LCD_PutCmd (0x01); /* clear display */
LCD_PutCmd (0x06); /* entry mode set, increment & scroll left */
}
void LCD_SetPosition (unsigned int cX)
{
/* this subroutine works specifically for 4-bit Port A */
LCD_SetData (swap (cX) | 0x08);
LCD_PulseEnable();
LCD_SetData (swap (cX));
LCD_PulseEnable();
}
void LCD_PutChar (unsigned int cX)
{
/* this subroutine works specifically for 4-bit Port A */
output_high (LCD_RS);
LCD_SetData (swap (cX)); /* send high nibble */
LCD_PulseEnable();
LCD_SetData (swap (cX)); /* send low nibble */
LCD_PulseEnable();
output_low (LCD_RS);
}
void LCD_PutCmd (unsigned int cX)
{
/* this subroutine works specifically for 4-bit Port A */
LCD_SetData (swap (cX)); /* send high nibble */
LCD_PulseEnable();
LCD_SetData (swap (cX)); /* send low nibble */
LCD_PulseEnable();
}
void LCD_PulseEnable (void)
{
output_high (LCD_EN);
delay_us (3); // was 10
output_low (LCD_EN);
delay_ms (3); // was 5
}
void LCD_SetData (unsigned int cX)
{
output_bit (LCD_D0, cX & 0x01);
output_bit (LCD_D1, cX & 0x02);
output_bit (LCD_D2, cX & 0x04);
output_bit (LCD_D3, cX & 0x08);
} |
[/code] |
|
|
SherpaDoug
Joined: 07 Sep 2003 Posts: 1640 Location: Cape Cod Mass USA
|
|
Posted: Tue Jun 15, 2004 2:41 pm |
|
|
It sounds like a case of switch bounce. If you have a mechanical switch working against a pull up (or pull down) resistor then you will get a series of transitions every time the switch is opened or closed. This is because the metal of the switch contacts actually bounces a bit as contact is made or broken. To confirm this look carefully at the switch signal with a scope.
To solve the problem search for "switch bounce" or "contact bounce" on this BBS or the web. A good solution is not easy. Usually it requires some analog circuitry or a software timer that confirms the switch is actually opening or closing.
Good Luck _________________ The search for better is endless. Instead simply find very good and get the job done. |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Tue Jun 15, 2004 2:52 pm |
|
|
First I just want to say that my code was not intended as
a demo of how to make an interrupt-driven push button switch.
At the time I wrote it, I just wanted to show various aspects
of how to use the INT_EXT interrupt.
Now I guess I'm going to have to sit down and write some
push button code. I'll try to do that today, or at least by
tomorrow. And of course, anyone else can respond to this
thread as well. |
|
|
rnielsen
Joined: 23 Sep 2003 Posts: 852 Location: Utah
|
|
Posted: Tue Jun 15, 2004 3:23 pm |
|
|
Here's an interrupt routine that tries to debounce three buttons. I'm not sure if it's the cleanest or best but it seems to work quite nicely for me. I used a 18F452, running at 20MHZ, and used all three external interrupt pins.
Code: |
#int_EXT
EXT_isr()
{
unsigned int16 j;
int1 save, current;
save = input(PIN_B0);// save current state of input
for(j = 0; j < 300; j++)// wait statement
{
if((current = input(PIN_B0)) != save)// if the input has changed state while we wait
{
j = 0;// start counting over
}
save = current;// save new state of input
}
if(!input(PIN_B0) && !pushed)// if we�re still pushed
{
pushed = 1;// set flag to call button routine - global variable
pad = 0x60;// value for button routine to evaluate - global variable
}
else
{
pad = 0x70;// if we�re not pushed, set to default value
}
}// end of int_EXT
#int_EXT1
EXT1_isr()
{
unsigned int16 j;
int1 save, current;
save = input(PIN_B1);
for(j = 0; j < 300; j++)
{
if((current = input(PIN_B1)) != save)
{
j = 0;
}
save = current;
}
if(!input(PIN_B1) && !pushed)
{
pushed = 1;
pad = 0x50;
}
else
{
pad = 0x70;
}
}// end of int_EXT1
#int_EXT2
EXT2_isr()
{
unsigned int16 j;
int1 save, current;
save = input(PIN_B2);
for(j = 0; j < 300; j++)
{
if((current = input(PIN_B2)) != save)
{
j = 0;
}
save = current;
}
if(!input(PIN_B2) && !pushed)
{
pushed = 1;
pad = 0x30;
}
else
{
pad = 0x70;
}
}// end of int_EXT2
|
|
|
|
Bill_Smith
Joined: 06 Feb 2004 Posts: 26 Location: Curitiba, Brazil
|
|
Posted: Tue Jun 15, 2004 3:42 pm |
|
|
SherpaDoug and PCM Programmer, thank you for your replies.
SherpaDoug: I confirmed your suspicions with a storage scope. I'm getting multiple edges on both press and release of the pushbutton. I suppose I could kludge in a couple of NAND gates to clean up the edges, but then I would probably [spam] off the 10,000 other folks out there who would like to see PCM's software solution, me included.
I found a couple of interesting indications while investigating my code a little more:
If I reduce the delay in the main routine to 10ms, I get fewer jumps in transitioning from one MODE to the next. Somewhere after 50 or 60 presses of the pushbutton, the processor goes whacky and locks up. Probably a stack overflow but that's only a guess.
Cheers |
|
|
Neutone
Joined: 08 Sep 2003 Posts: 839 Location: Houston
|
|
Posted: Tue Jun 15, 2004 6:45 pm |
|
|
For what it's worth I have never posted any debouncing routines that would would work well with an interupt. They were all based on the inputs being scaned on a periodic time base. What might work for you is to disable the edge interupt as soon as it occurs and set a timer to zero. Then when the timer interupts enable the edge interupt. This would be easy to operate and prevent excessive interupts. If your running a periodic loop you might find the examples I posted work better than what you can get working in an interupt. On the other hand I am wondering what PCM hand in mind. |
|
|
Jerry I
Joined: 14 Sep 2003 Posts: 96 Location: Toronto, Ontario, Canada
|
INT_EXT Interrupts On Both Edges |
Posted: Tue Jun 15, 2004 9:29 pm |
|
|
I agree with Neutone; I don't think interupts are need to read a switch inputs.
What I have always done is to use the internal timer free running. Timer interupts every 10ms, this is the delay I use to debounce the switches.
When I detect a change in state of the switches I clear debounce_count=0;
At this point every time the timer interupts debounce_count++ increments
when it reaches say a count of 6 which is approx. 60ms I service the switch routine.
I do not wait in a loop for a delay for debouncing the switch.
I do use states, and as I poll the switches I check for the state and continue processing other code.
Sample Code
Code: |
enum swt_states {poll, init_debounce, debounce, debounce_ok};
#INT_RTCC
clock_isr()
{
TEST_BIT = !TEST_BIT;
if (bstate == debounce) // inc debounce count.
debounce_time++;
if (--high_count == 0)
{
rtctic++;
if (event_ctr % 2 == 0)
{
sec_ctr++;
event_ctr = 0;
}
high_count = EVENT_TMR;
}
exit_clock_isr:;
}
void poll_swts()
{
if (bstate == poll)
{
sw1_cur_state = SW1; //rb7
sw2_cur_state = SW2; //rb6
sw3_cur_state = SW3; //rb5
sw4_cur_state = SW4; //rb4
rtctic = 0;
update_swt_states();
rtctic = 0;
return;
}
if (bstate == init_debounce)
{
debounce_time = 0x00;
rtctic = 0;
bstate = debounce;
return;
}
if (bstate == debounce)
{
if (debounce_time >= DEBOUNCE_CNT) // DEBOUNCE_CNT = 4-6
{ // increase for less
// bounce
bstate = debounce_ok;
service_swt1();
service_swt2();
service_swt3();
service_swt4();
}
return;
}
if (bstate == debounce_ok)
{
rtctic = 0;
bstate = poll;
return;
}
}
void update_swt_states()
{
if (sw4_cur_state != sw4_old_state)
{
sw4_changed = TRUE;
}
else
sw4_changed = FALSE;
if (sw3_cur_state != sw3_old_state)
{
sw3_changed = TRUE;
}
else
sw3_changed = FALSE;
if (sw2_cur_state != sw2_old_state)
{
sw2_changed = TRUE;
}
else
sw2_changed = FALSE;
if (sw1_cur_state != sw1_old_state)
{
sw1_changed = TRUE;
}
else
sw1_changed = FALSE;
if ((sw4_changed) || (sw3_changed) || (sw2_changed) ||
(sw1_changed))
{
sw1_old_state = sw1_cur_state;
sw2_old_state = sw2_cur_state;
sw3_old_state = sw3_cur_state;
sw4_old_state = sw4_cur_state;
bstate = init_debounce;
}
}
void service_swt1()
{
if (!SW1) // If switch releases prior to debounce no
// code is executed
{
YOUR CODE...................
}
}
void service_swt2()
{
if (!SW2)
{
YOUR CODE...................
}
}
void service_swt3()
{
if (!SW3)
{
YOUR CODE...................
}
}
void service_swt4()
{
if (!SW4)
{
YOUR CODE...................
}
}
main()
{
bstate = poll;
while(TRUE)
{
poll_swts();
DO REST OF APP
}
}
|
No waisting processor time in a delay loop.
Good luck; |
|
|
Bill_Smith
Joined: 06 Feb 2004 Posts: 26 Location: Curitiba, Brazil
|
|
Posted: Wed Jun 16, 2004 1:06 pm |
|
|
Thank you rnielsen and Jerry I for your responses, I am always amazed by the creativity of the human mind in solving a problem. I took a closer look at both of your methods, and I think that I will try to implement Jerry's. I discovered that the 18F452 has some really cool timers, and since I have 3 timers that are doing nothing in my current 18F452 design, I think I will take this opportunity to get to know them a little better.
Jerry I: Could you please check my declarations below? What are their start up values? The rtctic variable increments continually, do you ever reset it? What is the variable sec_ctr for?
#define DEBOUNCE_CNT 5 // 5x10ms
#define SW1 input(PIN_B0) // Pushbutton
short TEST_BIT;
int debounce_time;
int high_count;
int rtctic;
int event_ctr;
int sec_ctr;
int EVENT_TMR;
int sw1_changed;
int sw1_old_state;
int sw1_cur_state;
int bstate;
Cheers |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Wed Jun 16, 2004 1:46 pm |
|
|
I am glad everyone else is posting code. Then I don't have
to write a specialized routine which wouldn't be the best way
to solve your problem anyway.
The way I was going to do it, was to disable external interrupts
after getting the first keypress. Then set a (hardware) timer for
the debounce delay. That uses a lot of resources and it's not
necessary. Normally, I use a system "tick" at 10 ms, and just
check the keypad every tick. The tick period provides the
debounce time.
There was one recent project where we did use interrupts.
Four push-buttons were connected to pins RB4-RB7.
The customer had a requirement wherein the time at which
a button was pushed had to be recorded at a resolution of
a few micro-seconds. This is because there were many
different keypads, and the outcome was decided based
on which user was the first one to press a button.
So we couldn't use the 10 ms tick method. Instead, we
used the interrupt-on-change feature. In the INT_RB
isr, we read and saved the value of Timer1, and the
state of the keys, and set a flag. Debouncing was
done outside the isr, by keeping a count of calls to
the check_keypad routine (similar to the CCS kbd.c
method). |
|
|
Jerry I
Joined: 14 Sep 2003 Posts: 96 Location: Toronto, Ontario, Canada
|
|
Posted: Wed Jun 16, 2004 9:00 pm |
|
|
Updated variables I used
int sw1_changed; SHOULD BE short or int1
int sw1_old_state; SHOULD BE short or int1
int sw1_cur_state; SHOULD BE short or int1
int bstate;
Old Code below really not required
int sec_ctr; I used var to count seconds not req.
int event_ctr; not req
short TEST_BIT; not req. used for testing pin toggles after int
int rtctic; not req
#define EVENT_TMR 38 // Event Timer every approx 1/2 sec
assuming 10MHz Clock
setup_timer_0(RTCC_INTERNAL|RTCC_DIV_128|RTCC_8_BIT);
Goot Luck
Bill_Smith wrote: | Thank you rnielsen and Jerry I for your responses, I am always amazed by the creativity of the human mind in solving a problem. I took a closer look at both of your methods, and I think that I will try to implement Jerry's. I discovered that the 18F452 has some really cool timers, and since I have 3 timers that are doing nothing in my current 18F452 design, I think I will take this opportunity to get to know them a little better.
Jerry I: Could you please check my declarations below? What are their start up values? The rtctic variable increments continually, do you ever reset it? What is the variable sec_ctr for?
#define DEBOUNCE_CNT 5 // 5x10ms
#define SW1 input(PIN_B0) // Pushbutton
short TEST_BIT;
int debounce_time;
int high_count;
int rtctic;
int event_ctr;
int sec_ctr;
int EVENT_TMR;
int sw1_changed;
int sw1_old_state;
int sw1_cur_state;
int bstate;
Cheers |
|
|
|
SteveS
Joined: 27 Oct 2003 Posts: 126
|
Debouncing switches |
Posted: Thu Jun 17, 2004 6:31 am |
|
|
While we are on the subject - Maxim makes a nice I2C debounce series MAX6816-8. They are hard to get sometimes and not real cheap, but real handy. They have an interrupt output when any input changes (after debounce). It takes away the debounce processing and lets you interface a bunch of discrete push buttons without taking up a bunch of pins. I put them on my front panel/user interface board and then I have a nice small cable to the processor board. Note that the debounce period (fixed) may be too slow for some encoders - good for buttons and switches though.
- SteveS |
|
|
mnv
Joined: 04 Aug 2004 Posts: 7
|
|
Posted: Fri Aug 06, 2004 5:49 am |
|
|
i created my own solution for debouncing.
i've got 5 buttons which are each connected to its port pins and rb0-interrupt is used for recognizing, when a button is pressed. this is done with a diode connected on each button to the interrupt line. this interrupt line is connected to a 680 resistor and the resisitor is connected to a 100n-capacitor against ground and to the rb0-pin. this is for a smooth sink and rise of the interrupt signal so it needs a few milliseconds. and this is longer than an eventually debounce could last.
sorry for my terrible english ;) |
|
|
Guest
|
|
Posted: Fri Aug 06, 2004 7:03 am |
|
|
The simplest way to you (for the current state of your software) is to disable any further interrupts one the EXT_IT is fired. So:
#int_ext
.........
disable_interrupts(INT_EXT); // disable furthers
................
In main():
delay_ms(...);
enable_interrupts(INT_EXT); // re-enable after the delay |
|
|
Guest Guest
|
HW debounce??? |
Posted: Fri Aug 06, 2004 11:25 am |
|
|
Maybe a bad question for a programming forum, but has anyone tried using one of these "Schmitt trigger" devices to avoid debounce? Do they work? |
|
|
Will Reeve
Joined: 30 Oct 2003 Posts: 209 Location: Norfolk, England
|
|
Posted: Fri Aug 06, 2004 11:54 am |
|
|
I thought I would post how I allways handle push buttons (I allways place them on RB4-7 and use internal pull-up's and RB4-7 interrupt on change):
example button defines:
#define FIREPIN PIN_B4
#define MODEPIN PIN_B5
#define VOLPIN PIN_B6
#define FREQPIN PIN_B7
#define FIRE 0x01
#define MODE 0x02
#define VOL 0x04
#define FREQ 0x08
#int_RB
RB_isr(void) {
disable_interrupts(INT_RB); // prevent bounce
if (!input(FIREPIN)) iButton |= FIRE; else iButton &= ~FIRE;
if (!input(MODEPIN)) iButton |= MODE;
if (!input(VOLPIN)) iButton |= VOL;
if (!input(FREQPIN)) iButton |= FREQ;
set_timer1(1);
enable_interrupts(INT_TIMER1);
} // RB_isr(void)
The FIRE button in this case if a button which you hold down and the rest of the code can also tell when you release it, which is kind of neat! The other puttons set iButton and the code clears it when that action has been completed.
The INT_TIMER1 interrupt just does this:
#int_TIMER1
TIMER1_isr(void){
disable_interrupts(INT_TIMER1);
enable_interrupts(INT_RB);
} // TIMER1_isr(void)
This arrangment works well you just mask iButton with the button you want to check, like:
if (iButton&MODE)
for instance.
Keep well all,
Will |
|
|
|
|
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
|