mborgerson
Well-known member
After working for a while with downconverting 40KHz ultrasonic signals and thinking about the timing of conversions for Pk-Pk voltage measurement, I decided that I needed a T4 frequency counter that could work with minimal external hardware and accept low-level sine wave signals (about 0.2 Volts Pk-Pk). These requirements eliminate the standard frequency measurement libraries, which want signals that, at a minimum, pass through the digital input transition voltage.
I looked over some earlier posts about using the analog comparators and decided that might lead to a usable frequency measurement system. I modified some example code from TelephoneBill for comparator setup and devised an algorithm that collects the starting and ending CPU clock counts over an integral number of input cycles that fit within a specified collection window. I've tested it with windows from 2mSec to 1 second. For input signals I used a function generator, the output of a DS3232 temperature-stable 32768Hz oscillator, and a PWM output from the Teensy 4.0 running the counter code. Not too surprisingly, the later source gives perfect results because both the frequency measurement and the PWM generation are based on the same oscillator.
Here is the code:
and here are some results:
The 100Hz update gives pretty good (~ 1PPM noise) results with the 32768Hz square wave. The measured frequency is about 0.3Hz or about 100 PPM high. I'm not sure how much of that is drift in the T4.0 clock frequency and how much is due to issues with the timing of the interrupt handlers in the code.
The overall impact on the T4.0 is pretty small, as the ISR uses about 50 to 100nSec for each cycle of the incoming signal. That shouldn't have much impact at input frequencies under 100KHz.
Eventually, I'll get around to packaging this algorithm into a library with features such as auto adjustment of the comparator threshold, selectable input pins, and selectable comparator filtering and hysteresis.
I looked over some earlier posts about using the analog comparators and decided that might lead to a usable frequency measurement system. I modified some example code from TelephoneBill for comparator setup and devised an algorithm that collects the starting and ending CPU clock counts over an integral number of input cycles that fit within a specified collection window. I've tested it with windows from 2mSec to 1 second. For input signals I used a function generator, the output of a DS3232 temperature-stable 32768Hz oscillator, and a PWM output from the Teensy 4.0 running the counter code. Not too surprisingly, the later source gives perfect results because both the frequency measurement and the PWM generation are based on the same oscillator.
Here is the code:
Code:
/************************************************
* Test frequency counting with analog comparator
*/
#include <TimeLib.h>
//DEFINE variables
//================
const int LEDpin = 13;
const int pwmpin = 12;
const int IRQmarkpin = 2;
#define CMP_SCR_IER (1<<4)
#define CMP_SCR_IEF (1<<3)
#define CMP_SCR_CFR (1<<2)
#define CMP_SCR_CFF (1<<1)
#define IRQMARKHI digitalWriteFast(IRQmarkpin, HIGH);
#define IRQMARKLO digitalWriteFast(IRQmarkpin, LOW);
#define LEDON digitalWriteFast(LEDpin, HIGH);
#define LEDOFF digitalWriteFast(LEDpin, LOW);
#define LEDTOGGLE digitalToggleFast(LEDpin);
//*******************************************************
void setup() {
//Initialize the digital pin 13 as an output for LED.
pinMode(LEDpin, OUTPUT);
delay(500);
Serial.begin(9600);
Serial.println("\n\nComparator frequency counter test.");
InitFCounter(50); // init with 100Hz update
analogWriteFrequency(12, 100000);
analogWrite(12, 128);
}
void loop() {
if(Serial.available()){
char ch = Serial.read();
if(ch == '0')InitFCounter(1); // init with 1Hz update
if(ch == '1')InitFCounter(10); // init with 10Hz update
if(ch == '2')InitFCounter(100);
if(ch == '3')InitFCounter(500);
if(ch == '!') RunTest(8);
}
delay(10);
}
void RunTest(uint16_t numsamples) {
float fs;
uint16_t i;
if (numsamples < 1) numsamples = 1;
if (numsamples > 100) numsamples = 10;
Serial.println();
for (i = 0; i < numsamples; i++) {
fs = CTFrequency();
Serial.printf("%8.3f\t",fs);
delay(5);
while (!CTAvailable()) {
}
}
Serial.println();
}
volatile uint32_t inputcycles;
IntervalTimer FCTimer;
const int FCInPin = 18;
void InitFCounter(uint16_t updateRate) {
//Derived from an example program by TelephoneBill posted to the PJRC forums
//on 7/22/2020
//Enable ACMP3 to threshold an analogue a.c. input signal - 9 code steps below
//Note: Signal is via 0.1 mfd into pin 18. 10 turn 10K pot across 3v3 and GND with wiper also to pin 18.
//Set 10K pot to central position (5K). This will bias the d.c. voltage on pin 18 to midway 3v3 and GND.
//ACMP3 input is pin 18 (ACMP3_IN0 = AD_B1_01 : Default connection - no need for muxing)
//Voltage reference (INM7) is 6-bit internal DAC, VIN2 = VDD (3.3v)
pinMode(FCInPin, INPUT_DISABLE);
CCM_CCGR2 |= CCM_CCGR2_XBAR1(CCM_CCGR_ON); //(1) turn clock on for xbara1
CCM_CCGR3 |= 0x03000000; //(2) enable clocks to CG12 of CGR3 for ACMP3
CMP3_CR0 = 0b01110011; //(3) FILTER_CNT=7; HYSTCTR=0x03
CMP3_CR1 = 0b00010101; //(4) SE=0, high power, COUTA, no output pin, enable; mode #2A
CMP3_DACCR = 0b11011111; //(5) Set DAC = 1/2 of VIN2 (3.3v) = 1.65v
CMP3_MUXCR = 0b00000111; //(6a) CMP_MUX_PSEL(0) | CMP_MUX_MSEL(7) Input pins select;
//(6b) plus = IN0 (pin 18), minus = DAC (code 7). PSTM = 0 (Pass Through Mode Disabled)
attachInterruptVector(IRQ_ACMP3, &CMP3_ISR );
NVIC_SET_PRIORITY(IRQ_ACMP3, 32); // high priority
NVIC_ENABLE_IRQ(IRQ_ACMP3);
inputcycles = 0;
CMP3_SCR = CMP_SCR_CFR + CMP_SCR_CFF; // clear the flags and interrupt enables
CMP3_SCR = CMP_SCR_IER; // enable rising edge interrupt
// Serial.printf("CMP3_SCR = %02X\n", CMP3_SCR);
delay(10); // default first delay of 10mSec
// now set up an intervaltimer to do auto updates
FCTimer.begin(FC_ISR, 1000000 / updateRate);
Serial.printf("Updating results at %u Hz.",updateRate);
}
volatile uint32_t startcount;
volatile uint32_t endcount;
volatile float ctfrequency;
volatile bool ctavailable = false;
float CTFrequency(void) {
ctavailable = false;
return ctfrequency;
}
bool CTAvailable(void) {
return ctavailable;
}
// This timer interrupt handler updates the frequency result
// at regular intervals in a background process
void FC_ISR(void) {
uint32_t dcycnt;
CMP3_SCR = CMP_SCR_CFR + CMP_SCR_CFF; // clear the flags and interrupt enables
dcycnt = endcount - startcount;
inputcycles--; // correct for initial count
ctfrequency = (float)inputcycles * F_CPU / (float)dcycnt;
CMP3_SCR = CMP_SCR_CFR + CMP_SCR_CFF; // clear the flags and interrupt enables
CMP3_SCR = CMP_SCR_IER; // enable rising edge interrupt
ctavailable = true;
inputcycles = 0; // starts next collection cycle
}
void CMP3_ISR(void) {
uint32_t cycnt;
CMP3_SCR = CMP_SCR_CFR | CMP_SCR_CFF; // clear the flags
cycnt = ARM_DWT_CYCCNT;
if (inputcycles == 0) { // first count
startcount = cycnt;
inputcycles++;
} else {
inputcycles++;
endcount = cycnt;
}
CMP3_SCR = CMP_SCR_IER; // re-enable rising edge interrupt
#if defined(__IMXRT1062__) // Teensy 4.x
asm("DSB"); // Without this instruction, ISR is double-triggered!
#endif
}
and here are some results:
Code:
***************************** Updating results at 1 Hz. *****************************
Signal Generator one Volt P-P sine wave
29596.561 29596.949 29596.920 29598.580 29596.016 29602.420 29606.143 29604.896
29601.080 29601.527 29605.471 29609.793 29608.955 29607.771 29602.066 29598.531
29596.510 29596.693 29601.701 29603.809 29600.992 29609.068 29616.295 29610.814
DS3232 temperature-compensated 32768Hz 3.3V Square Wave
32768.285 32768.285 32768.285 32768.285 32768.285 32768.285 32768.285 32768.285
32768.285 32768.285 32768.285 32768.285 32768.285 32768.285 32768.289 32768.289
32768.285 32768.289 32768.289 32768.285 32768.285 32768.285 32768.285 32768.285
*****************************Updating results at 10 Hz. *****************************
Signal Generator one Volt P-P sine wave
29643.133 29641.783 29641.473 29640.031 29638.350 29640.938 29638.734 29642.098
29650.271 29652.678 29648.486 29645.918 29641.189 29642.561 29644.586 29645.654
29640.988 29642.490 29641.896 29644.201 29643.551 29645.105 29649.979 29651.631
DS3232 temperature-compensated 32768Hz 3.3V Square Wave
[COLOR="#0000CD"]32768.297 32768.289 32768.297 32768.297 32768.285 32768.293 32768.289 32768.289
32768.297 32768.289 32768.293 32768.289 32768.285 32768.297 32768.289 32768.289
32768.293 32768.285 32768.293 32768.297 32768.289 32768.293 32768.289 32768.289
[/COLOR]
100 KHz PWM output from same T4.0
100000.000 100000.000 100000.000 100000.000 100000.000 100000.000 100000.000 100000.000
100000.000 100000.000 100000.000 100000.000 100000.000 100000.000 100000.000 100000.000
100000.000 100000.000 100000.000 100000.000 100000.000 100000.000 100000.000 100000.000
*****************************Updating results at 100Hz *****************************
DS3232 temperature-compensated 32768Hz 3.3V Square Wave
[COLOR="#0000CD"]32768.289 32768.301 32768.312 32768.289 32768.301 32768.312 32768.332 32768.254
32768.289 32768.320 32768.312 32768.270 32768.312 32768.234 32768.270 32768.270
32768.289 32768.254 32768.289 32768.312 32768.234 32768.289 32768.289 32768.301[/COLOR]
Signal Generator one Volt P-P Sine wave
29665.957 29664.818 29664.770 29664.947 29664.520 29662.271 29662.510 29660.861
29649.871 29655.635 29656.688 29658.031 29658.635 29658.447 29656.309 29658.566
29670.418 29669.453 29668.377 29667.584 29669.215 29670.082 29667.881 29667.822
Signal Generator 2.5 Volt P-P Sine wave 288KHz
288590.812 288579.062 288591.781 288590.219 288559.844 288595.438 288579.062 288585.625
288517.312 288544.438 288546.781 288562.344 288565.406 288577.188 288561.188 288533.281
288476.719 288484.625 288471.719 288483.062 288464.219 288453.094 288464.031 288473.562
***************************** Updating results at 500 Hz. *****************************
DS3232 temperature-compensated 32768Hz 3.3V Square Wave
32768.098 32768.262 32768.309 32768.195 32768.262 32768.371 32768.262 32768.371
32768.594 32768.262 32768.309 32768.418 32768.371 32768.418 32768.371 32768.262
32768.152 32768.371 32767.877 32768.371 32768.309 32768.418 32768.262 32768.262
Signal Generator one Volt P-P Sine wave
29662.463 29662.867 29661.754 29665.295 29665.092 29664.938 29665.035 29666.104
29667.924 29666.328 29669.512 29668.229 29667.418 29668.229 29667.824 29667.621
29674.604 29673.389 29672.275 29672.496 29676.773 29674.705 29674.604 29675.008
The 100Hz update gives pretty good (~ 1PPM noise) results with the 32768Hz square wave. The measured frequency is about 0.3Hz or about 100 PPM high. I'm not sure how much of that is drift in the T4.0 clock frequency and how much is due to issues with the timing of the interrupt handlers in the code.
The overall impact on the T4.0 is pretty small, as the ISR uses about 50 to 100nSec for each cycle of the incoming signal. That shouldn't have much impact at input frequencies under 100KHz.
Eventually, I'll get around to packaging this algorithm into a library with features such as auto adjustment of the comparator threshold, selectable input pins, and selectable comparator filtering and hysteresis.