I am trying to use an Arduino Pro Mini 5v to read a sensor value from my car's speedometer sensor (square wave between 0-5khz and then translate that to a signal that my gauge cluster expects (0-140hz). I am reading the frequency using the pulseIn() command, performing some basic math, and then writing the data out to another pin using a version of the blinkWithoutDelay code (elapsedMicros() ). I am seeing some odd behavior when I get above 20Hz where the output frequency seems to jump in increments of 10-20hz instead of the linear increment I would expect. Once it hits 80hz, no matter what I do with the input signal the output frequency will not increase.
On my bench, I am feeding pin 10 a 5v square wave of varying frequencies from a signal generator and then monitoring the output frequency on pin 7 with my scope.
Is it possible that I am hitting the limits of the processor? I would think that at 16Mhz clock speed pushing up to even 200hz should not come close. It seems more likely that I am doing something dumb...
Here is my code:
//Speedometer multiplier
float sx = 2.0; //This variable is for drivetrain multiplier, FD, wheel/tire size, VSS type will all affect wheel speed calibration. This variable is in place to allow for easy fine tuning.
//Let's define some pins!
//Input Pins
const int sip = 10; // Speedo input pin
//Output Pins
const int sop = 7; // the number of the Speedometer pin
// Define Variables
int sopState = LOW; // used to set Speedo pin state
unsigned long sopTime = 0; // the duration of the speedometer off pulse (microseconds)
unsigned long sipTime = 0; // the duration of the high side of the input square wave
unsigned long sPrevMircos = 0; // will store last time Speedometer signal was updated
float dt = 0.0141129;
void setup() {
pinMode(sip, INPUT);
pinMode(sop, OUTPUT);
}
void loop() {
unsigned long currentMillis = millis();
unsigned long currentMicros = micros();
// Capture input pin low time
sipTime = pulseIn(sip, LOW, 300000);
// Transform the input length to output lengtch
sopTime = sipTime / sx / dt - 2000;
// Output Frequency generation code
if (currentMicros - sPrevMircos >= sopTime) {
sPrevMircos = currentMicros;
// if the LED is off turn it on and vice-versa:
if (sopState == LOW) {
sopState = HIGH;
} else {
sopState = LOW;
}
}
// Write sensor value to output pin
digitalWrite(sop, sopState);
//Speedometer multiplier
float sx = 2.0; //This variable is for drivetrain multiplier, FD, wheel/tire size, VSS type will all affect wheel speed calibration. This variable is in place to allow for easy fine tuning.
//Let's define some pins!
//Input Pins
const int sip = 10; // Speedo input pin
//Output Pins
const int sop = 7; // the number of the Speedometer pin
// Define Variables
int sopState = LOW; // used to set Speedo pin state
unsigned long sopTime = 0; // the duration of the speedometer off pulse (microseconds)
unsigned long sipTime = 0; // the duration of the high side of the input square wave
unsigned long sPrevMircos = 0; // will store last time Speedometer signal was updated
float dt = 0.0141129;
void setup() {
pinMode(sip, INPUT);
pinMode(sop, OUTPUT);
}
void loop() {
unsigned long currentMillis = millis();
unsigned long currentMicros = micros();
// Capture input pin low time
sipTime = pulseIn(sip, LOW, 300000); //pulseIn(pin, value, timeout usec)
// Transform the input length to output lengtch
sopTime = sipTime / sx / dt - 2000;
// Output Frequency generation code
if (currentMicros - sPrevMircos >= sopTime) {
sPrevMircos = currentMicros;
// if the LED is off turn it on and vice-versa:
if (sopState == LOW) {
sopState = HIGH;
} else {
sopState = LOW;
}
}
// Write sensor value to output pin
digitalWrite(sop, sopState);
Your problem is of course the vast rang of frequencies.
At 1 kHz the timer overflows in about 65 seconds, while at 5 MHz the timer will overflow in about 13 ms.
For low frequencies (under say 10 kHz) I would look for pulses per time unit. A regular millis() based loop could check every 500 ms or so, counting up to 10,000 pulses at 20 kHz. Reset the timer to zero every time you read it.
At higher frequencies turn that around: look for time it takes to reach a number of counts. This can easily be done using a compare match interrupt on TCNT1 (set the timer to CTC - clear timer on compare, so it starts counting from zero at that point), which could be called at 10,000 and records the micros() value. That number you compare with the previous recorded micros() value, and you know how much time passed. This will automatically kick in the moment you go over 20 kHz, as then the timer reaches the compare match.
All that's left is a bit of program logic to detect when you have to calculate the new speed from the micros value recorded by the timer interrupt.
Do make sure your Arduino runs at >10 MHz, as the counter can only count pulses at up to fCLK/2. An 8 MHz Pro Mini is not suitable.
BStanley346:
I am trying to use an Arduino Pro Mini 5v to read a sensor value from my car's speedometer sensor (square wave between 0-5Mhz. . . .
How did you determine the frequency range of the speedometer sensor?
I'm struggling to conceive how or why a sensor measuring some mechanical motion would generate megahertz frequencies.
The ESP32's Pulse Counter API is a programmable pulse counter that can read input pulses up to 80Mhz without using any CPU clock cycles, an independently operating module.
float sx = 2.0; //This variable is for drivetrain multiplier, FD, wheel/tire size, VSS type will all affect wheel speed calibration. This variable is in place to allow for easy fine tuning.
//Let's define some pins!
//Input Pins
const int sip = 10; // Speedo input pin
//Output Pins
const int sop = 7; // the number of the Speedometer pin
// Define Variables
int sopState = LOW; // used to set Speedo pin state
unsigned long sopTime = 0; // the duration of the speedometer off pulse (microseconds)
unsigned long sipTime = 0; // the duration of the high side of the input square wave
unsigned long sPrevMircos = 0; // will store last time Speedometer signal was updated
float dt = 0.0141129;
void loop() {
unsigned long currentMillis = millis();
unsigned long currentMicros = micros();
// Capture input pin low time
sipTime = pulseIn(sip, LOW, 300000); //pulseIn(pin, value, timeout usec)
// Transform the input length to output lengtch
sopTime = sipTime / sx / dt - 2000;
// Output Frequency generation code
if (currentMicros - sPrevMircos >= sopTime) {
sPrevMircos = currentMicros;
// if the LED is off turn it on and vice-versa:
if (sopState == LOW) {
sopState = HIGH;
} else {
sopState = LOW;
}
}
// Write sensor value to output pin
digitalWrite(sop, sopState);
1: lets have some consistency - mircos or micros?
2: (CLUE) what does this code do?
sopTime = sipTime / sx / dt - 2000;
I apologize, the code I posted is a subset of a larger sketch that monitors a few other inputs with much shorter frequencies, I had used millis for those sensors and realized that I needed micros for this one.
This is essentially a frequency conversion based on a variable that will eventually be driven by a potentiometer. This is to allow for driveline variation (a different tire size for instance would adjust the input frequency and needs to be accounted for. I have been going through the code and commenting out and changing sections to see what I can do to get it to run faster and oddly enough this line definitely drives the responsiveness through the floor when using digital.Write. I have been able to use the same code for the input and calculations and successfully use tone() to output the required frequencies consistently. This hit the limitation of not being able to display below 31hz. I tried a hybrid approach of the code above up to 31hz and then tone above that, however, the extra heft of the additional code results in the above example only being consistent up to an output of 20hz because of the cycle times. I think that a timer/interrupt approach is going to be the sweet spot.
Thanks for your time and information, it is much appreciated.
I'm really sorry, I mistyped, the frequency range is between 0-140 khz, which is obviously significantly easier to attain with the 16Mhz processor. I want to respond to everyone but I can only reply once every 15minutes.
After playing around more today, I have found that the code that reads the signal pulse and converts the frequency is rock-solid in the range desired. I was able to use the tone() function to display the desired output frequency as well, the issue seems to be with the micros() compare/flip-flop code that doesn't scale up well.
I think that using a timer/interrupt is going to be my best bet.
I just want to say thank you for all of the helpful responses.