Author Topic: Polling to Interrupt  (Read 6923 times)

0 Members and 1 Guest are viewing this topic.

Offline vidamTopic starter

  • Supreme Robot
  • *****
  • Posts: 423
  • Helpful? 1
  • Robotronics.org
    • DC/MD/VA Robotics and Automation Team
Polling to Interrupt
« on: June 25, 2008, 02:15:24 PM »
Hi, I want to change this polling code to use interrupts for atmega 168. Can anyone tell me how to do it?

Code: [Select]

float pulsein(int pin_number)
{

   //set up timer for prescaler 256
   TCCR2B |= (1 << CS22);
   unsigned int positive_width, negative_width;
   positive_width = 0, negative_width=0;
   // for 4 pulse measurements
   while(TCNT2 <= 18750){
      while ( PIND & (1<<pin_number)){
        positive_width++;
     }
        negative_width++;
}

   return (positive_width/4);

}
     
       


  }

unsigned int pulse_width = duration;

Start Timer at 50 Hz.


while(PIND & (1 << PIND2))
{
  pulse_width++;
}

  negative_pulse_width++;

}

// End timer after 4 cycles at 50 Hz

Divide positive_pulse_width / 3
Divide negative_pulse_width / 3

pulse _width = positive_pulse_width / 3;

Offline bens

  • Expert Roboticist
  • Supreme Robot
  • *****
  • Posts: 335
  • Helpful? 3
Re: Polling to Interrupt
« Reply #1 on: June 25, 2008, 02:22:00 PM »
Look up pin-change interrupts in the datasheet (or use an external interrupt pin and look up external interrupts in the datasheet).

Offline vidamTopic starter

  • Supreme Robot
  • *****
  • Posts: 423
  • Helpful? 1
  • Robotronics.org
    • DC/MD/VA Robotics and Automation Team
Re: Polling to Interrupt
« Reply #2 on: June 25, 2008, 03:08:14 PM »
Can pin-change interrupts be detected on a rising edge and a falling edge. how can I make the distinction?


Offline bens

  • Expert Roboticist
  • Supreme Robot
  • *****
  • Posts: 335
  • Helpful? 3
Re: Polling to Interrupt
« Reply #3 on: June 25, 2008, 03:23:44 PM »
External interrupts can be set to trigger on specific edges.  Pin-change interrupts will occur whenever the value of the pin changes.  To tell what kind of transition happened, just look at the current state of the pin (if it's high, you detected a rising edge; if it's low, you detected a falling edge).  Because of the way pin-change interrupts work, this kind of detection can get a bit more tricky if you are trying to work with pulses on multiple pins.  In this case, store the state of the bank of pins (i.e. the PINx register) right before you enable the pin-change interrupt for that bank.  Then when the interrupt occurs, compare your stored state to the current state to find out which bits have changed (this also gives you the type of transition).  Store the current state to your global and repeat.  You will get an interrupt every time one of the bank's pins you have masked for changes, and you can compare the previous state to the current state to determine which one(s).

- Ben

Offline vidamTopic starter

  • Supreme Robot
  • *****
  • Posts: 423
  • Helpful? 1
  • Robotronics.org
    • DC/MD/VA Robotics and Automation Team
Re: Polling to Interrupt
« Reply #4 on: June 25, 2008, 06:33:38 PM »
I have written some code and now I have more questions. How do I calculate the timer after interrupt????


Code: [Select]
WORD pulsein(int pin_number)
{
switch(pin_number)
{
case 4:
PCMSK2 |= (1<<PCINT20); //  tell pin change mask to listen to pin21

break;

case 5:
PCMSK2 |= (1<<PCINT21); //  tell pin change mask to listen to pin20


break;

case 1:
PCMSK2 |= (1<<PCINT17); //  tell pin change mask to listen to pin17

break;
        } // end switch
PCICR |= (1 << PCIE2); // enabled interrupts on PCINT23..16 pin
SREG |= (1 << 7); // enable global interrupts
           

// get timer after interrupt????

    pulse_width = pulse_start - current timer;

   return pulse_width;

}


SIGNAL (SIG_PCINT)
{
   // One of pins 21, 20, or 17 just changed states
   // check if rising edge on pin input

  if(PIND & (1 << PIN4))
    //rising edge
   {
   // start timer to measure pulse width

    pulse_start = start timer


   }
 else(PIND & (1<<PIN5))
 {
   // start timer to measure pulse width

    pulse_start = start timer

 }
 else(PIND & (1<<PIN1))
{
   // start timer to measure pulse width

   pulse_start = start timer
}


}



Offline bens

  • Expert Roboticist
  • Supreme Robot
  • *****
  • Posts: 335
  • Helpful? 3
Re: Polling to Interrupt
« Reply #5 on: June 25, 2008, 06:46:07 PM »
Use a hardware timer such as timer1 set with a prescaler appropriate for the expected duration of the pulse and the measurement resolution you desire.  You don't have to do anything fancy with the timer, just run it in normal mode.  Take a look at the datasheet for more information on timer1 and its registers.

Note: be sure that your pulse will happen before the timer overflows, otherwise your time measurement will be wrong.  If you can't be sure of this, make sure you notice when the timer is about to overflow and set a flag that will tell you that the current pulse was too long to measure.

- Ben

P.S. I think your PC interrupt ISR is wrong.  The argument to the ISR should have a number indicating which pin-change bank the ISR is for.  The mega168 has three pin change banks, each one of which has its own interrupt: one for port B, one for port C, and one for port D.  As you seem to have already noticed, you use a pin change mask to select which pins in a bank should generate a PC interrupt for that bank (you can use the mask to restrict it to a single pin, which makes things easy, or you can set it up so multiple pins in the bank can trigger that bank's interrupt).

Offline vidamTopic starter

  • Supreme Robot
  • *****
  • Posts: 423
  • Helpful? 1
  • Robotronics.org
    • DC/MD/VA Robotics and Automation Team
Re: Polling to Interrupt
« Reply #6 on: June 25, 2008, 06:56:26 PM »
Use a hardware timer such as timer1 set with a prescaler appropriate for the expected duration of the pulse and the measurement resolution you desire.  You don't have to do anything fancy with the timer, just run it in normal mode.  Take a look at the datasheet for more information on timer1 and its registers.

When the interrupt is triggered I start the timer with the prescaler set: TCCR2B |= (1 << CS22);

Quote
Note: be sure that your pulse will happen before the timer overflows, otherwise your time measurement will be wrong.  If you can't be sure of this, make sure you notice when the timer is about to overflow and set a flag that will tell you that the current pulse was too long to measure.

I think a CTC timer will work in this case? It is the clear timer count when reaching overflow.

Quote

P.S. I think your PC interrupt ISR is wrong.  The argument to the ISR should have a number indicating which pin-change bank the ISR is for.  The mega168 has three pin change banks, each one of which has its own interrupt: one for port B, one for port C, and one for port D.  As you seem to have already noticed, you use a pin change mask to select which pins in a bank should generate a PC interrupt for that bank (you can use the mask to restrict it to a single pin, which makes things easy, or you can set it up so multiple pins in the bank can trigger that bank's interrupt).

I looked at the datasheet and can't get information on ISR for the interrupt ????

Offline bens

  • Expert Roboticist
  • Supreme Robot
  • *****
  • Posts: 335
  • Helpful? 3
Re: Polling to Interrupt
« Reply #7 on: June 25, 2008, 07:12:13 PM »
When the interrupt is triggered I start the timer with the prescaler set: TCCR2B |= (1 << CS22);

I suggest you start the timer during the initialization phase of your program and just set the timer1 counter to zero when the interrupt is triggered:

TCNT1 = 0;

Quote
I think a CTC timer will work in this case? It is the clear timer count when reaching overflow.

There's no need to use CTC mode.  CTC allows you to make it so that the timer overflows before it reaches its theoretical maximum of 0xFFFF.  You have no reason to artificially decrease this overflow point as all this does is restrict the duration you can time.  Normal mode is simpler to use and should work just fine for you.  The timer counter will count from 0 to 0xFFFF before overflowing back to 0.

Quote
I looked at the datasheet and can't get information on ISR for the interrupt ????

Assuming you're using a mega168, you have access to the following #defines (taken from the WinAVR file iomx8.h, which is included automatically in your program by <avr/io.h> if your device is set as mega168):

/* Pin Change Interrupt Request 0 */
#define PCINT0_vect         _VECTOR(3)
#define SIG_PIN_CHANGE0         _VECTOR(3)

/* Pin Change Interrupt Request 1 */
#define PCINT1_vect         _VECTOR(4)
#define SIG_PIN_CHANGE1         _VECTOR(4)

/* Pin Change Interrupt Request 2 */
#define PCINT2_vect         _VECTOR(5)
#define SIG_PIN_CHANGE2         _VECTOR(5)

Use the one that corresponds to the bank that has the pin(s) you want to generate interrupts.  For example, you would use a PCINT2_vect ISR for pins PCINT0 - PCINT7 (i.e. pins PB0 - PB7).

- Ben

Offline vidamTopic starter

  • Supreme Robot
  • *****
  • Posts: 423
  • Helpful? 1
  • Robotronics.org
    • DC/MD/VA Robotics and Automation Team
Re: Polling to Interrupt
« Reply #8 on: June 25, 2008, 07:20:29 PM »

Assuming you're using a mega168, you have access to the following #defines (taken from the WinAVR file iomx8.h, which is included automatically in your program by <avr/io.h> if your device is set as mega168):

/* Pin Change Interrupt Request 0 */
#define PCINT0_vect         _VECTOR(3)
#define SIG_PIN_CHANGE0         _VECTOR(3)

/* Pin Change Interrupt Request 1 */
#define PCINT1_vect         _VECTOR(4)
#define SIG_PIN_CHANGE1         _VECTOR(4)

/* Pin Change Interrupt Request 2 */
#define PCINT2_vect         _VECTOR(5)
#define SIG_PIN_CHANGE2         _VECTOR(5)

Use the one that corresponds to the bank that has the pin(s) you want to generate interrupts.  For example, you would use a PCINT2_vect ISR for pins PCINT0 - PCINT7 (i.e. pins PB0 - PB7).

- Ben

I'm looking at iomx8.h and the datasheet and am not seeing how PB0-PB7 relates to PCINT0-PCINT7? How did you link these?
I'm using Pin D 4,5,1


Offline bens

  • Expert Roboticist
  • Supreme Robot
  • *****
  • Posts: 335
  • Helpful? 3
Re: Polling to Interrupt
« Reply #9 on: June 25, 2008, 07:40:38 PM »
You can just look at page 2 of the datasheet, which shows all of the mega168's pins.  For example, pin 1 is described as:

(PCINT19/OC2B/INT1) PD3

What this means is that pin PD3 is PCINT19, OC2B (timer2 PWM output B), and INT1 (external interrupt 1).

You can also look at section 13 of the datasheet (I/O ports).  Page 79 starts the description of the alternate functions of port B pins.  Here you can see which pins correspond to which pin-change interrupt pins.  What ends up happening is that port B corresponds to PCINT bank 0, port C corresponds to PCINT bank 1, and port D corresponds to PCINT bank 2.  You'll need to know the relationship between pin and pin-change enumeration to properly mask for the pins you want.

- Ben

Offline vidamTopic starter

  • Supreme Robot
  • *****
  • Posts: 423
  • Helpful? 1
  • Robotronics.org
    • DC/MD/VA Robotics and Automation Team
Re: Polling to Interrupt
« Reply #10 on: June 25, 2008, 07:48:58 PM »
Thanks...alot...Ben  :)

Here is the final code, I think. Once more let me know if this is the correct methodology or if I left anything out?
[edit] I made some minor edits to the code. pwm_incoming is a global variable of type int.


Code: [Select]

WORD pulsein(int pin_number)
{
switch(pin_number)
{
case 4:
PCMSK2 |= (1<<PCINT20); //  tell pin change mask to listen to pin20
break;

case 5:
PCMSK2 |= (1<<PCINT21); //  tell pin change mask to listen to pin21
break;

case 1:
PCMSK2 |= (1<<PCINT17); //  tell pin change mask to listen to pin17
break;
}

PCICR |= (1 << PCIE2); // enabled interrupts on PCINT23..16 pin
SREG |= (1 << 7); // enable global interrupts

while(1){

}
return pwm_incoming;
}


ISR (PCINT2_vect)
{
uint8_t tcnt1;
   // One of pins 21, 20, or 17 just changed states

   // check if rising on pin input
  if((PIND & (1 << PIN4)) || (PIND & (1<<PIN5)) || (PIND & (1<<PIN1)) )
    //rising edge
   { 
    // start timer to measure pulse width
      TCNT1 = 0; 
      return;
    }

/* Stop timer 1, current value is pulse width. */
  tcnt1 = TCNT1;
  TCCR1B = (1 << CS12); // prescale at 256
  pwm_incoming = tcnt1;
 
}
« Last Edit: June 25, 2008, 08:19:59 PM by vidam »

Offline bens

  • Expert Roboticist
  • Supreme Robot
  • *****
  • Posts: 335
  • Helpful? 3
Re: Polling to Interrupt
« Reply #11 on: June 25, 2008, 09:25:23 PM »
Quote
TCCR1B = (1 << CS12); // prescale at 256

You should move this outside of your ISR and call this as part of your program's initialization routine.  As it is now, this line doesn't get called until the falling edge of your first pulse, meaning that your first pulse won't be measured.  You only need to start the timer once.  You can just leave it running after that.

Also, I see no way for you to get out of the infinite while loop in your pulsein() function.  Is this intentional or are you just trying to delay until after you've detected a pulse?  If it's the latter, that's not currently what your code does.

Other problems:

1) You aren't currently making a distinction between your pins, and you have no way of telling which pin has changed.  This means your code currently doesn't work.  If you get a rising edge on PD1 and then a falling edge on PD4, your code will register that as a pulse.  Even worse, if you detect a falling edge on PD1 but PD4 or PD5 is high, you will instead think you have detected a rising edge.

You should have separate timing variables for each pin that can receive a pulse, and you need to store the previous state of your pins so that you can use this as a point of comparison in your ISR to determine which pin has changed.  Does this make sense?  This will require cleverer use of timer1 (rather than resetting it to 0 when a pulse starts, you should just record it as the start time and when the pulse ends, compute the pulse length as TCNT1 - startTime).

2) You aren't protecting yourself against abnormally long pulses that are longer than timer1 can measure.  Trying to measure such a pulse will give you an incorrect value for the pulse length.  I'm not sure if this is a concern, but a robust program would take precautions against such things.

- Ben

Offline vidamTopic starter

  • Supreme Robot
  • *****
  • Posts: 423
  • Helpful? 1
  • Robotronics.org
    • DC/MD/VA Robotics and Automation Team
Re: Polling to Interrupt
« Reply #12 on: June 25, 2008, 10:09:16 PM »
Quote
TCCR1B = (1 << CS12); // prescale at 256

You should move this outside of your ISR and call this as part of your program's initialization routine.  As it is now, this line doesn't get called until the falling edge of your first pulse, meaning that your first pulse won't be measured.  You only need to start the timer once.  You can just leave it running after that.

Fixed.

Quote
Also, I see no way for you to get out of the infinite while loop in your pulsein() function.  Is this intentional or are you just trying to delay until after you've detected a pulse?  If it's the latter, that's not currently what your code does.

I am not sure how to fix it. I removed the infinite loop.

Quote
Other problems:

1) You aren't currently making a distinction between your pins, and you have no way of telling which pin has changed.  This means your code currently doesn't work.  If you get a rising edge on PD1 and then a falling edge on PD4, your code will register that as a pulse.  Even worse, if you detect a falling edge on PD1 but PD4 or PD5 is high, you will instead think you have detected a rising edge.

You should have separate timing variables for each pin that can receive a pulse, and you need to store the previous state of your pins so that you can use this as a point of comparison in your ISR to determine which pin has changed.  Does this make sense?  This will require cleverer use of timer1 (rather than resetting it to 0 when a pulse starts, you should just record it as the start time and when the pulse ends, compute the pulse length as TCNT1 - startTime).



I think I addressed this problem in the code at the end of this post.

Quote

2) You aren't protecting yourself against abnormally long pulses that are longer than timer1 can measure.  Trying to measure such a pulse will give you an incorrect value for the pulse length.  I'm not sure if this is a concern, but a robust program would take precautions against such things.

- Ben

I'm not sure how to handle this precaution. I think when I reset the timer it should take care of this problem after checking for overflow.

Code: [Select]
WORD pulsein(int pin_number)
{

PCMSK2 = 0x00;

switch(pin_number)
{
case 4:
PCMSK2 |= (1<<PCINT20); //  tell pin change mask to listen to pin20
break;

case 5:
PCMSK2 |= (1<<PCINT21); //  tell pin change mask to listen to pin21
break;

case 1:
PCMSK2 |= (1<<PCINT17); //  tell pin change mask to listen to pin17
break;
}

PCICR |= (1 << PCIE2); // enabled interrupts on PCINT23..16 pin
SREG |= (1 << 7); // enable global interrupts


return pwm_incoming;
}


ISR (PCINT2_vect)
{
uint8_t start_time5, start_time4, start_time1,tcnt1;
   // One of pins 21, 20, or 17 just changed states
int p1=0, p4=0, p5=0;
if(TCNT1 > 65535)
    TCNT1=0;
// we only care if there was a change from 0 to 1 from the previous state
// check if rising on pin input
if((state_p4==0) && (PIND & (1 << PIN4)))
{
p4=1;
start_time4 = TCNT1;
state_p4 = 1;
return;
}
else if((state_p5==0) && (PIND & (1<<PIN5)))
  {
  p5=1;
      start_time5 = TCNT1;
  state_p5 = 1;
      return;
  }

    else if((state_p1==0) && (PIND & (1<<PIN1)))
    {
p1 = 1;
start_time1 = TCNT1;
state_p1 = 1;
return;
    }

/* Stop timer 1, current value is pulse width. */
 
  tcnt1 = TCNT1;
    if(p4)
  pwm_incoming = tcnt1 - start_time4;
  if(p5)
  pwm_incoming = tcnt1 - start_time5;
  if(p1)
pwm_incoming = tcnt1 - start_time1;

 
}

Offline bens

  • Expert Roboticist
  • Supreme Robot
  • *****
  • Posts: 335
  • Helpful? 3
Re: Polling to Interrupt
« Reply #13 on: June 26, 2008, 12:56:47 AM »
I can try to take a closer look at your code tomorrow when I have more time, but for now I can make a few comments.  First of all, I may have had you make things slightly more complicated than necessary because I thought you were trying to measure pulses on multiple pins at the same time.  I didn't notice that you were only enabling the pin-change interrupt for one pin at a time, which simplifies things.  Basically I recommend you just use some globals to store state information so that when the ISR occurs you know what's going on.  For example, if the pin goes low, does that mean that your high pulse just ended or does that mean that you are now ready to wait for the next high pulse to begin because you called pulseIn() in the middle of a high pulse?

So when you call pulseIn, store in a global the pin you care about (so the ISR can know about it) and set the measuring state as "ready" or soemthing like that.  Then when the ISR happens, if the state of the pin is low, you don't have to do anything because you just saw the end of a pulse that was already in progress when pulseIn() was called.  When the ISR happens, the state of the pin you care about is high, and the measuring state is "ready", you know it's now time to start measuring the pulse, so you can set TCNT1 = 0 and the measuring state as "measuring".  When the ISR happens, the state of the pin you care about is low, and the measuring state is "measuring", you know your pulse is done, at which point you can save TCNT1 as your pulselength and set the measuring state as "done", which can be your flag to the rest of your program that you have a valid new pulse measurement now.

If you want watch out for the case where TCNT1 has overflowed and hence your pulse measurement is bad, you can enable the timer1 overflow interrupt and use that to set a "bad pulse" global that can signal your program to ignore the most recently measured pulse, but you'll have to be careful to only have the timer1 overflow interrupt enabled during that brief window while you're measuring the high portion of the pulse (i.e. between the time when you set TCNT1 = 0 and the time when you next set pulsewidth = TCNT1).

That's probably how I would implement pulseIn() if I wanted it to be purely interrupt-driven.  If I didn't care about having pulseIn() block execution I'd probably just implement it using polling because that is much, much easier.  But interrupts let you go off and do other things while you're measuring the pulses, so that can be a big plus.

- Ben

Offline vidamTopic starter

  • Supreme Robot
  • *****
  • Posts: 423
  • Helpful? 1
  • Robotronics.org
    • DC/MD/VA Robotics and Automation Team
Re: Polling to Interrupt
« Reply #14 on: June 26, 2008, 08:18:13 AM »
Here is the code, again and I added a check for interrupt if timer overflow.

Code: [Select]
// Global variables
int pwm_incoming;
int state_p1=0; // initial state on pin1
int state_p4=0; //initial state on pin 4
int state_p5 = 0; //initial state on pin 5

// init code (main)
   TCCR2B |= (1 <<CS22); //prescale clock at 256
   TCNT2 = 0;                   // start clock
   TIMSK2 |= (1<<TOIE2);  // enable check for overflow of timer/counter 2
   state_p1=0; state_p4=0, state_p5=0; // set initial state on pins to 0


WORD pulsein(int pin_number)
{
  PCMSK2 = 0x00;  // probably unnecessary step
  pwm_incoming = 0;
  switch(pin_number){
    case 1:
      PCMSK2 |= (1<<PCINT17);
     break;
   case 4:
     PCMSK2 |= (1<<PCINT20);
  case 5:
     PCMSK2 |= (1<<PCINT21);
}//end switch
return pwm_incoming;
} // end pulsein

// an interrupt for detecting pin state changes

ISR (PCINT2_vect)
{
  uint8_t start_t1; // store initial time at state change
  uint8_t tcnt1; // for final time
  if((state_p1==0) && (PIND & (1<<PIN1))){
    start_t1= TCNT2;
    state_p1 = 1;
   }
   else if(!(PIND & (1<<PIN4)))
     state_p4 = 0;
  if((state_p4==0) && (PIND & (1<<PIN4))){
    start_t1= TCNT2;
    state_p4 = 1;
   }
   else if(!(PIND & (1<<PIN4)))
     state_p4 = 0;
if((state_p5==0) && (PIND & (1<<PIN5))){
    start_t1= TCNT2;
    state_p5 = 1;
   }
   else if(!(PIND & (1<<PIN5)))
     state_p5 = 0;

    // stop the timer
   tcnt1 = TCNT2;
  pwm_incoming = tcnt1 - start_t1;
 

}


// timer interrupt if overflow on timer counter 2
ISR (TIMER2_OVF_vect){
  // stop the clock
  TCCR2B = 0x00;
  TCCR2B != (1 <<CS22); // prescale value of 256
  TCNT2=0; //reset counter
  TIMSK2 |= (1<<TOIE2); // enable check for overflow
  pwm_incoming = 0; // may have gotten a bad pulse

}

Offline bens

  • Expert Roboticist
  • Supreme Robot
  • *****
  • Posts: 335
  • Helpful? 3
Re: Polling to Interrupt
« Reply #15 on: June 26, 2008, 12:16:31 PM »
I feel like you'd be better off actually testing it out and trying to get it to work for yourself rather than having me try to imagine what it will do.  I've given you what my approach would be in pseudocode and I've somewhat walked you through the registers you would need to use, so I think you can take it from here and make things work through testing and modification.

During my three seconds of looking at it I did notice this, however:

TCCR2B != (1 <<CS22); // prescale value of 256

I believe you want to use the operator |= here, not !=.  I assume this was a typo but you need to be more careful.

Also, you don't need to do anything with TCCR2B in your ISR.

- Ben

Offline vidamTopic starter

  • Supreme Robot
  • *****
  • Posts: 423
  • Helpful? 1
  • Robotronics.org
    • DC/MD/VA Robotics and Automation Team
Re: Polling to Interrupt
« Reply #16 on: June 26, 2008, 07:32:23 PM »
Thanks for the guidance on selecting the pin interrupts, settings, and clock/timer settings. The atmega168 datasheet is vast >300 pages which is daunting to a beginner.

 I had bought an expensive AVR development kit that did fail to teach interrupts on a level I needed.

I will be testing the code out in the next few days.


Offline bens

  • Expert Roboticist
  • Supreme Robot
  • *****
  • Posts: 335
  • Helpful? 3
Re: Polling to Interrupt
« Reply #17 on: June 26, 2008, 09:15:58 PM »
The atmega168 datasheet is vast >300 pages which is daunting to a beginner.

I suggest you just take it a little bit at a time.  Try to understand the basics, and then read the specific sections you need when you want to use a hardware peripheral like the ADC or the UART or pin-change interrupts.

- Ben

 


data_list