T4.X Frequency Counter using analog comparator

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:

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.
 
Back
Top