How to effectively read speed from incoming pulses

Status
Not open for further replies.

450nick

Active member
Hi all!

I have developed the following code based on some examples I've found on this forum using interrupts to check for pulses seen on an input pin. This seems to work nicely, but I'm now trying to turn these pulses into a speed value. I now have a speed value but have 2 problems. The first is that I wanted to construct the length traveled per pulse of the tooth wheel using "(1/teeth)/(diffRatio*WheelCirc)" but the 1/teeth part seems to always return a zero. What's the best way of dealing with this calculation?

Furthermore, I'm currently only looking at each pulse on its own. Is there a neat way of averaging maybe the last 10 values on a rolling basis to give a smoother input?

Thanks in advance!

Code:
//Speed
volatile unsigned long VSS_count=0;
unsigned long VSS_prior_count=0;
int pin_VSS = 14;

void pulse() {
 VSS_count = VSS_count + 1;
}

int SPEED_MPH;
int SPEED_STEPS;
int SPEED_SEND;
int currentMillis;
int lastMillis;
unsigned long VSS_new_count;
int teeth = 17;
unsigned long diffRatio = 3.42;
int WheelCirc = 2126; //mm
float toothDistance=427701;   //(1/teeth)/(diffRatio*WheelCirc); //= 36566.91;
unsigned long elapsedTime;
unsigned long speedRaw;

void setup()
{
 Serial.begin(9600);
//toothDistance = (1/teeth)/(diffRatio*WheelCirc);

  //---Speed Input Count-----------------------------------
 pinMode(pin_VSS, INPUT_PULLUP);
 attachInterrupt(digitalPinToInterrupt(pin_VSS), pulse, RISING);
 NVIC_SET_PRIORITY(IRQ_PORTA, 0);

}

void loop() {

 if (VSS_count != VSS_prior_count) {
  elapsedTime = (millis() - lastMillis);
  speedRaw = (toothDistance)/(elapsedTime);
  VSS_prior_count = VSS_count;
  Serial.println(speedRaw/1000);
  lastMillis = millis();
 }

}
 
Last edited:
Try this:

toothDistance = (1.0/teeth)/(diffRatio*WheelCirc);

or this:

toothDistance = (1.0f/teeth)/(diffRatio*WheelCirc);

The compiler should see "1.0f" and realize you intend this to all be done with float math rather than integers.

If that doesn't get the compiler to use float math, then try a more forceful approach, maybe like this:

toothDistance = ((float)1.0/(float)teeth)/((float)diffRatio*(float)WheelCirc);
 
Regarding the rolling average: I like this if I only want to smooth. Experiment with the alpha value to see the effect.

Code:
float measure ()
{
  constexpr float alpha = 0.01f; // alpha=1 -> no averaging,  alpha=0 ->infinite averaging
  static float average = 0.0f; 

  float newValue = doSomeMeasurement();
  average = average + alpha * (newValue - average); //https://en.wikipedia.org/wiki/Exponential_smoothing
  return average;
}
 
Try this:

toothDistance = (1.0/teeth)/(diffRatio*WheelCirc);

or this:

toothDistance = (1.0f/teeth)/(diffRatio*WheelCirc);

The compiler should see "1.0f" and realize you intend this to all be done with float math rather than integers.

If that doesn't get the compiler to use float math, then try a more forceful approach, maybe like this:

toothDistance = ((float)1.0/(float)teeth)/((float)diffRatio*(float)WheelCirc);

Ok none of those work, but I can get it to work if broken in two...

running 1.0/teeth gives 0.06 which is correct, but not accurate enough - my calculator comes up with 0.0588235294117647‬

then I can do WheelCirc/diffRatio (just realised that calc was wrong!) to get 621.6374269005848‬ and multiply by the previous number. So the result should be 36.56690746474028‬ or 36.56mm of wheel rotation for 1 pulse on the 17 tooth wheel in the gearbox.

because of the rounding, Arduino comes up with 41.65 which is about 12% error. I could hard code the number but would be nice to leave settings in there so I could go back and change just the wheel circumference or diff ratio if needed in future. Is there a way around it or should I just hand do the calc in the comments and hard code the answer?
 
Last edited:
running 1.0/teeth gives 0.06 which is correct, but not accurate enough

Is the problem the math, or the printing of the result?

Code:
void setup() {
}

void loop() {
  float n = 1.0 / 17.0;
  Serial.print("default print: ");
  Serial.println(n);
  Serial.print("6 digit print: ");
  Serial.println(n, 6);
  Serial.println();
  delay(1000);
}

If you are using Teensy 3.x or 4.0, you can use double instead of float, for much higher precision on the math side.
 
You defined diffRatio as unsigned. Change to float and you get the right result.

Code:
constexpr unsigned teeth = 17;
constexpr float diffRatio = 3.42;
constexpr float WheelCirc = 2126;                                         //mm
constexpr float toothDistance = (1.0f / teeth) * (WheelCirc / diffRatio); //= 36566.91;

void setup()
{
    while (!Serial);
    Serial.println(toothDistance);
}

void loop()
{
}

Prints 36.57
 
Amazing! It works, thanks both... So I now have a solid reading of the speed based on the pulses generated from the other teensy. Awesome. I was running the pulses divided by a factor of 10 so I could see the LED pulsing and verify the speed was changing when I turned the pot. I've now scaled it up to full speed and I don't think the Serial print on the receiver can keep up so I need to lower the print rate and add averaging.

I will take a look at Luni's rolling average but a thought occurred - should I count the time in batches of for example the time to see 10 pulses on the input and then use the rolling average on the output of that figure? A realistic low point would be 5mph, and at this speed there would be around 60 pulses per second.

Code:
//Speed
volatile unsigned long VSS_count=0;
unsigned long VSS_prior_count=0;
int pin_VSS = 14;

void pulse() {
 VSS_count = VSS_count + 1;
}

int SPEED_MPH;
int SPEED_STEPS;
int SPEED_SEND;
int currentMillis;
int lastMillis;
unsigned long VSS_new_count;
int teeth = 17;
float diffRatio = 3.42;
int WheelCirc = 2126; //mm
float toothDistance=(1.0/(teeth));
float disttravelled = WheelCirc/diffRatio;
unsigned long elapsedTime;
unsigned long speedRaw;
int pulsegroup = 0;

void setup()
{
 Serial.begin(9600);
toothDistance = toothDistance*disttravelled;
  //---Speed Input Count-----------------------------------
 pinMode(pin_VSS, INPUT_PULLUP);
 attachInterrupt(digitalPinToInterrupt(pin_VSS), pulse, RISING);
 NVIC_SET_PRIORITY(IRQ_PORTA, 0);

}

void loop() {
  noInterrupts ();
  VSS_new_count = VSS_count;
  interrupts ();

pulsegroup = VSS_new_count - VSS_prior_count;
 if (pulsegroup >9) {
  elapsedTime = (millis() - lastMillis);
  speedRaw = ((VSS_new_count - VSS_prior_count)*60*1000/(toothDistance*elapsedTime))/10;
  
  VSS_prior_count = VSS_new_count;
  Serial.println(speedRaw);
  lastMillis = millis();
 }
}

Just tested this code and it seems to work pretty well
 
Here's a question... I'm using this code to generate my pulses:

Code:
/* 2126mm per tyre revolution = 470 revs per km
diff ratio 3.42 so 1607 revs per km on gearbox = 1.6074 revs per m 

So 10mph = 4.4704 m/s = *3.42 diff ratio = 7.19 revs/s = 122.23 pulses /s (17 teeth on gearbox wheel)  = interval time of 8.181ms = 8181
200mph = 89.408 m/s = 42.0545 revs/s *3.42 diff ratio = 143.826 GB revs/s = 2445.05 pulses /s = interval time of 0.4089ms = 409

*/

int sensorPin = A0;              // select the input pin for the potentiometer
int sensorValue = 0;            // variable to store the value coming from the sensor

unsigned long val = 0;                    //value to be used with map
const int speedled =  13;      // the number of the LED pin //speed pulse
const int speedpin =  12;      // the number of the pulse pin //speed pulse


int ledState = LOW;            // ledState used to set the LED
int ledState2 = LOW;           // ledState used to set the LED

unsigned long previousMicros = 0;      // will store last time LED was updated
unsigned long interval = 500;          // interval at which to blink (milliseconds
unsigned long time = 0;
unsigned long previousTime = 0;      // will store last time Time was updated
float Hz = 0;
int i = 1;


void setup()
{
  Serial.begin(115200);
  // Serial.println("Start...");
  // set the digital pin as output:
  pinMode(speedpin, OUTPUT);
  pinMode(speedled, OUTPUT);   
}

void loop()
{
  sensorValue = analogRead(sensorPin);
  //Serial.println(analogRead(sensorPin));
  interval = map(sensorValue, 100, 1023, 409, 8181);        // interval adjuster from .40ms to 8.18ms //8196   1169
  
  unsigned long currentMicros = micros();

  time = currentMicros - previousMicros;
  if (time > interval)                             // 17 cycles of HIGH to LOW on pin 14 speedpin (17 teeth on T56 speed sensor)
  {
    previousMicros = currentMicros;
    i++;

    if (i == 2)
    {
      time = currentMicros - previousTime;
      previousTime = currentMicros;
      Hz = 1000000.0 / time;
      Serial.println(Hz, 2);                     // 2 digits
      
    }

      ledState = !ledState;                      //  1 -> 0 -> 1 -> 0 etc 
      digitalWrite(speedpin, ledState);
      digitalWrite(speedled, ledState);

  }
}

So it pulses from 10mph to 200mph by mapping the pot input to the pulse interval (0.4 to 8.2ms).

Now my reader can read this, it can see 10mph and 200mph, but I've noticed that from around 10 to around 70mph I can control to within around 1mph, but the faster it goes, the bigger the jump in speed for the same movement of the pot, like the response is not linear so the input to get from 150 to 200 is tiny. Why's that then??

[EDIT] Ahhh got it, because the interval increase is non linear. My maths isn't strong enough to know if there's an easy way to map the pot such that the response is linear. Any ideas?

[EDIT#2] Partially solved wi
Code:
  potReading = analogRead(sensorPin);
  //Serial.println(analogRead(sensorPin));
  potReading = potReading*potReading;
  potReading = potReading/100;
  interval = map(potReading, 10465, 100, 8181, 409);        // interval adjuster from .40ms to 8.18ms //8196   1169
but still not quite right, more sensitive at one end than the other
 
Last edited:
Regarding the rolling average: I like this if I only want to smooth. Experiment with the alpha value to see the effect.

Code:
float measure ()
{
  constexpr float alpha = 0.01f; // alpha=1 -> no averaging,  alpha=0 ->infinite averaging
  static float average = 0.0f; 

  float newValue = doSomeMeasurement();
  average = average + alpha * (newValue - average); //https://en.wikipedia.org/wiki/Exponential_smoothing
  return average;
}

Hi Luni,

Where do you put this code for it to work? I tried dropping it in and replacing "doSomeMeasurement()" with speedRaw but this doesn't work - you have an example with it in? Does it run in the loop?
 
Status
Not open for further replies.
Back
Top