//TestT4017 - QTIMER TEST PROGRAM for T4 (Phase Lock 60 KHz with UK MSF Time Signal)
//==================================================================================
//Author: TelephoneBill
//Date: 07 OCT 2019
//NOTES:
//TestT4017 - Teensy 4.0 - uses ACMP3, QT1 Timer1, QT1 Timer2.
//Phase Lock of T4 generated 60 KHz sq wave (Timer1 output on Pin 12) with MSF Radio Receiver 60 KHz input via 0.1 mfd capacitor (on pin 23).
//ACMP3 analogue comparator employed to convert MSF Receiver signal to a sq wave. ACMP3 input on Pin 23 is biased to mid voltage from wiper of
//a 10 turn pot (across 3v3 and GND). Adjusted so that "no signal" input can just create noise on ACMP3 output (this ensures comparator threshold
//in mid position). Output from ACMP3 is Pin 2 and this is wire jumpered to Pin 11 input of Timer2, so that sq wave triggers capture of Timer2
//value (150 MHz clock).
//Pins:
//Pin0 = ISR Strobe
//Pin1 = Action Strobe (timing when action happens)
//Pin2 = ACMP3_OUT (output) via XBAR1 and EMC_04
//Pin11 = Capture input for QT1 Timer2
//Pin12 = Output OFLAG for QT1 Timer1
//Pin13 = Diagnostic LED (One Second Flash)
//Pin18 = Comparator CMP1 input 60 KHz from radio receiver
//IDE Monitor Commands:
//a - Set ActionTime (a120000)
//aa - increment ActionTicks (bring forward by 12000 counts)
//B - Set Crystal Bias Current (0,1,2,3)
//c - Toggle ControlOn
//C - Set Comp11Val1/Comp11Val2
//d - Increment Comp11Val1 (d123) - decrease frequency
//D - Increment LSET - decrease frequency fine
//f - Toggel FineControlOn
//i - Invert output waveform
//p - Print TCVAverage/Comp11Val1/LSET value every second
//P - Print all TCVHist array
//r - Toggle RecordOn
//s - Decrement HCycleTicks by 1234
//T - Print CORE Temp
//u - Decrement Comp11Val1 (u123) - increase frequency
//U - Decrement LSET - increase frequency fine
//v - Print current ISRTicks
//w - Increments HCycleTicks by 1234
//z - Toggle Out60KHzOn
//definitions
#define TCVArraySize 1250 //size of history array for TCValues
#define ItemsPerLine 20 //printed output per line (TCValue history array)
byte Byte1, Byte2, Byte3, Byte4, Byte5, Byte6, Byte7;
volatile int a, b, c, N1Ptr;
volatile uint32_t ISRTicks, ISRComp1Ticks, ISRComp2Ticks, ISRCapt2Ticks, Bias, BiasValue, HCycleTicks, Secs, PrevSecs, T1, Increment, Time1, Time2, ElapsedTime;
volatile uint32_t ActionTicks, ActionTime, TCVHist[TCVArraySize];
volatile uint16_t TMR1Capt2Value, Count1, ISRControlTicks, Comp11Val1, Comp11Val2;
uint32_t TCVAverage, PrevTCVAverage;
int LSET, Diff;
float CPUTemp;
boolean ControlOn, ActionRequired, FineControlOn, PrintControlOn, RecordOn, InvertOn, Out60KHzOn, Out60KHzState;
//SETUP
//=====
void setup() {
//initialise general hardware
Serial.begin(9600); //setup serial port
pinMode(0, OUTPUT); //pin 0 as digital output (ISR timing)
pinMode(1, OUTPUT); //pin 1 as digital output (action timing strobe)
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);
//===================================================================
//Disable all timers for QT1
TMR1_ENBL = 0;
//intialise QT1 Timer1 registers (QT1_Timer1 = 60 KHz Output signal)
TMR1_CTRL1 = 0; //stop all functions of QT1 Timer1 (Timers are known as 0,1,2,3)
//status and control register
TMR1_SCTRL1 = 0; //clear prior to setting
TMR1_SCTRL1 |= 0b0000000000000001; //no compare interrupt, no overflow, input edge flag enabled, rising capture, no forcing, no invert, external pin is OFLAG
TMR1_LOAD1 = 0; //counter starts counting from zero
TMR1_COMP11 = 1250-1; //16.7uS - count up to this value
TMR1_CMPLD11 = 1250-1; //load compare register with value from this register
//comparator status and control register
TMR1_CSCTRL1 = 0; //clear prior to setting
TMR1_CSCTRL1 |= 0b0000001001000001; //no debug, no fault, no altload, no reload, no trigger, count up, TCF1EN, no COMP2, COMP1
//configure Teensy pin Capture pin
IOMUXC_SW_MUX_CTL_PAD_GPIO_B0_01 = 1; //sets up pin 12 as the "external pin" for QT1 Timer1 (see R.M. page 309).
//===================================================================
//intialise QT1 Timer2 registers (QT1_Timer2 = Internal Sync 60 KHz for capturing phase)
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 |= 0b0000010011000000; //no compare interrupt, no overflow, input edge flag enabled, rising/falling capture, no forcing, no invert, external pin is input
TMR1_LOAD2 = 0; //counter starts counting from zero
TMR1_COMP12 = 1250-1; //16.7uS - count up to this value
TMR1_CMPLD12 = 1250-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
//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 QT1 Timer1
TMR1_CTRL1 = 0b0011000000100011; // 001(Count rising edges Primary Source),1000(IP Bus Clock),00 (Secondary Source = 0),
// 0(Count Once),1(Count up to Compare),0(Count Up),0(Co Channel Init),011(Toggle OFLAG on Compare)
//start QT1 Timer2
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)
//enable Timer1 and Timer2 simultaneously for QT1
TMR1_ENBL = 0b0000000000000110;
//===================================================================
//prepare interrupt parameters
attachInterruptVector(IRQ_QTIMER1, QT1_isr);
NVIC_ENABLE_IRQ(IRQ_QTIMER1);
//===================================================================
//enable ACMP3 to threshold radio receiver output (0.1 mfd into pin 23 and 10 Turn pot)
//ACMP3_OUT (output) is on T4 pin 2 (via XBAR1 and EMC_04)
//input is pin 18 (ACMP3_IN0 = AD_B1_01 : Default - no need for muxing) - 0.1 mfd Capacitor from radio
//voltage reference (INM7) is 6-bit internal DAC, VIN2 = VDD (3.3)
//comparator output state available - read CMP1_SCR LSBit
CCM_CCGR2 |= CCM_CCGR2_XBAR1(CCM_CCGR_ON); //turn clock on for xbara1
XBARA1_SEL3 = 0x001C; //this connects XBAR1_INOUT6 to XBAR1_IN28 (ACMP3_OUT)
IOMUXC_SW_MUX_CTL_PAD_GPIO_EMC_04 = 3; //ALT3 connects XBAR1_INOUT6 to T4 Pin 2
IOMUXC_GPR_GPR6 |= 0b00000000000001000000000000000000; //IOMUXC_XBAR_DIR_SEL_6 = 1 (direction select for XBAR_INOUT6)
CCM_CCGR3 |= 0x03000000; //enable clocks to CG12 of CGR3 for ACMP3
CMP3_CR0 = 0b00000000; //FILTER_CNT=0; HYSTCTR=0
CMP3_CR1 = 0b00010111; //SE=0, high power, COUTA, output pin, enable; mode #2A
CMP3_DACCR = 0b11011111; //Set DAC = 1/2 of VIN2 (3.3v)
CMP3_MUXCR = 0b00000111; //CMP_MUX_PSEL(0) | CMP_MUX_MSEL(7) Input pins select; plus = IN0 (pin 18), minus = DAC (code 7). PSTM = 0 (Pass Through Mode Disabled)
//initialise variables
Count1 = 0;
ISRTicks = 0;
ISRControlTicks = 0;
HCycleTicks = 0;
Secs = 0;
PrevSecs = 0;
Comp11Val1 = 250;
Comp11Val2 = Comp11Val1 + 1;
LSET = 5;
T1 = 1000; //time T1 set for 1000
N1Ptr = 0;
ActionTicks = 0;
ActionTime = 120000; //every second
ActionRequired = false;
RecordOn = true;
Out60KHzOn = true;
}
//ISR ROUTINE FOR QT1
//===================
void QT1_isr() { //ISR for QT1
//test Timer2 flags (60 KHz capture timer)
if (TMR1_CSCTRL2 & TMR_CSCTRL_TCF1) { //test if COMP1 flag set Timer2
TMR1_CSCTRL2 &= ~(TMR_CSCTRL_TCF1); //clear COMP1 flag
ISRComp2Ticks++;
}
if (TMR1_SCTRL2 & TMR_SCTRL_IEF) { //test if input edge capture event Timer2
TMR1_SCTRL2 &= ~(TMR_SCTRL_IEF); //clear input edge flag
ISRCapt2Ticks++;
TMR1Capt2Value = TMR1_CAPT2; //read the capture value Timer2
TMR1_CAPT2 = 0; //reset the capture register
}
//test Timer1 flags (60 KHz output timer)
if (TMR1_CSCTRL1 & TMR_CSCTRL_TCF1) { //test if COMP1 flag set Timer1
digitalWriteFast(0,1);
TMR1_CSCTRL1 &= ~(TMR_CSCTRL_TCF1); //clear COMP1 flag
ISRComp1Ticks++;
//TIMER1 ACTIONS
//==============
//Output frequency adjustments for 60 KHz. Compare Register holds no. of clocks required for a toggle of the output state. More clocks - slower toggle (and freq).
//Normally, the CMPLoad register uses 1249 clocks per toggle. Every now and again (ValX seconds), it will use 1248 (which makes it incrementally faster by 6.6667 nS).
//So ValX controls the output frequency in a very fine way - bigger ValX then lower the freq. Val1 is always 1 count below Val2 - so always marginally faster freq
//when Val1 in use than when Val2 is in use. LSET controls the proportion of time using either Val1 or Val2. If Count1 (0 to 9) is above or equal LSET then it uses Val1,
//otherwise it will use Val2 (so a higher value of LSET makes the output lower frequency, because Val2 in use more than Val1).
Count1++;
if (Count1>=10) {
Count1 = 0;
}
ISRControlTicks++; //when ISRControlTicks reaches Val1 or Val2, then ISRControlTicks is reset zero
if (Count1>=LSET) { //LSET = 0 to 9 - determines the fractional ratio of Val1 to Val2 (higher LSET = lower frequency)
if (ISRControlTicks>=Comp11Val1) { //Val1 = Val2 - 1, so this code is higher frequency (faster trace)
//more ISRControlTicks used (slower freq)
ISRControlTicks = 0;
TMR1_CMPLD11 = 1248; //count for just above 60 KHz (higher than 60 KHz)
TMR1_CMPLD12 = 1248;
}
else {
TMR1_CMPLD11 = 1249; //count for just below 60 KHz (lower than 60 KHz)
TMR1_CMPLD12 = 1249;
}
}
else {
if (ISRControlTicks>=Comp11Val2) { //Val2 = Val1 + 1, so this code is lower frequency (slower trace)
//less ISRControlTicks used (higher freq)
ISRControlTicks = 0;
TMR1_CMPLD11 = 1248; //count for just above 60 KHz (higher than 60 KHz)
TMR1_CMPLD12 = 1248;
}
else {
TMR1_CMPLD11 = 1249; //count for just below 60 KHz (lower than 60 KHz)
TMR1_CMPLD12 = 1249;
}
}
//test for record time (make N1Tot consecutive TCV measurements in total)
if (RecordOn) {
TCVHist[N1Ptr] = TMR1Capt2Value; //save Capture (phase) value in history
N1Ptr++;
if (N1Ptr>=TCVArraySize) {
N1Ptr = 0;
}
}
//update Half Cycle ticks (each tick is 8.3333 uS - 120,000 needed for one second)
HCycleTicks++; //this updates every 8.3333 uS - uniquely identifies every ISR pass
if (HCycleTicks>=120000) { //one second has now elapsed
digitalWriteFast(13,1); //flash the LED on
HCycleTicks = 0; //rollover of Timer1 ticks
Secs++; //update seconds counter
}
if (HCycleTicks==2000) {
digitalWriteFast(13,0); //flash the LED off
}
//invert the output if desired (double time for Compare1 and Compare2 registers)
if (InvertOn) {
InvertOn = false;
TMR1_CMPLD11 = 2499;
TMR1_CMPLD12 = 2499;
}
//update control action required timer
ActionTicks++;
if (ActionTicks>=ActionTime) { //action is required at ActionTime
digitalWriteFast(1,1); //action timing strobe (for scope use)
ActionRequired = true;
ActionTicks = 0;
}
}//end of Timer1 Actions
//update count
ISRTicks++;
asm volatile ("dsb"); // wait for clear memory barrier
digitalWriteFast(0,0);
}
//MAIN LOOP
//=========
void loop() {
int i, j;
//call KeyInput() routine
KeyInput();
//test for control action needed
if (ActionRequired) {
ActionRequired = false; //action only once until next action time arrives
//compute TCVAverage for most recent TCVArraySize values
i = N1Ptr; j = 0; TCVAverage = 0;
while (j<TCVArraySize) {
TCVAverage += TCVHist[i-j];
j++;
if ((i-j)<0) {i += TCVArraySize;}
}
TCVAverage = TCVAverage/TCVArraySize;
digitalWriteFast(1,0);
//compute change in TCVAverage
Diff = TCVAverage - PrevTCVAverage; //measure the change of TCValue average since last second
PrevTCVAverage = TCVAverage; //update previous value of TCVAverage
//test if automatic frequency control activated
if ((ControlOn)&&(!FineControlOn)) {
//Coarse Frequency/Phase Control
if (Diff>=20) { //positive means 60 KHz Timer 1 too fast
Comp11Val1+=5; //slow down
}
if (Diff<=-20) { //negative means 60 KHz Timer 1 too slow
Comp11Val1-=5; //speed up
}
if ((Diff<20)&&(Diff>-20)) {
if (TCVAverage>625) { //nudge the phase to centre (625)
Comp11Val1++; //slow down
}
if (TCVAverage<625) { //nudge the phase to centre (625)
Comp11Val1--; //speed up
}
}
}
if ((ControlOn)&&(FineControlOn)) {
//Fine Frequency/Phase Control
if (Diff>5) { //positive means 60 KHz Timer 1 too fast
Comp11Val1++; //slow down
}
if (Diff<-5) { //negative means 60 KHz Timer 1 too slow
Comp11Val1--; //speed up
}
if (TCVAverage>625) { //nudge the phase to centre (625)
LSET++; //slow down
if (LSET>9) {LSET=0; Comp11Val1++;}
}
if (TCVAverage<625) { //nudge the phase to centre (625)
LSET--; //speed up
if (LSET<0) {LSET=9; Comp11Val1--;}
}
}
Comp11Val2 = Comp11Val1 + 1; //update the second compare value in line with first
}
//print results of control action
if ((Secs>PrevSecs)&&(PrintControlOn)) {
Serial.print("Secs = "); Serial.print(Secs);
Serial.print(", Comp11Val1 = "); Serial.print(Comp11Val1);
Serial.print(", LSET = "); Serial.print(LSET);
Serial.print(", TCVAverage = "); Serial.print(TCVAverage);
Serial.print(", Diff = "); Serial.print(Diff);
Serial.println();
PrevSecs = Secs;
}
}
//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);
}
}
//Print Time1 and Time2 routine
void PrintTimes() {
ElapsedTime = Time2 - Time1;
Serial.print("Time1 = "); Serial.print(Time1);
Serial.print(", Time2 = "); Serial.print(Time2);
Serial.print(", ElapsedTime = "); Serial.println(ElapsedTime);
}
//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 'B': //set Bias Current for 24 MHz oscillator
//task goes here...
if (Serial.available()>=3) {
Byte2 = Serial.read();
BiasValue = Byte2-0x30;
Bias = BiasValue << 12;
XTALOSC24M_MISC0 = (XTALOSC24M_MISC0 & 0xFFFF9FFF) | Bias;
}
Serial.print("Bias = "); Serial.println(Bias);
break;
case 'C': //count value
//task goes here...
if (Serial.available()>=5) {
Byte2 = Serial.read();
Byte3 = Serial.read();
Byte4 = Serial.read();
Comp11Val1 = ((Byte2-0x30) * 100) + ((Byte3-0x30) * 10) + ((Byte4-0x30) * 1);
Comp11Val2 = Comp11Val1 + 1;
}
Serial.print("Comp11Val1 = "); Serial.print(Comp11Val1); Serial.print(", Comp11Val2 = "); Serial.println(Comp11Val2);
break;
case 'D': //increment LSET - lower frequency fine
//task goes here...
LSET++;
if (LSET>9) {
LSET = 0;
Comp11Val1++;
Comp11Val2 = Comp11Val1 + 1;
}
Serial.print("LSET = "); Serial.print(LSET);
Serial.print(", Comp11Val1 = "); Serial.print(Comp11Val1);
Serial.print(", Comp11Val2 = "); Serial.println(Comp11Val2);
break;
case 'P': //print TCV history array
//task goes here...
c = TCVArraySize/20;
for (a=0; a<c; a++) {
Serial.printf("%04d ", a+1);
for (b=0; b<ItemsPerLine; b++) {
Serial.printf(" %04d", TCVHist[(ItemsPerLine*a)+b]);
}
Serial.println();
}
break;
case 'T': //CPU core temp
//task goes here...
CPUTemp = tempmonGetTemp();
Serial.print("CPUTemp (deg C) = "); Serial.println(CPUTemp);
break;
case 'U': //decrement LSET - raise frequency fine
//task goes here...
LSET--;
if (LSET<0) {
LSET = 9;
Comp11Val1--;
Comp11Val2 = Comp11Val1 + 1;
}
Serial.print("LSET = "); Serial.print(LSET);
Serial.print(", Comp11Val1 = "); Serial.print(Comp11Val1);
Serial.print(", Comp11Val2 = "); Serial.println(Comp11Val2);
break;
case 'a': //set ActionTime
//task goes here...
if (Serial.available()>=8) {
Byte2 = Serial.read();
Byte3 = Serial.read();
Byte4 = Serial.read();
Byte5 = Serial.read();
Byte6 = Serial.read();
Byte7 = Serial.read();
ActionTime = ((Byte2-0x30) * 100000) + ((Byte3-0x30) * 10000) + ((Byte4-0x30) * 1000) + ((Byte5-0x30) * 100) + ((Byte6-0x30) * 10) + ((Byte7-0x30) * 1);
}
Serial.print("ActionTime = "); Serial.println(ActionTime);
break;
case 'c': //toggle ControlOn
//task goes here...
ControlOn = !ControlOn; //toggle ControlOn
Serial.print("ControlOn = "); Serial.println(ControlOn);
break;
case 'd': //increment count value (freq down)
//task goes here...
Comp11Val1++;
Comp11Val2 = Comp11Val1 + 1;
Serial.print("Comp11Val1 = "); Serial.print(Comp11Val1); Serial.print(", Comp11Val2 = "); Serial.println(Comp11Val2);
break;
case 'f': //toggle FineControlOn
//task goes here...
FineControlOn = !FineControlOn; //toggle ControlOn
Serial.print("FineControlOn = "); Serial.println(FineControlOn);
break;
case 'i': //set InvertOn
//task goes here...
InvertOn = true;
Serial.print("InvertOn = "); Serial.println(InvertOn);
break;
case 'p': //toggle PrintControlOn
//task goes here...
PrintControlOn = !PrintControlOn; //toggle ControlOn
Serial.print("PrintControlOn = "); Serial.println(PrintControlOn);
break;
case 'r': //toggle RecordOn
//task goes here...
RecordOn = !RecordOn; //toggle RecordOn
Serial.print("RecordOn = "); Serial.println(RecordOn);
break;
case 's': //decrement HCycleTicks by 1234 (S1234)
//task goes here...
if (Serial.available()>=6) {
Byte2 = Serial.read();
Byte3 = Serial.read();
Byte4 = Serial.read();
Byte5 = Serial.read();
Increment = ((Byte2-0x30) * 1000) + ((Byte3-0x30) * 100) + ((Byte4-0x30) * 10) + ((Byte5-0x30) * 1);
HCycleTicks = HCycleTicks - Increment;
if (HCycleTicks>=120000) {HCycleTicks -= 120000;}
}
Serial.print("HCycleTicks = "); Serial.println(HCycleTicks);
break;
case 't': //print Times
//task goes here...
PrintTimes();
break;
case 'u': //decrement count value (freq up)
//task goes here...
Comp11Val1--;
Comp11Val2 = Comp11Val1 + 1;
Serial.print("Comp10Val1 = "); Serial.print(Comp11Val1); Serial.print(", Comp11Val2 = "); Serial.println(Comp11Val2);
break;
case 'v': //print ISRTicks, ISRComp2Ticks, ISRCapt2Ticks
//task goes here...
Serial.print("ISRTicks = "); Serial.println(ISRTicks);
Serial.print("ISRComp1Ticks = "); Serial.println(ISRComp1Ticks);
Serial.print("ISRComp2Ticks = "); Serial.println(ISRComp2Ticks);
Serial.print("ISRCapt2Ticks = "); Serial.println(ISRCapt2Ticks);
break;
case 'w': //increment HCycleTicks by 1234 (S1234)
//task goes here...
if (Serial.available()>=6) {
Byte2 = Serial.read();
Byte3 = Serial.read();
Byte4 = Serial.read();
Byte5 = Serial.read();
Increment = ((Byte2-0x30) * 1000) + ((Byte3-0x30) * 100) + ((Byte4-0x30) * 10) + ((Byte5-0x30) * 1);
HCycleTicks = HCycleTicks + Increment;
if (HCycleTicks>=120000) {HCycleTicks -= 120000;}
}
Serial.print("HCycleTicks = "); Serial.println(HCycleTicks);
break;
TMR1_SCTRL1 |= 0b0000000000000001;
case 'z': //toggle Out60KHzOn
//task goes here...
Out60KHzOn = !Out60KHzOn; //toggle RecordOn
Serial.print("Out60KHzOn = "); Serial.println(Out60KHzOn);
if (Out60KHzOn) {
TMR1_SCTRL1 = 0; //clear prior to setting
TMR1_SCTRL1 |= 0b0000000000000001;
}
else {
TMR1_SCTRL1 = 0;
}
break;
}
}
}
}