Teensy 3.1 and Flextimer(s) - Counting external pulses accurately

Status
Not open for further replies.

TelephoneBill

Well-known member
My thanks to colleague “FAlameda” for pointing me in the right direction on this little experiment.

In addition to their use as “timers”, the Flextimer(s) in Teensy 3.1 may also be used as “pulse counters” to count external pulses edges.

If you have a steady train of pulses, this mode of operation is a bit like using an external clock signal. But rather than injecting the external clock through the ARM chip pins 32 or 33 (which the schematic diagram shows are already used for the Teensy 16 MHz crystal), you can inject this “external clock” through one of the standard PORT pins. You may then also “trap and read the value” of the Flextimer at any instant using another standard PORT pin.

In this experiment, I wanted to count every single cycle of a 10 MHz oscillator, just to prove that the frequency was precisely 10,000,000 Hz and not 9,999,999 Hz or 10,000,001 Hz. I trap and read the value using another precise signal source of 1 pulse per second (1PPS) – from a Rubidium Standard Freq Module (some cheap GPS modules will also provide a 1 PPS).

Table 36-1 of the Freescale reference manual, or paragraph 36.4.25, illustrates that the Flextimer can be set into “Quadrature Decoder Mode”. If your program code sets QUADMODE = 1, then the pin associated with PHA is available as an input pin for inserting external pulses which the Flextimer will count in its 16 bit counter. The up/down direction of counting is set by the pin associated with PHB (PHB high = count upwards, low = count downwards).

To read the value of the 16 bit counter, you can then use one of the “channels” associated with the Flextimer. I use CH0.

The following code shows how this mode of operation was configured for my experiment using Flextimer 1:

//(1) Setup statements
void setup() {
//configure Flextimer 1
FTM1_MODE = 0x05; //set write-protect disable (WPDIS) bit to modify other registers
//FAULTIE=0, FAULTM=00, CAPTEST=0, PWMSYNC=0, WPDIS=1, INIT=0, FTMEN=1(no restriction FTM)
FTM1_SC = 0x00; //set status/control to zero = disable all internal clock sources
FTM1_CNT = 0x0000; //reset count to zero
FTM1_MOD = 9999; //max modulus = 9999 (gives count = 10000 on roll-over)
FTM1_C0SC = 0x04; // CHF=0, CHIE=0 (disable interrupt, use software polling), MSB=0 MSA=0, ELSB=0 ELSA=1 (input capture - rising edge), 0, DMA=0

//enable FTM1 interrupt within NVIC table
NVIC_ENABLE_IRQ(IRQ_FTM1);

//configure Teensy port pins
PORTA_PCR12 |= 0x700; //MUX = alternative function 7 on Chip Pin 28 (FTM1_QD_PHA) = Teensy Pin 3
PORTA_PCR13 |= 0x700; //MUX = alternative function 7 on Chip Pin 29 (FTM1_QD_PHB) = Teensy Pin 4
PORTB_PCR0 |= 0x300; //MUX = alternative function 3 on Chip Pin 35 (FTM1_CH0) = Teensy Pin 16

//set flextimer quad decode mode and enable overflow interrupt
FTM1_QDCTRL = 0x0F; //see section 36.3.21 of ref manual for details
FTM1_SC = 0x40; // (Note – FTM1_SC [TOF=0 TOIE=1 CPWMS=0 CLKS=00 (internal clocks disabled) PS=000 [no prescale divide])
}

//(2) ISR routine for FlexTimer1 Module
extern "C" void ftm1_isr(void) {
if ((FTM1_SC & FTM_SC_TOF) != 0) { //read the timer overflow flag (TOF in FTM1_SC)
FTM1_SC &= ~FTM_SC_TOF; //if set, clear overflow flag
}
FTM1CountOVFlow++; //increment overflow counter
}

//(3) main loop statements
void loop() {
//read FTM1 Ch0 value if valid
if ((FTM1_C0SC&0x80) != 0) { //look for CH0 event flag
FTM1Ch0CountValue = FTM1_C0V; //read CH0 value
FTM1_C0SC &= ~0x80; //clear CH0 event flag
}
}

Explanation…

The code above is divided into three sections – (1) Setup, (2) Interrupt Service Routine, and (3) Main Loop.

In “Setup”, notice that the internal clock source (the normal 48 MHz peripheral clock) is switched off and never switched back on - the last statement in this section “FTM1_SC = 0x40;” is only enabling the overflow interrupt flag. I set the count modulus to 9999 so that the 16 bit counter will overflow on a count of 10,000, which will be every millisecond for 10 MHz external pulse rate. I’m also using a polling technique via CH0 of FTM1 in the main loop to read the value at a fixed instant in time. That value is trapped when Teensy pin 16 goes high from the 1 PPS source.

The ISR routine simply resets the TOF flag in the “FTM1_SC” register, and then increments the overflow count program variable “FTM1CountOVFlow”.

The main loop code is polling the CH0 event flag and resetting it appropriately. I also transfer the “instant value” of the 16 bit counter into a program variable “FTM1Ch0CountValue”, so that I can display this on my PC screen using other statements (not shown above) during a later time in the main loop.

To summarise, the three port pins employed are Teensy pin 3 (inject my 10 MHz signal), Teensy pin 4 (connected to logic high +5v), and Teensy pin 16 (inject my 1 PPS signal).

The experiment was run for over one hour. The displayed value of “FTM1Ch0CountValue” was “3986” and never varied from that figure over the hour (save for a momentary flicker occasionally down to “3985” and then back to “3986”) This is an arbitrary absolute value and depends only on the instant that the 10 MHz was connected to the Teensy board. The flicker is illustrating that just occasionally, the edge of the 1 PPS is not quite catching the last cycle – but it corrects this on the next count and hence restores “3986”.

This experiment illustrated that the each cycle of the 10 million cycles was being counted successfully by the Flextimer counter, and that precisely 10 million cycles were being executed every second for the whole of the hour period. If the frequency had been 1 Hz above (or below), then “3986” would eventually creep upwards in value (or downwards) at a rate of change of one unit per second. I did deliberately upset the 10 MHz afterwards to demonstrate this was the case.

Considering that more than 36 billion cycles had been accurately counted by the Teensy board, then I consider this experiment a resounding success  !!
 
Considering the state of the datasheet this is indeed great work!

So...here is the 10000000 cycles question...how do you make use of the "3986" or whatever number happens to pop up in order to determine whether or not the 10MHZ signal is not "walking away"?
Do you monitor the counter's "creep" until it's stable and then use this as the target value or is there some other magical +*-/x%10 calculation taking place ?
 
If you display values in the serial monitor at a rate of 1 second (triggered from the 1pps source), then you get what appears to be a "static" display (it is readable). By displaying the arbitrary number ("3986" or whatever) within the serial monitor, then you can easily keep a check over an extended period. Now the counted cycles has to be precisely 10,000 per rollover (because the modulus is set 9999 and including zero = 10,000). So if the frequency of the input pulses is 10,000,000 Hz, then you will get 1,000 rollovers in the precise 1 second period. And by displaying the arbitrary value every second, there is a "synchronism" between the input pulses and the 1 PPS to keep the serial monitor display always at "3986". My rubidium frequency standard is good to better than 1 nanoSecond per second. I calibrate it against GPS over a period of hours [7 or 8]) and I also check the stability of the rubidium against a double oven crystal just to see if they differ. The surprise is that GPS often "wanders" in the short term - the ruby is very good short term.

So in conclusion, the "synchronism" maintains the arbitrary value constant, and the long term display shows it is not "walking away".

The real beauty of the flextimers is that these peripherals are running under circuitry independant from the Teensy cpu, so interrupts don't materially affect them.
 
For some reason (that is under investigation) my AN (Arbitrary Number) keeps sliding down , so I'll have to resolve this first before I continue with finding a way to actually make use of it.
 
@TelepnoneBill -- I am trying to do nearly the same thing, but I am not confident about the CH0 part. In your code, you can't guarantee that the CH0 event detect in the loop occurred before or after the FTM1CountOVFlow++. Therefore you don't know to use FTM1CountOVFlow or FTM1CountOVFlow-1 when computing the number of 10 MHz cycles that occurred. Even wrapping the CH0 part in cli() won't help because CH0 is captured in hardware.

Have you figured out any better way ?

p.s. You can simplify some hardware when interfacing to a 10 MHz sinusoidal reference:
1) You can tie Teensy pin 4 using digitalWrite(4, HIGH)
2) I use CMP1 as a comparator with 0 V threshold. It detects a 100 mV 10 MHz sinusoid perfectly. With this I use pin A9 as the sinusoidal input; CMP1_OUT is on pin 10 which I wire directly to pin3 (FTM1_QD_PHA). I can post the CMP1 code if needed.
 
Last edited:
@Jp3141 - To clarify my experiment, the CH0 event in my code is not directly related to the count of pulses from PHA. I'm using the CH0 event as a simple "strobe" to "highlight" the current value of the Flextimer register at the instant in time of the strobe rising edge. So the display of FTM1Ch0CountValue happens to be "3986" but it could equally well have been any other value from 0 to 9999.

It is the fact that the strobing periodicity is PRECISELY 1 second (to within a few nanoSeconds or so) that the value in the display remains constant over the hour of the experiment. As long as the drift of my 1PPS strobe remained less than 100 nanoSecs (and that the drift of the PHA pulse frequency was also less), then the value displayed would remain at "3986". And the fact that it did remain constant over that time was telling me that my strobe was a precise "10,000th multiple" of whatever the pulse frequency was on PHA. It is this fact which says that the count must be 10,000,000 per second and not 9,999,999. 10,000,000 IS an exact 10,000th multiple, whereas 9,999,999 (or 10,000,001) is not so,

My PHA source was a 10 MHz OCXO, so its stability was fairly reliable. The value of FTM1CountOVFlow++ in this experiment was rather academic. However, that is not to say that FTM1CountOVFlow++ isn't useful. It can be used as a reliable counter...

My PHA source was a constant predicted 10 MHz source (and the comparison against the Rubidium confirmed that). If your source for PHA is some other frequency (and not necessarily constant) then if you use the same "overflow value" of 10,000 (the MOD value = 9999), then the value of FTM1CountOVFlow++ will give you the total count since the time of starting. The total count of PHA pulses will simply be "(FTM1CountOVFlow times 10,000) + FTM1Ch0CountValue".

The ISR routine will ONLY be called by the interrupt nature of the OVERFLOW of the register (I programmed for this specific event). So "FTM1CountOVFlow " will be an accurate tally of the number of times that this OVERFLOW has happened since the start. If you use the rising edge of another signal to be your start PHA signal, then simply read the value of the "FTM1Ch0CountValue" (exactly same as FTM1_C0V) at that start time and subtract it from the overall total count of "(FTM1CountOVFlow times 10,000) + FTM1Ch0CountValue" recorded when your PHA signal next rises. That will give you the precise number of PHA pulses in the interval period of one "start signal rising edge" to the next "start signal rising edge".

You don't even need the MOD value to be 9999. You can use any integer less than 65536 (equals the max count possible in a 16 bit register). The only real constraint is that the time required to execute the ISR (including any delay in it being triggered) must be less that the OVERFLOW time period.

===

Thanks for your second comment. Yes, I'm interested to see your code for using CMP1. I have not used this feature before so it would be an excellent way to detect the output of an oscillator whose amplitude is small but greater than 100 mVolts. In another forum post I made on using the Teensy 3.1 as a frequency standard, you can see I used pin14 of a 4046 chip which has a built-in amplifier to get the threshold level up. Even a simple single common emitter transistor can be useful.
 
I think I have this figured out. When the FTM overflows and an input capture occurs nearly simultaneously, it is difficult to know which occurred first, and therefore if the pre-overflow or post overflow counter value should be used. My solution is to compare FTM1_C0V and FTM1_CNT at the interrupt; if C0V is lower, then the capture must have occurred after the overflow; if it is higher, then it may have occurred before. The way to decide that is to delay some small time, and do another check. This check ensures that the possible FTM1_C0V in the earlier test is clearly greater than the FTM1_CNT at that test time. A diagram shows the process:
32-bit FlexTimer and Input Capture Algorithm.png

Here is some code that implements that. It is probably possible to optimize the ISR.

Code:
/*
 * Connect 10 MHz sinewave input (200 mV peak) to Teensy pin 23 (A9)
 * Connect pin 10 (CMP1_OUT) to pin 3 (FTM1_QD_PHA)
 * Connect 1 pps trigger to pin 16 (A2)
 */

const int LED_PIN = 13;
volatile uint32_t CountValue, CountValueOld;
volatile uint32_t FTM1CountOVFlow; // don't need to initialize since rollover is handled
volatile bool CaptureFlag = false;
bool CountingOK = false;

#define DACEN       ((uint32_t)(((n) & 1) << 7))          // Enables the DAC. When the DAC is disabled, it is powered down to conserve power
#define VRSEL       ((uint32_t)(((n) & 1) << 6))          // Supply Voltage Reference Source Select
#define VOSEL(n)    ((uint32_t)(((n) & 0b111111) << 0))   // DAC Output Voltage Select
#define CMP_MUX_PSEL(n)    ((uint32_t)(((n) & 0b111) << 3))   // Plus Input Mux Control
#define CMP_MUX_MSEL(n)    ((uint32_t)(((n) & 0b111) << 0))   // Minus Input Mux Control

void setup() {
  pinMode(LED_PIN, OUTPUT);
  pinMode(4, INPUT_PULLUP);  // Needed for Quad decoder  
  Serial.begin(0); 
  while (!Serial);
  Serial.println(__FILE__ " " __DATE__ " " __TIME__);
  ComparatorSetup();
  FTM1Setup();
  Serial.printf("%9s %11s\n", "Frequency", "CountValue");
}

void loop() {
  unsigned long  ppsWatchdog = millis() + 1100;
  while (!CaptureFlag && millis() < ppsWatchdog); // loop and wait up to 1.1 s
  if (CaptureFlag) {
    CaptureFlag = false;  // clear flag from channel
    if (CountingOK) Serial.printf("%9lu %11lu\n", CountValue-CountValueOld, CountValue);
    CountValueOld = CountValue;
    CountingOK = true; // postpones displaying until 2 CaptureFlags in a row
  } else { 
    Serial.println("1 pps missing.");
    CountingOK = false;
  }
}

// Setup FlexTimer1
void FTM1Setup() { 
  // input capture 1 pps on Teensy pin 16 (A2), 10 MHz clock from Teensy pin 3
  // FTM1 Channel 0
  FTM1_MODE = FTM_MODE_WPDIS | FTM_MODE_FTMEN;
  FTM1_FILTER = 0x00; // no filtering
  FTM1_SC = 0; 
  FTM1_CNTIN = 0; // Count initial value
  FTM1_CNT = 0x0000; // Reset the count to CNTIN
  FTM1_MOD = 0xFFFF; // max modulus = 65535
  FTM1_C0SC =   FTM_CSC_ELSA; // Rising edge no interrupts
  //configure Teensy input capture channels
  PORTA_PCR12 |= PORT_PCR_MUX(7); //MUX = alternative function 7 on Chip Pin 28 (FTM1_QD_PHA) = Teensy Pin 3 -- Signal to count
  PORTA_PCR13 |= PORT_PCR_MUX(7); //MUX = alternative function 7 on Chip Pin 29 (FTM1_QD_PHB) = Teensy Pin 4 -- Tie high
  PORTB_PCR0  |= PORT_PCR_MUX(3); //MUX = alternative function 3 on Chip Pin 35 (FTM1_CH0) = Teensy Pin 16 (A2)
 
  SIM_SOPT4 |= SIM_SOPT4_FTM1CH0SRC(0); // 00 source is FTM1_CH0 signal (1 pps)

  //set flextimer quad decode mode and enable overflow interrupt
  FTM1_QDCTRL = FTM_QDCTRL_QUADMODE | FTM_QDCTRL_QUADIR | FTM_QDCTRL_QUADEN; // comes in on pin 3
  FTM1_SC = FTM_SC_TOIE | FTM_SC_CLKS(0); // actually not important in QUAD mode ?
  
  NVIC_ENABLE_IRQ(IRQ_FTM1);
  // NVIC_SET_PRIORITY(IRQ_FTM1, 48);
  Serial.println("FlexTimer FTM1 Setup Complete");
}

void ComparatorSetup(void) {
  // Output is on pin10 (PTC4, MCU pin 49, Teensy pin 10)
  // Input is pin A9 (PTC2, Teensy pin 23, MCU pin 45)
  // Reference is 6-bit internal DAC
  // DAC reference VIN1 = VREF (1.19 V), VIN2 = VDD (3.3)

  SIM_SCGC4 |= SIM_SCGC4_CMP; //Clock to Comparator
  CORE_PIN10_CONFIG = PORT_PCR_MUX(6); //Alternate function 6: Teensy pin10 = CMP1_OUT, PTC4

  CMP1_CR0 = 0b00000000; // FILTER_CNT=0; HYSTCTR=0
  CMP1_CR1 = 0b00010111; // SE=0, high power, COUTA, output pin, enable; mode #2A
  // read CMP1_SCR LSB is analog comparator output state
  CMP1_DACCR = 0; //Disable DAC ==> reference = 0 V
  //PTC2 (pin 23) is default CMP1_IN0
  //PTC3 (pin  9) is default CMP1_IN1

  // IN0 (Comparator plus) = 
  CMP1_MUXCR = CMP_MUX_PSEL(0) | CMP_MUX_MSEL(7); // Input pins select; plus = IN0 (pin 23), neg = DAC (code 7) 
  Serial.println("Comparator CMP1 Setup Complete");
}

extern "C" void FASTRUN ftm1_isr(void) { // Process CHF first, unless counter is close to overflow
 if (FTM1_SC & FTM_SC_TOF) { // read the timer overflow flag (TOF in FTM1_SC) rate = 10 MHz/65536 ~ 152 Hz, 6.6 ms
    FTM1_SC &= ~FTM_SC_TOF;   // clear interrupt from timer overflow
    FTM1CountOVFlow++;       
  } // TOF

  // now poll CH0 and see if it had any activity
  if (FTM1_C0SC & FTM_CSC_CHF) { // rate = 1 pps
    FTM1_C0SC &= ~FTM_CSC_CHF;  // clear flag from channel
    CaptureFlag = true;
    if (FTM1_C0V < FTM1_CNT) {CountValue = (FTM1CountOVFlow << 16) + FTM1_C0V; return;} // very rare - just happened since interrupt
    else {CountValue = (FTM1CountOVFlow << 16) + FTM1_C0V  - (1 << 16); return;} // from 10 us into previous cycle
  }  
  delayMicroseconds(10); // to split the interval -- should be longer than max. interrupt latency
  if (FTM1_C0SC & FTM_CSC_CHF) { // try again, now 10 us into new cycle; good if no other interrupts are > 10 us
    FTM1_C0SC &= ~FTM_CSC_CHF;  // clear flag from channel
    CaptureFlag = true;
    CountValue = (FTM1CountOVFlow << 16) + FTM1_C0V; // happened between just after start and 100 us into cycle
  } 
} // ISR

This also includes the code I use to use the comparator for a low amplitude sinusoidal input (I have 10 MHz, ~ 200 mv pk).
 
Last edited:
Status
Not open for further replies.
Back
Top