If you can use two AVRs: connect them with SPI or whatever (low priority interrupts), use 20 MHz for the frequency measuring AVR and writhe the SPI interrupt routine in assembler. You need to count the number of input pulses during a given period (e.g. 1 second) and the number of CPU clocks taken for these pulses. You get the frequency by dividing.
I'm using the following ICP interrupt handler for a similar task:
Code: Select all
/*
General Description:
The input capture pin is connected to a VCO. We want to measure the frequency
of that VCO as exact as possible because it represents our analog value.
This is done by counting VCO pulses during one timer1 period and measure the
total time interval in timer1 ticks during all those pulses. The quotient is
then proportional to the frequency. The resulting ADC resolution is 16 bits
for one timer1 period. If we sum up multiple timer1 periods, we can increase
the resolution accordingly (e.g. 20 bits for a sum of 16 periods).
Limitations:
The current interrupt implementation requires:
- VCO frequency > clock / 65536 (= 61 Hz @ 4 MHz clock)
- VCO frequency < clock / (32 + 1) (= 120kHz @ 4MHz clock)
The evaluation code in the main loop may require tighter limits.
*/
/* configs for io.h */
#define __SFR_OFFSET 0
#define _VECTOR(N) __vector_ ## N /* io.h does not define this for asm */
#include <avr/io.h> /* for CPU I/O register definitions and vectors */
#include "hardware.h"
#define x1 r24
#define x2 r25
#define x3 r26
//#define AVERAGE_CNT (F_CPU / T1_PRESCALE / 65536 + UPDATE_FREQ / 2) / UPDATE_FREQ
#define AVERAGE_CNT 22
/* we choose an averaging count which gives a total measurement time as close as
* possible to 1/50Hz. This should result in good ripple rejection.
* F_CPU = 4e6
* 22 * 65536 / 4e6 = 0.360448s ~ 18/50Hz (deviation = 2.24% of a mains period)
*/
.text
.global SIG_INPUT_CAPTURE1
.type SIG_INPUT_CAPTURE1, @function
SIG_INPUT_CAPTURE1:
intCapture1: ;2 (branch taken from vector)
push x1 ;2
in x1, SREG ;1
push x1 ;2
in x1, TIFR ;1
andi x1, 1 << TOV1 ;1
brne .tm1HadOverflow ;1 -> 10
.ignoreOverflow:
lds x1, _totalPulses ;2
subi x1, lo8(-1) ;1
sts _totalPulses, x1 ;2
lds x1, _totalPulses+1 ;2
sbci x1, hi8(-1) ;1
sts _totalPulses+1, x1 ;2
.capture1Ready:
pop x1 ;2
out SREG, x1 ;1
pop x1 ;2
reti ;4
;--------
; 29 cycles for typical intr / 40 for ignored overflow
; avg. 5 cycles latency -> fmaxload = fcpu/34 (117k @ 4M)
; process data once every timer 1 overflow
.tm1HadOverflow: ;1 for branch taken
ldi x1, 1 << TOV1 ;1
out TIFR, x1 ;1
lds x1, _timerWrapCnt ;2
dec x1 ;1
breq .doEval ;1 -> 17
sts _timerWrapCnt, x1 ;2
rjmp .ignoreOverflow ;2 -> 21
.doEval: ;1 for branch taken
push x2 ;2
push x3 ;2
in x1, ICR1L ;1 -> 21 get captured value before next capture
in x2, ICR1H ;1 11 cycles latency + 21 = 32 -> fmax = fcpu/32 (125k @ 4M)
lds x3, _totalPulses ;2
subi x3, lo8(-1) ;1
sts totalPulses, x3 ;2 store values in global variable (no underscore)
lds x3, _totalPulses+1 ;2
sbci x3, hi8(-1) ;1
sts totalPulses+1, x3 ;2
clr x3 ;1
sts _totalPulses, x3 ;2 clear local counter
sts _totalPulses+1, x3 ;2
sei ;1 -> 40 cycles until intr enable
; now we have time and can relax :-)
lds x3, _startPulsePos
sts _startPulsePos, x1 ; store new pulsePos
sub x1, x3
lds x3, _startPulsePos+1
sts _startPulsePos+1, x2
sbc x2, x3 ; x2:x1 holds pos2 - pos1 now
sts totalClocks, x1
sts totalClocks+1, x2
ldi x3, AVERAGE_CNT
sts _timerWrapCnt, x3
sbrc x2, 7
dec x3 ; account for negative delta
sts totalClocks+2, x3
pop x3
pop x2
rjmp .capture1Ready
; Local symbols:
.lcomm _totalPulses, 2 ; total captured pulses
.lcomm _startPulsePos, 2 ; pulse position of starting edge in timer1 counts
.data
.type _timerWrapCnt, @object
.size _timerWrapCnt, 1
_timerWrapCnt: ; remaining number of overflows until measurement end
.byte AVERAGE_CNT
Please don't ask me for details about this code
![Smile :-)](images/smilies/icon_smile.html)