How to improve Bicycle Speedometer

Hi, everone!

Together with my kid I'm assembling speedometer for his bicycle. I know it is cheaper to buy one but the process is more intresting :wink:

I used for the basis this project - https://create.arduino.cc/projecthub/139683/arduino-lcd-display-as-a-bicycle-speedometer-6a6568?ref=tag&ref_id=speedometer&offset=2

But I had to modify it since I'm using I2C connection to display.

I have two questions:

1) how is it possible to avoid flicking of the display and
2) reset the speed if the bicycle stops.

Code below! Thank you in advance.

#include <Wire.h> // 
#include <LiquidCrystal_I2C.h> // 

LiquidCrystal_I2C lcd(0x3F,16,2); //


float start, finished;
float elapsed, time;
float circMetric=1.580; // wheel circumference (in meters)
float circImperial; // using 1 kilometer = 0.621371192 miles
float speedk, speedm;    // holds calculated speed vales in metric and imperial

void setup()
{
 // convert metric to imperial for MPH calculations
 circImperial=circMetric*.62137;

 // the syntax with digitalPinToInterrupt should allow portability
 //among different Arduino models - see https://www.arduino.cc/reference/en/language/functions/external-interrupts/attachinterrupt/
 attachInterrupt(digitalPinToInterrupt(2), speedCalc, RISING); // interrupt called when sensors sends digital 2 high (every wheel rotation)
 //attachInterrupt(0, speedCalc, RISING); // interrupt called when sensors sends digital 2 high (every wheel rotation)

 //start now (it will be reset by the interrupt after calculating revolution time)
 start=millis();

 // set up the LCD's number of columns and rows:
 lcd.init(); // инициализация LCD
 lcd.backlight();

 // Print a transitory message to the LCD.
 lcd.print("CKOPOCTOMEP");
 delay(2000); //just to allow you to read the initialization message

 // top serial dialogue speed improves precision
 Serial.begin(115200);
}

void speedCalc()
{
 //Function called by the interrupt

 if((millis()-start)>100) // 100 millisec debounce
   {
   //calculate elapsed
   elapsed=millis()-start;

   //reset start
   start=millis();

   //calculate speed in km/h
   speedk=(3600*circMetric)/elapsed;

   //calculate speed in mph
   speedm=(3600*circImperial)/elapsed;
   }
}

void loop()
{
 // The loop will be interrupted by the sensor each time the
 // magnet passes near the sensor, in other words once per revolution

 // Top line in the 16 char, 2 lines display - speed data
 lcd.setCursor(0,0);
 lcd.print("                ");
 lcd.setCursor(0,0);
 lcd.print(int(speedk));
 lcd.print(" km/h ");


 //bottom line the 16 char, 2 lines display - time data
 lcd.setCursor(0,1);
 lcd.print("                ");
 lcd.setCursor(0,1);
 lcd.print(int(elapsed));
 lcd.print(" ms/rev      ");

 // adjust for personal preference to minimise flicker
 //delay(250);
}

Please edit your post to add code tags ("</>" button on editor), as described in "How to use the forum".

Change the interrupt routine to just count pulses.

In loop, periodically calculate the speed from pulse count and millis.

When the bike stops there will be no pulses and next time you calc, speed falls to zero.

The flicker comes from the fact that you blank the display prior to printing your information every time. It is best to just update the display. Combining that with reply #2 you get (untested)

#include <Wire.h> //
#include <LiquidCrystal_I2C.h> //

LiquidCrystal_I2C lcd(0x3F, 16, 2); //


unsigned long start;
volatile unsigned long tickCount;
unsigned long lastTickCount;

const unsigned long debounceTime = 100; // msecs of debounce for interrupts
const unsigned long updateTime = 5000;  // msecs between updating speed and display

const float circMetric = 1.580; // wheel circumference (in meters)
// using 1 kilometer = 0.621371192 miles
const float circImperial = circMetric * .62137; // convert metric to imperial for MPH calculations
float speedk, speedm;    // holds calculated speed vales in metric and imperial

void setup()
{

  // the syntax with digitalPinToInterrupt should allow portability
  //among different Arduino models - see https://www.arduino.cc/reference/en/language/functions/external-interrupts/attachinterrupt/
  attachInterrupt(digitalPinToInterrupt(2), tickCalc, RISING); // interrupt called when sensors sends digital 2 high (every wheel rotation)
  //attachInterrupt(0, speedCalc, RISING); // interrupt called when sensors sends digital 2 high (every wheel rotation)

  //start now (it will be reset by the interrupt after calculating revolution time)
  start = millis();

  // set up the LCD's number of columns and rows:
  lcd.init(); // инициализация LCD
  lcd.backlight();

  // Print a transitory message to the LCD.
  lcd.print("CKOPOCTOMEP");
  delay(2000); //just to allow you to read the initialization message
  lcd.clear();

  // top serial dialogue speed improves precision
  Serial.begin(115200);
}

void tickCalc()
{
  static unsigned long lastTickTime = 0;

  if ( millis() - lastTickTime >= debounceTime ) {
    // debounce time
    tickCount++;
    lastTickTime += debounceTime;
  }
}
void speedCalc(unsigned long revolutions, unsigned long time)
{
  float speed = 3600.0 * revolutions / time;
  //calculate speed in km/h
  speedk = speed * circMetric;

  //calculate speed in mph
  speedm = speed * circImperial;
}


void loop()
{
  // The loop will be interrupted by the sensor each time the
  // magnet passes near the sensor, in other words once per revolution
  unsigned long elapsed;

  unsigned long tempTickCount;
  unsigned long now = millis();

  if ( now - start >= updateTime ) {
    noInterrupts();  // turn off interrupts while copying count
    tempTickCount = tickCount;
    interrupts();
    elapsed = now - start;
    speedCalc( tempTickCount - lastTickCount, elapsed );
    start = now;
    lastTickCount = tempTickCount;

    // Top line in the 16 char, 2 lines display - speed data
    lcd.setCursor(0, 0);
    lcd.print(int(speedk));
    lcd.print(" km/h     ");

    //bottom line the 16 char, 2 lines display - time data
    lcd.setCursor(0, 1);
    lcd.print(int(elapsed));
    lcd.print(" ms/rev      ");
  }
}

i think it would be better if the code in the interrupt simply captures the time between interrupts and any calculations are performed in loop() just before updating the display

the following table indicates the time between revolutions for various speed in mph.

k 4819.49
   msec    mph k/msec
 5000.0    1.0    1.0
 4000.0    1.2    1.2
 3200.0    1.5    1.5
 2560.0    1.9    1.9
 2048.0    2.4    2.4
 1638.4    2.9    2.9
 1310.7    3.7    3.7
 1048.6    4.6    4.6
  838.9    5.7    5.7
  671.1    7.2    7.2
  536.9    9.0    9.0
  429.5   11.2   11.2
  343.6   14.0   14.0
  274.9   17.5   17.5
  219.9   21.9   21.9
  175.9   27.4   27.4
  140.7   34.2   34.2
  112.6   42.8   42.8

the actual speed calculation can be performed in loop(). besides capturing the time (msec) / revolution, the interrupt can set a flag indicating a new value is available.

"k" can be pre-calculated so that mph = k / dMsec -- k for 27 in. tire, 4819.49

k    = (3600.0 / 5280) * PI * (tireDiaIn / 12.0) * 1000;

similarly conversion to another set of units (km) should only involve a single constant that can be precalculated.

leaky integration (averaging) can be used to smooth results if needed

mph  += ((k / dMsec) - mph) / 10;

some code

#define butPin  A1

byte butStateLst;
byte butState;

int  tireDiaIn  = 27;
float k;
float mph  = 0;

// -----------------------------------------------------------------------------
void loop (void) {
    static unsigned long msecLst = 0;
           unsigned long msec    = millis ();
           unsigned long dMsec;
    static byte          flag    = 0;

    // check for event
    butState = digitalRead (butPin);
    if (butStateLst != butState)  {
        butStateLst = butState;
        
        if (LOW == butState)  {
            dMsec   = msec - msecLst;
            msecLst = msec;
            flag ++;
        }
    }

    // process new event
    if (flag)  {
        flag = 0;
        mph  += ((k / dMsec) - mph) / 10;
        Serial.println (mph);
    }
}

// -----------------------------------------------------------------------------
void setup (void) {
    Serial.begin (115200);

    pinMode (butPin, INPUT_PULLUP);
    butStateLst = butState = digitalRead (butPin);

    k    = (3600.0 / 5280) * PI * (tireDiaIn / 12.0) * 1000;

    // verify k
    float circFt = 7.1;
    float msec;
    for (msec = 5000; msec > 100; msec *= 0.8) {
        float fps    = circFt * 1000 / msec;
        float mph    = fps * 3600 / 5280;

        Serial.print ("  ");
        Serial.print (msec);

        Serial.print ("  ");
        Serial.print (mph);

        Serial.print ("  ");
        Serial.print (k);

        Serial.print ("  ");
        Serial.print (k / msec);
        Serial.println ("");
    }
}

some results

  5000.00  0.97  4819.49  0.96
  4000.00  1.21  4819.49  1.20
  3200.00  1.51  4819.49  1.51
  2560.00  1.89  4819.49  1.88
  2048.00  2.36  4819.49  2.35
  1638.40  2.95  4819.49  2.94
  1310.72  3.69  4819.49  3.68
  1048.58  4.62  4819.49  4.60
  838.86  5.77  4819.49  5.75
  671.09  7.21  4819.49  7.18
  536.87  9.02  4819.49  8.98
  429.50  11.27  4819.49  11.22
  343.60  14.09  4819.49  14.03
  274.88  17.61  4819.49  17.53
  219.90  22.01  4819.49  21.92
  175.92  27.52  4819.49  27.40
  140.74  34.40  4819.49  34.24
  112.59  43.00  4819.49  42.81
0.41
2.17
4.28
5.90
7.82
9.21
10.63
11.74
12.79
13.71
14.58
15.33
16.01
16.59
17.06
17.53
17.93
18.22
18.51
18.57
18.86
18.96
20.64
22.59
22.35
22.36
22.47
22.27
22.21
21.99
21.90
21.80
23.17
25.40
24.94
24.47
24.01
23.54
23.24
22.97
22.61
22.20
21.53
23.09
26.59
26.16
25.64
25.09
24.50
23.96
23.56
23.04
22.72
23.17
28.51
27.65
26.79
26.00
25.34
24.84

jremington:
Please edit your post to add code tags ("</>" button on editor), as described in "How to use the forum".

Thank you for advice!

wildbill:
Change the interrupt routine to just count pulses.

In loop, periodically calculate the speed from pulse count and millis.

When the bike stops there will be no pulses and next time you calc, speed falls to zero.

blh64:
The flicker comes from the fact that you blank the display prior to printing your information every time. It is best to just update the display. Combining that with reply #2 you get (untested)

#include <Wire.h> //

#include <LiquidCrystal_I2C.h> //

LiquidCrystal_I2C lcd(0x3F, 16, 2); //

unsigned long start;
volatile unsigned long tickCount;
unsigned long lastTickCount;

const unsigned long debounceTime = 100; // msecs of debounce for interrupts
const unsigned long updateTime = 5000;  // msecs between updating speed and display

const float circMetric = 1.580; // wheel circumference (in meters)
// using 1 kilometer = 0.621371192 miles
const float circImperial = circMetric * .62137; // convert metric to imperial for MPH calculations
float speedk, speedm;    // holds calculated speed vales in metric and imperial

void setup()
{

// the syntax with digitalPinToInterrupt should allow portability
  //among different Arduino models - see attachInterrupt() - Arduino Reference
  attachInterrupt(digitalPinToInterrupt(2), tickCalc, RISING); // interrupt called when sensors sends digital 2 high (every wheel rotation)
  //attachInterrupt(0, speedCalc, RISING); // interrupt called when sensors sends digital 2 high (every wheel rotation)

//start now (it will be reset by the interrupt after calculating revolution time)
  start = millis();

// set up the LCD's number of columns and rows:
  lcd.init(); // инициализация LCD
  lcd.backlight();

// Print a transitory message to the LCD.
  lcd.print("CKOPOCTOMEP");
  delay(2000); //just to allow you to read the initialization message
  lcd.clear();

// top serial dialogue speed improves precision
  Serial.begin(115200);
}

void tickCalc()
{
  static unsigned long lastTickTime = 0;

if ( millis() - lastTickTime >= debounceTime ) {
    // debounce time
    tickCount++;
    lastTickTime += debounceTime;
  }
}
void speedCalc(unsigned long revolutions, unsigned long time)
{
  float speed = 3600.0 * revolutions / time;
  //calculate speed in km/h
  speedk = speed * circMetric;

//calculate speed in mph
  speedm = speed * circImperial;
}

void loop()
{
  // The loop will be interrupted by the sensor each time the
  // magnet passes near the sensor, in other words once per revolution
  unsigned long elapsed;

unsigned long tempTickCount;
  unsigned long now = millis();

if ( now - start >= updateTime ) {
    noInterrupts();  // turn off interrupts while copying count
    tempTickCount = tickCount;
    interrupts();
    elapsed = now - start;
    speedCalc( tempTickCount - lastTickCount, elapsed );
    start = now;
    lastTickCount = tempTickCount;

// Top line in the 16 char, 2 lines display - speed data
    lcd.setCursor(0, 0);
    lcd.print(int(speedk));
    lcd.print(" km/h    ");

//bottom line the 16 char, 2 lines display - time data
    lcd.setCursor(0, 1);
    lcd.print(int(elapsed));
    lcd.print(" ms/rev      ");
  }
}

gcjr:
i think it would be better if the code in the interrupt simply captures the time between interrupts and any calculations are performed in loop() just before updating the display

the following table indicates the time between revolutions for various speed in mph.

k 4819.49

msec    mph k/msec
5000.0    1.0    1.0
4000.0    1.2    1.2
3200.0    1.5    1.5
2560.0    1.9    1.9
2048.0    2.4    2.4
1638.4    2.9    2.9
1310.7    3.7    3.7
1048.6    4.6    4.6
  838.9    5.7    5.7
  671.1    7.2    7.2
  536.9    9.0    9.0
  429.5  11.2  11.2
  343.6  14.0  14.0
  274.9  17.5  17.5
  219.9  21.9  21.9
  175.9  27.4  27.4
  140.7  34.2  34.2
  112.6  42.8  42.8




the actual speed calculation can be performed in loop(). besides capturing the time (msec) / revolution, the interrupt can set a flag indicating a new value is available.

"k" can be pre-calculated so that mph = k / dMsec -- k for 27 in. tire, 4819.49


k    = (3600.0 / 5280) * PI * (tireDiaIn / 12.0) * 1000;




similarly conversion to another set of units (km) should only involve a single constant that can be precalculated.

leaky integration (averaging) can be used to smooth results if needed


mph  += ((k / dMsec) - mph) / 10;





some code


#define butPin  A1

byte butStateLst;
byte butState;

int  tireDiaIn  = 27;
float k;
float mph  = 0;

// -----------------------------------------------------------------------------
void loop (void) {
    static unsigned long msecLst = 0;
          unsigned long msec    = millis ();
          unsigned long dMsec;
    static byte          flag    = 0;

// check for event
    butState = digitalRead (butPin);
    if (butStateLst != butState)  {
        butStateLst = butState;
       
        if (LOW == butState)  {
            dMsec  = msec - msecLst;
            msecLst = msec;
            flag ++;
        }
    }

// process new event
    if (flag)  {
        flag = 0;
        mph  += ((k / dMsec) - mph) / 10;
        Serial.println (mph);
    }
}

// -----------------------------------------------------------------------------
void setup (void) {
    Serial.begin (115200);

pinMode (butPin, INPUT_PULLUP);
    butStateLst = butState = digitalRead (butPin);

k    = (3600.0 / 5280) * PI * (tireDiaIn / 12.0) * 1000;

// verify k
    float circFt = 7.1;
    float msec;
    for (msec = 5000; msec > 100; msec *= 0.8) {
        float fps    = circFt * 1000 / msec;
        float mph    = fps * 3600 / 5280;

Serial.print ("  ");
        Serial.print (msec);

Serial.print ("  ");
        Serial.print (mph);

Serial.print ("  ");
        Serial.print (k);

Serial.print ("  ");
        Serial.print (k / msec);
        Serial.println ("");
    }
}




some results


5000.00  0.97  4819.49  0.96
  4000.00  1.21  4819.49  1.20
  3200.00  1.51  4819.49  1.51
  2560.00  1.89  4819.49  1.88
  2048.00  2.36  4819.49  2.35
  1638.40  2.95  4819.49  2.94
  1310.72  3.69  4819.49  3.68
  1048.58  4.62  4819.49  4.60
  838.86  5.77  4819.49  5.75
  671.09  7.21  4819.49  7.18
  536.87  9.02  4819.49  8.98
  429.50  11.27  4819.49  11.22
  343.60  14.09  4819.49  14.03
  274.88  17.61  4819.49  17.53
  219.90  22.01  4819.49  21.92
  175.92  27.52  4819.49  27.40
  140.74  34.40  4819.49  34.24
  112.59  43.00  4819.49  42.81
0.41
2.17
4.28
5.90
7.82
9.21
10.63
11.74
12.79
13.71
14.58
15.33
16.01
16.59
17.06
17.53
17.93
18.22
18.51
18.57
18.86
18.96
20.64
22.59
22.35
22.36
22.47
22.27
22.21
21.99
21.90
21.80
23.17
25.40
24.94
24.47
24.01
23.54
23.24
22.97
22.61
22.20
21.53
23.09
26.59
26.16
25.64
25.09
24.50
23.96
23.56
23.04
22.72
23.17
28.51
27.65
26.79
26.00
25.34
24.84

Thank you so much!!! I'm new to arduino programming, so I need time to understand it and try it) Will revert back! Thank you once again!

I use the pulseIn() function on my car speedo that uses a HE sensor.

I haven't needed to attend to bouncing.

The pulseIn() function uses a timeout parameter. I set that to 5 sec. If it times out then the sketch deduces the speed to be zero.

This works accurately and reliably from 0 to 110 kph.

I use a 4-digt, 7-segment LED display. No flicker and bright.

John.