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

INT_EXT Interrupts On Both Edges
Goto page 1, 2  Next
 
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion
View previous topic :: View next topic  
Author Message
Bill_Smith



Joined: 06 Feb 2004
Posts: 26
Location: Curitiba, Brazil

View user's profile Send private message Visit poster's website

INT_EXT Interrupts On Both Edges
PostPosted: Tue Jun 15, 2004 2:02 pm     Reply with quote

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

View user's profile Send private message

PostPosted: Tue Jun 15, 2004 2:41 pm     Reply with quote

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

View user's profile Send private message

PostPosted: Tue Jun 15, 2004 2:52 pm     Reply with quote

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

View user's profile Send private message

PostPosted: Tue Jun 15, 2004 3:23 pm     Reply with quote

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

View user's profile Send private message Visit poster's website

PostPosted: Tue Jun 15, 2004 3:42 pm     Reply with quote

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. Smile

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

View user's profile Send private message

PostPosted: Tue Jun 15, 2004 6:45 pm     Reply with quote

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

View user's profile Send private message

INT_EXT Interrupts On Both Edges
PostPosted: Tue Jun 15, 2004 9:29 pm     Reply with quote

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; Smile
Bill_Smith



Joined: 06 Feb 2004
Posts: 26
Location: Curitiba, Brazil

View user's profile Send private message Visit poster's website

PostPosted: Wed Jun 16, 2004 1:06 pm     Reply with quote

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

View user's profile Send private message

PostPosted: Wed Jun 16, 2004 1:46 pm     Reply with quote

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

View user's profile Send private message

PostPosted: Wed Jun 16, 2004 9:00 pm     Reply with quote

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 Very Happy


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

View user's profile Send private message

Debouncing switches
PostPosted: Thu Jun 17, 2004 6:31 am     Reply with quote

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

View user's profile Send private message

PostPosted: Fri Aug 06, 2004 5:49 am     Reply with quote

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








PostPosted: Fri Aug 06, 2004 7:03 am     Reply with quote

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???
PostPosted: Fri Aug 06, 2004 11:25 am     Reply with quote

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

View user's profile Send private message Send e-mail Visit poster's website MSN Messenger

PostPosted: Fri Aug 06, 2004 11:54 am     Reply with quote

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
Display posts from previous:   
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion All times are GMT - 6 Hours
Goto page 1, 2  Next
Page 1 of 2

 
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