How to Calculate Wheel Speed

This article is coped from a document here.

By Chris Uustal - 7/9/22

### Purpose

Overview the recommended method for calculating wheel speed of the vehicle using a pulse train from a hall effect sensor.

### Explanation

The wheels have magnets on them. Each time a magnet passes over the wheel speed sensor, we get a pulse. The frequency of these pulses tells us our wheel speed by converting frequency into angular speed which can then be translated to linear speed (assuming you know the size of your wheel and the number of pulses/magnets per rotation). We generally run 8-inch wheels that have 20 magnets/pulses per rotation. Let’s say you’re going 70mph--that translates to 31.3 m/s. A 16 inch diameter wheel is 0.4m, so that wheel has a circumference of 1.26m. This means at 70mph the wheel is making 31.3/1.26=24.8 rotations per second. Let’s say 25 for easy math. With the 20 magnets/pulses per rotation and 25 rotations per second, this means we’re getting 20\*25=500 pulses per second, or one per every 2 milliseconds.

What does this tell us about how fast we have to sample the wheel speed sensors? That we have to sample so unreasonably fast that it’s probably more efficient to do it based on an interrupt--but how? And do we have to do all the math in an interrupt?

### Interrupt Code

As a quick refresher, an interrupt is a special bit of code on a microcontroller that you configure to always happen the instant some event occurs that will literally interrupt other code that is being executed. Here’s more on how to set up interrupts on ESP32 (our microcontroller of choice) here. Since this code will be running frequently and interrupting other code, we want it to be as fast as possible. The best way to do this is by doing the fewest and simplest operations possible. We’ll probably need to track time using the micros()command which returns the number of microseconds since the microcontroller last turned on (unsigned long, overflows every ~70 min). The only thing we care about is the duration between the last pulse and the current pulse, so that means we also need to track when the last pulse occurred. So, to make sure we’re as accurate as possible, let’s first store the time of the current pulse into some variable--let’s call that variable currentPulseTime.

```
//START OF INTERRUPT
currentPulseTime = micros();

```
Since we need to know when the previous pulse occurred, let’s assume that at the end of our code we’ll move over the value of currentPulseTime to a different variable called previousPulseTime.

```
previousPulseTime = currentPulseTime;

```
Now we want to know how long it’s been since the last pulse occurred, because we can use the inverse of that duration to calculate our rotational frequency. Let’s call this duration pulseDuration.

```
pulseDuration = currentPulseTime - previousPulseTime;

```

And that’s it. We can do all our higher level processing on that pulse duration somewhere outside the loop at a slower rate where it’s more acceptable to miss a few samples (eg. in a software timer or task-based structure). The full code for the interrupt (with the proper syntax and stuff) is:

```
void IRAM\_ATTR vss\_isr() {
 currentPulseTime = micros();
 pulseDuration = currentPulseTime - previousPulseTime;
 previousPulseTime = currentPulseTime;
}

```
Now we just have to make sure we add the following code to our setup function to make sure the interrupt is configured correctly, add the variables we used above as global variables of type unsigned long, and we’re ready to go:

```
//GPIOpin = whatever pin you connected the wheel speed sensor to
//vss\_isr = whatever your function name is
/\* RISING is the “mode” of the interrupt, use RISING if your pin pulses HIGH when the magnet passes, FALLING if your pin pulses LOW when the magnet passes, or CHANGE if you use a sensor that latches high and low with alternating magnets \*/
attachInterrupt(GPIOpin, vss\_isr, RISING);

```

### Task Code

Now all we need is a higher level function that reads those pulse durations and can tell us the actual wheel speed from that. Look over the Tasks and Timers section of this document for some background. The easiest way to do this is to create a task which reads the pulse duration, converts it into a frequency, then multiplies that frequency by a scalar to get our vehicle speed.

```
//Define some constants that will be useful
const float wheelCircumference = 1.26; //wheel circumference in m
const float MPStoMPH = 2.24; // m/s to MPH constant
Const int magnetCount = 20;
//We multiply by 1M because our pulseDuration is measured in uS
float pulseFrequency = 1000000.0 / (float)pulseDuration;
float wheelFrequency = pulseFrequency / (float)magnetCount;
float wheelSpeed = wheelFrequency \* wheelCircumference \* MPStoMPH;

```
And that’s it, the above code just regularly samples the duration it’s getting from the interrupt and uses that to directly calculate the wheel speed as a float that can be used for logging purposes, safety checking, displaying back to the driver, or anything else.

A few notes on efficiency:

The above code isn’t super efficient, but it is quite simple. One trivial simplification that could be made is to have all the conversion factors for converting from pulse frequency to wheel speed done in a singular calculation using a scalar that can be calculated at compile time. This scalar would just be wheelCircumference \* MPStoMPH / magnetCount. Since floating point operations take a long time to compute, reducing them as much as possible would help with execution speed. You could condense this even more if you really wanted to by putting together the duration to frequency conversion with the scalar to yield:

```
const float wheelScalar = 1000000 \* wheelCircumference \* MPStoMPH / magnetCount;
wheelSpeed = wheelScalar / pulseDuration;

```
While this does make the code quite difficult to understand, it would reduce the number of separate floating point operations that occur at run-time since most of the operations are done at compile-time.

If you really wanted to improve the efficiency of the above code, you could actually make all the code do integer math with some scalar instead, because that will always yield the fastest results (integer math takes a few cycles, float math takes a few hundred cycles). You can actually use the optimizations we did above for the float math here as well and you’ll get a value for wheelScalar of about 141120--if we multiply this by 10, then we can still get quite granular data about the speed of the vehicle (0.1mph accuracy), and we only have to do a single conversion to floating point values for when we actually log or display this value of dividing the value by 10 (which we can do much slower since user-facing tasks only need to be updated at most once every 40ms). This could even be implemented automatically with a getter function as part of the struct or object that contains all these variables already.