Linear Speedometer using a hall effect sensor

Hello everyone,

Hoping to get some advice here, and I hope this isn't too long-winded. As the title of the topic suggests I'm working towards a linear speedometer, nothing too fancy as the pretty stuff will be done on the housing (I've definitely got a lot more confidence machining than I have programming at this level).

I'm using an ominpolar hall effect sensor from Allegro I think, I've used these in the past for another project and it seems to be working fine. It's reading the magnet, and I've done the basic sketch to illuminate the on board LED of the UNO.

The screen is a basic 128 x 32 OLED thing, and again it's functioning well. I've experimented with a little splash screen in another sketch which I'll incorporate at a later date once I've figured out the actual functionality of the speedometer.

But for this sketch below what I'm trying to do is read the magnet passing by, and display this as a bar from left to right in smooth increments (0-40mph it's for a very small motorcycle).

And then as the bike slows down the magnet passes by less frequently and I'd like the bar to slowly dip down back to zero with some "resistance". Similar to how the needle on a normal analogue speedometer would work but in a linear way.

I'm using the U8Glibrary as I found some useful examples elsewhere to study, but I've hit a wall with this one. This sketch below seems to jump between readings and display them erratically as opposed to smoothly, and I'm not sure how to make it fall back down to 0 gradually.

Thankyou in advance to anyone who has taken the time to have a look at this.

#include "U8glib.h"

U8GLIB_SSD1306_128X32 u8g(U8G_I2C_OPT_DEV_0 | U8G_I2C_OPT_NO_ACK | U8G_I2C_OPT_FAST);

unsigned long startMillis;
unsigned long currentMillis;
const unsigned long period = 50;

int magnetsense = 0;
int laststate = 1;


int sensPin = 2;
int counter = 0;

float vespeed;
float radius = 0.5;
float pi = 3.14;
float circumference = 2 * radius * pi;

void setup() {

  u8g.setColorIndex(1);
  Serial.begin(115200);
  startMillis = millis();
  pinMode(sensPin, INPUT_PULLUP);

}

void loop() {

  u8g.firstPage();
  do {

    draw();
  } while ( u8g.nextPage() );

  currentMillis = millis();
  magnetsense = digitalRead(sensPin);

  if (magnetsense == 0 && laststate == 1) {
    laststate = 0;

    if (currentMillis - startMillis >= period && laststate == 0) {
      vespeed = circumference / (currentMillis - startMillis) * 1000;
      startMillis = currentMillis;
      counter++;
    }
  }


  else {
    if (currentMillis - startMillis >= period && laststate == 0) {
      startMillis = currentMillis;
      laststate = 1;
    }
  }
}

void draw() {
  u8g.drawBox(0, 0, vespeed, 32);
}

Hello yome.

I made a speedo for my car using a Hall Effect sensor & magnet.

What you are aiming for is certainly do-able.

I display my speed on a 4-digit LED display as a number.

The device is reliable and accurate after calibration.

I'll take a look at your code.

John.

yome,

you're polling the pin to detect the transition and testing if it is the transition you want.

this can work, but Arduino provides a function to do this for you: pulseIn()

this function detects the transition you are watching for, say from LO to HI, then measures the duration the pin is HI (or LO) and returns that duration.

all I then do is multiply that duration by a factor to give the speed; calibration against the car's speedo allows me to determine the exact correct value of that factor to produce an exact speed reading.

with your code, it is a good habit to include comments that explain the aim of each piece of code.

john.

Eyup John,

Really appreciate you taking the time to look over the code. I'm still very new to all this so I'll research the PulseIn and see if I can figure it out.

Do you have any experience with the U8Glib at all? As I'm still struggling to get the bar to operate smoothly on the screen. No doubt there's some sort of averaging or smoothing code I'm oblivious to.

Thankyou again.

    if (magnetsense == 0 && laststate == 1) {

don't you want to measure the time between the above events, not between the previous time this was not true and now true

    static unsigned long  msecLst = 0;
           unsigned long  msec    = millis ();
    long  rpm;

    magnetsense = digitalRead(sensPin);
    if (magnetsense == 0 && laststate == 1) {
        rpm     = 60 * 1000000 / (msec - msecLst);
        msecLst = msec;
    }
    laststate = magnetsense;

I don't know that OLED device but I did use an OLED at one point to display the speed. I displayed it as a number not a bar.

The OLED was slow to refresh, could that be an issue with yours?

I suggest get the code measuring the speed working first and worry about the display second.

PulseIn() is definitely better than polling.

Are you able to test your device on the workbench rather than on the bike?

John.

I agree with Greg. Measure the rpm by timing the gap between the sensor changing from 0 to 1 and the next time it changes from 0 to 1, ie. one revolution.

Also, get the rpm calculation working first, then worry about the linear display and smoothing. Just print the rpm value to the display as a number for now.

HillmanImp:
this can work, but Arduino provides a function to do this for you: pulseIn()

this function detects the transition you are watching for, say from LO to HI, then measures the duration the pin is HI (or LO) and returns that duration.

Thankyou again John for the knowledge. Sorry if this is a silly question, it's starting to fry my brain a little... If pulseIn() measures the duration between HI or LO would this not only be half the process for an omnipolar hall effect sensor?

Would I not need to measure the duration between HI and then the subsequent HI?

HillmanImp:
I suggest get the code measuring the speed working first and worry about the display second.
Are you able to test your device on the workbench rather than on the bike?

No doubt you're right, step by step. I've got the device setup here with me yes, definitely no point starting to get everything mounted before it will work here!

Thankyou to Greg for your time to write that out, as I say unfortunately this stuff is beyond my capability really but I'll do my best to study that version of the code.

PaulRB:
I agree with Greg. Measure the rpm by timing the gap between the sensor changing from 0 to 1 and the next time it changes from 0 to 1, ie. one revolution.

Thankyou for the reply! I'll have another go and see what happens.

yome,

I was initially was using your strategy of polling and determining the time b/w successive LO/HI transitions. That gives the time for one rev and from that I derived the speed using my estimate of the wheel's diameter, as you are.

When I discovered the pulseIn() function I was able to measure the duration the pin was HI and the duration it was LO (I think my pin goes LO when the magnet passes). The ratio of the two durations was something like 1 to 35 (for argument's sake). That means the pin is near the magnet for 10 degrees of rotation (LO) and away from the magnet for 350 degrees of rotation (HI). So the pulse duration of the pin being LO represents 1/36th of a complete rotation. The car has moved 1/36th of the wheel circumference.

I just multiply the duration by a factor and call that the speed. I test it on the car against the car's speedo (which is accurate). If the car speed is 20 and the gadget reads 17, then I adjust the factor by multiplying it by 20/17.

Does that make sense? We don't need to measure a full 360 degree rotation, just a sector, as long as that sector is constant over our speed range. It also depends on the miilis() accuracy being adequate for our purposes. The gadget reliably gives accurate readings over 0 to 110 kph.

So it turned out to be surprisingly simple coding task. Will post the relevant code.

John.

This is my function to calculate the speed:

void detCarSpeed(){
  unsigned long duration;
  duration = pulseIn(HEpin, HIGH, HEtimeout)/1000; //pulse length in millis
  if (duration == 0) carSpeed = 0;       //timout so call it zero kph
  else {carSpeed = (wheelConstant/duration); //adjust HEconstant to fine tune
  wheelClicks++;
  }

I defined these at top of sketch:

#define wheelConstant 7141
#define HEtimeout 5000000

7141 is my factor that gives me a correct speed reading.

HEtimeout is time the funtion waits to detetect the transition (5 sec); If no transition within 5 seconds, the wheel is rotating so slowly I'm calling the speed zero. If you don't do this the code will will not move on which could be a problem.

John.

John,

My criticism of your code from the post above is that it wastes a lot of time. If the input pin is HIGH for 350 out of 360 degrees, your code will be stuck in the pulseIn() function for 97% of the time. That time is wasted, and you may find you need that time for updating your display.

Maybe you should try measuring the LOW pulse. That is only 3% of the time, leaving your code with lots more time to update the display without missing pulses. For increased accuracy, keep the value in microseconds, don't convert to milliseconds as you do now. The potential problem with this idea may be the level of inconsistency of the timing of the LOW pulse, you will have to experiment to find out how steady that is.

Thanks for the comment, Paul.

Sorry, but I've given contradictory info. I am indeed using the pulsIn() function to measure the shorter duration. From my code that means the pin is HI. Previously I said I thought it was LO.

I was completely experimenting with this method and had no idea how it would work. I wondered if the short pulse would be enuf of a duration to allow a good computation of speed, but it is.

There's no need here for fast processing of the data. I deliberately delay updating the display to once per second. That's fast enuf for the driver's needs and I don't want a flickering display. So, within that second there's time for the Nano to grab the duration, calculate the speed, update the trip meter, determine if an over speed alert (a buzzer) shud be generated & send the speed to Bluetooth. There's no need to sense every rotation. I cud grab one every five seconds and that wud be fine.

I suppose micros() wud give greater accuracy than millis() but I don't need it. I'm displaying the speed to zero decimal places, and updating the trip meter by 0.1km intervals.

The proof is in the pudding. The gadget works in displaying accurate and reliable speed from 0 to 110 kph. That's all I need.

John.