Period of a frequency

Status
Not open for further replies.

StevenD

New member
Hello.

I've got a project with the main purpose is to get the period of two frequencies. The frequency I care about is about 33 kHz. Based on some initial testing it seems that I need to get this period reliably to 1 nanosecond resolution (.1 would be ideal). I am using a Teensy 4.0 using two interrupt function to read the pulses. I currently have a working prototype where I can accurately read both signals and I can get my 1 nanosecond resolution if I read the pulses for about a second.

My original implementation was to use interrupt functions to increment a counter, then at a set amount of delay total the pulses and get period (time/frequency) in micro seconds. This worked, but even with one second of counting time if I missed a single pulse it would be different enough to not be desirable. I am pretty sure the problem there was I could read the pulses and time accurately, but the first and last pulse may or may not have triggered, and as I said in a static system I would see changes that based on the hardware we bought for this project would make it undesireable.

With that in mind, I changed from reading pulses in a fixed time to reading a number of pulses and when I had roughly what I knew to be about one seconds worth of pulses I would then trigger an if condition and grab the time. This is working much better and I can pretty reliably get the period to within .5 nanoseconds.

My questions are:
  1. Can I get this period faster (within 1 nanosecond resolution)? (10 - 100 ms would be a goal)
  2. 2. Is there a better way to do this

There's quite a bit more code, but I will include the important bits relevant to this message below. Also of note, the TPeriod is vastly more important than the XPeriod. If there is a way to prioritize it above everything else.


Code:
volatile unsigned long presCount = 0;
volatile unsigned long tempCount = 0;
double xPeriod, TPeriod;
unsigned long lastMicros;


//Interrupt functions called when associated DI goes high
void pulse() {
  tempCount = tempCount + 1;
}

void pulse2() {
  presCount = presCount + 1;
  if ( presCount > U16FromRegMap(134) ) {			//This is a settable variable, but ~33000 for my case
    double deltaMicros = (micros() - lastMicros);
    //Get average period
    xPeriod = deltaMicros / tempCount;
    TPeriod = deltaMicros / presCount;
    presCount = 0;
    tempCount = 0;
    lastMicros = micros();
  }
}


void setup()
{
  //Set the frequency counter pins
  pinMode(3, INPUT_PULLDOWN);
  attachInterrupt(digitalPinToInterrupt(3), pulse, RISING);
  pinMode(9, INPUT_PULLDOWN);
  attachInterrupt(digitalPinToInterrupt(9), pulse2, RISING);
  lastMicros = micros();
}


void loop()
{
  //Nothing really here relevant to the period calculations
  delay(100);
}
 
2. Is there a better way to do this

Timer input capture is generally considered to the best way.

The hardware timers have the ability to capture their rapidly incrementing counter value into another register when a change is detected on a specific input pin. This avoids sensitivity to interrupt latency, which can vary somewhat depending on many factors. The timer hardware always reliably captures the timestamp (in terms of its own counter) without any software involvement. Then it generates an interrupt and software can read the result. On Teensy 4.0 the timers can run at 150 MHz, so each capture gives about 6.7ns resolution.

The FreqMeasureMulti library does this, so it's a good place to start. It might meet your needs without needing to learn the inner working of the timers. But if you do want to get into those details, FreqMeasureMulti can at least give you an easier starting point than having to build it all from the ground up.


If you want to stay with the pin interrupts approach, which is sensitive to variance in interrupt latency, you might do better to read ARM_DWT_CYCCNT rather than micro().
 
I second everything Paul says...

Another option: I have seen and have played with a little, is to try to change to a way such that each of the pins generate their own hopefully unique interrupt, so you can reduce the overhead. There are other threads that talk about it.

The sort of shortish version of this is: Each T4.x pin actually can be assigned to two different IO port.
That is there are two ranges of IO ports: Normal mode: 1-5 and High speed mode 6-9. At startup time we map all of the IO pins to the High speed mode (6-9).

Why this can be interesting? There is only one IRQ defined GPIO(6-9) - IRQ_GPIO6789 = 157, // RT1060 only
So all of the IO pins yous do a: attachInterrupt to, will in the end map to this IRQ and then run a default interrupt handler:
Code:
FASTRUN
void irq_gpio6789(void)
{
	irq_anyport(&GPIO6_DR, isr_table_gpio1);
	irq_anyport(&GPIO7_DR, isr_table_gpio2);
	irq_anyport(&GPIO8_DR, isr_table_gpio3);
	irq_anyport(&GPIO9_DR, isr_table_gpio4);
}
Which quickly check each one if they have pins defined and are in the appropriate interrupt state and call your function...

However if these pins are mapped back down to the lower GPIO port numbers. You will see each of these ports actually have two unique IRQs to each of them:
Code:
        IRQ_GPIO1_0_15 =        80,
        IRQ_GPIO1_16_31 =       81,
        IRQ_GPIO2_0_15 =        82,
        IRQ_GPIO2_16_31 =       83,
        IRQ_GPIO3_0_15 =        84,
        IRQ_GPIO3_16_31 =       85,
        IRQ_GPIO4_0_15 =        86,
        IRQ_GPIO4_16_31 =       87,
        IRQ_GPIO5_0_15 =        88,
        IRQ_GPIO5_16_31 =       89,
There are IOMUXC registers that say which pins should be mapped to high or low:
Code:
	IOMUXC_GPR_GPR26 = 0xFFFFFFFF;
	IOMUXC_GPR_GPR27 = 0xFFFFFFFF;
	IOMUXC_GPR_GPR28 = 0xFFFFFFFF;
	IOMUXC_GPR_GPR29 = 0xFFFFFFFF;
Which at startup are all mapped high. Setting a bit to 0 will cause the corresponding pin to the lower port range.

Why Interesting: You mention Pin3 and Pin 9
By my tables I see:
Code:
3	EMC_05	[COLOR="#FF0000"]4.5	[/COLOR]			PWM4_B2		2:TX_SYNC	IO-07	1:5		
 9	B0_11	[COLOR="#FF0000"]2.11[/COLOR]				PWM2_B2, QT4_2		1:TX2_RX2		2:11
I show the low port numbers: so Pin 3 is Port 9(4) pin 5 So you could use IRQ_GPIO4_0_15
and Pin 9 is: Port 7(2) pin 11 So you could use interrupt: IRQ_GPIO2_0_15

So again you could map the two pins down, and then write your own Interrupt handler, and use attachInterruptVector to set these interrupts to your own functions, which can probably assume the interrupt is for them and only them... removing a little overhead.


Sorry a little long...
 
Thanks for the input.

For clarification, this input capture can be set up to trigger on a change on a pin (in my case, pin 3 or 9 rising), the hardware timer counts are stored in some register/memory location. Then I could read the starting counts versus the triggered counts to know the time (in counts)?

This sounds like something that would work for me, my follow up question is can I "arm" the input capture and then "disarm" it since I don't want every pulse to trigger it?
 
KurtE, thanks for the reply, but honestly that is a bit over my head at the moment. I may revisit your suggestion if I have time, but I think for now this is what I am going to do.

I got around to looking at the documents for the FreqMeasure and FreqCount library, and if this is still true:

FreqCount vs FreqMeasure
FreqCount: best for 1 kHz to 8 MHz (up to 65 MHz with Teensy 3.0 & 3.1)
FreqMeasure: best for 0.1 Hz to 1 kHz

I think I am going to set up my higher frequency signal with the FreqCount library (hey hey I'm already wired for it!).

But I think I am going to continue to use my code for the lower frequency which instead of time based is based on the total number of pulses read. The reason I think I want to do it this way is that I have more control over how the measurement is done, and _any_ time based measurement makes for more error in my calculation which needs to be super precise. Some napkin maths I worked out

Getting one pulse extra in one second gives a different period by ~.0009 µs.
1000000 / 33400 = 29.94011976
1000000 / 33401 = 29.93922338 ~ -.0009

Whereas if the number of microseconds is off by one the difference is only .00003 µs
1000001 / 33400 = 29.94014970 ~ +.00003
So even 20 microseconds of delay between when the pulse is counted versus when the time variable is set gives less error than missing a single pulse.

I think using the FreqCount library for the higher frequency will also save some CPU time if I understand correctly.
 
my follow up question is can I "arm" the input capture and then "disarm" it since I don't want every pulse to trigger it?

The answer is yes, this is possible, but to do it you would need to dive pretty deeply into learning how the timer hardware works. The timers in this chip are loaded with advanced features, which is pretty awesome but also comes with a steep learning curve.

With a 33 kHz frequency, you'd probably have a much easier time just using FreqMeasure or FreqMeasureMulti collect all period measurements and simply ignore the ones you didn't want. Or just use them all. Teensy 4.0 is so incredibly fast that processing 33000 measurements per second is pretty easy.
 
I highly recommend Paul's FreqMeasureMulti library, which is in TeensyDuino. The example named Three_PWM_in_serial_output does almost exactly what you want. Every 0.5 seconds (which you can change to 1.0 seconds) the program computes the sum of all periods and computes the average frequency, and it does this for each of 3 input channels. Make sure you have TeensyDuino 1.54 or newer, because that version fixed a bug for T4.x.
 
Status
Not open for further replies.
Back
Top