Hall Effect speedometer, help with code ! [SOLVED]

hello guys,
i am trying to make a small speedometer, so what i have is a hall effect sensor connected to my arduino (tested it all connection are perfect and works fine) so there is no hardware problem, so what i am trying to do is catching the speed of a wheel on which i glued a piece of magnetic, i need help please with the codes in a way that the speed goes up as long as i am pressing the throttle and goes back to 0 when the wheels stop rotating. what is happening is that the speed gets back to zero when there is no magnetic piece on hitting the hall effect sensor, but when i keep the magnetic piece on the hall effect sensor range the speed goes up and up till i remove it

here are my codes :

int hall_effect_pin = 2;
int hall_effect_state = 0;
int LED_pin = 9;
const float pi = 3.1415926535897932384626433832795; //const float means a number that can't change pi = 3.1415926535897932384626433832795
const float wheel_radius = 0.015; //m
float perimeter = 0;
int Speed = 0;
int hall_effect_detection_number = 0;

void setup(){
  pinMode(hall_effect_pin, INPUT);
  pinMode(LED_pin, OUTPUT);
  Serial.begin(9600);
}

void loop(){
  perimeter = 2*pi*wheel_radius;
  //Serial.println(perimeter);
  
  
  hall_effect_state = digitalRead(hall_effect_pin);
  if(hall_effect_state == LOW){
    digitalWrite(LED_pin, HIGH);
    hall_effect_detection_number++;
    
   
  }else{
    digitalWrite(LED_pin, LOW);
    hall_effect_detection_number = 0;
  }
  
  if(hall_effect_detection_number > 0){
    Speed = hall_effect_detection_number*perimeter;
  }else if(hall_effect_detection_number == 0){
    Speed = 0;
  }
  
  Serial.println(Speed);
  
}
 perimeter = 2*pi*wheel_radius;
  //Serial.println(perimeter);

Perimeter will never change. So why not just enter it as a const and eliminate the rest.

hall_effect_state = digitalRead(hall_effect_pin);
  if(hall_effect_state == LOW){
    digitalWrite(LED_pin, HIGH);
    hall_effect_detection_number++;
    
   
  }else{
    digitalWrite(LED_pin, LOW);
    hall_effect_detection_number = 0;
  }

with this the detection number variable will almost always be zero.

Instead try having the Hall effect trigger an Interrupt every time the magnet passes.

Count the number of interrupt's that happen over a set period of time.

Then use a formula to calculate the speed.

Print that then reset and do it over again .

const float pi = 3.1415926535897932384626433832795; //const float means a number that can't change pi = 3.1415926535897932384626433832795

Might I suggest that you get real.

Hutkikz:

 perimeter = 2*pi*wheel_radius;

//Serial.println(perimeter);




Perimeter will never change. So why not just enter it as a const and eliminate the rest.

with this the detection number variable will almost always be zero.

Instead try having the Hall effect trigger an Interrupt every time the magnet passes.

Count the number of interrupt's that happen over a set period of time.

Then use a formula to calculate the speed.

Print that then reset and do it over again .

about the perimeter, it's because i am building this program for later too, the final sketch will include only the perimeter value as you said

about the interrupt, i was looking into that, i still didn't get what the interrupt do exactly, as i understood 2 forms:
1- is that interrupt will interrupt the program to finish this command
2- is that interrupt will let you make other things in the same time

so can you please help with that ? example or anything ?

PaulS:

const float pi = 3.1415926535897932384626433832795; //const float means a number that can't change pi = 3.1415926535897932384626433832795

Might I suggest that you get real.

what do you mean ? this is the most realistic exact value :confused:
the result will be 2 numbers after the comma

this is the most realistic exact value :confused:

Is it? A float has 6 to 7 digits of precision. Wishful thinking won't improve that.

Also, what's wrong with existing PI (defined in Arduino.h) or M_PI (math.h)?

oqibidipo:
Also, what's wrong with existing PI (defined in Arduino.h) or M_PI (math.h)?

i didn't know about it :confused:

Using float variables on Arduino is 100x slow and very unnecessary.
If you need close measure, calculate the circumference to work with in integer mm. That is close without fractions, then show the for-human output as whatever you convert the work value.

The compiler turns perimiter into a single value if wheel_radius is constant.

 perimeter = 2*pi*wheel_radius;
  //Serial.println(perimeter);

Speed = distance / time. The only distance you have is circumference, you need time which should be microseconds (micros() is to the nearest 4, still 250x closer than millis()) for best accuracy.

You put the magnet pm the wheel hub, right? If you put 3 more then your device will know about speed changes 4x faster on average. With only 1 magnet, the bike will roll 2 meters or more between detections.

I think that perhaps a gear tooth counter (also a Hall device) could detect spokes going by at the hub. That would give information many times per revolution.

Why not simply use the FreqMeasure library? It works perfectly, and does exactly what you need.

https://www.pjrc.com/teensy/td_libs_FreqMeasure.html

Regards,
Ray L.

GoForSmoke:
Using float variables on Arduino is 100x slow and very unnecessary.
If you need close measure, calculate the circumference to work with in integer mm. That is close without fractions, then show the for-human output as whatever you convert the work value.

but i need the most exact speed what can i do then ?

GoForSmoke:
Speed = distance / time. The only distance you have is circumference, you need time which should be microseconds (micros() is to the nearest 4, still 250x closer than millis()) for best accuracy.

lol yes right i don't know how i forgot that but i am still experimenting with how should i do it

GoForSmoke:
You put the magnet pm the wheel hub, right? If you put 3 more then your device will know about speed changes 4x faster on average. With only 1 magnet, the bike will roll 2 meters or more between detections.

yes on the wheel hub, it's a very tiny RC car which i am having fun with adding some stuff, it's around 20cm fast cadillac
can you please clarify what you mean about the 4 magnets ?! i didn't understood how this method work
but i can't put anymore magnets because the wheel will get really heavy and comparing to other one, (wheel is 3cm diameter)

GoForSmoke:
I think that perhaps a gear tooth counter (also a Hall device) could detect spokes going by at the hub. That would give information many times per revolution.

i think you mean like an open circuit which close everytime the teeth hit the other end ? if yes i thought about it but it's not quiet the best for this application

RayLivingston:
Why not simply use the FreqMeasure library? It works perfectly, and does exactly what you need.

FreqMeasure Library, for Measuring Frequencies in the 0.1 to 1000 Hz range, or RPM Tachometer Applications

Regards,
Ray L.

you mean this will get the voltage from pin 8 and count it and calculate the frequency ?

A toy car? Just count the microseconds for any revolution and divide the circumference x 1,000,000 by that to get circumference units per second. The modulo operator will give you a remainder to work with, you could get 3 places and still be way faster than using floats.

Make a few timed runs using IR beam interrupt to get the real speed vs the calculated speed and adjust your circumference to calibrate.

GoForSmoke:
A toy car? Just count the microseconds for any revolution and divide the circumference x 1,000,000 by that to get circumference units per second. The modulo operator will give you a remainder to work with, you could get 3 places and still be way faster than using floats.

Make a few timed runs using IR beam interrupt to get the real speed vs the calculated speed and adjust your circumference to calibrate.

can you please give example with code ? i didn't get it much :frowning:

I can't test this with a jumper. The contact bounce shows up as big numbers.

It probably needs some debugging but it compiles and runs.

const byte LED_pin = 9;

// wheel radius in mm * 10 * 2 * ( pi * 10000 ) = 94.24800 mm circumference
// if you work in Small Units and scale back later, integers are plenty accurate.
// remember, this value has to be divided by microseconds per turn.
const unsigned long wheel_circumference = 150UL * 2UL * 31416UL;

unsigned long Speed = 0;
unsigned long PrevSpeed = 0;

volatile byte hall_rising = 0; // interrupt flag
volatile unsigned long irqMicros;

unsigned long startMicros;
unsigned long elapsedMicros;

unsigned long displayStartMillis;
const unsigned long displayWaitMillis = 100;


void wheel_IRQ()
{
  irqMicros = micros();
  hall_rising = 1;
}

void setup()
{
  pinMode( 2, INPUT ); // pin # is tied to the interrupt
  pinMode( LED_pin, OUTPUT );
  Serial.begin( 9600 ); // can this be faster? faster would be better
  Serial.println(wheel_circumference);
  delay( 1000 );

  attachInterrupt( 0, wheel_IRQ, RISING ); // pin 2 looks for LOW to HIGH change
}

void loop()
{
  if ( hall_rising == 1 )
  {
    digitalWrite( LED_pin, HIGH );
    elapsedMicros = irqMicros - startMicros;
    startMicros = irqMicros;
  }
  else
  {
    digitalWrite( LED_pin, LOW );
  }

  hall_rising = 0;

  // print speed 1000/Wait (10) times per second
  if ( millis() - displayStartMillis >= displayWaitMillis )
  {
    Speed = wheel_circumference / elapsedMicros;
    
    displayStartMillis += displayWaitMillis;
    if ( Speed != PrevSpeed )
    {
      Serial.println(Speed); // this shows mm/sec with no remainder
    }
    PrevSpeed = Speed;
  }
}

i have read the program and seems interesting, it's a bit complicated didn't manage to understand so i have some questions (i commented them in the codes) :
i need please to well understand this and take notes explaining what is happening
and about the LED it doesn't light up, i tried other pins seems not hardware then i checked the could i don't see any problem with the codes i couldn't find out what makes it not goes HIGH and then i tested it and it works by itself

// wheel radius in mm * 10 * 2 * ( pi * 10000 ) = 94.24800 mm perimeter
//why the wheel radius x10 ??
const unsigned long wheel_perimeter = 150UL * 2UL * 31416UL;
//what do you mean by these ?
unsigned long startMicros;
unsigned long elapsedMicros;

unsigned long displayStartMillis;
const unsigned long displayWaitMillis = 100;
//what does IRQ means ? and what does this function do exactly ? 
//what does irqMicros means ? :/
void wheel_IRQ()
{
  irqMicros = micros();
  hall_rising = 1;
}
//i have no idea, i just use the standard :/
Serial.begin( 9600 ); // can this be faster? faster would be better
//about this function, i read the explanation here on this website i really don't understand it :(
//i know the 0 is equal to pin2 but what should the second variable do ? and how you select the third ?
//from where do you get these ? ive see many of this and can't know from where tried some google //search there is no sites that explain the last variable 
attachInterrupt( 0, wheel_IRQ, RISING );

firashelou:
i have read the program and seems interesting, it's a bit complicated didn't manage to understand so i have some questions (i commented them in the codes) :
i need please to well understand this and take notes explaining what is happening

//what does this UL means ?

const unsigned long wheel_circumference = 150UL * 2UL * 31416UL;



UL forces the compiler to treat the constant as an unsigned long. It may not be necessary always.



//Why need the unsign ?
unsigned long Speed = 0;
unsigned long PrevSpeed = 0;



I nave no need of values < 0

Arduino time functions millis() and micros() return unsigned long values. 

Clock-hours on a round clock, replace 12 with 0 and you have unsigned with values 0 to 11. Okay? Clock.
The difference between two times is the later time minus (difference) the earlier time, always.
7 - 4 = 3.... the clock hand at 7 turns left 4 to arrive at 3.
1 - 11 = 2 .... hand at 1 turns left 11 to arrive at 2.
Unsigned math loops itself, signed math overflows + to - and vice-versa and needs a fix step to do time.

I also prefer byte to int if the values will be within 0 to 255, like most for loop indexes. They use less RAM.



//what do you mean by these ?
unsigned long startMicros;
unsigned long elapsedMicros;

unsigned long displayStartMillis;
const unsigned long displayWaitMillis = 100;




The first two are for timing the wheel revs and the second two are for slowing the display down and could probably be 200 or more = 5 lines a second or less.



//what does IRQ means ? and what does this function do exactly ?
//what does irqMicros means ? :confused:
void wheel_IRQ()
{
  irqMicros = micros();
  hall_rising = 1;
}




IRQ is interrupt routine. It is set to trigger when the line from the sensor goes LOW to HIGH.
micros() returns unsigned long how many microseconds since chip start to the nearest 4. 
micros() rolls over a bit after 90 minutes, that's the longest interval micros() can time. We're safe.



//i have no idea, i just use the standard :confused:
Serial.begin( 9600 ); // can this be faster? faster would be better



Faster serial gets your prints out quicker. If you are doing through Arduino USB PC, use 115200 to help keep the output buffer emptying quicker. Be aware that printing can mess with timing, the buffer is 64 chars. At 115200, 11.5 chars could send per millisecond. Baud is bits/sec with start bit, 8 data bits, stop bit. 9600 baud is 960 data chars/sec which is less than 1 per millisecond removed from the print buffer.
So, lol, which is better? 



//about this function, i read the explanation here on this website i really don't understand it :frowning:
//i know the 0 is equal to pin2 but what should the second variable do ? and how you select the third ?
//from where do you get these ? ive see many of this and can't know from where tried some google //search there is no sites that explain the last variable
attachInterrupt( 0, wheel_IRQ, RISING );




That is to start the interrupt, INT0 on UNO pin 2 to run the function wheel_IRQ when the pin goes from LOW to HIGH. The function here saves the time and sets a flag for the code and it does this while the rest of the code is stopped so it needs to be quick, and normal operation resumes when the IRQ is finished (with overhead of saving chip registers before the IRQ and restoring after).

Here, some links to make your life easier. Bookmark these, there's no linky-button in quick-reply.
If you don't have an Arduino bookmarks folder, this will make a start.

Basic background except there is better coverage of breadboarding your own Duino around an AVR chip.
https://www.arduino.cc/en/Tutorial/Foundations

Look up commands, operators, etc, that you don't know starting here.

This is the reference for the standard C libraries that Arduino uses. This is the LibC manual.
http://www.nongnu.org/avr-libc/user-manual/modules.html

Here's the tutorials that are in your IDE under the File->Examples menu but illustrated.
https://www.arduino.cc/en/Tutorial/HomePage

These are the came with your Arduino IDE Libraries, how to use each one.

Many many things to try. Check out capacitive sensing and leds as light detectors, they can also get color.
http://playground.arduino.cc/

If you want to know the specs on your board(s)

More than beginner topics.
https://www.arduino.cc/en/Hacking/HomePage

Fun with bits. Get the most out of your bytes.
http://playground.arduino.cc/Code/BitMath

about the radius why you multiply it by 10 ?

GoForSmoke:
Unsigned math loops itself, signed math overflows + to - and vice-versa and needs a fix step to do time.

you mean it counts till a specific number of hours and after it restart ?

GoForSmoke:
unsigned long startMicros;
unsigned long elapsedMicros;

why micros and not millis ?

GoForSmoke:
unsigned long displayStartMillis;
const unsigned long displayWaitMillis = 100;

i didn't get what you mean :confused: why do you need it ?

GoForSmoke:
micros() returns unsigned long how many microseconds since chip start to the nearest 4

what do you mean by nearest 4 ?

GoForSmoke:
Faster serial gets your prints out quicker. If you are doing through Arduino USB PC, use 115200 to help keep the output buffer emptying quicker.

so you suggest i use 115200 or more for most applications ?

about the websites yes i've used some and others seems interesting, i saved them in a folder to try them :slight_smile:

firashelou:
about the radius why you multiply it by 10 ?

Measure radius in 10th of mm instead of mm increases accuracy. Do you have calipers or a micrometer?
The smaller the unit of measure, the finer you can calibrate... tune... that number to get speed right.
But that number has to multiply by pi times a scaling factor of how many places we want PI (I went with 4, you can't measure the difference more makes) and then divide by millions (of microseconds per second) to give mm/sec output, at least that's the idea but I don't have the Hall switch.

you mean it counts till a specific number of hours and after it restart ?

why micros and not millis ?

With micros() you can time closer than 1/1000 second, down to 4/1000000 second. That's more accuracy.

micros() starts at 0 and adds 1 every 16 clock cycles. After 4294967295 microseconds comes 0 again.
That's not even 90 minutes, it's 71.5-some.

millis() starts at 0 and adds 1 every 1000 micros and rolls over a bit after 49.71 days.

i didn't get what you mean :confused: why do you need it ?
what do you mean by nearest 4 ?

micros() is only good enough to measure down to 4 millionths of a second. The returns round off to 4. It's what you get so don't let the name get you, it is not to the microsecond but the units are microseconds and there are 1000 microseconds per 1 millisecond.

so you suggest i use 115200 or more for most applications ?

about the websites yes i've used some and others seems interesting, i saved them in a folder to try them :slight_smile:

Yes, over that USB link use the fastest you can get to empty the serial output buffer fastest.

With a Leonardo or Teensy you can connect at USB speed native to the chip, not rinky-dink TTL serial.

The top part needs to be replaced with this. It was wrong before. mm x 100 can be replaced with a calibration value once you can time the car well enough.

// wheel radius in mm * 100 * 2 * ( pi * 10000 ) = 94.248000 mm circumference.
// 6 0's were used in scaling up radius and pi, 6 places are divided in the end
// and the units work out. You can use integers more accurate than float on 
// Arduino at greatly faster speed. Both type of long can hold any 9-digits.
// Arduino variable type long long can hold any 19 digits is 19 place accuracy.
// if you work in Small Units and scale back later, integers are plenty accurate.
// remember, this value has to be divided by microseconds per turn.
const unsigned long wheel_circumference = 1500UL * 2UL * 31416UL; // = 94,248,000
// wheel circumference gets divided by microseconds, 1,000,000/sec (usec or us).
// wheel turns once for 94248000 mm/100 in 1000000 usecs =

and

void loop()
{
  if ( hall_rising == 1 )
  {
    digitalWrite( LED_pin, HIGH );
    elapsedMicros = irqMicros - startMicros;
    startMicros = irqMicros;
    hall_rising = 2;
  }
  else if  ( hall_rising == 2 )
  {
    digitalWrite( LED_pin, LOW );
    hall_rising = 0;
  }

  // print speed 1000/Wait (10) times per second
  if ( millis() - displayStartMillis >= displayWaitMillis )
  {
    Speed = wheel_circumference / elapsedMicros;
    
    displayStartMillis += displayWaitMillis;
    if ( Speed != PrevSpeed )
    {
      Serial.println(Speed); // this shows mm/sec with no remainder
    }

    PrevSpeed = Speed;
  }
}

When you see this code, think of a machine with moving parts.

The IRQ that records the time or soon after the pin changed from LOW to HIGH and sets a flag.

The interrupt flag handler that calculates how long since the last turn, resets the time mark, turns the led ON and changes the flag.

The led handler that turns the led OFF and sets the flag to 0 to wait for the IRQ to make it 1.

The output to serial routine that runs 10x a second as long as the speed changes. No change, no serial output.

Each part works only when it needs to as it needs to. It is event-driven where time and pin state change and even the value of variables --are-- events. The flag variable used is really state since it can be 0 to 2 and there is a very minimal state machine between the IRQ and handlers.

Please note that this loop() should run in excess of 10,000x per second. The RC wheel should take longer by far to turn once.