Page 1 of 2

AVR-USB frequency meter

Posted: Wed Apr 09, 2008 8:18 am
by ps1x
Is there way to make usb frequency meter like EasyLogger?

I have done it with rs232 and it counts this way:
Fast timer counts ticks of itself between signal pulses, that way measurement done very quickly.
Can it be done on single chip like EasyLogger? i mean is there enought resources left for this job?

p.s. sorry for my poor english, but i try make it better :)

Posted: Thu Apr 10, 2008 11:46 am
by christian
You can do this with signals up to ~ 3 kHz. This gives an interrupt rate of ~ 300 microseconds which should be compatible with AVR-USB. For higher frequencies, you need a prescaler or simply count input pulses during a given period of time.

Posted: Mon Apr 14, 2008 7:42 pm
by spiff
christian wrote:You can do this with signals up to ~ 3 kHz. This gives an interrupt rate of ~ 300 microseconds which should be compatible with AVR-USB. For higher frequencies, you need a prescaler or simply count input pulses during a given period of time.

How about using the event capture function of a timer. When triggered externally, the timer value is stored WITHOUT needing precise timing of the interrupts.

A different approach would be to make it try both ways. First by trying to measure the time between pulses (as you suggest), then by counting pulses in a fixed amount of time.

Posted: Mon Apr 14, 2008 8:01 pm
by christian
If you use the input capture unit, you still get an interrupt every cycle. That gives the maximum frequency of ~ 3 kHz.

Other than that, the input capture unit can be very handy for precise measurements. I had frequency resolutions of more than 22 bits.

Posted: Mon Apr 14, 2008 8:16 pm
by spiff
christian wrote:If you use the input capture unit, you still get an interrupt every cycle. That gives the maximum frequency of ~ 3 kHz.

Is it necessary to generate an interrupt on the input capture. Wouldn't it be possible to just read the captured value from the main loop?

Posted: Mon Apr 14, 2008 8:18 pm
by christian
And take the difference with what? You need two captures which are a known number of cycles apart. This is easiest done with an interrupt.

Posted: Mon Apr 21, 2008 12:45 pm
by ps1x
But wait. How to do that, while INT0 and INT1 are used?

Posted: Mon Apr 21, 2008 12:49 pm
by christian
With the input capture unit, use pin ICP1. It captures the value of timer 1 when it triggers and generates an interrupt.

Posted: Mon Apr 21, 2008 5:58 pm
by ps1x
But i have frequencys from 1 to 30000 Hz. And You say this will work for 300 Hz. I need count input pulses which i make with outstanding hardware comparator.

Where can i read about WinAVR C language? its very different with CV-AVR...

Best regards!

Posted: Mon Apr 21, 2008 6:55 pm
by christian
You could connect your input signal to T0 AND to ICP. If the input frequency is below 3 kHz, use ICP for high resolution, if it's above 3 kHz, count pulses. This is not straight forward to implement and has problems if the input frequency changes, but it's the only idea I have which does not require external hardware.

Regarding gcc: see http://www.nongnu.org/avr-libc/user-manual/pages.html for a good introduction.

Posted: Mon Apr 21, 2008 7:18 pm
by ps1x
But T0 is just 8bit timer. It cant contain large values, right?
And to setup T1 i must use

Code: Select all

TCCR1B=0x06;

?
And then, read counted pulses using

Code: Select all

TIFR1

?

Posted: Mon Apr 21, 2008 8:12 pm
by christian
You can extend Timer0 in software, but that's not easy. Connect the signal to T1 instead and switch between modes as required.

The entire task is not easy. You must be very careful to avoid problems when you are in ICP mode and the frequency increases. I would not recommend this as a beginner's project, at least not with this complexity. If you could use always the same algorithm, it would be much easier.

Posted: Mon Apr 21, 2008 10:17 pm
by Guest
Well, this is my study project and i simply must do it, either i whant it or not. But while it is study project i'm still intresting in it as in hobby project and want to get it. I have very experienced last days, but now kinda stucked. I cant understand main algorithm in program good.

i know:

Code: Select all

1. Count pulses with T1 input capture.
2. Count time with other timer.
3. When time reaches 1 sec. Send T1 contents to USB-HID.


if i cant get enough power for 2 tasks, maybe make other AVR just counting and this one as for sample SPI > USB-HID.

Posted: Tue Apr 22, 2008 10:47 am
by christian
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 :-)

Posted: Tue Apr 22, 2008 12:27 pm
by Guest
Thank you for reply. I will not ask about it :) Anyway Asm is Oo for me. I connected 2 AVR's using UART and it works fine (using AVR-USB CDC). Now i will make it UART -> HID. Thank you.
Best regards.