millis() - start_time does not give any values between 190 and 260

Hello there,

The project I am currently working on is a speedometer that measures the speed of a rotating wheel using a hall effect sensor. Whenever the magnet, placed on the wheel, passes the hall effect sensor, the program uses the time between the last two passes of the magnet to calculate the speed during that period.

Normally what happens is that when the sensor is triggered, the current time, millis() is stored to a variable called start_time. Then, when the sensor is triggered again, a variable, delta_t is calculated as the difference between the current time and start_time. Then start_time is set to the current time.

However, when triggering the hall effect sensor about 5 times a second, I get strange results. For some reason, delta_t will not resolve to any numbers between 190 and 260! Meaning the speedometer will read 13MPH, or 19MPH, but nothing in between.

Here is my code:

#include <SevenSeg.h>

// Give names to the pins to be used for each segment
int a_pin = 9;
int b_pin = 12;
int c_pin = 3;
int d_pin = 8;
int e_pin = 7;
int f_pin = 2;
int g_pin = 11;
int decimal_pin = 4;

// And the digit pins
const int num_of_digits = 3;
int digit_1 = 10;
int digit_2 = 6;
int digit_3 = 5;
int digit_pins[num_of_digits] = {digit_1, digit_2, digit_3};

int hall_effect_pin = 0;

// Initialize the seven segment display
SevenSeg disp(a_pin, b_pin, c_pin, d_pin, e_pin, f_pin, g_pin);

// Useful variables for counting rotations. 
volatile long start_time = 0;
volatile long delta_t = 20000;

// Size of the wheel and various helpful constants
// Change wheel_diameter_inches to whatever is needed
float wheel_diameter_inches = 20.0;
float wheel_circumference_inches = wheel_diameter_inches * 3.141592;
float inches_per_mile = 63360.0;

float current_mph = 0;

volatile bool triggered = false;

void setup() {
  Serial.begin(9600);
  // Tell the library what common pins to use for digits, and what pin is for the decimal point
  disp.setDigitPins(num_of_digits, digit_pins);
  disp.setDPPin(decimal_pin);
  disp.setDigitDelay(1000);

  // Use the hall effect pin as an input
  pinMode(hall_effect_pin, INPUT);

  // Draws a fun opening animation
  String intro_string = "   SPEEDOTRON 2000   ";
  while (millis() <= (intro_string.length()-2)*200){
    int first_letter_index = round(millis() / 200);
    disp.write(intro_string.substring(first_letter_index, first_letter_index + 3));
  }
}

void loop() {
  bool hall_effect_state = digitalRead(hall_effect_pin);
  if (hall_effect_state == LOW and !triggered){
    trigger();
    count_revolution();
  } else if (hall_effect_state == HIGH){
    triggered = false;
  }
  // Update display every second
  current_mph = convert_delta_t_to_mph(delta_t);
  
   //If there is more than 2 seconds with no revolutions, the speed is 0 mph
  if (millis() - start_time > 2000){
    current_mph = 0.0;
  }
  
  // Write the speed to the 7 segment display
  disp.write(current_mph);
  delay(5);
}

void count_revolution(){
  // Whenever the revolution happens, mark down how long it took to turn, and set the new start time
  if (millis() - start_time > 100){
    delta_t = millis() - start_time;
    //Serial.println(delta_t);
    start_time = millis();
  }
  Serial.print("Delta t: ");
  Serial.print(delta_t);
  Serial.print(" - MPH: ");
  Serial.println(convert_delta_t_to_mph(delta_t));
}

void trigger(){
  triggered = true;
}

float convert_delta_t_to_mph(float delta_t){
  // This takes the time between the revolutions and returns the speed calculated from it
  float delta_t_seconds = delta_t / 1000.0;
  float in_per_second = wheel_circumference_inches / delta_t_seconds;
  float in_per_hour   = in_per_second * 3600.0;
  float mi_per_hour   = in_per_hour / inches_per_mile;
  float miles_per_hour_rounded = round(100 * mi_per_hour) / 100.0;
  return(miles_per_hour_rounded);
}

And here is a sample of the serial output. Notice the lack of numbers between 190 and 260

12:31:16.630 -> Delta t: 5321 - MPH: 0.67
12:31:16.809 -> Delta t: 184 - MPH: 19.40
12:31:16.972 -> Delta t: 184 - MPH: 19.40
12:31:17.160 -> Delta t: 175 - MPH: 20.40
12:31:17.356 -> Delta t: 184 - MPH: 19.40
12:31:17.804 -> Delta t: 459 - MPH: 7.78
12:31:18.145 -> Delta t: 358 - MPH: 9.97
12:31:18.328 -> Delta t: 185 - MPH: 19.30
12:31:18.522 -> Delta t: 184 - MPH: 19.40
12:31:18.709 -> Delta t: 174 - MPH: 20.52
12:31:18.993 -> Delta t: 267 - MPH: 13.37
12:31:19.220 -> Delta t: 268 - MPH: 13.32
12:31:19.433 -> Delta t: 184 - MPH: 19.40
12:31:19.591 -> Delta t: 174 - MPH: 20.52
12:31:20.140 -> Delta t: 544 - MPH: 6.56
12:31:20.680 -> Delta t: 532 - MPH: 6.71
12:31:20.840 -> Delta t: 184 - MPH: 19.40
12:31:21.041 -> Delta t: 184 - MPH: 19.40
12:31:21.220 -> Delta t: 175 - MPH: 20.40
12:31:21.417 -> Delta t: 184 - MPH: 19.40
12:31:21.676 -> Delta t: 268 - MPH: 13.32
12:31:22.012 -> Delta t: 358 - MPH: 9.97
12:31:22.484 -> Delta t: 459 - MPH: 7.78
12:31:22.870 -> Delta t: 367 - MPH: 9.73
12:31:23.120 -> Delta t: 267 - MPH: 13.37
12:31:23.286 -> Delta t: 183 - MPH: 19.51
12:31:23.490 -> Delta t: 175 - MPH: 20.40
12:31:23.677 -> Delta t: 184 - MPH: 19.40
12:31:23.836 -> Delta t: 175 - MPH: 20.40
12:31:24.020 -> Delta t: 185 - MPH: 19.30
12:31:24.189 -> Delta t: 175 - MPH: 20.40
12:31:24.749 -> Delta t: 552 - MPH: 6.47
12:31:24.932 -> Delta t: 184 - MPH: 19.40
12:31:25.104 -> Delta t: 175 - MPH: 20.40
12:31:25.385 -> Delta t: 266 - MPH: 13.42

Even when the 7 segment display is disabled, the problem still persists! I am at a loss as to what could cause this discrepancy. Any help is appreciated!

Note: The measurements are mostly accurate below delta_t=170

EDIT: The board is an Arduino Leonardo. The hall effect sensor is an AH1815 non latching hall effect sensor (https://cdn.sparkfun.com/assets/4/4/8/2/a/AH1815.pdf). Pins 1, 2, and 3 of the sensor are connected to 5V, GND, and digital pin 0, respectively. There is a pullup resistor between digital pin 0 and 5V, and a 10nf capacitor between 5V and GND.

You've defined delta_t as a long with global scope, yet you pass it with the call:

  • Serial.println(convert_delta_t_to_mph(delta_t));*

but the function signature for the conversion is:

float convert_delta_t_to_mph(float delta_t){

you're putting a 4-byte long on the stack, but pulling it off the stack as an IEEE floating point number. Why even pass it as a parameter if you're making it a global?

Whether or not it is causing a problem I would eliminate as many as possible of the calculations done with floats in the convert_delta_t_to_mph() function as they are very time consuming. Work in integers and convert the value returned to a float. Why is delta_t a float in the first place as it is derived by subtracting one integer value from another ?

Whilst making changes I suggest that you change start_time to unsigned int as it will never be negative. It is also sensible to read the value of millis() just once at the start of loop() and save the value in a variable to avoid repeated calls to millis()

Whether or not it is causing a problem I would eliminate as many as possible of the calculations done with floats in the convert_delta_t_to_mph() function as they are very time consuming. Work in integers and convert the value returned to a float. Why is delta_t a float in the first place as it is derived by subtracting one integer value from another ?

Whilst making changes I suggest that you change start_time to unsigned int as it will never be negative. It is also sensible to read the value of millis() just once at the start of loop() and save the value in a variable to avoid repeated calls to millis()

econjack:
you're putting a 4-byte long on the stack, but pulling it off the stack as an IEEE floating point number. Why even pass it as a parameter if you're making it a global?

The compiler does an implicit typecast but it is good form to explicitly typecast so the reader knows it is intentional.

Also, delta_t and start_time should be declared as 'unsigned long' since that is what millis() returns.

Hello NotQuiteAmish,
Welcome to the forum.
For posting your code correctly with code tags on your first post ++Karma;

What is the point of this:

void trigger(){
  triggered = true;
}

Especially when you make triggered false you just put it inline.

And this?

  delay(5);

You obviously know how to use millis(). Using delay leads to problems later on. Use your knowledge of millis(0) and get rid of all delays.

Here is a version of your code which avoids unnecessary computations if nothing has changed. It may not fix your issue but it is worth implementing and it eliminates the delay.

#include <SevenSeg.h>

// Give names to the pins to be used for each segment
int a_pin = 9;
int b_pin = 12;
int c_pin = 3;
int d_pin = 8;
int e_pin = 7;
int f_pin = 2;
int g_pin = 11;
int decimal_pin = 4;

// And the digit pins
const int num_of_digits = 3;
int digit_1 = 10;
int digit_2 = 6;
int digit_3 = 5;
int digit_pins[num_of_digits] = {digit_1, digit_2, digit_3};

int hall_effect_pin = 0;

// Initialize the seven segment display
SevenSeg disp(a_pin, b_pin, c_pin, d_pin, e_pin, f_pin, g_pin);

// Useful variables for counting rotations. 
volatile unsigned long start_time = 0;
volatile unsigned long delta_t = 0;
volatile unsigned long last_delta_t = 0;

// Size of the wheel and various helpful constants
// Change wheel_diameter_inches to whatever is needed
float wheel_diameter_inches = 20.0;
float wheel_circumference_inches = wheel_diameter_inches * 3.141592;
float inches_per_mile = 63360.0;

float current_mph = 0;

void setup() {
  Serial.begin(9600);
  // Tell the library what common pins to use for digits, and what pin is for the decimal point
  disp.setDigitPins(num_of_digits, digit_pins);
  disp.setDPPin(decimal_pin);
  disp.setDigitDelay(1000);

  // Use the hall effect pin as an input
  pinMode(hall_effect_pin, INPUT);

  // Draws a fun opening animation
  String intro_string = "   SPEEDOTRON 2000   ";
  while (millis() <= (intro_string.length()-2)*200){
    int first_letter_index = round(millis() / 200);
    disp.write(intro_string.substring(first_letter_index, first_letter_index + 3));
  }
  start_time = millis();
}

void loop() {
  static bool last_hall_effect_state = HIGH;
  bool hall_effect_state = digitalRead(hall_effect_pin);
  
  // Look for HIGH to LOW transition
  if (last_hall_effect_state == HIGH && hall_effect_state == LOW){
    count_revolution();
  }
  last_hall_effect_state = hall_effect_state;

  //If there is more than 2 seconds with no revolutions, the speed is 0 mph
  if (millis() - start_time > 2000){
    current_mph = 0.0;
    start_time = millis();
    disp.write(current_mph);
  }
  else if (delta_t != last_delta_t){
    last_delta_t = delta_t;
    current_mph = convert_delta_t_to_mph(delta_t);
    // Write the speed to the 7 segment display
    disp.write(current_mph);
    Serial.print("Delta t: ");
    Serial.print(delta_t);
    Serial.print(" - MPH: ");
    Serial.println(current_mph);
  }
}

void count_revolution(){
  // Whenever the revolution happens, mark down how long it took to turn, and set the new start time
  unsigned long temp_delta = millis() - start_time;
  if (temp_delta > 100){
    delta_t = temp_delta;
    //Serial.println(delta_t);
    start_time = millis();
  }
}

float convert_delta_t_to_mph(unsigned long delta_t){
  // This takes the time between the revolutions and returns the speed calculated from it
  float delta_t_seconds = (float) delta_t / 1000.0;
  float in_per_second = wheel_circumference_inches / delta_t_seconds;
  float in_per_hour   = in_per_second * 3600.0;
  float mi_per_hour   = in_per_hour / inches_per_mile;
  float miles_per_hour_rounded = round(100 * mi_per_hour) / 100.0;
  return(miles_per_hour_rounded);
}

I would be suspicious of all the serial output - you've got the serial baud rate down at 9600, which is 960 bytes/second. Arduino serial has IIRC a 64-byte buffer on transmit - Serial.print() puts the data into the buffer, and when the transmit complete interrupt fires for each byte, it loads in the next byte and tells it to start sending that - but if you're giving it data faster than it can send it, this buffer will fill up. When this happens, printing to serial becomes blocking, and it will sit there at the Serial.print() until enough has been sent to fit all of what you're trying to print into serial.

A first step to check this would be to crank up the baud rate (9600 seems to be a very popular speed for Arduino stuff, but why I'm not sure - 115200 works fine, and sends data 12 times faster). Be sure to use the same baud rate in serial monitor, otherwise you'll get garbage characters.

9600 baud is s l o w, I'd suggest using 115200 as the default for Serial unless there's a real reason not to. If for instance you have to talk to a device that's limited to 9600, or sending RS232 serial down a very long cable, 9600 might be suitable.

Thanks everyone for the suggestions.

I was away from the board for several hours today so I did my best to clean up the code by fixing variable types, pointless functions, and extra millis() calls. Here is what I ended up with:

#include <SevenSeg.h>

// Choose the anode pins for the digits
const int num_of_digits = 3;
int digit_pins[num_of_digits] = {10, 6, 5};

// Initialize the seven segment display
SevenSeg disp(9, 12, 3, 8, 7, 2, 11);

int hall_effect_pin = 0;

// Useful variables for counting rotations. Revolutions is volatile because it will be changed 
// by the interrupt function
unsigned long start_time = 0;
unsigned long delta_t = 20000;
unsigned long current_time = 0;

// Size of the wheel and various helpful constants
// Change wheel_diameter_inches to whatever is needed
float wheel_diameter_inches = 20.0;
float wheel_circumference_inches = wheel_diameter_inches * 3.141592;
float inches_per_mile = 63360.0;

float current_mph = 0.0;

bool triggered = false;

void setup() {
  // Serial.begin(115200);
  // Tell the library what common pins to use for digits, and what pin is for the decimal point
  disp.setDigitPins(num_of_digits, digit_pins);
  disp.setDPPin(4);
  disp.setDigitDelay(1000);

  // Use the hall effect sensor to trigger the calculation
  pinMode(hall_effect_pin, INPUT);

  String intro_string = "   SPEEDOTRON 2000   ";
  while (millis() <= (intro_string.length()-2)*200){
    int first_letter_index = round(millis() / 200);
    disp.write(intro_string.substring(first_letter_index, first_letter_index + 3));
  }
}

void loop() {
  current_time = millis();
  bool hall_effect_state = digitalRead(hall_effect_pin);
  // Only trigger once for each pass of magnet
  if (hall_effect_state == LOW and !triggered){
    triggered = true;
    count_revolution();
  } else if (hall_effect_state == HIGH){
    triggered = false;
  }
  
  current_mph = get_mph();
  
  //If there is more than 2 seconds with no revolutions, the speed is 0 mph
  if (current_time - start_time > 2000){
    current_mph = 0.0;
  }
  // Write the speed to the 7 segment display
  disp.write(current_mph);
}

void count_revolution(){
  // Whenever the revolution happens, mark down how long it took to turn, and set the new start time
  if (current_time - start_time > 50){
    delta_t = current_time - start_time;
    start_time = current_time;
  }
  //Serial.print("Delta t: ");
  //Serial.print(delta_t);
  //Serial.print(" - MPH: ");
  //Serial.println(get_mph());
}

float get_mph(){
  // This takes the time between the revolutions and returns the speed calculated from it
  float delta_t_seconds = delta_t / 1000.0;
  float in_per_second = wheel_circumference_inches / delta_t_seconds;
  float in_per_hour   = in_per_second * 3600.0;
  float mi_per_hour   = in_per_hour / inches_per_mile;
  float miles_per_hour_rounded = round(100 * mi_per_hour) / 100.0;
  //Serial.println(delta_t);
  return(miles_per_hour_rounded);
}

I tried to crank up the baud rate, as DrAzzy and MarkT reccommended, however the problem persisted. I also tried disabling serial entirely, but there was still no change. Another thing I tried was to change the refresh rate of the led display, no dice.

However, I just tried something new that partially gave some insight into the problem. I took a wire and shorted the connection between the GND and Output pins of the hall effect sensor, to simulate what the sensor would do when triggered. And when I manually triggered pin 0 using this method I found values between 190 and 260!

Before this, the way I triggered the sensor was by holding a magnet very close to the hall effect sensor, so close that I often had to tap it against the sensor.

This seems to imply that it is a problem with my hall-effect sensor or the circuit around it. The pull-up resistor from 5V to pin 0 is 100 ohms, and the capacitor between 5V and GND is 10nF.

Am I misusing the sensor in some way? And why would the timing of the sensor be influenced in this way? From what I read hall-effect sensors were supposed to be very reactive and high-speed.

(Side note: my magnet, which is just a refrigerator magnet, has to be within a half an inch to the sensor. Is there any way to extend this range, besides just buying a stronger magnet?)

Thanks for the help :slight_smile:

One way to test (without an actual sensor) is to use a PWM output to generate a signal into the same pin as the sensor input.

I use rare earth magnets to stimulate my Hall effect sensor.

100 Ohms seems very low for a pullup. 100 times the resistance would be better (10K)