Max output frequency

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);

Thanks in advance.

//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);

1: lets have some consistency - mircos or micros?

2: (CLUE) what does this code do?

sopTime = sipTime / sx / dt - 2000;

Input frequency 0..5MHz? And trying to use pulseIn? No chance, far too fast.

This needs a fast frequency counter to measure, count the pulses that every millisecond
and you'll get a number from 0..5000

On the ATmega328 used in the Pro Mini arduino pin 5 corresponds to the T1 input
which can be configured to clock timer1:

void setup()
{
  TCCR1A = 0x00 ;   // normal mode, 16 bits.
  TCCR1B = 0x07 ;   // clock timer1 from T1 (Arduino pin 5 on ATmega328)
  ....
  ....
}

Thereafter TCNT1, the timer1 counter register will clock according to the signal on pin 5.
This is a hardwired pin, can't change it.

Thus you could do something like:

void setup()
{
  TCCR1A = 0x00 ;   // normal mode, 16 bits
  TCCR1B = 0x07 ;   // clock timer1 from T1 (Arduino pin 5 on ATmega328)
  Serial.begin (115200) ;
}

#define MIN_OUTPUT_PERIOD (1000000L / 140)    // in us
#define MAX_INPUT_FREQ 5000000L               // in Hz
#define MAX_INPUT_KHZ (MAX_INPUT_FREQ / 1000) // in kHz
#define INPUT_KHZ_TO_OUTPUT_PERIOD (MAX_INPUT_KHZ * MIN_OUTPUT_PERIOD)

void loop()
{
  static unsigned int current_reading ;  // count for latest millisecond, 16 bits
  static unsigned int last_reading = 0 ;  // previous count

  static unsigned long next_read_us = 0;  // timestamp when last read the TCNT1 register

  if (micros() - next_read_us >= 1000)  // every millisecond:
  { 
    next_read_us += 1000 ; // set up for next time (1000 us later)

    last_reading = current_reading ;  // update the counts
    current_reading = TCNT1 ; 

    unsigned int input_kHz = current_reading - last_reading ; // get difference to ignore wraparound
    long output_period_us = INPUT_KHZ_TO_OUTPUT_PERIOD / input_kHz ;  // convert
  }

  static unsigned long next_output_ts = 0;

  if (micros() - next_output_ts >= output_period_us/2)  // whenever output transition is due
  {
    next_output_ts += output_period_us/2 ;
    toggle_output_pin() ;  // toggle the relevent output
  }
}

+1 for the T1 timer/counter input.

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.

johnerrington:

//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);





1: lets have some consistency - mircos or micros?

2: (CLUE) what does this code do?

sopTime = sipTime / sx / dt - 2000;
  1. 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.
  2. 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.