# Tachometer accuracy problems at low frequencies

Hello,

This is my first post but I have already done some research in this forum, but I didn’t find out how to solve this problem yet.
I built this code for a tachometer:

``````int analogPin = 3;
unsigned long t1=0;
unsigned long t0=0;
unsigned long interval=0;
int digitalPin = 2;
volatile int cnt = 0;

void setup()
{
Serial.begin(9600) ;
attachInterrupt(digitalPinToInterrupt(digitalPin), IRQ, RISING);
t0=millis();
}

void loop() {
t1=millis();
interval = t1-t0;
if(interval >= 1000){

Serial.print(t1);
Serial.print(" , ");
Serial.print(interval);
Serial.print(" , ");
Serial.println(cnt);
cnt=0;
t0=millis();
}
}

void IRQ()
{
cnt++;
}
``````

The frequency is displayed in [Hz] for better troubleshooting. The usage range would be from 16 Hz to 180 Hz.

Also, I built the circuit attached, based on this topic in order to test the code. AC sine wave is conditioned to a 0 to 5V DC voltage in order to trigger Arduino Uno digital pin 2. The sine wave is generated by a computer soundcard output that is controlled by a program from this site. . The value of the sine wave is 1.7V RMS.
Apparently the circuit is OK because I put an oscilloscope and it is generating an square wave signal, 0 to 5V DC, and the frequency is accurate.

However, when I run the code in Arduino, the frequency calculation is not accurate at low frequencies.

This is the result from 1000Hz generated: accurate

Sample time [ms] interval [ms] frequency [Hz]
1 1000 1000 1009
2 2000 1000 1008
3 3000 1000 1006
4 4000 1000 1008
5 5000 1000 1006
6 6000 1000 1007
7 7000 1000 1006
8 8000 1000 1007
9 9000 1000 1008
10 10000 1000 1006

This is the result from 30Hz generated: average of 61.6Hz, far away from accuracy

Sample time [ms] interval [ms] frequency [Hz]
1 1000 1000 57
2 2000 1000 72
3 3000 1000 60
4 4000 1000 60
5 5000 1000 66
6 6000 1000 59
7 7000 1000 56
8 8000 1000 68
9 9000 1000 58
10 10000 1000 60

This is the result from 20Hz generated: average of 79.3Hz, far away from accuracy

Sample time [ms] interval [ms] frequency [Hz]
1 1000 1000 81
2 2000 1000 84
3 3000 1000 73
4 4000 1000 74
5 5000 1000 82
6 6000 1000 73
7 7000 1000 74
8 8000 1000 79
9 9000 1000 79
10 10000 1000 94

In order to understand what is going on, I ran the code below.

``````int analogPin = 3;
unsigned long t1;
float val =0;
int pin = 2;
volatile int Time =0;
volatile int lastTime =0;
volatile int deltaTime=0;
volatile uint8_t cnt = 0;

void setup()
{
Serial.begin(115200) ;
attachInterrupt(digitalPinToInterrupt(pin), IRQ, RISING);

}

void loop() {

t1=micros();

if(cnt>0)
Serial.print(t1);
Serial.print(" , ");
Serial.print(cnt);
Serial.print(" , ");
Serial.println(val);
}

void IRQ()
{
cnt++;
Time = micros();
deltaTime = Time-lastTime;
lastTime = Time;
}
``````

Thus, I pick up the digital count from digital pin 2 and compare with “analogRead” from analog pin 3.

The table below summarize the results for 30Hz from signal generator.

Sample Sample Time [us] Sample period [us] Count Analog Read value [V]
1 4688140 1872 172 0.2
2 4690012 1868 172 0.22
3 4691880 1872 172 0.22
4 4693752 1868 172 0.21
5 4695620 1872 172 0.19
6 4697492 1868 173 0.22
7 4699360 1872 173 5
8 4701232 1868 173 5
9 4703100 1872 173 4.99
10 4704972 1868 173 4.99
11 4706840 1872 173 4.99
12 4708712 1868 173 5.01
13 4710580 1876 173 5.01
14 4712456 1864 173 4.99
15 4714320 1872 173 4.99
16 4716192 1868 173 4.99
17 4718060 1872 173 1.58
18 4719932 1868 173 0.19
19 4721800 1872 173 0.21
20 4723672 1868 173 0.22
21 4725540 1872 173 0.22
22 4727412 1868 173 0.21
23 4729280 1872 173 0.19
24 4731152 1868 177 4.26
25 4733020 1872 177 5.01
26 4734892 1868 177 4.99
27 4736760 1872 177 4.99
28 4738632 1868 177 5
29 4740500 1872 177 4.98
30 4742372 1868 177 4.98
31 4744240 1872 177 4.99
32 4746112 1868 177 4.98
33 4747980 1872 177 4.99
34 4749852 1868 177 4.99
35 4751720 1872 178 0.19
36 4753592 1876 178 0.2
37 4755468 1864 178 0.21
38 4757332 1868 178 0.22
39 4759200 1872 178 0.22
40 4761072 1868 178 0.2
41 4762940 1872 178 0.18
42 4764812 1868 179 5.01
43 4766680 1872 179 5
44 4768552 1868 179 4.99
45 4770420 1872 179 4.98
46 4772292 1868 179 4.99
47 4774160 1872 179 4.99
48 4776032 1868 179 4.97
49 4777900 1872 179 4.97
50 4779772 1868 179 4.98

From the results, it appears to be counting more than once when there is only 1 rising of the edge. That’s where I am stuck.

Does anyone knows why it is counting more than should be? Any solution for that? I have already tried to switch from “RISING” to “FALLING” with no success.

Best reagards

vrsensorcircuit.pdf (17.5 KB)

``````  cnt=0;
``````

cnt is an int that takes two bytes. Setting the value to 0 requires setting the high byte to 0 and the low byte to 0. What happens when the interrupt fires between setting the high byte and setting the low byte?

Why are you not printing the value of cnt with the other data. Perhaps the data is not as inaccurate as you think. Or perhaps the inaccuracy is not where you think it is.

As a side note, 27 ohms is awfully small value for a base current limiting resistor. What is the AC input voltage?

At lower frequencies, the slope at zero crossing is less, more chance for any noise to create false triggers. You wouldn't see those on the scope unless you use a really short timebase.

PaulS:
Why are you not printing the value of cnt with the other data.

PaulS, thanks for the reply. Actually the value of cnt was printed. The column frequency [Hz] is the value of "cnt". As it gives a sample per second, I wrote frequency to better describe, but the value is "cnt" printed:

``````Serial.println(cnt);
``````

PaulS:
Setting the value to 0 requires setting the high byte to 0 and the low byte to 0. What happens when the interrupt fires between setting the high byte and setting the low byte?

But thinking that way, shouldn't be worse when the frequency rises? But what I'm facing is that is accurate at high 1000Hz and not accurate around 30Hz. If I correctly understand your point, at 30Hz the sample period would be 33ms, enough time to set "cnt" to 0 before another interrupt.

Anyway, do you a have a better solution to set "cnt" to 0?

Thanks!

aarg:
As a side note, 27 ohms is awfully small value for a base current limiting resistor.

aarg, thanks for the reply. Yes, it is a small resistor, but other resistors simply didn’t work. Actually a 150ohms also worked, but the shape of the 27ohms seemed to be better. However, both have this problem at low frequencies.

aarg:
What is the AC input voltage?

The AC input at the circuit is 1.7V rms. How can I know what is the maximum base current for a transistor? I didn’t find this specific in the datasheet.

That’s a pretty small voltage. What is the output impedance of the signal generator? What will be the output impedance of the device that drives it, in the final application?

If it is a digital scope, have you looked at the signal into the micro at the microseconds per sweep settings to see whether it is bouncing? There is no hysteresis in the input.

Not to discount that you also have software problems…

Anyway, do you a have a better solution to set "cnt" to 0?

``````noInterrupts();
cnt = 0;
interrupts();
``````

Don't let an interrupt happen while you reset a multi-byte variable.

As it gives a sample per second, I wrote frequency to better describe, but the value is "cnt" printed:

I don't think that the column header DID a better job...

It takes time to print things. While you are printing stuff, the stuff could be changing.

You should make copies (between noInterrupts() and interrupts()) of stuff you want to print/manipulate and you should reset the original (between noInterrupts() and interrupts()) before printing anything.

The table below summarize the results for 30Hz from signal generator.
From the results, it appears to be counting more than once when there is only 1 rising of the edge. That’s where I am stuck.

You’re only measuring counts per second. At 30Hz, what does ±1 count represent in terms of accuracy?

Your pulse count “cnt” information updates with ultra high response within the interrupt (this is good).
Your time calculation “t1” is based on polling millis() within the loop which has much slower response, further impeded by the printing, further impeded by the slow baudrate of 9600 (this is bad).

Try putting “t1” in your IRQ function and use
volatile unsigned long t1=0;

Also, t0 should record the previous t1.
Now your “interval” will be completely based on information derived from the interrupt routine. Your accuracy will be based on using millis() and 1 second measurement (1 part in 1000 or 0.1%).

aarg:
That's a pretty small voltage. What is the output impedance of the signal generator?

I don't know exactly how much is the impedance, but genereally soundcards output are about 100 ohms.

aarg:
What will be the output impedance of the device that drives it, in the final application?

The final application will be AC output from variable reluctance sensor from a motorcycle, that is 220 ohms.

aarg:

I don't get what is to worry about.

aarg:
If it is a digital scope, have you looked at the signal into the micro at the microseconds per sweep settings to see whether it is bouncing? There is no hysteresis in the input.

At the the rising edge of the waves there is no bouncing, it is a square wave 0 to 5V DC.

aarg:
Not to discount that you also have software problems...

I know the software is not improved, but I still couldn't figure out how the frequency is so far from expected: ~60Hz when generating 30Hz and 80Hz when generating 20Hz. If you did, please tell me.

The main problem is in your signal rather than your code. Yes, it can be imporved with protected transfer of the values out of the interrupt. But, when I test your code with a square wave generated by the Arduino at 35Hz the output is

1000 , 1000 , 36
2000 , 1000 , 35
3000 , 1000 , 35
4000 , 1000 , 35
5000 , 1000 , 35
6000 , 1000 , 35
7000 , 1000 , 35
8000 , 1000 , 35
9000 , 1000 , 35
10000 , 1000 , 35

``````int analogPin = 3;
unsigned long t1=0;
unsigned long t0=0;
unsigned long interval=0;
int digitalPin = 2;
volatile int cnt = 0;

void setup()
{
Serial.begin(9600) ;
attachInterrupt(digitalPinToInterrupt(digitalPin), IRQ, RISING);
t0=millis();
tone(2,35); //add 35 HZ test signal
}

void loop() {
t1=millis();
interval = t1-t0;
if(interval >= 1000){

Serial.print(t1);
Serial.print(" , ");
Serial.print(interval);
Serial.print(" , ");
Serial.println(cnt);
cnt=0;
t0=millis();
}
}

void IRQ()
{
cnt++;
}
``````

dlloyd, thanks for the reply. I will update these changes when I get home.

dlloyd:
You’re only measuring counts per second. At 30Hz, what does ±1 count represent in terms of accuracy?

1 count at 30Hz would be 3.3%, it’s a small value when compared to the current reading of ~100% error (~60Hz).
However, when reading 1000Hz, the reading is fair, ~0.7% of error (1007Hz).

cattledog, thanks a lot. This is something I haven't thought to try out. Great idea!
But still, if the problem is in the signal generated, do you know why I didn't see this "error" in scope or when reading the frequency with a multimeter?

cattledog:
The main problem is in your signal rather than your code. Yes, it can be imporved with protected transfer of the values out of the interrupt. But, when I test your code with a square wave generated by the Arduino at 35Hz the output is

1000 , 1000 , 36
2000 , 1000 , 35
3000 , 1000 , 35
4000 , 1000 , 35
5000 , 1000 , 35
6000 , 1000 , 35
7000 , 1000 , 35
8000 , 1000 , 35
9000 , 1000 , 35
10000 , 1000 , 35

``````int analogPin = 3;
``````

unsigned long t1=0;
unsigned long t0=0;
unsigned long interval=0;
int digitalPin = 2;
volatile int cnt = 0;

void setup()
{
Serial.begin(9600) ;
attachInterrupt(digitalPinToInterrupt(digitalPin), IRQ, RISING);
t0=millis();
tone(2,35); //add 35 HZ test signal
}

void loop() {
t1=millis();
interval = t1-t0;
if(interval >= 1000){

Serial.print(t1);
Serial.print(" , ");
Serial.print(interval);
Serial.print(" , ");
Serial.println(cnt);
cnt=0;
t0=millis();
}
}

void IRQ()
{
cnt++;
}

``````At the the rising edge of the waves there is no bouncing, it is a square wave 0 to 5V DC.
``````

Your data, where the count increments by 4 at the rising edge, would indicate otherwise. There is some sort of noise on the lead edge.

20 4723672 1868 173 0.22
21 4725540 1872 173 0.22
22 4727412 1868 173 0.21
23 4729280 1872 173 0.19
24 4731152 1868 177 4.26
25 4733020 1872 177 5.01
26 4734892 1868 177 4.99
27 4736760 1872 177 4.99
28 4738632 1868 177 5
29 4740500 1872 177 4.98
30 4742372 1868 177 4.98

why I didn’t see this “error” in scope or when reading the frequency with a multimeter?

The multimeter is probably not responding to the rapid changes. What kind of oscilloscope are you using? What time base were you using when looking at signal. If its the PC software scope referenced in your first post, I’m not sure I’d trust it.

cattledog:
Your data, where the count increments by 4 at the rising edge, would indicate otherwise. There is some sort of noise on the lead edge.

cattledog:
What kind of oscilloscope are you using? What time base were you using when looking at signal. If its the PC software scope referenced in your first post, I'm not sure I'd trust it.

Your right. In the beginning I thought that problem should be in the circuit, but after checking with multimeter ans oscilloscope, the circuit seemed to be trustful.
However, this explanation about the multimeter and scope seems to better explain the root cause of the problem.
It is the PC scope I am using. Why it should not be trusted?

It is the PC scope I am using. Why it should not be trusted?

It may be fine, but I have no experience with it, and don't understand the sample frequency, the triggering, the time base, etc, and all the settings you would rely on to pick up very short spikes on the lead edge of a 30 Hz pulse.

The hardware experts will have to help you decide if the circuit itself is capable, why there appears to be less noise at higher frequencies, and how to filter the noise without loosing the response at higher frequencies. You could certainly throw a capacitor across the input to the Arduino and see if that fixes it.

You may want to start a new topic in the "General Electronics" section of the forum to address the circuit and noise suppression issues.

Bingo. For an input circuit, I would recommend a comparator IC with some positive feedback for hysteresis. It’s a really standard, conventional circuit that you can easily adjust to different input signal requirements. By the way, pay great heed to dlloyd’s comments in reply #7. It’s right on. I thought to mention it, but I didn’t want things to get sidetracked from the main problem.

Test code:

``````int digitalPin = 2;

volatile unsigned long cnt = 0;
volatile unsigned long tIrq = 0;
volatile unsigned long tPrevious = 0;
volatile unsigned long tElapsed = 0;

unsigned long printInterval = 1000000;
unsigned long lastPrint = 0;

void setup()
{
Serial.begin(9600) ;
attachInterrupt(digitalPinToInterrupt(digitalPin), IRQ, RISING);
tone(2, 35); //add 35 HZ test signal
}

void loop() {
if (micros() - lastPrint >= printInterval) {
Serial.print(tIrq);
Serial.print(" , ");
Serial.print(tElapsed);
Serial.print(" , ");
Serial.println(cnt);
cnt = 0;
lastPrint = micros();
}
}

void IRQ()
{
cnt++;
tPrevious = tIrq;
tIrq = micros();
tElapsed = tIrq - tPrevious;
}
``````

Results (35Hz, 1000Hz, 10000Hz):

999312 , 28540 , 36
1998348 , 28540 , 35
2997388 , 28544 , 35
3996428 , 28540 , 35
4995472 , 28544 , 35
5994516 , 28548 , 35
6993552 , 28548 , 35
7992588 , 28540 , 35
8991632 , 28540 , 35
9990668 , 28540 , 35
10989708 , 28544 , 35
11988748 , 28540 , 35
12987792 , 28548 , 35
13986836 , 28552 , 35
14985872 , 28548 , 35
15984908 , 28540 , 35
17012492 , 28540 , 36
18011536 , 28544 , 35
19010580 , 28552 , 35

111099196 , 1000 , 1001
112100196 , 1000 , 1001
113101196 , 1000 , 1001
114102196 , 1000 , 1001
115103196 , 1000 , 1001
116104196 , 1000 , 1001
117105196 , 1000 , 1001
118106196 , 1000 , 1001
119107196 , 1000 , 1001
120108196 , 1000 , 1000
121109196 , 1000 , 1000
122110196 , 1000 , 1000
123111196 , 1000 , 1000
124112196 , 1000 , 1000
125113196 , 1000 , 1000
126114196 , 1000 , 1000
127114196 , 1000 , 1001
128115196 , 1000 , 1001
129116196 , 1004 , 1001
130117196 , 1000 , 1001
131118196 , 1000 , 1001
132119196 , 1000 , 1001

004456 , 100 , 10007
6005556 , 100 , 10008
7006656 , 100 , 10008
8007856 , 100 , 10007
9008956 , 100 , 10008
10010056 , 100 , 10009
11011356 , 100 , 10009
12012560 , 100 , 10009
13013756 , 100 , 10009
14014956 , 100 , 10009
15016156 , 100 , 10008
16017356 , 100 , 10008
17018556 , 100 , 10008
18019756 , 100 , 10008
19020956 , 100 , 10008
20022156 , 100 , 10008
21023356 , 100 , 10008
22024556 , 100 , 10008
23025756 , 100 , 10008
24026956 , 100 , 10008
25028156 , 100 , 10008
26029356 , 100 , 10008
27030556 , 100 , 10008
28031756 , 100 , 10008

It can be seen that there's ±4µs or ±1count error except at 10KHz the resolution of elapsed time is getting low (±1%).

EDIT: I agree with aarg ... hysteresis is needed. Without feedback, a slow rising signal with 4mV+ noise would get amplified to extra transitions on a transistor's output.

When ever I need to look at a pulse input I usually use a 74HC14 Schmidt trigger inverter on the pulse
input to sharpen up the rising and falling edge to prevent false edges.

Hey guys, good news. I changed the resistors to 10k and 1k and worked. Also I ran the code with my previous code and the dlloyd code. It seems not to vary so much. Indeed, I suppose the problem was the low resistor limiting the transistor base current, right?

Frequencies: 16Hz, 35Hz, 1000Hz, 10000Hz tested with signal generation from soundcard

my code dlloyd improvement
1000 , 1000 , 18 964392 , 62092 , 17
2000 , 1000 , 17 1957436 , 62096 , 16
3000 , 1000 , 17 2950472 , 62080 , 16
4000 , 1000 , 17 3943524 , 62088 , 16
5000 , 1000 , 16 4998648 , 62072 , 17
6000 , 1000 , 17 5991700 , 62080 , 17
7000 , 1000 , 16 6984748 , 62084 , 19
8000 , 1000 , 18 7977788 , 62072 , 16
9000 , 1000 , 16 8970832 , 62076 , 16
10000 , 1000 , 16 9963884 , 62092 , 16

1000 , 1000 , 36 14986508 , 28372 , 36
2000 , 1000 , 35 16007908 , 11284 , 37
3000 , 1000 , 35 17000956 , 28364 , 35
4000 , 1000 , 36 17994004 , 28368 , 35
5000 , 1000 , 35 18987052 , 28364 , 35
6000 , 1000 , 35 20008476 , 28384 , 36
7000 , 1000 , 35 21001520 , 28384 , 35
8000 , 1000 , 36 21994568 , 28380 , 35
9000 , 1000 , 35 23015968 , 28360 , 37
10000 , 1000 , 35 24009012 , 28364 , 35

1000 , 1000 , 1009 15010736 , 992 , 1008
2000 , 1000 , 1007 16011724 , 996 , 1008
3000 , 1000 , 1006 17012708 , 984 , 1007
4000 , 1000 , 1008 18013696 , 992 , 1007
5000 , 1000 , 1006 19014684 , 992 , 1007
6000 , 1000 , 1008 20014680 , 992 , 1008
7000 , 1000 , 1006 21015664 , 996 , 1008
8000 , 1000 , 1007 22016652 , 996 , 1008
9000 , 1000 , 1007 23017640 , 992 , 1008
10000 , 1000 , 1006 24018628 , 992 , 1007

1000 , 1000 , 10080 999992 , 100 , 10077
2000 , 1000 , 10072 2000880 , 100 , 10076
3000 , 1000 , 10061 3001768 , 100 , 10077
4000 , 1000 , 10071 4002752 , 100 , 10077
5000 , 1000 , 10062 5003740 , 96 , 10076
6000 , 1000 , 10071 6004628 , 100 , 10076
7000 , 1000 , 10062 7005612 , 100 , 10076
8000 , 1000 , 10072 8006500 , 100 , 10077
9000 , 1000 , 10072 9007488 , 100 , 10077
10000 , 1000 , 10061 10008472 , 96 , 10076

Thanks!