Frequency Measurement Using Microcontrollers
Use the 9S12 HCS12 counting/timing unit and a C language example program as a frequency counter
Using a microcontroller pulse accumulator
Many embedded applications require that you measure frequency or tally pulses from a sensor or signal source. Microcontrollers often include pulse accumulators that count pulses applied to an input pin without the need for the processor to service an interrupt for each input transition. Generally, you can just read the current pulse tally from a counter register.
The Freescale 9S12/HCS16 MCU includes a particularly useful pulse accumulator as part of its ECT (Enhanced Capture Timer) module. Using it, and a little bit of C code, you can easily build a frequency counter or tally counter capable of counting rates from DC to 5 MHz.
The PDQ Board's operating system includes high level C library functions for accessing the processor's enhanced capture timer and pulse accumulator systems. This webpage provides an example program for accessing those functions for frequency measurement.
This program measures a frequency applied to pin 17 on the Digital IO Header (H8
) of the PDQ Board. The maximum voltage allowed on pin 17 is 5.0 volts, the minimum voltage is 0 volts. The signal should be referenced to the ground at pin 1 on H8
.
Frequency is measured by using the Pulse Accumulator A
hardware in Event counting mode. The Enhanced Capture/Timer #3 hardware is also used to fire an interrupt every 100ms (10Hz). An ISR calculates the difference in pulses counted by the Pulse Accumulator and saves the measured Hz in the global variable fHz
. The ISR is allowed to fire ISR_MULTIPLIER
times before the frequency is calculated.
Setting the counter's period
The main parameter in this example ISR_MULTIPLIER
. This value sets the measurement period in seconds such that:
period in seconds = (ISR_TIME/ONE_MS) * ISR_MULTIPLIER / 1000
In the existing source code below ISR_MULTIPLIER
is set to 10. (ISR_TIME/ONE_MS)
is 100, resulting in a 1 second period.
Before adjusting the value of ISR_MULTIPLIER
read the "Note about Accuracy" in the source code.
C example source code for a frequency counter
Application source code:
1: #include <mosaic\allqed.h> // include all of the QED and C utilities 2: 3: // *********************** Measure Frequency Example *********************** 4: // 5: // This program measures a frequency applied to pin 17 on the Digital IO Header (H8) 6: // of the PDQ Board. The maximum voltage allowed on pin 17 is 5.0 volts, 7: // the minimum voltage is 0 volts. The signal should be referenced to pin 1 on H8. 8: 9: // Frequency is measured by using the Pulse Accumulator A hardware in "Event 10: // counting mode". The Enhanced Capture/Timer #3 hardware is also used to 11: // fire an interrupt every 100ms (10Hz). An interrupt service routine (ISR) 12: // calculates the difference in pulses counted by the Pulse Accumulator and 13: // saves the measured Hz in the global variable 'fHz'. The ISR is allowed 14: // to fire ISR_MULTIPLIER times before the frequency is calculated. This gives 15: // us the ability to measure pulses over a period of time longer than the 16: // maximum time for a single ISR. 17: 18: 19: 20: // ************************** Warning about Rollovers ************************** 21: // The maximum value an unsigned 16bit integer can hold is 65535. There are 22: // two separate values in this program which need to be designed with this 23: // overflow limit in mind. 24: // 25: // The first value is the ISR period: 26: // By default each tick of TCNT is 1.6 microseconds. This means that the longest 27: // period for an ISR is 65535 * 1.6 microseconds or 104.8576 ms. This program 28: // uses an even period of 100ms for the rollover. 29: // 30: // The second value is the number of pulses: 31: // The number of pulses that can be counted in a 16 bit number limits the period 32: // we can count over. If the highest frequency we want to measure is "fMax", the 33: // longest period we can measure is: 34: // 35: // longest period in seconds = 65536 / fMax 36: // 37: // fMax should be chosen to give you some headroom above the highest frequency 38: // you think you will be measuring. 39: 40: 41: // **************************** Note about Accuracy **************************** 42: // The accuracy of your measurement is determined by the measurement period. 43: // A longer period results in more accuracy (but a lower maximum frequency). 44: // The period is set by 45: // 46: // period in seconds = (ISR_TIME/ONE_MS) * ISR_MULTIPLIER / 1000 47: // 48: // You should change the ISR_MULTIPLIER value to adjust the period. Setting 49: // the ISR_TIME lower will increase the load on the CPU. 50: // 51: // The accuracy of your measurement is given by 52: // 53: // (+/- accuracy in Hz) = 1 / period in seconds 54: // 55: // For a period of 2 seconds, the accuracy is +/- 0.5 Hz. 56: 57: 58: 59: // ********** Interrupt Service Routine for Timed Function Calling ********** 60: // The TCNT counter in the Enhanced Capture Timer (ECT) system is a free-running 61: // counter. The TCNT frequency is 20 MHz divided by n, and the corresponding 62: // TCNT period is 50 nanoseconds times n. 63: // Valid values for the prescaler n are decimal 1, 2, 4, 8, 16, 32, 64, 128; 64: // see ECTPrescaler() 65: 66: // We leave the prescaler at decimal 32, 67: // yielding a 1.6 microsecond TCNT period, and a 104.8576 ms rollover (overflow) time. 68: 69: 70: 71: 72: 73: #define ONE_MS 625 // 625 counts of 1.6 microsecond TCNT = 1 ms 74: 75: // Specifies time in TCNT periods between calls to FunctionTimer() 76: // This will execute FunctionTimer() once every 100 milliseconds (that is, every 62500 TCNT periods) 77: // Note, any value for ISR_TIME that is greater than 65535 is invalid 78: // (unsigned) is extremely important here, otherwise this will result in a negative number 79: #define ISR_TIME ((unsigned)ONE_MS * 100) 80: 81: 82: // This is the number of times the ISR fires before we make a measurement 83: // Change this value to change the period of the measurement. Read notes above! 84: #define ISR_MULTIPLIER 20 85: 86: 87: // ********** Global variables ********** 88: uint timea; // previous value of TCNT timer 89: uint timeb; // current value of TCNT timer 90: 91: 92: uint pulsesa; // previous value of PULSE_A 93: uint pulsesb; // current value of PULSE_A 94: 95: uint timex; // the scheduled TCNT value of the previous ISR was 96: uint timey; // the scheduled TCNT value of the current ISR 97: 98: float fHz; // Read this from your main() or anywhere to get 99: // the frequency in Hz. 100: 101: uint isrRollover; 102: uint isrDelay; 103: ulong deltaPulses; 104: 105: 106: 107: 108: // Uncomment this to enable an output pin which toggles 109: // every time the ISR fires. This helps determine the frequency 110: // for the ISR 111: //#define DEBUG_ISR 112: 113: 114: 115: // OC3-based ISR, updates the 'fHz' global variable 116: _Q void FunctionTimer(void) 117: { 118: //always add one to this rollover count 119: isrRollover++; 120: 121: //isrRollover is always a value between 0 and ISR_MULTIPLIER-1 inclusive 122: isrRollover = isrRollover % ISR_MULTIPLIER; 123: 124: // if the rollover is 0, we update the frequency 125: if( isrRollover == 0 ) 126: { 127: // read the TCNT and PULSE_A variables as soon as possible 128: timeb = timea; 129: pulsesb = pulsesa; 130: timea = TCNT; 131: pulsesa = PulseRegRead( PULSE_A ); 132: 133: // read the scheduled ISR time 134: timey = timex; 135: timex = TC3; 136: 137: // calculate delay between ISR firing and code execution, if any 138: isrDelay = timeb-timey-(timea-timex); 139: 140: // calculate frequency 141: deltaPulses = (long)ONE_MS*(pulsesa-pulsesb); 142: fHz = (float)deltaPulses / (isrDelay+(float)ISR_MULTIPLIER*ISR_TIME); 143: fHz = fHz * 1000; 144: } 145: 146: // If defined this will toggle all pins on PortT which are configured 147: // as outputs. PortT is configured in main() 148: #ifdef DEBUG_ISR 149: PORTT = ~PORTT; 150: #endif 151: 152: 153: TC3 += ISR_TIME; // set OC3 count for next interrupt. 154: // A slower way to do this is: OCRegWrite(TCNT+ISR_TIME, 3); 155: // Because we executed ECTFastClear(), this access to TC3 156: // automatically resets the OC3 interrupt flag 157: } // so that new OC3 interrupts will be recognized. 158: 159: // This is crucial to the operation of the interrupt. 160: MAKE_ISR(FunctionTimer); // Make the function into an ISR 161: 162: 163: 164: _Q void StopFunctionTimer(void) 165: { 166: ECTInterruptDisable(3); // locally disable OC3, same as: TIE &= ~OC3_MASK; 167: } 168: 169: // inits variables and locally enables OC3 interrupt; 170: // does not globally enable interrupts! 171: _Q void StartFunctionTimer(void) 172: { 173: StopFunctionTimer(); // locally disable OC3 while we set it up 174: ATTACH(FunctionTimer, ECT3_ID); // post the interrupt service routine 175: ECTFastClear(); // allows the ISR to omit calling ECTClearInterruptFlag() 176: OCAction(OC_NO_ACTION, 3); // confirm that no automatic pin action occurs on PT3 177: OutputCompare(3); // set channel 3 as output compare 178: OCRegWrite(TCNT+ONE_MS, 3); // starts in 1 ms, same as: TC3 = TCNT + ONE_MS; 179: ECTClearInterruptFlag(3); // clear flag, same as: TFLG1 = OC3_MASK; 180: ECTInterruptEnable(3); // locally enable OC3, same as: TMSK1 |= OC3_MASK; 181: isrRollover = 0; // initialize our rollover counter 182: } 183: 184: 185: 186: 187: // ********************* main *********************************** 188: 189: int main( void ) 190: { 191: InitElapsedTime(); // init TIMESLICE_COUNT 192: 193: StartFunctionTimer(); // enable OC3 interrupt to calculate frequency 194: 195: // This configures the Pulse A accumulator 196: // The first parameter, 0, instructs the hardware to not fire an interrupt on every edge 197: // The second parameter, 0, prevents an interrupt from firing every time the accumulator overflows 198: // The third parameter, PULSE_A_RISING_EDGE, only counts rising edges on pin 17 of H8 199: // The final parameter, 1, configures the timer in 16 bit mode. A 0 would setup 2 timers in 8 bit mode 200: PulseASetup ( 0, 0, PULSE_A_RISING_EDGE, 1 ); 201: 202: // only enable interrupts after pulse accumulator is running and ISR is configured 203: ENABLE_INTERRUPTS(); 204: //StartTimeslicer(); // start timeslicer; also calls ENABLE_INTERRUPTS 205: 206: #ifdef DEBUG_ISR 207: // if this is enabled 208: // pin 24 on H8 is an output 209: // it will toggle every time the Output Compare fires 210: PORTT_DIRECTION = 0x01; 211: #endif 212: 213: // This loop runs forever, and prints the frequency measured on pin 17 of H8 214: while( 1 ) 215: { 216: // Delay for 65535 * 5 Microseconds. 217: // This delay slows down printing to the serial port. Printing faster, 218: // or at full speed, can overwhelm the Mosaic Terminal 219: MicrosecDelay(65535); 220: MicrosecDelay(65535); 221: MicrosecDelay(65535); 222: MicrosecDelay(65535); 223: MicrosecDelay(65535); 224: printf("Frequency in Hz = %.3f\n", fHz ); 225: } 226: 227: return 0; 228: }
See also → How to Measure Analog Distance Sensor