Arduino UNO. I have stripped the code of everything not related to this issue.
I am working on a project that takes an input frequency from 0Hz to 50Hz. I have tried a couple different ways to read the frequency, but am having some issues. My first attempt was to compare the pin state with the previous pin state, assigned variables as float. This works well in my final code for the full range of frequencies, but it does not include decimal places. If I input 10.5Hz, I get a readout of 10.00Hz for example, or the output toggles between 10.00Hz and 11.00Hz. I need to get Two decimal places.
Now I’m looking at pulseInLong. This works above an input frequency of 30.5Hz, but with an error of 0.03Hz, the output is 30.53Hz. If I input 30.49Hz, the output is -30.52Hz. It goes negative and any input lower yields an exponentially higher negative output. 17.00Hz input returns an output of -174.xxHz.
How can I read a frequency range from 0.00 to 50.00Hz with Two decimal places of accuracy?
The backstory: I am building a speedometer motor driver to use a mechanical speedometer with a newer transmission that puts out an electrical signal. I have attached a motor to the speedometer and have a working prototype. The motor has an optical disk that is being read with an Interrupt that returns a frequency from about 15Hz to 1450Hz. PID code compares the speed of the motor to the desired speed, the desired speed is calculated based on the input frequency and an equation modeled on an exponential decay curve. I tried using Interrupts for the input frequency, but was getting up to 20% error.
//Input frequency
const int frqPin = 2; //Input Pin
int pulseHigh; // Integer variable to capture High time of the incoming pulse
int pulseLow; // Integer variable to capture Low time of the incoming pulse
float pulseTotal; // Float variable to capture Total time of the incoming pulse
float frequency; // Calculated Frequency
const int oneSecond = 1000; //Meassured time for Input Frequency
int frqPinState = LOW;//for Input Frequency
int prevFrqPinState = LOW;//for Input Frequency
float frq = 0;//Input Frequency
float frq1 = 0;//increments with each VSS pulse
float frq3 = 0;//Input Frequency buffer
unsigned long timer = 0;//Input frequency timer
void setup()
{
Serial.begin(115200);//enable serial monitor
pinMode(frqPin, INPUT); //Declared Pin 2 as Input
}
void loop()
{
Serial.print(" Frq is ");
Serial.print(frq);
Serial.print(" Frequency is ");
Serial.println(frequency);
//Input Frequency===============================
frqPinState = digitalRead(frqPin); //Reads State of Input Pin
if (frqPinState == LOW) //Creates a previous state
{
prevFrqPinState = LOW;
}
if (frqPinState == HIGH && prevFrqPinState == LOW) //Pulses Counter
{
prevFrqPinState = frqPinState;
frq1++;//counter to determine input frequency
}
//End of Input Frequency counter1
/*
pulseHigh = pulseInLong(frqPin, HIGH);
pulseLow = pulseInLong(frqPin, LOW);
pulseTotal = pulseHigh + pulseLow; // Time period of the pulse in microseconds
frequency = 1000000 / pulseTotal; // Frequency in Hertz (Hz)
*/
//End of Input Frequency counter2
if (millis() - timer >= oneSecond) //Time Counter
{
frq = frq1;//set frq to incoming signal
timer = millis(); //Resets Time
frq1 = 0; //Resets Frequency
//End of Input Frequency
timer = millis(); //Resets Time
}
}//end of void loop
Well, first of all, you can not measure 0 Hz. It is straight DC, direct current, no pulsation, nothing to measure. What is the frequency of a 9 volt battery?
For other low frequency measurements, look at using millis() and reading an input pin for low or high and computing the number of milliseconds between a low and the next high. That tells you the time for a pulse and you can compute the frequency from that. Even using that method, you will need some maximum time that you can allow when the frequency gets closer and closer to 0.0.
Paul
metalangst:
How can I read a frequency range from 0.00 to 50.00Hz with Two decimal places of accuracy?
My gut feeling tells me your requirements are unrealistic. Here are a few thoughts that might help refine your requirements. I will ignore measurement uncertainty to make this easier.
You want a value from 0.00 to 50.00 or 0 to 5000 fixed point. That is 12.3bits. This is the first indication this is hard.
If you like percentage more: 0.01 of 50Hz is 0.02% accuracy. Measuring things in life with 1% accuracy is generally considered good. You want a lot better.
One thing you did not mention is the update interval. How often do you want to measure the speed?
metalangst:
The motor has an optical disk that is being read with an Interrupt that returns a frequency from about 15Hz to 1450Hz.
Does that mean at your 50Hz motor speed you get 1450Hz pulses from the sensor? So, there is a 1:29 ratio. What happens at 15Hz? Is that 0.5Hz motor speed? What happens below? Do you have a link to the datasheet of the sensor?
Under that scenario assuming you use pulse counting you would need to measure for 3.5 seconds to get the full resolution you want.
Paul_KD7HB:
For other low frequency measurements, look at using millis()
That was my first thought as well but millis() does not provide the resolution you are looking for. Theoretically it is just below 10bits.
Your UNO has one 16-bit Timer with an Input Capture. But I am not sure the resolution provides enough headroom. If you clock the timer to fast it will overflow at low frequencies. If you clock it to slow you cannot tell the difference between the values at high end e.g., between 50.00 and 49.99. Can you confirm I understood how the sensor works correctly with the 1:29 ratio?
Thank you for the replies. Klaus, That is the kind of information that I was looking for. I believe your understanding of how the sensor works is correct, the optical disk has 64 holes. This morning, I changed the time of millis from One second to Two, Three, and Four. Four seconds gave greater detail and a longer response to change. This is for a speedometer in a vehicle, so adding a delay to the response time is not good. Two seconds returned 0.5 accuracy, but the output still toggles. I tried to change the variables from float to int, but the output still toggled. This question is about getting an accurate low frequency timer, but my actual end goal is to reduce the needle fluctuation. I thought about using some other method to sample the frequency, I’m open to suggestions. I think if the input frequency is rounded off, or truncated of the decimal places, it may reduce the toggling output. Also, I may look at using a faster micro processor.
You gave to think about the relationship between response time sample size , and the resolution of the result.
These are all related , the more samples you take , the longer it takes , but higher resolution.
I’ve been thinking about this. My 1Hz input is a 5vdc pulse for 500mS and 0v for 500mS, a 1 second square wave. At that frequency I won’t get a sample rate of less than half a second, no matter what software acrobatics I come up with. If the square wave has a 50% duty cycle I could sample for 500mS and multiply by 2. At 1.11Hz the vehicle is traveling at 2mph, or about 3 feet a second. I could use a variable to define the duration of millis, and decrease the sample time at higher speeds. With a sample rate of 4 seconds, I will travel 12 feet at 2mph before the speedometer updates. I’m not sure I want to go this route as I find I have the option of increasing the input frequency.
In order to get Two decimal places: 1/.01=100, so 100 seconds minimum sample time. Not for this project.
The circuit in my truck takes the signal from the VSS/Reluctor sensor of 40 pulses per driveshaft revolution, runs it through the Ratio Adapter and produces a 128,000 pulse per mile signal, from there a divide by 32 circuit produces a 4,000ppmile signal, finally a divide by 2 circuit produces the 2,000ppmile signal.
I shall tap into the 128,000ppm signal to get a 35.55Hz signal at 1mph with a period of 28mS. I can reduce the sample time from One second to 250mS while increasing the sample size from One to Eight.
I still need to round off the decimal places though. As it is currently, ignoring decimal places the input is a range from 0 to 60Hz, so 60 steps. The output is a pwm range of 0 to 255, so 255 steps. That’s a ratio of 1:4.25 between input and output, and it goes through a couple nasty equations for the PID and the torque curve. Code posted for reference.
The optical sensor on the motor is a QVE00112. The motor is from a Lexmark printer. I looked up the sensor specsheet and it shows Propagation Delay of 5uS, and Output Rise and Fall Time of 70nS. I couldn’t find this sensor for sale anywhere.
Again, thanks for the replies. I got my answer and am headed in a different direction now.
//PID
float error = 0;//difference between actual and desired values
float motor_speed = 0;//desired speed= input frequency x motHz equation
float optical_speed = 0;//actual speed= frequency of the optical disk
float integral = 0;//gets larger over time, used to correct the long-term average
int ITIME = 50;//determines how much The i and d change with each iteration
float derivative = 0;//compensates for rapid changes
float error_prior = 0;
float output = 0;//
float Kp = 0.76;//sets how much error will affect the output
float Ki = 0.005;//sets how much the most recent error will affect the output
float Kd = 36.125;//sets how much the difference between current and past reading affects output
int BIAS = 360;//baseline amount when motor starts
float val20 = 0;//used to square the output for the PWM equation
int pwm_val = 0;
#define error (motor_speed-optical_speed)//
#define integral (integral+(error*ITIME))//
#define derivative ((error-error_prior)/ITIME)//
#define output ((Kp*error)+(Ki*integral)+(Kd*derivative)+BIAS)//
#define motor_speed ((0.079*(val8))+((29.931*frq)/100)+302.31)//2kppm to OpticalFreq
#define val20 (pow(output,2))//
#define drivePID (0.00003*(val20)+(0.0306*output)+46.896)//MotorSpeed to PWM
#define driveCal (frq*3)//
#define val8 (pow(frq,2))
The MOST accurate way to measure is using a hardware timer in capture mode. That will give you a precise count in uSec or whatever time increment the timer is programmed for. You can capture the full period (capture on one edge of the input signal) or half-period (capture on both edges of the input signal). The latter will work well, and give you twice as many readings, PROVIDED the input is truly 50% duty cycle. Once you have the period, you can calculate frequency to any arbitrary precision, using either floating point calculations (good good for no more than 7 significant digits), or one of the "Big Number" libraries, which can handle arbitrary precision, at the expense of speed. Of course, your ACTUAL accuracy will be limited by the accuracy of the clock frequency of your Arduino, and that is where many/most AVR-based Arduinos kinda suck as they use rather inaccurate ceramic resonators rather than far more precise ceramic crystals. The resonators are inaccurate on their best days (as bad as 1%), and their frequency also varies considerably over temperature.
The FreqMeasure library, running on a board with a crystal oscillator, would be a good place to start. It will do a far better job right out of the box than any pure software solution.
metalangst:
If I input 10.5Hz, I get a readout of 10.00Hz for example, or the output toggles between 10.00Hz and 11.00Hz. I need to get Two decimal places.
You are measuring pulses per second so you get an integer number of pulses per second. If you want two decimal places you would need to measure pulses per 100 seconds and divide by 100. That is going to be a SLOW update rate all the time.
To get the frequency you could measure seconds per pulse and invert. The update rate will be slow when you get below 1 Hz. To measure a 0.01 Hz signal you would again have to wait for 100 seconds. Do you really need two decimal places down to a hundredth of a Hz?
The input is an analog signal generated by a reluctor ring that produces an infinite number of frequencies between 0Hz and 60Hz for stationary up to 90MPH. The display is an analog meter that has an infinite number of positions between 0MPH and 90MPH detected by eyeball and marked in 5MPH increments. I figure the frequency by dividing the pulse per mile by the time it takes to travel one mile in seconds. The results are whole numbers for every 9 MPH, the rest have decimal places. I’m focusing on two decimal places because that is what my bench signal generator puts out. The working model that I hooked up to my truck is very close to the trucks original speedometer, so I know it works well enough without the two decimal places. I’m just looking for ways to improve it. At 10mph the control signal is 5.56Hz and the pwm value is 85, but I have 5Hz, or 6Hz. Here are a few values: 9mph@5.00Hz, 10mph@5.56Hz, 11mph@6.11Hz, 12mph@6.67Hz, 13mph@7.22Hz. One decimal place would help.
Here is a picture of the 2000ppm signal and the 40 tooth reluctor signal, and a picture of the 128kppm. The Reluctor signal goes through a schmitt trigger to produce a nice square wave, It gets converted through a ratio adaptor to adjust for tire size and differential. The 128k signal looks like a modified square wave.
Here is a link to a video I made of the modified speedo next to a stock speedo.
If I'm understanding correctly, what you're doing is kinda backwards. If you want speed, you need to measure the FREQUENCY of the input pulses. THEN scale that for the gearing. Scaling the input pulses is discarding resolution, and slowing the input rate, which means slower response.
To get fast response, you should be working on a pulse-by-pulse basis, so you get a reasonable update rate even at low speeds. A hardware counter can be setup to directly measure the period of the reluctor waveform, and that can be easily converted to frequency, then digitally filtered to smooth the output. The reluctor signal should require nothing but level clamping, and a Schmitt trigger to ensure fast transitions. Even duty cycle is unimportant, since you only need to measure on a single edge per cycle
Indeed. I’m tapping into the existing system in the vehicle and I chose the signal furthest downstream. The 40 tooth reluctor actually produces a signal close to 128k pulses per mile, the conversion is typically something close to times .9 so the 128k ppm signal looks like a better frequency. There is a greater spread between points so no need for decimal places. An example of the 128kppm values: 9mph at 320Hz, 10mph at 355.55Hz, 11mph at 391.11Hz, 12mph at 426.66Hz, 13mph at 462.22Hz. I need the signals from the DRAC for the ECM, and antilock brakes, so I’ll use the 128k signal, but the signal from the reluctor would work too. Thanks for the feedback.