Go Down

Topic: pulseIn without pausing the program... (Read 7540 times) previous topic - next topic

zerocle

Im working on using a remote control to do some functions on my arduino... Im using the code from here: http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1176098434/7 for my remote input. The function works great, it does pretty much everything I need, except it stops the program when it waits for the remote input. It uses a while loop in combination with the pulseIn to test the length of the input, Ive tried replacing the while for an if, which also works, except it still stops the program on the pulseIn command. I was thinking the best way to get this working would be to use interrupts somehow, but im new to microcontrollers and need some help fully understanding interrupts. Would that be the best route or would I be better off accomplishing this some other way?

ckiick

This is just off the top of my head, so read the appropriate documentation carefully.

Interrupts would be the way to go. That way the arduino can do other things and it will get yanked when it needs to do something.

The basic approach would be to put the IR input onto one of the external interrupt pins.  You then write an interrupt routine that gets called when the input changes.  The interrupt routine would do a couple of very simple things.

interrupt()
   ready = 0
   if pulse start
        save = clock (either millis or CLOCK0)
   else
        duration = clock - save
        ready = 1

Then your main loop can, when it gets a chance, look at ready and if it is set to 1, read the pulse length in duration.  Be sure you know what units you are using.

There's more to it than that, but this is the basic idea.  Look at the interrupt support in the Wiring reference, also check out the ATMega data sheet.  Plus read the source code for PulseIn.

HTH

Chris J. Kiick
Robot builder and all around geek.

mem

If you use the input capture pin (pin 8) you can have most of the work done automatically in the interrupt handler. I extracted the following example that shows how you can use it but the sketch has  never been run in this form so may need a little testing

Code: [Select]
             
volatile unsigned int Ticks;         // holds the pulse count as .5 us ticks
int icpPin = 8;                       // this interrupt handler must use pin 8


ISR(TIMER1_CAPT_vect){
  if( bit_is_set(TCCR1B ,ICES1)){       // was rising edge detected ?  
      TCNT1 = 0;                        // reset the counter      
  }
  else {                                // falling edge was detected  
       Ticks = ICR1;
  }    
  TCCR1B ^= _BV(ICES1);                 // toggle bit value to trigger on the other edge    
}

void setup()                    // run once, when the sketch starts
{
 Serial.begin(9600);  
 pinMode(icrPin,INPUT);
 TCCR1A = 0x00;         // COM1A1=0, COM1A0=0 => Disconnect Pin OC1 from Timer/Counter 1 -- PWM11=0,PWM10=0 => PWM Operation disabled
 TCCR1B = 0x02;         // 16MHz clock with prescaler means TCNT1 increments every .5 uS (cs11 bit set
 Ticks = 0;             // default value indicating no pulse detected
 TIMSK1 = _BV(ICIE1);   // enable input capture interrupt for timer 1
}

int getTick() {
 int akaTick;       // holds a copy of the tick count so we can return it after re-enabling interrupts
 cli();             //disable interrupts
 akaTick = Ticks;
 sei();             // enable interrupts
 return akaTick;  
}

void loop()                     // run over and over again
{
 static int prevTick = 0;

 if( getTick()  != prevTick) {
    prevTick = getTick();
    Serial.print( prevTick);     // print the tick value only when it changes    
 }
}


In the above sketch, calling getTick will return the width of a pulse on pin 8 in .5 microsecond steps. This handles pulses up to a little over 30 milliseconds. If longer times are needed then a larger prescale divider should be used.  Note that timer1 is used so some of the PWM outputs will not be available.

zerocle

#3
Feb 01, 2008, 09:09 pm Last Edit: Feb 01, 2008, 09:12 pm by zerocle Reason: 1
So if I understand your psuedocode correctly, what your doing is pretty much writing your own pulse command, it records the clock time when the signal drops low, then once the signal goes high again subtracts the saved time from the current clock time? makes sense, now I just need to figure out how to impliment that in my program. I will try this out when I get home, unfortunately im at work right now :(

Wow, thanks for that last post, you deffinately posted that while i was responding to the second post! lol (I love this forum :) )
Im having some trouble following that code you posted, but I will do some back research on it and try and follow it a little better, thanks alot for the help guys and or gals :)

mem

zerocle, what is the maximum duration of your pulses, I can modify the code so you can test it. Also, do you want to start measuring from the rising or falling edge?

zerocle

well Im using an old sony stereo remote that seems to work perfectly with the timing system posted on the article i linked to. The pulses start at the falling edge, my IR reciever is HIGH when its inactive, then pulses low when it gets a signal, stores them in the 12 integer array, then computes a key code, for example the power button on my remote is key code 21, so right now I have the code wait for the pulseIn(IRReciever, LOW), then it checks to see if it is less than 2200 milliseconds, then reads the rest of the pulses, computes the keycode, and returns it, when I get that return, I run it through an if statement, (this is all off memory)
int keycode = getIRKey();
if(keycode == 21)
{
//do magical things
}

the problem is the getIRKey function actually waits(pauses the program) until it gets the leading edge of the infrared code, so what i need it to do is check to see if that leading edge/keycode has been read, if not then loop through again, check, if not loop through, and then if it has do some stuff, in this case, magical things lol

Thanks again!!!!

mem

#6
Feb 01, 2008, 11:20 pm Last Edit: Feb 01, 2008, 11:21 pm by mem Reason: 1
I have modified the sketch so that it starts to measure from the trailing edge and stores the pulse lengths in an array. Its thrown together really quickly so wont work first time but I hope it gives you an idea of what you can do. You will need to flesh out the DecodeFrame function for anything useful to be detected.

Code: [Select]

#define icpPin            8         // this interrupt handler must use pin 8
#define TICKS_PER_uS      2          // number of timer ticks per microsecond
#define NUMBER_PULSES     13              // number of pulses in on frame of sony remote  
#define START_PULSE_LEN   (2400 * TICKS_PER_uS) // start pulse is 2.4ms long (note clock counts in 0.5 us ticks)
volatile unsigned int Pulses[ NUMBER_PULSES+ 1]; // array holding pulses as 1 us ticks
volatile uint8_t  PulseCount;             // number of pulses received in current frame

                 
ISR(TIMER1_CAPT_vect){
  if(! bit_is_set(TCCR1B ,ICES1)){       // was falling edge detected ?  
      TCNT1 = 0;               // reset the counter      
  }
  else {                          // rising  edge was detected  
       if(ICR1 >= START_PULSE_LEN)
           PulseCount = 0;    
       if(PulseCount < NUMBER_PULSES) {
          Pulses[++PulseCount] = ICR1 / TICKS_PER_uS;  // store pulse length as microsoeconds
       }
  }    
  TCCR1B ^= _BV(ICES1);                 // toggle bit value to trigger on the other edge    
}

void setup()                    // run once, when the sketch starts
{
 Serial.begin(9600);  
 pinMode(icpPin,INPUT);
 PulseCount = 0;  
 TCCR1A = 0x00;         // COM1A1=0, COM1A0=0 => Disconnect Pin OC1 from Timer/Counter 1 -- PWM11=0,PWM10=0 => PWM Operation disabled
 TCCR1B = 0x02;         // 16MHz clock with prescaler means TCNT1 increments every .5 uS (cs11 bit set
 TIMSK1 = _BV(ICIE1);   // enable input capture interrupt for timer 1
}

int DecodeFrame() {
 int result;  
 cli();             //disable interrupts
 for( int i=1; i <=  NUMBER_PULSES; i++){
     result = 0; // code here to decode pulses
                     // NOTE- interrupts are off here so do nothing that takes lots of time.
 }  
 PulseCount = 0;     // get ready for next frame.
 sei();             // enable interrupts
 return result;

}

void loop()                     // run over and over again
{
 int value;
 if(PulseCount  >= NUMBER_PULSES  ){
    value = DecodeFrame() ;
    Serial.print(value);
}
     
}

zerocle

mem, your both a saint and a devil, thank you so much for your code! but this last hour of work is going to really suck because now all I wanna do is go home and try it! I think I follow your code pretty well, and if I do have any problems, it shouldnt be to hard to fix, so ill let you know how it goes in an hour or so! THANKS AGAIN!

zerocle

sorry about being so slow on the response, some stuff came up, but it works great!!!! your code is awesome! thanks man, the only problem is i get doubles some times, I was thinking I should add like a half second pause after it reads something, you have any ideas?

mem

#9
Feb 02, 2008, 06:36 am Last Edit: Feb 02, 2008, 06:46 am by mem Reason: 1
Glad its helped.

By doubles do you mean a repeat of the previous frame? I think the protocol says that pulses will repeat every 30ms or so if a button is held down. Is this what you are seeing?

One enhancement you may want to consider implementing is to have the interrupt handler do the decoding and store the result in an array. This has the benefit that you are much less likely lose any data if your app was busy with a task that delayed calls to get the next result. This could be implemented using simple queuing logic.

Have a search to see if anyone has posted code for reading and writing to queue buffers. If not, you could modify the code I posted here by removing the function to take the average value (this has no meaning in yr app) and add a function to read data off the head of the Queue (add a variable called head that indexes the next unread decoded value.)  

If your queue logic prevented reading and writing to the same location ( i.e. never read from the head if it equals the tail then you can remove the enable/disable interrupt code.

Anyway, good luck with the project.

Oh and post your code. I would like to see how its turned out

eustace

Thanks for posting that link, mem.  I'm going to use the Qdata code from that thread to try to smooth noisy ultrasonic sensor and encoder data.

zerocle

lol im really, really going to try and follow that, ill post it when its done with that, but here is the code as it stands right now, I really wanna learn how to use some timer functions, for example something to check elapsed time, so that when I do animations they actually, ya know, work... ah well it will make sense in the code...

Mind you, alot of this isnt my code, and its VERY sloppy, this is the first write
Code: [Select]
#define icpPin            8         // this interrupt handler must use pin 8
#define TICKS_PER_uS      2          // number of timer ticks per microsecond
#define NUMBER_PULSES     13              // number of pulses in on frame of sony remote  
#define START_PULSE_LEN   (2400 * TICKS_PER_uS) // start pulse is 2.4ms long (note clock counts in 0.5 us ticks)

int calibrationTime = 10;        
long unsigned int lowIn;        
long unsigned int pause = 5000;  
boolean lockLow = true;
boolean takeLowTime;  
int pirPin = 3;
int motionLED = 11;
int ir_pin = 2;
int powerLED = 12;
int outputLEDS[3];
int debug = 0;
int start_bit = 2000;
int bin_1 = 1000;
int bin_0 = 400;
int LEDAnimation = 0;

boolean playAnimation = false;
int animationStep = 0;
int OutputLED = 12;
boolean Calibrated;
boolean CheckSensor;
volatile unsigned int Pulses[ NUMBER_PULSES+ 1];
volatile uint8_t  PulseCount;

ISR(TIMER1_CAPT_vect){
  if(! bit_is_set(TCCR1B ,ICES1)){ 
      TCNT1 = 0;
  }
  else {
       if(ICR1 >= START_PULSE_LEN)
           PulseCount = 0;    
       if(PulseCount < NUMBER_PULSES) {
          Pulses[++PulseCount] = ICR1 / TICKS_PER_uS;
       }
  }    
  TCCR1B ^= _BV(ICES1);
}
void setup(){
 Calibrated = false;
 CheckSensor = false;
 outputLEDS[0] = 10;
 outputLEDS[1] = 9;
 outputLEDS[2] = 7;
 Serial.begin(9600);
 pinMode(pirPin, INPUT);
 pinMode(powerLED, OUTPUT);
 pinMode(motionLED, OUTPUT);
 pinMode(outputLEDS[0], OUTPUT);
 pinMode(outputLEDS[1], OUTPUT);
 pinMode(outputLEDS[2], OUTPUT);
 pinMode(ir_pin, INPUT);
 digitalWrite(powerLED, HIGH);          //not ready yet
 digitalWrite(motionLED, LOW);
 digitalWrite(outputLEDS[0], LOW);
 digitalWrite(outputLEDS[1], LOW);
 digitalWrite(outputLEDS[2], LOW);
 pinMode(icpPin,INPUT);
 PulseCount = 0;  
 TCCR1A = 0x00;
 TCCR1B = 0x02;
 TIMSK1 = _BV(ICIE1);
 delay(50);
}

int DecodeFrame() {
 int result;  
 cli();
 if(debug == 1) {
   Serial.println("-----");
 }
 for(int i=1;i<NUMBER_PULSES;i++) {
   if (debug == 1) {
       Serial.print(Pulses[i]);
   }            
   if(Pulses[i] > bin_1) {
     Pulses[i] = 1;
   }  else {
     if(Pulses[i] > bin_0) {
       Pulses[i] = 0;
     } else {
      Pulses[i] = 2;
     }
   }
 }
 
 for(int i=1;i<NUMBER_PULSES;i++) {
   if(Pulses[i] > 1) {                  
     return -1;
   }
 }
 result = 0;  
 int seed = 1;                                      
 for(int i=1;i<NUMBER_PULSES;i++) {
   if(Pulses[i] == 1) {
     result += seed;
   }
   seed = seed * 2;
 }  
 PulseCount = 0;
 sei();
 return result;

}
void loop(){
 int value;
 if(PulseCount  >= NUMBER_PULSES  ){
    value = DecodeFrame() ;
    Serial.print(value);
 }
 if(value == 43)
 {
   CheckSensor = !CheckSensor;
   if(CheckSensor)
   {
     Serial.println("Powering on...");
     digitalWrite(powerLED, HIGH);
   }
   else
   {
     Serial.println("Powering down...");
     digitalWrite(powerLED, HIGH);
     digitalWrite(motionLED, LOW);
     playAnimation = false;
     Calibrated = false;
   }
 }
 if(value == 2659)
 {
   LEDAnimation ++;
   if(LEDAnimation > 2)
   {
     LEDAnimation = 0;
   }
 }
 if(CheckSensor == true)
 {
   if(Calibrated == true)
   {
     digitalWrite(motionLED, HIGH);
     CheckPIR();
   }
   else
   {
     CalibrateSensor();
     Serial.println("Calibration Complete, starting program...");
     digitalWrite(powerLED, LOW);
     Calibrated = true;
   }
 }
 else
 {
   Calibrated = false;
   digitalWrite(motionLED, LOW);
 }
 delay(100);
}
void CheckPIR()
{
   if(digitalRead(pirPin) == HIGH){
//TURN ON LEDS
   if(lockLow){  
     lockLow = false;            
     delay(50);
   }        
   takeLowTime = true;
 }

 if(digitalRead(pirPin) == LOW){      

   if(takeLowTime){
     lowIn = millis();          //save the time of the transition from high to LOW
     takeLowTime = false;       //make sure this is only done at the start of a LOW phase
   }
   if(!lockLow && millis() - lowIn > pause){  
     lockLow = true;  
   
     //TURN OFF LEDS

     delay(50);
   }
 }
 return;
}

axileon

hi...  actually i need to use the program for a R/C transmitter and receiver.. i intend to use the pulseIn function also. but i am having problems with the servo jittering....

is there another function to capture the timing of the incoming pulse from the receiver? i know of the pin 8 for timer 1 but i need 3 pins for 3 channels

mem

#13
Mar 03, 2008, 11:51 am Last Edit: Mar 03, 2008, 11:55 am by mem Reason: 1
Quote
hi...  actually i need to use the program for a R/C transmitter and receiver.. i intend to use the pulseIn function also. but i am having problems with the servo jittering....

is there another function to capture the timing of the incoming pulse from the receiver? i know of the pin 8 for timer 1 but i need 3 pins for 3 channels

This is a cross post! My I suggest that contributions to your request are posted in the other thread here: http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1204020386

(the problem with posting in more than one place is that people seeing only one thread may spend time composing and posting an answer to a question that has already been answered. If you ever find that this happens to you, you will see how annoying it can be  ;).

axileon

hey.. sorry about that... thanks for replying i will check out what u have written  :)

Go Up