T4 Quad Timer Capture and Output

Status
Not open for further replies.

TelephoneBill

Well-known member
I wish to use a Teensy 4 Quad Timer as both an external signal generator (output) and at the same time capture its current count value from another external signal (input). I want to compare the phases of these two signals - if at all possible. This is easily done with an FTM timer in the Teensy 3 world.

But in reading the Ref Manual CH53 carefully, it looks as though there can only be ONE external interface to a quad timer at any given moment. Can anyone confirm this?

My reason for thinking this may be the case is that register TMRx_SCTRLn on page 3131 bit 0 (OEN) talks about configuring the output "on THE external pin". This suggests there is only ONE external pin (and if its an input, it cannot simultaneously be an output).

Also, when attempting to configure the CAPTURE feature, the IOMUX and XBAR registers never describe the "Capture Input" specifically for the Quad Timers. They do refer to the "Quad Timer Input". I was making an assumption that any input must be the capture pin, but that may not be true (as when timers are chained in series, the second one will need an input). Its beginning to look as though you cannot do "output" and "capture" at the same time... :(
 
The plot thickens. Reading page 3128, the register TMRx_CTRLn bits 8,7 control the "Secondary Count Source" (SCS) and it now looks as if one of the timers in a quad group can use the other "counters input pins" to trigger a capture of its own counter.

If so, then one "counter" (meaning Timer) e.g. Timer0, could set its own "external pin" as an output for OFLAG, while simultaneously using Timer1's (from the same Quad Group) "external pin" as the SCS to capture its own counter. Again, if anyone can confirm or deny this, I would be obliged.

Phew, this manual requires a lot of reading to get to grips with. I will test my "theories" and report the results...
 
I first tried to use one pin of a QUAD TIMER as an input for another, but this was far too ambitious to get working in one step. So I then decide to break the problem down into manageable steps.

The first step was to copy the CAPTURE example given by Manitou (https://github.com/manitou48/teensy4/blob/master/qtmr_capture.ino) and get that working - which it did, straight out of the can. However, something odd happened. I tried to port the essential bits of code into my own experimental project - and then the capture didn't work. I even got to the stage where mine was an almost exact copy, and it still did not work. More of this in a moment.

So I then went back to square one and decided to code my experiment from the basic pages of the Reference Manual, but using the same principles derived from Manitou's work. The first job was NOT to make a CAPTURE, but to make a simple COMPARE test to get a timer working and to output the OFLAG on to pin 11 as a signal to display on the scope. This was relatively easy and it worked great.

I then used the same code, but made small changes to the registers in order to turn the "external pin 11" from being an output into an input signal for the CAPTURE (it cannot be both). Surprisingly, there was no code needed to alter the direction of pin 11. When it came to testing, something very strange happened. I had a "v" command input at the serial monitor to print out some variables storing the "tick counts" of when the ISR fired. The first "v" typed, and the second also, worked... but subsequent attempts failed to print out results (but did not complain about the "v" inputs). After 17 times typing "v", then suddenly all the data that should have been printed appeared on screen. And from that moment onwards, it works perfectly. This number of 17 was very consistent.

I now suspect that this same issue was happening when I first tried to copy Manitou's code. But he had used an array to build up a fair size of data, whereas my code was trying to output just one value at a time. It looks like a bug somewhere in Teensyduino, and I think Paul is actually working on this as I write. (I will wait to hear what progress has been made).

For my own code, to get the actual CAPTURE value, type "w" (<CRLF>). Then values from 0 to 10000 will appear on the monitor.

This is only half way to where I want to be, but I'm including the code so far in case anyone else might want to investigate how it works and get CAPTURE values working on their own machines.

Tomorrow, I will make further steps to try use pin 11 from this timer to capture a value from another timer (in the same quad group), but where that second timer uses its own "external pin" to output its own OFLAG as a sq wave signal simultaneously. If that works, then the next step will be to include the XBAR as a means of using different pins for triggering captures.

Code:
//TestT4013 - QTIMER TEST PROGRAM for T4 (CAPTURE TEST on QT1 Timer2)
//===================================================================
//Author: TelephoneBill
//Original Example from: MANITOU's https://github.com/manitou48/teensy4/blob/master/qtmr_capture.ino
//Date: 29 AUG 2019
//Version: 001

//NOTES:
//TestT4013 - Have now changed "external pin 11" from being an OFLAG output in my last experiment (TestT4012) to being a CAPTURE input.
//You will need to jumper pin 23 (sq wave generator) to pin 11 in order to trigger the capture mechanism (from the rising edges of the sq wave input).
//HOWEVER - There is something ODD happening when the program first runs. Typing "v" to print "ISRTicks, ISRCompTicks, ISRCaptTicks" does not always print
//correctly in the serial monitor. It seems to fail to send output to the monitor, but after very many (about 17) attempts at typing "v<CRLF>" then it will suddenly display
//all the data that should have done previously. Typing "w<CRLF>" will then print out the current CAPTURE value (should be less than 10000, which is the CMPLD2 value).
//It looks like a buffer size has to get filled up first before any more characters will be output to the monitor (Teensyduino code area - maybe a bug).
//Notice that pin 11 is defined by the IOMUXC_SW_MUX_CTL_PAD_GPIO_B0_02 statement. The interesting fact is that in the previous experiment, this was an output pin
//for the OFLAG, but now it is an input pin - but there is nothing specifying the pins direction, so it must be "bi-directional" internally.

//OPERATION:
//The external T4 pin 11 becomes the trigger for making a CAPTURE. The analogWrite command on pin 23 generates a 4.5 KHz sq wave. The rising edges of this input signal (when
//jumpered from pin 23 to pin 11) then provide the stimulus to activate the internal capture, which is stored in register TMR1_CAPT2 (16 bits). By using the Input Edge Flag (IEF)
//of the SCTRL2 register (bit 10, R.M. page 3130), then an interrupt can be created which allows the TMR1_CAPT2 value to be stored into a local variable. This IEF flag must
//be cleared (in the ISR) in order to allow further interrupts to take place.

//definitions
byte Byte1;
volatile uint32_t ISRTicks, ISRCompTicks, ISRCaptTicks;
volatile uint16_t TMR1Capt2Value;
float CPUTemp;

//SETUP
//=====
void setup() {
  //initialise general hardware
  Serial.begin(9600);                 //setup serial port
  pinMode(13, OUTPUT);                  //pin 13 as digital output
  FlashLED(4);                          //confidence boost on startup

  //turn on clocks for Timer QT1 (CG13)
  CCM_CCGR6 |= CCM_CCGR6_QTIMER1(CCM_CCGR_ON);

  //intialise QT1 Timer2 registers
  TMR1_CTRL2 = 0;                     //stop all functions of QT1 Timer2 (Timers are known as 0,1,2,3)

  //status and control register
  TMR1_SCTRL2 = 0;                    //clear prior to setting
  TMR1_SCTRL2 |= 0b0000010001000000;  //no compare interrupt, no overflow, input edge flag enabled, rising capture, no forcing, no invert, external pin is input  
  
  TMR1_LOAD2 = 0;                     //counter starts counting from zero
  TMR1_COMP12 = 10000-1;              //66.7uS - count up to this value
  TMR1_CMPLD12 = 10000-1;             //load compare register with value from this register

  //comparator status and control register
  TMR1_CSCTRL2 = 0;                   //clear prior to setting
  TMR1_CSCTRL2 |= 0b0000001001000001; //no debug, no fault, no altload, no reload, no trigger, count up, TCF1EN, no COMP2, COMP1
  
  attachInterruptVector(IRQ_QTIMER1, QT1_isr);
  NVIC_ENABLE_IRQ(IRQ_QTIMER1);

  //configure Teensy pin Capture pin
  IOMUXC_SW_MUX_CTL_PAD_GPIO_B0_02 = 1; //sets up pin 11 as the "external pin" for QT1 Timer2 (see R.M. page 309).

  //start timer
  TMR1_CTRL2 = 0b0011000100100011;      // 001(Count rising edges Primary Source),1000(IP Bus Clock),10 (Secondary Source = Timer2 input pin), 
                                        // 0(Count Once),1(Count up to Compare),0(Count Up),0(Co Channel Init),011(Toggle OFLAG on Compare)

  analogWrite(23,128);                  //generate PWM 4.5 KHz sq wave
}
  

//ISR ROUTINE FOR QT1
//===================
void QT1_isr() {                          //ISR for QT1
  //test flags
  if (TMR1_CSCTRL2 & TMR_CSCTRL_TCF1) {   //test if COMP1 flag set
    TMR1_CSCTRL2 &= ~(TMR_CSCTRL_TCF1);   //clear COMP1 flag
    ISRCompTicks++;
  }
  if (TMR1_SCTRL2 & TMR_SCTRL_IEF) {      //test if input edge capture event
    TMR1_SCTRL2 &= ~(TMR_SCTRL_IEF);      //clear input edge flag
    ISRCaptTicks++;
    TMR1Capt2Value = TMR1_CAPT2;          //read the capture value
  }

  //update count
  ISRTicks++;

  asm volatile ("dsb");  // wait for clear  memory barrier
}


//MAIN LOOP
//=========
void loop() {
  //call KeyInput() routine
  KeyInput();
}

//SUBROUTINES
//===========
//Flash LED routine
void FlashLED(int m) {
  for (int n=0;n<m;n++) {
    digitalWriteFast(13, 1);          //set pin 13 high
    delay(100);
    digitalWriteFast(13, 0);          //set pin 13 low
    delay(100);
  }
}

//KeyInput routine
void KeyInput() {
  //process any keystrokes available
  if (Serial.available()>0) {
    //read the incoming byte
    Byte1 = Serial.read();
    if (Byte1>0x20) {
      switch (Byte1) {
      case 'T':  //CPU core temp
        //task goes here...
        CPUTemp = tempmonGetTemp();
        Serial.print("CPUTemp (deg C) = "); Serial.println(CPUTemp);
        break;
      case 'v':  //print ISRTicks, ISRCompTicks, ISRCaptTicks
        //task goes here...
        Serial.print("ISRTicks = "); Serial.println(ISRTicks);
        Serial.print("ISRCompTicks = "); Serial.println(ISRCompTicks);
        Serial.print("ISRCaptTicks = "); Serial.println(ISRCaptTicks);
        break;
      case 'w':  //print QT3 Timer1 Capture Value
        //task goes here...
        Serial.print("TMR1Capt2Value = "); Serial.println(TMR1Capt2Value);
        break;
     }
    }
  }
}
 
Have done a bit more work today. Today's first step was to extend QT1 to have two timers running (both of the QT1 group). Timer2 still used Pin11 as the trigger input for its own counter, but I added Timer1 to give an output OFLAG on Pin12. I also set both timers to use identical COMP1 values and LOAD values, such that they were effectively synchronised on startup, and both using the 150 MHz peripheral clock. They stayed synchronised indefinitely (as expected).

In this condition, Timer2 CAPTURE values were effectively the same as "would be from Timer1, were it to be read at the same instant that the external signal rising edge happens".

The results of this were fantastic. I could clearly read the "phase" of Timer1 in comparison with the external rising edge (causing the capture on Timer2). The added code has also done something else to that serial monitor problem, for now it is behaving perfectly on typing a "v" command - or any other command!!

===

The second step I have taken tonight has moved this even further on, which is very exciting. Over last weekend, I was experimenting with a new method of controlling a precision output sq wave signal frequency. Without digital capacitors, I had to find a new method of frequency control. I'm now using the "drop one count" principle every so often, so I'm "dithering between two values for the comparator, which are only one count apart, but every so often not doing the drop". And I now am able to control exactly when that "failed drop" happens.

The two values (one count apart) causes 6.6 nS of jitter - perfectly acceptable. And changing which two values I use, means that I have excellent frequency coarse frequency control, together with fine frequency control by adjusting the time when I "fail to drop a count". The failure to drop a count does not introduce any additional jitter (in fact it will remove some).

So I then decided to test this out with my 60 KHz radio receiver, picking up the UK time signal (the station is known as "MSF", like the U.S. "WWVB"). I'm using the MSF signal into an analog comparator ACMP3 on the Teensy4, which goes in on pin18 and comes out as a brilliant sq wave on pin2.

Now using this waveform to control the capture of Timer2 - and making the compare/load values identical for both Timer1 and Timer2 - and using the "drop count" frequency control - then I have manual control of the Timer1 60 KHz sq wave, and can read the phase of it (by reading the phase of Timer2) with respect to the MSF signal.

The control of the Teensy4 generated 60 KHz (from Timer1) is absolutely astonishing. I'll post some scope pictures later. In addition to the above description, I have used a small piece of copper foil tape stuck to the surface of the 1062 chip, but extended over one edge to stick also to the 24 MHz crystal top surface. Carefully bridging over the crystal capacitors, this is making the crystal be in close temperature coupling to the chip surface temp - and thereby keeping it more "constant" (though it does take time to settle).

The only thing now missing from a remarkable stabilised 60 KHz precision source is auto control to keep the phase of Timer1 in tight synch with the MSF signal. But this should now be easy to do with a bit of averaging of the MSF signal measurement provided by the capture values.

The Teensy4 is going to be a wonderful controller.
 
Status
Not open for further replies.
Back
Top