PDA

View Full Version : Timer Input Capture



slomobile
11-08-2012, 12:26 PM
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.

#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

slomobile
11-08-2012, 04:05 PM
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?


// 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
}
}
}

cTn
12-18-2012, 08:48 PM
@slomobile this could probably help you (if you still have problems with timers)
https://github.com/cTn-dev/FlightController-MK20DX128/blob/master/controller/receiver.h

slomobile
12-23-2012, 08:57 PM
@slomobile this could probably help you (if you still have problems with timers)
https://github.com/cTn-dev/FlightController-MK20DX128/blob/master/controller/receiver.h

That is exactly what I needed.

cTn
12-24-2012, 05:51 AM
good, glad i could help :)

internship
03-11-2013, 08:25 AM
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

ZTiK.nl
03-11-2013, 09:43 AM
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 (https://github.com/cTn-dev/Phoenix-FlightController) you want.

internship
03-11-2013, 10:05 AM
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 (https://github.com/cTn-dev/Phoenix-FlightController) you want.

No this is not the one

ZTiK.nl
03-11-2013, 10:12 AM
Okay, sorry :)

internship
03-11-2013, 10:35 AM
Okay, sorry :)

But hanks for trying

cTn
03-11-2013, 01:25 PM
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

internship
03-12-2013, 08:39 AM
http://bit.ly/ZDj6tA
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?

cTn
03-12-2013, 03:05 PM
you should read about NVIC (Nested Vectored Interrupt Controller) in the k20 datasheet.

dave
08-05-2013, 08:06 PM
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.


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


/* 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

(dataServo.throttlePulse * 1000000) / (31250 / 2) is from the code form this site: http://www.digitalmisery.com/2013/06/timer-input-capture-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:

(dataServo.throttlePulse * 1000000) / (31250 / 2)
I write:

// ONLY FOR DEBUG INFORMATION!!!!
Serial.println( FTM0_C0V );
I read steadly the value: 22

How can I scale it to milliseconds?

Thank in advance

dave
08-11-2013, 02:15 PM
Hi
Nobody can tell me how to convert the timer into millseconds?

tni
08-11-2013, 02:41 PM
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.