Just wonder if the experts here could clarify some details for me regarding use of the Teensy 4.1 GPT1/2 timers for a frequency counter I’m making. I was inspired to play with the timers by the recent Pendulum Clock timing thread here:
https://forum.pjrc.com/threads/65156-Pendulum-clock-accuracy-GPSDO-10Mhz-1Hz-pulse
Following links from there to the helpful manitou48 GitHub page gave me some further useful timer code examples:
https://github.com/manitou48/teensy4
Anyway I wrote some test code to run on a VFD display test board I’d just made. Forgive the less than optimised hack version. The code was initially written to count a 10MHz test signal using GPT2 via pin 14 as described in the Pendulum Clock thread. The divided count is output to the VFD as hours, mins and seconds elapsed time. In the long term there will be an input from a frequency standard, rubidium or GPSDO. I was pleased the example code ran well. I then added a second counter, feeding GPT1 the same clock via pin 25. A second count from GPT1 was added to my data display to show both timers were working as expected, they both count 10MHz simultaneously no problems. I’ve pasted in the two different approaches to timer to pin mapping from the manitou48 examples for each GPTn timer, see my two separate start functions.
Output:
Anyway, I’m not very knowledgeable about low level ARM hardware, wonder if anyone can help with these questions arising from the interesting exercise to date.
The setup routine for GPT2 includes:
IOMUXC_SW_MUX_CTL_PAD_GPIO_AD_B1_02 = 8; // select input pin (14)
IOMUXC_GPT2_IPP_IND_CLKIN_SELECT_INPUT = 1;
Whereas the example code for GPT1 uses:
*(portConfigRegister(25)) = 1; // ALT 1 Clock pin is 25
I’m thinking setting the two IOMUXC... library derived variables does the same job as the one line of GPT1 setup code but I’m not totally clear that I can edit either to select a different input pin . GPIO is clearly being mapped to the ARM pin that connects to Teensy 14, but how do we know to use pad? 8 of the ARM? How would I change this line to map other pins, that might help me understand the code better! The second method is succinct, I don’t quite understand the portConfigRegister() function though, I’m wondering if I can substitute any digital pin for 25? Any disadvantage to this later approach?
My next step will be to set up a high resolution period based low frequency counter, I’m interested in being able to measure a 32kHz xtal output accurately. The plan is to use one counter to count the low frequency input so I can average things over multiple cycles, minutes total, using the input signal counter to trigger reads of the 10MHz frequency standard count. I have no idea how to set up one of the counters with a preload so that I can time the input over a set period. Also I’m not sure how to then make the GPT counter generate an interrupt when it hits overflow, any guidance welcomed.
Finally - if I start the sampling counter using say: GPT2_CR |= GPT_CR_EN; immediately after reading the standard counter, what sort of latency error am I likely to see, I’d have thought coding this for a 600 MHz ARM processor renders the potential error pretty tiny?
Thanks to anyone who can clarify some of the above for me, it will help my understanding of Teensy timing code considerably I’m sure,
Steve
https://forum.pjrc.com/threads/65156-Pendulum-clock-accuracy-GPSDO-10Mhz-1Hz-pulse
Following links from there to the helpful manitou48 GitHub page gave me some further useful timer code examples:
https://github.com/manitou48/teensy4
Anyway I wrote some test code to run on a VFD display test board I’d just made. Forgive the less than optimised hack version. The code was initially written to count a 10MHz test signal using GPT2 via pin 14 as described in the Pendulum Clock thread. The divided count is output to the VFD as hours, mins and seconds elapsed time. In the long term there will be an input from a frequency standard, rubidium or GPSDO. I was pleased the example code ran well. I then added a second counter, feeding GPT1 the same clock via pin 25. A second count from GPT1 was added to my data display to show both timers were working as expected, they both count 10MHz simultaneously no problems. I’ve pasted in the two different approaches to timer to pin mapping from the manitou48 examples for each GPTn timer, see my two separate start functions.
Code:
// ---------------------------------------------------------------------------------------------------------------------------------------------
// Counter/VFD display test code 18/03/21
//
// For Hitachi interfaced VFD display
// Uses modified version of Arduino LiquidCrystal library to resolve display cold reset problems if standard library used
//
// Hardware:
// Teensy 4.1
// Noritake CU20029_UW1J VFD display 2 links made to enable European character set(JP5) plus JP3, connects reset to pin 3 of CN2
//
// Teensy display pins: 0 RS, 1 RW/WR (optional use),2 E/RD, 3..6 D4-D7, 7 Reset
// Teensy counter i/o: 14 GPT2 clock in, 25 GPT1 clock in, 15 test signal 10 MHz out
//
// -----------------------------------------------------------------------------------------------------------------------------------------------
#include "Arduino.h"
#include <VFD_Display.h> // Modified LiquidCrystal lib allows for hardware reset of VFD before inititalisation, adds 500ms wait also
// Hardware initialisation by constructor removed, all display intialisation is via begin() method
// Needs a hardware connection to reset pin. Additional reset method added to modified library: reset(resetPin).
#define LED 13 // Teensy LED on 13
#define WR_DISPLAY 1 // Display read/write, normally ground when only writes used
#define RESET_PIN 7 // Pin 7 connected to VFD display reset (display pin 3 with JP3 shorted), low resets the VFD
VFD_Display vfd(0, 2, 3, 4, 5, 6); // Constructor parameters for VFD library version same as for standard LiquidCrystal library
// 6 wire display interface used: 0=RS,(pin 1 RW/WR not used here,set low),2=E/RD,3..6=D4-D7
elapsedMillis readTimer = 0;
void setup()
{
pinMode(LED, OUTPUT);
pinMode(15,OUTPUT);
pinMode(WR_DISPLAY, OUTPUT);
digitalWrite(WR_DISPLAY,LOW);
vfd.reset(RESET_PIN); // Additional library method provides full hardware reset of display with 500 ms delay afterwards
vfd.begin(20, 2); // begin() method does all hardware initialsation, should be called following a reset of VFD
vfd.setCursor(0, 0);
//analogWriteFrequency(11, 10000000); // for separate freq source GPT1 can test jumper 10MHz to its clock pin 25
//analogWrite(11, 128);
analogWriteFrequency(15, 10'000'000); // generate 10Mhz on pin15, jumper pin 15 to pin14 for GPT2 on pin 14 testing
analogWrite(15, 128);
vfd.print("Elapsed time:");
startGPT2Counter(); // Sets up GPT2 with clock input on pin 14
startGPT1Counter(); // Sets up GPT1 with clock input on pin 25
readTimer = 501;
}
void loop()
{
if (readTimer > 300)
{
uint64_t count1 = readGPT1Counter();
double secondsGPT1 = count1 / 10E6;
uint64_t count2 = readGPT2Counter();
double timeInSeconds = count2 / 10E6;
readTimer = 0;
// set the cursor to column 0, line 1
uint8_t mins = timeInSeconds/60;
float seconds=timeInSeconds-(mins*60);
uint8_t hrs = mins/60;
displayCounters(hrs,mins,seconds,secondsGPT1);
flashLED();
}
} //loop
void displayCounters(uint8_t hrs,uint8_t mins,float seconds,double secondsGPT1)
{
vfd.setCursor(0, 1); // print the elapsed time since reset:
if (hrs<10) vfd.print(0);
vfd.print(hrs);
vfd.print(":");
if (mins<10) vfd.print(0);
vfd.print(mins);
vfd.print(":");
if (seconds<10) vfd.print(0);
vfd.print(seconds,1);
vfd.print(" 1:");
vfd.print(secondsGPT1,0);
//Serial.printf("timestamp: %fs | %ull cts \n", timeInSeconds, count);
}
void flashLED(void)
{
static bool statusLED = false;
statusLED=(statusLED?false:true);
digitalWrite(LED,statusLED); // turn the LED on or off alternately
}
void startGPT2Counter(void)
{
CCM_CCGR0 |= CCM_CCGR0_GPT2_BUS(CCM_CCGR_ON);
GPT2_CR = 0;
GPT2_SR = 0x3F; // clear all prior status
GPT2_CR = GPT_CR_CLKSRC(3); // 3 external clock
IOMUXC_SW_MUX_CTL_PAD_GPIO_AD_B1_02 = 8; // select input pin (14)
IOMUXC_GPT2_IPP_IND_CLKIN_SELECT_INPUT = 1;
GPT2_CR |= GPT_CR_EN; // Enable GPT2
}
void startGPT1Counter(void)
{
CCM_CCGR1 |= CCM_CCGR1_GPT(CCM_CCGR_ON) ; // enable GPT1 module
GPT1_CR = 0;
GPT1_SR = 0x3F; // clear all prior status
GPT1_CR = GPT_CR_CLKSRC(3); // | GPT_CR_FRR ; // 3 external clock
*(portConfigRegister(25)) = 1; // ALT 1 Clock pin is 25
GPT1_CR |= GPT_CR_EN; // enable GPT1
}
uint64_t readGPT2Counter(void)
{
static uint64_t curTime = 0;
static uint32_t lastCnt = GPT2_CNT; // initialize on first call to current counter value
uint32_t now = GPT2_CNT; // extend 32bit counter to 64 bit (need to call readCounter at least once per 7min)
curTime += (now - lastCnt);
lastCnt = now;
return curTime;
}
uint64_t readGPT1Counter(void)
{
static uint64_t curTime = 0;
static uint32_t lastCnt = GPT1_CNT; // initialize on first call to current counter value
uint32_t now = GPT1_CNT; // extend 32bit counter to 64 bit (need to call readCounter at least once per 7min)
curTime += (now - lastCnt);
lastCnt = now;
return curTime;
}
Output:
Anyway, I’m not very knowledgeable about low level ARM hardware, wonder if anyone can help with these questions arising from the interesting exercise to date.
The setup routine for GPT2 includes:
IOMUXC_SW_MUX_CTL_PAD_GPIO_AD_B1_02 = 8; // select input pin (14)
IOMUXC_GPT2_IPP_IND_CLKIN_SELECT_INPUT = 1;
Whereas the example code for GPT1 uses:
*(portConfigRegister(25)) = 1; // ALT 1 Clock pin is 25
I’m thinking setting the two IOMUXC... library derived variables does the same job as the one line of GPT1 setup code but I’m not totally clear that I can edit either to select a different input pin . GPIO is clearly being mapped to the ARM pin that connects to Teensy 14, but how do we know to use pad? 8 of the ARM? How would I change this line to map other pins, that might help me understand the code better! The second method is succinct, I don’t quite understand the portConfigRegister() function though, I’m wondering if I can substitute any digital pin for 25? Any disadvantage to this later approach?
My next step will be to set up a high resolution period based low frequency counter, I’m interested in being able to measure a 32kHz xtal output accurately. The plan is to use one counter to count the low frequency input so I can average things over multiple cycles, minutes total, using the input signal counter to trigger reads of the 10MHz frequency standard count. I have no idea how to set up one of the counters with a preload so that I can time the input over a set period. Also I’m not sure how to then make the GPT counter generate an interrupt when it hits overflow, any guidance welcomed.
Finally - if I start the sampling counter using say: GPT2_CR |= GPT_CR_EN; immediately after reading the standard counter, what sort of latency error am I likely to see, I’d have thought coding this for a 600 MHz ARM processor renders the potential error pretty tiny?
Thanks to anyone who can clarify some of the above for me, it will help my understanding of Teensy timing code considerably I’m sure,
Steve