Introduction: How to Measure High Frequency and Duty Cycle, Simultaneously, Using a Microcontroller.

I know what you think: "Huh? There are lots of Instructables on how to use microcontrollers to measure signal frequency. Yawn." But wait, there is a novelty in this one: I describe a method of measuring frequencies much higher than a microcontroller (MCU) can bear and the signal's duty cycle - all at the same time!

The device's frequency range spans from ~43 Hz to ~450 kHz, while duty cycle ranges from 1% up to 99%.

Let me explain the "can bear" part: an MCU measures period of a square wave signal, T, by tracking time between two subsequent transition events. For example, low-to-high voltage jumps on one of its I/O pins. It does it by counting the number of pulses of its own internal clock. Naively, the upper limit for measured frequencies should obey the Nyqvist-Shannon sampling theorem; i.e., it would roughly equal to the half of the MCUs clock frequency. In reality the limit is much, much lower, because the MCU must execute code to handle interrupts, save variables, do arithmetic operations, display results, etc. In my experiments with a 48 MHz MCU the minimal number of clock cycles between measurable transitions was about 106. Hence, the upper limit of the measurable frequency range in this case would be 48,000 / 212 / 2 = 226.4 kHz.

While the MCU measures signal's period, it can also determine its pulse width, P: the time of signal voltage remaining high. In other words, time between low-to-high and high-to-low transitions. The signal's duty cycle is then defined as the following percentage:

Duty = 100% * P / T

Just like in the case of frequency, there is a practical limit on pulse width. Using the above example, 106 clock cycles would limit the pulse width to no less than 2.21 microseconds. Or, no less than 50% at 226.4 kHz.

One of the ways of boosting up the upper frequency limit of square wave signals is the application of digital dividers that utilize flip-flops. Dividing the input frequency by n would extend the measurable upper range n times. This is great news, digital dividers have one fundamental flaw: divided signal loses the pulse width (and duty cycle) information! Because of the way the dividers work, their output has always 50% duty cycle. Bummer...

In the following pages, however, I will show how to digitally divide frequency and preserve the original pulse width allowing me to measure signals well beyond the limits imposed by direct counting.

Step 1: Digital Frequency Division

Traditional digital frequency dividers use flip-flops; this tutorial nicely explains principles how to construct dividers using standard JK flip-flops. This solves the problem of input frequencies too high for the MCU, but has one major drawback: divided signal has 50% duty cycle irrespective of the input signal's duty! To see why it's the case look at the first two figures. The original signal with period T and pulse width P is fed into the clock pin of a JK flip-flop while its J and K pins are held high at all times (first figure). 3.3V logic is assumed throughout. Let's suppose that the flip-flop is triggered by the positive (i.e., rising) edge of the clock. Under these conditions, changes of the state of the output pin (individual "flips" and "flops") occur every time the clock pin goes from low to high. The clock's high to low transition (i.e., the negative edge) is completely ignored. See the second figure. The output pin, Q, emits a signal whose period is twice as long as the original period, i.e., it's frequency is halved. The output's pulse width is always equal to T. Consequently, the original pulse width, P, is lost.

Adding another JK flip-flop in a configuration shown in the third figure divides the original frequency by 4. Adding more flip-flops in the same sequential manner divides frequency by subsequent powers of 2: 8, 16, 32, etc.

Problem: how to divide frequency of a square wave while preserving it's pulse width?

The idea is to properly add a negative edge triggered JK flip-flop to the mix. Let's call it "Neg FF"; see fourth figure. Here, "properly" means that the J and K pins of the new flip-flop are tied to the Q and Qbar output pins, respectively, of the divider-by-4 ("Pos FF") illustrated in the previous figure. (In here, "bar" is the horizontal bar over the Q symbol indicating logical negation.) To see what this achieves take a look at the function table of the "Neg FF" in the fifth figure: Neg's output pins, Q and Qbar, mirror the state of its input pins, J and K, respectively. Which means they mirror the state of the Pos' Q and Qbar. But, the Neg's flip-flop action must wait for the negative edge of the original signal, which arrives at time P after the positive edge. Aha!

The resulting waveforms are illustrated in the sixth figure. "Pos Q" outputs signal at 1/4th frequency, "Pos Qbar" is it inverse, "Neg Q" follows "Pos Q" shifted by pulse width P, and "Neg Qbar" is its inverse. You can verify that the logical AND of "Pos Qbar" and "Neg Q" produces a pulse train characterized by the original pulse width P and 1/4th the frequency. Bingo!

At first I used exactly this output signal to feed the MCU. However, it turned out to be problematic for very short pulse widths because of the MCU's 106 cycles limitation mentioned in the Introduction. I've solved this small problem by picking another output: "Pos Qbar" AND "Neg Qbar" instead. One look at the waveforms should convince you that pulse width of this particular waveform, P', varies between T and 2T instead of (0, T) range for P. The P can be easily recovered from P' by:

P = 2T - P'

Step 2: Recommended Hardware

I truly like the relative newcomer to electronic hobbyists: Atmel SAM D21 MCUs based on the 32-bit ARM Cortex M0+ processor operating at 48 MHz clock rate, much higher than the older Atmels. For this project I bought:

Step 3: The Circuit

The first figure shows a simplified schematic of the frequency/duty meter. The 3.3 V CMOS logic is assumed throughout. Consequently, the input square wave's amplitude must be between the corresponding VIH level (i.e., 2 V) and 3.3 V. If not, you need to scale it up or down accordingly. In most cases a simple voltage divider would suffice. If you want to design your version of the meter at a different logic level, then you have to use another micro controller (MCU), battery, and a display that work at the desired level. The logic gates and flip-flops used in this project work with logic levels anywhere between 2 V and 6 V and should be OK in most cases.

As shown, the ItsyBitsy MCU uses pins 9-13 to communicate with the display through the software SPI protocol. The 3V pin delivers power to the entire circuit. Digital input pin 3 accepts the analyzed signal, while pins 2 and 4 control the signal source: either direct signal coming through gate AND3 (low input frequencies), or signal divided by 4 through gate AND4 (high input frequencies) as described in Step 2. The code, discussed in the next step, automatically detects the incoming frequency range and appropriately switches the signal source.

The schematic does not show the true complexity of digital chip connections. The second image shows how the project would look on a breadboard. Input signal comes through a red wire to the 2CLK pin of the dual positive edge flip-flop. CAUTION: Normally, all the J and K pins of this flip-flop should be held high, but SN74HC109 in particular features the Kbar pin - an inverted K pin - instead. Hence, this pin must be grounded! The first negative edge flip-flop in SN74HC112 has its 1K and 1J pin connected to the 1Q and 1Qbar pins of SN74HC109. The second flip-flop in SN74HC112 is unused and its input pins (2K, 2J, 2CLRbar) are grounded. All other extra pins PREbar (preset) and CLRbar (clear) in all flip-flops must be connected to logical high. Unused clock and output pins are left unconnected. Similarly, unused input pins in all gates are grounded, while unused output pins are left unconnected. As I discussed in my "Invisible Killer of the Phone Ring" Instructable, grounding unused input pins of logical chips eliminates random oscillations and saves battery power.

Step 4: The Code and Measuring Low Frequencies

Naturally, all the action happens in the code linked below. When the input incoming on pin 3 switches from digital low to high, the MCU starts counting pulses of its internal 48 MHz clock. It notes the moment of high to low transition and continues the count until the next low to high switch, when it restarts the whole process again. The first count represents pulse width, while the entire count represents the signal's period. And that's the whole secret.

The CPU notes these transitions via hardware interrupts. The SAMD21 has several clocks; my code uses TC3 one. Initially, I've started by reading the M0's data sheet bracing for a lot of effort in coding the interrupt handler, but soon I have discovered a very much related code in the Arduino Forum posts by users electro_95, MartinL, and Rucus whose contribution is duly acknowledged. I incorporated and modified their combined code into mine; saving me a lot of time!

As I previously mentioned, the signal resolution is limited by ~106 CPU cycles to execute code between interrupts. Digital division with pulse width preservation takes care of high frequencies. Low frequencies, on the other hand pose another challenge: since the TC3 clock counter is 16 bit long, it overflows after crossing the 65,536 counts limit. One can handle this situation by adding an overflow interrupt, but chose a different solution: TC3 can use a prescaled (i.e., software-divided) CPU clock instead of the hardware 48 MHz one. Thus, if the signal's period approaches the overflow limit, the code can instruct TC3 to use 24 MHz counts for the next period and, voila, the counter drops below 32,768 counts. For even lower frequencies the TC3 can be instructed to count 12 MHz pulses, etc. The appropriate prescaler is automatically determined based on the signal's frequency, with hysteresis, in order to keep the TC3 counter within the overflow limit. As a result, the lower end of the device's range is about 43 Hz.

You are welcome to fork the code and use it in your project, but please do mention its source when publishing results.

Link to the code.