Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 16 of 16

Thread: Timer Input Capture

  1. #1

    Timer Input Capture

    I need to decode the pulse stream from a Radio Control receiver using a Teensy3. Information about the RC hardware and signal is here http://www.frank-zhao.com/cache/vex_rx.php Below is working code for Teensy++ 2.0. Obviously those registers are not the same for Teensy3. I would like some guidance on the best way to get similar functionality. It must measure the pulsewidth of 1 to 8 pulses every 20mS without dramatically affecting the main loop. Each 20ms there is a single 7ms synch pulse and the remaining pulses are from 750uS to 2250uS (servo). I am aware that the Teensy3 ISR routines are still a work in progress. I would like this project to be one of the first to use them.
    Code:
    #ifndef ServoDecode_h
    #define ServoDecode_h
    //Attribution: ServoDecode library from mem at http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1228137503/all
    //adapted for Arduino 1.0.1 by Dustin Maki
    //Intended for Vex, but should work with any receiver giving access to the complete pulse train on a single pin
    #include "Arduino.h"
    // Teensy 2.0 ICP_PIN 22, Teensy++ 2.0 ICP_PIN 4
    #define ICP_PIN 4
    #define TICKS_PER_uS	2	    // number of timer ticks per microsecond
    typedef enum {NULL_state=-1, NOT_SYNCHED_state, ACQUIRING_state, READY_state, FAILSAFE_state } decodeState_t;
    String stateStrings[] = { "NOT_SYNCHED", "ACQUIRING", "READY", "in Failsafe"};
    #define MAX_CHANNELS    8	   // maximum number of channels we can store, don't increase this above 8
    #define MIN_IN_PULSE_WIDTH (750 * TICKS_PER_uS) //a valid pulse must be at least 750us (note clock counts in 0.5 us ticks)
    #define MAX_IN_PULSE_WIDTH (2250 * TICKS_PER_uS) //a valid pulse must be less than  2250us
    #define SYNC_GAP_LEN	(3000 * TICKS_PER_uS) // we assume a space at least 3000us is sync (note clock counts in 0.5 us ticks)
    //#define FAILSAFE_PIN   13  // if defined, this will set the given pin high when invalid data is received
    #define PULSE_START_ON_RISING_EDGE  0
    #define PULSE_START_ON_FALLING_EDGE (1<<ICES1)
    #define ACQUISITION_COUNT  8  // must have this many consecutive valid frames to transition to the ready state.
    volatile uint8_t pulseEnd = PULSE_START_ON_RISING_EDGE ; // default value
    static volatile uint16_t Pulses[ MAX_CHANNELS + 1]; // array holding channel pulses width value in microseconds
    static volatile uint16_t Failsafe[MAX_CHANNELS + 1]; // array holding channel fail safe values
    static volatile uint8_t Channel;	// number of channels detected so far in the frame (first channel is 1)
    static volatile uint8_t NbrChannels; // the total number of channels detected in a complete frame
    static volatile decodeState_t State;	   // this will be one of the following states:
    static volatile uint8_t stateCount;	   // counts the number of times this state has been repeated
    
    static void processSync()
    {
    // Sync was detected so reset the channel to 1 and update the system state
       Pulses[0] = ICR1 / TICKS_PER_uS;  // save the sync pulse duration for debugging
       if(State == READY_state) 
       {
    	   if( Channel != NbrChannels)
               {  // if the number of channels is unstable, go into failsafe
    		   State = FAILSAFE_state;
    	   }
       }
       else
       {
         if(State == NOT_SYNCHED_state)
         {
    	 State = ACQUIRING_state;	  // this is the first sync pulse, we need one more to fill the channel data array
    	 stateCount = 0;
         }
    	 else if( State == ACQUIRING_state)	
             {
    	    if(++stateCount > ACQUISITION_COUNT) 
                {
    		State = READY_state;	     // this is the second sync and all channel data is ok so flag that channel data is valid
    	        NbrChannels = Channel;       // save the number of channels detected
    	    }
             }
    	 else if( State == FAILSAFE_state)	
             {
    		 if(Channel == NbrChannels)
                     {  // did we get good pulses on all channels
    			State = READY_state;
    		 }
    	 }
       }
       Channel = 0;	 // reset the channel counter
    }
    
    // required interrupt service routines
    ISR(TIMER1_OVF_vect)
    {
      if(State == READY_state)
      {
        State = FAILSAFE_state;  // use fail safe values if signal lost
        Channel = 0; // reset the channel count
      }
    }
    
    ISR(TIMER1_CAPT_vect)
    {
      // we want to measure the time to the end of the pulse
      if( (_SFR_BYTE(TCCR1B) & (1<<ICES1)) == pulseEnd )
      {
        TCNT1 = 0;	 // reset the counter
        if(ICR1 >= SYNC_GAP_LEN)
        {   // is the space between pulses big enough to be the SYNC
    	processSync();
        }
        else if(Channel < MAX_CHANNELS) 
        {  // check if its a valid channel pulse and save it
    	if( (ICR1 >= MIN_IN_PULSE_WIDTH)  && (ICR1 <= MAX_IN_PULSE_WIDTH) )
            { // check for valid channel data
    	  Pulses[++Channel] = ICR1 / TICKS_PER_uS;  // store pulse length as microsoeconds
    	}
    	else if(State == READY_state)
            {
    	  State = FAILSAFE_state;  // use fail safe values if input data invalid
    	  Channel = 0; // reset the channel count
    	}
        }
      }
    }
    
    
    class ServoDecodeClass //ServoDecodeClass begin
    {
      public:
        ServoDecodeClass(){}
        
        void begin()
        {
          pinMode(ICP_PIN,INPUT);
          Channel = 0;
          State = NOT_SYNCHED_state;
          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)|_BV (TOIE1);   // enable input capture and overflow interrupts for timer 1
          for(uint8_t chan = 1; chan <=  MAX_CHANNELS; chan++)
        	  Failsafe[chan] = Pulses[chan]= 1500; // set midpoint as default values for pulse and failsafe
        }
        
        decodeState_t getState()
        {
          return State;
        }
        
        uint8_t getChanCount()
        {
          return NbrChannels;
        }
        
        void  setFailsafe(uint8_t chan, int16_t value)
        {
        // pulse width to use if invalid data, value of 0 uses last valid data
          if( (chan > 0) && (chan <=  MAX_CHANNELS)  ) 
          {
        	 Failsafe[chan] = value;
          }
        }
        void  setFailsafe()
        {
        // setFailsafe with no arguments sets failsafe for all channels to their current values
        // usefull to capture current tx settings as failsafe values
          if(State == READY_state)
            for(uint8_t chan = 1; chan <=  MAX_CHANNELS; chan++) 
            {
        	  Failsafe[chan] = Pulses[chan];
            }
        }
        
        int16_t GetChannelPulseWidth( uint8_t channel)
        {
          // this is the access function for channel data
          int16_t result = 0; // default value
          if( channel <=  MAX_CHANNELS)  
          {
             if( (State == FAILSAFE_state)&& (Failsafe[channel] > 0 ) )
        	   result = Failsafe[channel]; // return the channels failsafe value if set and State is Failsafe
        	 else if( (State == READY_state) || (State == FAILSAFE_state) )
             {
               bitClear(TIMSK1, _BV(ICIE1)|_BV (TOIE1));//disable Timer1 input capture and overflow interrupts 
        	   //cli();	 //disable interrupts
        	   result =  Pulses[channel] ;  // return the last valid pulse width for this channel
        	   //sei(); // enable interrupts
               bitSet(TIMSK1, _BV(ICIE1)|_BV (TOIE1));//enable Timer1 input capture and overflow interrupts
        	 }
          }
          return result;
        }
        
        
    };//ServoDecodeClass end
    
    
    // make one instance for the user
    extern ServoDecodeClass ServoDecode = ServoDecodeClass();
    
    void printRC()//used for debugging, testing, and capturing typical values for setting up constants
    {
    	// snipped this code to fit forum character limit
    }
    
    void parseRCbuttons(bool& L_UP_pushed, bool& L_DWN_pushed, bool& R_UP_pushed, bool& R_DWN_pushed, Servo& L_servo, Servo& R_servo)
    {
    	// note: if servo is at end of range, *_pushed will be false even when the button is actually pushed
    	// this behavior can be changed if desired by removing parentheses
        int servoTmp = 90;
    
        if(L_UP_pushed = (ServoDecode.GetChannelPulseWidth(5) < 1500-200) &&
    	 (servoTmp = L_servo.read()) < 180)// L_servo commanded and not already at maximum
    	{L_servo.write(servoTmp+1);}
    	if(L_DWN_pushed = (ServoDecode.GetChannelPulseWidth(5) > 1500+200) &&
    	 (servoTmp = L_servo.read()) > 0)// L_servo commanded and not already at minimum
    	{L_servo.write(servoTmp-1);}
    
    	if(R_UP_pushed = (ServoDecode.GetChannelPulseWidth(6) < 1500-200) &&
    	 (servoTmp = R_servo.read()) < 180)// R_servo commanded and not already at maximum
    	{R_servo.write(servoTmp+1);}
    	if(R_DWN_pushed = (ServoDecode.GetChannelPulseWidth(6) > 1500+200) &&
    	 (servoTmp = R_servo.read()) > 0)// R_servo commanded and not already at minimum
    	{R_servo.write(servoTmp-1);}  
    	
    	//illuminate LED on any button push
    	if(L_UP_pushed || L_DWN_pushed || R_UP_pushed || R_DWN_pushed)
    	{digitalWrite(6, HIGH);}
    	else
    	{digitalWrite(6, LOW);} 
    }      
    
    #endif
    Last edited by slomobile; 11-08-2012 at 12:32 PM. Reason: Added code

  2. #2
    I'm starting to understand that I'll need to configure an FTM FlexiTimer 0 or 1 in channel mode, and combine 2 channels for dual edge trigger. Something similar to the rough outline below. I'll need to change that code to read the pulsewidths from the channel registers instead of the counter directly. What I do not know is how to translate say FTM0 Channel 6 to a pin number. Is there a figure or chart anywhere that maps teensy3 pins to K20 channel registers?
    Also, how can I tell if the interrupt was triggered by a rising or falling edge?
    EDIT- I think I know the answer to the above question. Channel n will record the count value on rising interrupts and channel n+1 will record the count value on falling interrupts. By combining the channels, they read thre same pin. Is that correct? Again, how are they associated with a particular pin?

    Code:
    //	  Mode Configuration
    //	16bit up counting dual edge input capture timer on FTM0
    FTM0_MODE[WPDIS] = 1;
    FTM0_MODE[FTMEN] = 1;
    DECAPEN = 1;
    COMBINE = 1;
    CPWMS = 0;
    
    MSnA:MSnB = 0;
    ELSnB = 1;
    ELSnA = 1;
    
    // global
    static volatile uint8_t currentChannel;
    static volatile uint8_t pulsewidth[NUM_CHANNELS+2];//pulsewidth[0] holds the sync threshold, pulsewidth[NUM_CHANNELS+1] holds the synch pulsewidth
    pulsewidth[0] = 7000*PULSES_PER_uS;
    
    ISR // any edge triggered
    {	
    	if(risingEdge)
    	{
    		lastRisingEdge = FTM0_CNT;	
    		currentChannel	++;
    	}
    	else
    	{
    		lastFallingEdge = FTM0_CNT;
    		if((pulsewidth[currentChannel] = lastFallingEdge-lastRisingEdge) > pulsewidth[0])
    		{
    			currentChannel = 0; // end of synch pulse received
    			FTM0_CNT = 0; // count = 0; reset timer 
    		}
    		else // end of regular channel pulse received
    		{
    			// anything to do here?
    			// pulsewidth[currentChannel] already stored
    		}
    	}
    }
    Last edited by slomobile; 11-08-2012 at 04:17 PM.

  3. #3
    @slomobile this could probably help you (if you still have problems with timers)
    https://github.com/cTn-dev/FlightCon...ler/receiver.h

  4. #4

    Massive thanks

    Quote Originally Posted by cTn View Post
    @slomobile this could probably help you (if you still have problems with timers)
    https://github.com/cTn-dev/FlightCon...ler/receiver.h
    That is exactly what I needed.

  5. #5
    good, glad i could help

  6. #6
    Junior Member
    Join Date
    Mar 2013
    Posts
    4

    not see link

    Hi i can't se this, i am trying to do somtehing in the same way
    https://github.com/cTn-dev/FlightCon...ler/receiver.h

  7. #7
    Senior Member ZTiK.nl's Avatar
    Join Date
    Dec 2012
    Location
    Amsterdam
    Posts
    179
    Shorten your link to see all repositories, and select the one you want

    https://github.com/cTn-dev?tab=repositories

    I think this is the one you want.

  8. #8
    Junior Member
    Join Date
    Mar 2013
    Posts
    4

    no

    Quote Originally Posted by ZTiK.nl View Post
    Shorten your link to see all repositories, and select the one you want

    https://github.com/cTn-dev?tab=repositories

    I think this is the one you want.
    No this is not the one

  9. #9
    Senior Member ZTiK.nl's Avatar
    Join Date
    Dec 2012
    Location
    Amsterdam
    Posts
    179
    Okay, sorry

  10. #10
    Junior Member
    Join Date
    Mar 2013
    Posts
    4

    Smile

    Quote Originally Posted by ZTiK.nl View Post
    Okay, sorry
    But hanks for trying

  11. #11
    Quote Originally Posted by internship View Post
    Hi i can't se this, i am trying to do somtehing in the same way
    https://github.com/cTn-dev/FlightCon...ler/receiver.h
    http://bit.ly/ZDj6tA

  12. #12
    Junior Member
    Join Date
    Mar 2013
    Posts
    4

    Question

    Quote Originally Posted by cTn View Post
    Can it be true that the function NVIC_ENABLE_IRQ(IRQ_FTM1); are calling extern "C" void ftm1_isr(void) ?

    Where are you calling the pin for read incomming signal?

  13. #13
    you should read about NVIC (Nested Vectored Interrupt Controller) in the k20 datasheet.

  14. #14
    Hi
    To be honest I had already started with AVR similar projects, to read PWM signals from a RX receiver. Since I switched to Teensy 3 (because I need the more powerful processor) I rearranged my old projects using attachInterrupt() function and it works nicely.
    Now I wanted to rewrite my code and using instead the Input Capture feature of the micro. I find that feature very nice and it could be really solve the problems with my code. Just for starting I plugged one pin of T3 to one signal wire of my receiver and start setting the Input Capture function with FlexTimer0 to measure each lenght of each pulses coming from the same channel (I will write the code for more channels, when I m ok with one channel). I figure out the feature Input Capture as the possibility for the micro to "sense" the raising edge of a pulse and start a timer (through one interrupt). The timer keep running until the falling edge of the pulse is detected. So is the timer again read and the difference between both edges says to me how long the pulse was. Ok....keepign this in mind I wrote a program, which set the FTM0 as a timer starting with the raising edge of the incoming pulse.

    Code:
    void setupFTM0( void ) {
    
        /*****************************************************
        * The Port Control Module sets Teensy to use the ALT 4
        * so the following Channels are configurable:
        *
        *     Teensy Port Name  CPU Pin  Signal
        *
        *     22          PTC1      44   FTM0_CH0
        *     23          PTC2      45   FTM0_CH1
        *      9          PTC3      46   FTM0_CH2
        *     10          PTC4      49   FTM0_CH3
        *      6          PTD4      61   FTM0_CH4
        *     20          PTD5      62   FTM0_CH5
        *     21          PTD6      63   FTM0_CH6
        *      5          PTD7      64   FTM0_CH7
        *
        *****************************************************/
        const uint8_t throttlePulsePin = 22;
    
        pinMode( throttlePulsePin, INPUT );
    
        // Input filter to help prevent glitches from triggering the capture
        // only for CH0 (throttle)
        FTM0_FILTER = 0x07;
    
        // define MODE of operation for FMT0
        // At first the Write Protection Disable must be disabled
        // WPDIS = 1 => 0b0100 
        FTM0_MODE = 0x04;  // now can one write value in the bitfield
    
        // FAULTIE = 0, FAULTM = 0, CAPTEST = 0, PWMSYNC = 0, WPDIS = 1, INIT = 0, FTMEN = 1;
        // => 0b00000101
        FTM0_MODE = 0x05;
    
        // Flex Timer 0 configurations bits
        // The Clock source is Fixed Frequency Clock Running @ 31.25 kHz
        // FLL Clock Input = MCGFFCLK
        // Dividing that by 2 would have the counter roll over about every 4 seconds
    
        // SC: Status and Control
        // Overflow status flag and control bits to use the interrupt, clock source and prescaler
        // TOF = 0, TOIE = 0, CPWMS = 0, CLKS = 0b10, PS = 0b001 
        FTM0_SC = 0x11;
        FTM0_CNT = 0x0000;
        FTM0_MOD = 0xFFFF; // max modulus = 65535
    
        // C0SC: Channel n Status and Control
        // configure the timer for raising AND falling edge 
        // CHF = 0, CHIE = 1, MSB = 0, MSA = 0, ELS0B:ELS0A = 0b11, DMA = 0
        FTM0_C0SC = 0b01001100;
    
        // Enable FMT0 interrupt inside NVIC
        NVIC_ENABLE_IRQ( IRQ_FTM0 );
    
        // WPDIS = 0 write protecton enabled
        FTM0_FMS = 0x40;
    
        // PIN configuration alternative function 4 on pin 44 (Teensy 22)
        // MUX = 0b100 
        PORTC_PCR1 = 0x400;
    }
    and the interrupt rotutine reads the first value of the counter starting the timer (if the corresponding pin is found HIGH).

    Code:
    /* ISR procedures */
    extern "C" void ftm0_isr( void ) {
    
    
        if( ( GPIOC_PDIR & 0b0000000000000010 ) && ( FTM0_C0SC & 0x80 ) ) {
            
            // clear channel interrupt flag (CHF)
            FTM0_C0SC &= ~( 0x80 );
    
            // reset counter to zero
            FTM0_CNT = 0x00;
    
        } else {
    
            dataServo.throttlePulse = FTM0_C0V;
            
            // ONLY FOR DEBUG INFORMATION!!!!
            Serial.println( (dataServo.throttlePulse * 1000000) / (31250 / 2) );
    
            // clear channel interrupt flag (CHF)
            FTM0_C0SC &= ~( 0x80 );
       
        }
    }
    So I keep the ISR as easy as possible. Nevertheless I coudn't found a better way to calculate the length of the pulse with the "speed" of the processor (in my case 48 MHz).
    The above operation
    Code:
    (dataServo.throttlePulse * 1000000) / (31250 / 2)
    is from the code form this site: http://www.digitalmisery.com/2013/06...on-teensy-3-0/ but never explain how to calculate it.

    I must say the above code works pretty well in my case, so now I m going to expand the rest for the other signals.
    But just for my understanding, when for istance in my code instead of:
    Code:
    (dataServo.throttlePulse * 1000000) / (31250 / 2)
    I write:
    Code:
            // ONLY FOR DEBUG INFORMATION!!!!
            Serial.println( FTM0_C0V );
    I read steadly the value: 22

    How can I scale it to milliseconds?

    Thank in advance

  15. #15
    Hi
    Nobody can tell me how to convert the timer into millseconds?

  16. #16
    Senior Member
    Join Date
    Jan 2013
    Posts
    843
    Read the stuff from the link you posted again. It does explain where the "31250 / 2" comes from (clock/clock divider setup). FTM0_C0V is the captured value, it's relative to the timer module input clock.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •