calculate speed with integer math?

Hello everybody!

Im currently using floats to calculate speed and cadence from rotation durations but im starting to think if it is possible to ditch the floats altogether without using floats..

Im using Interrupt service routines (ISR) to capture the first millis() value, then increment the rotation counters, then when the rotation counters are more than a limit, do the speed & cadence(pedal rpm) calculations.

currently the calculations look like this:

speed = ((float(distance) / float(elapsedTime)) * 3.6);
cadence = ((float(_cadRotCount) / float(elapsedTime)) * 60000);

where all the variables (exept speed = float) are declared as unsigned integers of varying sizes.

the basic calculations are:
speed = distance / time
and
rpm = rotations / time

I figure i do want to display speed with 1 decimal accuracy, but rpm should be integer.
And i think i would rather use two variables for speed (speedInt&speedDeci) to display that decimal, instead of using float..
Something like this:

uint8_t speedInt = 25;
uint8_t speedDeci = 8;
#define printSpeed() serial.print(speedInt);serial.print(".");serial.print(speedDeci);
//printed speed = 25.8

So if i were traveling at 25.8km/h, the calculation could return 258 "100m/h", divided by 10 to give the speedInt, but then it would (probably?) be rounded up, so speedInt = 26?

My thoughts and ideas are resulting in a few actual questions:

1: how much additional int math can i afford and still be more effective than float math?

2: how do i split the last digit out of "258"?
2.5: is there a function to "bitshift" decimal numbers? (258 >> 1 = 25)

3: is it feasible that i can maintain accuracy without the use of floats?
.. Any hints/ideas/suggestions?

It will be easier to help if you provide an example of the raw data and the result that you want to get from it.

This is my guess of what you are trying to do, but please correct me if I am wrong,

distance = 12; // metres
time = 1000; // millisecs
speed = distance * 1000 / time; // 12 metres per sec
kmPerH = speed * 60 * 60 / 1000; // 43.2 km/h

When working with integers you must be careful to ensure that intermediate calculations do not overflow or underflow.

For example the speed calculation will work like this
speed = distance * 1000 / time;
distance * 1000 = 12000 -- which fits into an int
12000 / 1000 = 12 -- also fits into an int

However with a calculator this would give the same result
speed = distance / time * 1000;
but this won't work with integer maths because
distance / time is 12 / 1000 which gives 0 as the answer

If you want to preserve decimal places then multiply everything by 10 or 100 - for example 13.7 could be represented as 137

...R

xarvox:
1: how much additional int math can i afford and still be more effective than float math?

I think you mean fixed-point, not integer. Fixed-point arithmetic - Wikipedia Floats are far slower than fixed-point.

2: how do i split the last digit out of "258"?
2.5: is there a function to "bitshift" decimal numbers? (258 >> 1 = 25)

int units = value % 10 ; // 8
int tens = value / 10 ;  // 25

although you need to take care with negative values.
For fixed-point arithmetic use a power of two, not a power of ten, otherwise you'll not be very fast.

3: is it feasible that i can maintain accuracy without the use of floats?
.. Any hints/ideas/suggestions?

A long int has 32 bits of precision, a float has 23. But with fixed-point you risk overflow and loss of precision
through underflow.

I managed to piece it together with your assistance, thank you folks for your efforts!

There´s usually a 0.1km/H difference from what i expect, but its probably a rounding error..
Since its a bicycle project, i limited the speed value to uint8_t, a 255.9km/h will probably never be an issue.. :wink:

/* Speed stuff */
// author: H.Jonsson, dev@liggist.se
volatile uint16_t rotCount = 0;
volatile uint32_t rotTime = 0;
volatile uint16_t cadRotCount = 0;
volatile uint32_t cadRotTime = 0;
uint8_t numRotToMeasure = 3;
uint8_t numSpokeMagnets = 1;
volatile uint32_t odometer = 0;
uint8_t speed = 0;
uint8_t speedDec = 0;
uint8_t cadence = 0;
uint16_t tripLength = 0;
uint32_t lastSpdRep = 0;
uint32_t spdRepInterval = 1500;

uint32_t cirq = 2114; // mm

#define SpdPin 2
#define CadPin 3

uint32_t NOW = 0;

uint32_t lastDisplayUpdate = 0;
uint32_t displayUpdateFreq = 500;
uint8_t currentScreen = 2;
#define screenStart 0
#define screenSpdDist 1
#define screenSpdCad 2
#define screenMax 2

#include <LiquidCrystal.h>

LiquidCrystal lcd(9, 8, 7, 6, 5, 4);
#define beginLCD(data1,data2,data3) lcd.begin(16,2); printVarLCD(data1,data2); printLCD(1,1,data3);
#define writeLCD(pos,line,data) lcd.clear(); printLCD(pos,line,data);

#define printVarLCD(name,value) lcd.clear(); lcd.home(); lcd.print(name); lcd.print(value);
#define printVarLCD2(name,value) lcd.setCursor(0,1); lcd.print(name); lcd.print(value);
#define printLongVarLCD(name,val,suffix) lcd.clear(); lcd.home(); lcd.print(name); lcd.print(val); lcd.print(suffix);
#define printLongVarLCD2(name,val,suffix) lcd.setCursor(0,1); lcd.print(name); lcd.print(val); lcd.print(suffix);
#define printLCD(pos,line,data) lcd.setCursor(pos,line); lcd.print(data);
#define printScreenLCD(data1,data2) writeLCD(0,0,data1);printLCD(0,1,data2);
#define printSpeedLCD(data,decimal) lcd.clear(); lcd.home(); lcd.print(STR_SPD_SPEED); lcd.print(data); lcd.print(STR_DOT); lcd.print(decimal); lcd.print(STR_SPD_KMH);

#define SerialBaud 9600
#define beginSerial() Serial.begin(SerialBaud);
#define writeSerial(name,val) writeVarSerial(name,val,STR_BLANK);
#define writeVarSerial(prefix,value,sufix) Serial.println(STR_BLANK); Serial.print(prefix); Serial.print(value); Serial.println(sufix);
#define writeLongSerial(part1,part2,part3,part4) Serial.println(STR_BLANK); Serial.print(part1); Serial.print(part2); Serial.print(part3); Serial.println(part4);
#define printSpeedSerial(data,decimal) Serial.println(STR_BLANK); Serial.print(STR_SPD_SPEED); Serial.print(data); Serial.print(STR_DOT); Serial.print(decimal); Serial.println(STR_SPD_KMH);

#define STR_BLANK " "
#define STR_DOT "."
#define STR_SPD_SPEED "Spd: "
#define STR_SPD_KMH " Km/h"
#define STR_SPD_ODO "Trip: "
#define STR_SPD_M " m"
#define STR_SPD_KM " Km"
#define STR_CAD_CAD "Cad: "
#define STR_CAD_RPM " RPM"

void setup() {
    pinMode(SpdPin, INPUT);
    pinMode(CadPin, INPUT);
    attachInterrupt(digitalPinToInterrupt(SpdPin), speedISR, RISING);
    attachInterrupt(digitalPinToInterrupt(CadPin), cadISR, RISING);
    beginSerial();
    beginLCD("started"," BikeSys","speedTest");
}

void loop() {
    NOW = millis();
    doSpeedCalc();
}

void speedISR() {  
    if (!rotTime) {
    // if (!rotCount) {
        rotTime = NOW;
    }
    else {
        rotCount++;
    }
    odometer++;
}

void cadISR() {
  if (!cadRotTime) {
  // if (!rotCount) {
    cadRotTime = NOW;
  }
  else {
    cadRotCount++;
  }
}

bool checkElapsed(uint32_t last, uint32_t limit) { // Returns true if now - last is more than limit.
  return NOW - last > limit;
}

void doSpeedCalc(void) {
  // speed & distance
  noInterrupts();
  uint16_t _rotCount = rotCount;
  uint32_t _rotTime = rotTime;
  interrupts();
  if (_rotCount) {
    if (checkElapsed(_rotTime,(3000 * _rotCount))) {
      noInterrupts();
      rotCount = 0;
      rotTime = 0;
      speed = 0.0;
      interrupts();
    }
  }
  if (_rotCount >= numRotToMeasure) {
    noInterrupts();
    rotCount = 0;
    rotTime = 0;
    interrupts();
    uint32_t elapsedTime = 0;
    uint32_t distance = (cirq * _rotCount) * 100;
    elapsedTime = (NOW - _rotTime);
    // speed = distance / time
    uint32_t speedTemp = (distance / elapsedTime) * 36;
    speedTemp = speedTemp / numSpokeMagnets;
    speedTemp = speedTemp / 100;
    speed = speedTemp / 10;
    speedDec = speedTemp % 10;
  }
  /// cadence
  noInterrupts();
  uint16_t _cadRotCount = cadRotCount;
  uint32_t _cadRotTime = cadRotTime;
  interrupts();
  if (_cadRotCount) {
    if (checkElapsed(_cadRotTime,(3000 * _cadRotCount))) {
      noInterrupts();
      cadRotCount = 0;
      cadRotTime = 0;
      cadence = 0.0;
      interrupts();
    }
  }
  if (_cadRotCount >= numRotToMeasure) {
    noInterrupts();
    cadRotCount = 0;
    cadRotTime = 0;
    interrupts();
    uint32_t elapsedTime = (NOW - _cadRotTime);
    // cadence = rotations / time
    cadence = (((_cadRotCount * 10000) / elapsedTime) * 6);
  }
  if (checkElapsed(lastSpdRep,spdRepInterval)) {
    lastSpdRep = NOW;
    printSpeed();  
  }
}

void printSpeed(void) {
  if ((currentScreen == screenSpdDist) || (currentScreen == screenSpdCad)) {
    noInterrupts();
    uint16_t _odometer = odometer;
    interrupts();
    printSpeedSerial(speed,speedDec);
    printSpeedLCD(speed,speedDec);
    if (currentScreen == screenSpdDist) {
    tripLength = (_odometer * cirq) / 1000.0;
      if (tripLength > 1000) { // if trip more than 1km, display kilometers
        tripLength = tripLength / 1000.0;
        writeVarSerial(STR_SPD_ODO,tripLength,STR_SPD_KM);
        printLongVarLCD2(STR_SPD_ODO,tripLength,STR_SPD_KM);
      }
      else {  // if trip is less than 1km, display meters
        writeVarSerial(STR_SPD_ODO,tripLength,STR_SPD_M);
        printLongVarLCD2(STR_SPD_ODO,tripLength,STR_SPD_M);
      }
    }
    if (currentScreen == screenSpdCad) {
      printLongVarLCD2(STR_CAD_CAD,cadence,STR_CAD_RPM);
    }
  }
}

If anyone finds any issues or in-efficient code above, please let me know! :slight_smile:

..and the code is free-to-use&modify non-commercially as long as i get the credit :slight_smile:

For speed, if you limit to a uint8_t, the values range from 0 to 255. Working with a single decimal you can get values of 0.0 to 25.5. That's a bit low a speed for a bicycle. If you want higher values (you probably do), use a 16-bit integer.

Also it's best to work with at least one more decimal than you want for your final result, that increases the overall accuracy as there's less of a rounding error.

By the way, why are you using #define statements for what should be functions? Unless the compiler is super smart that's going to result in bloated code. It is also not exactly helping to improve readability and maintainability of your code.

wvmarle:
For speed, if you limit to a uint8_t, the values range from 0 to 255. Working with a single decimal you can get values of 0.0 to 25.5. That's a bit low a speed for a bicycle. If you want higher values (you probably do), use a 16-bit integer.

Im actually using a 32 bit unsigned integer as a intermediate variable for the speed calculation.(speedTemp)

wvmarle:
Also it's best to work with at least one more decimal than you want for your final result, that increases the overall accuracy as there's less of a rounding error.

Im actually using 3 "decimal values", two more than im using.

    uint32_t elapsedTime = 0;
    uint32_t distance = (cirq * _rotCount) * 100;  // this adds 2 unused  decimal values
    elapsedTime = (NOW - _rotTime);
    // speed = distance / time
    uint32_t speedTemp = (distance / elapsedTime) * 36; // this adds 1 decimal value since we want to multipy by 3.6 to translate into km/h
    speedTemp = speedTemp / numSpokeMagnets;
    speedTemp = speedTemp / 100;  // this removes the unused decimals
    speed = speedTemp / 10;         // this stores the km/h int value into speed (max 255km/h)
    speedDec = speedTemp % 10; // this stores the modulo, 1 decimal value.

wvmarle:
By the way, why are you using #define statements for what should be functions? Unless the compiler is super smart that's going to result in bloated code. It is also not exactly helping to improve readability and maintainability of your code.

I belive what youre referring to are the macros im using to simplify the code relating to serial & lcd prints.

#define doMacro(data1,data2) serial.print(data1); serial.print(data2)

in the example above, you´dd use it like a function, "doMacro("string ",123);" to print "string 123" to serial monitor.

I know, im having some issues with naming my macros, and thats partly due to the fact that im using alot of them, from a lot of different places in the code.
So even if their name doesnt immediately answer all specific questions like what line on the LCD is printed by what macro, it still makes a whole lot of sense to use macros instead of functions for these types of "problems".

Just consider that you´dd sometimes want to print a string, but sometimes want to print a value.
A classical function would need to be written twice, one to take strings as argument, the other to take values.
You can also interlink macros, to have one call the other, just like classical functions.

#define subtractAndPrint(data1,data2) doMacro("result: ",(data1-data2);

But the macro definition takes usually just one line of code (albeit a long one) while the corresponding function would take at least 3, if you format the function properly.
Also, since im using build flags, if i were to remove the "#define USE_SERIAL" from my main program, i can have a separate "flag switch" to remove any and all macros to prevent "Serial was not defined"-type compiling errors.

#define USE_SERIAL

#ifdef USE_SERIAL
#define serialPrint(value) Serial.print(value);
#else
#define serialPrint() ;
#endif

I would call the serialPrint() from loop, if i were to use serial, the macro would be "expanded"(by the compiler) to "Serial.print(value);" only if USE_SERIAL was defined.
If not, the compiler would "expand" the serialPrint() macro to ";".

If i were to do similar functionality with classical functions, i would have to use #ifdef / #endif around each optional piece of function declaration/definition as well as each code snippet that calls them.
This would mean at LEAST 2 extra lines of code on top of the minimum of 3 for a function.

Im not saying that my way is the best way, im just saying that its not that bad of a solution, as long as the names of the functions are somewhat self-explanatory and obvious. :slight_smile:

MarkT:
Floats are far slower than fixed-point.

Except for slightly inaccurate multiply and divide by powers of 10 for some odd reason.

If you want to use integers instead of floats then you likely want to use integers with more than 6 places.

You want to measure kilometers per hour to 2 places then use meters or mm for working units just so you can lose a few places in a division and still be more precise than your display needs to be.

Using an unsigned long you get 9 places that can be 0 to 9. Max value is 999,999,999mm, almost 1000Km range.

When scaling you need to be able to multiply before dividing to save losing digits, so you may need a lot of high range and might need to work to 10's or 100's of mm instead of down to 1's. Multiplies first and then divide.

speedTemp = (distance / elapsedTime) * 36;

More accurate would be 36 * distance / time.

Kind of important if you are close timing is that millis can be 1 off due to how they work.

If close timing matters, use micros().

Some time find yourself a gear tooth counter and see if it can read a wheel hub or spokes near that. You won't need magnets.

Last question is do you really need interrupts, especially when working with millis?

xarvox:
But the macro definition takes usually just one line of code (albeit a long one) while the corresponding function would take at least 3, if you format the function properly.

That's just because you put the commands of the macro on a single line - so no proper formatting.

Also the length of code in lines has little relation to the final size of the compiled code in bytes. There are often ways to decrease the compiled code by adding lines of C++ code - I've seen that happening often enough to my own code. Also a single line of code can compile to a single byte, or to a couple hundred bytes, depending on the line.

The number of lines of code has no importance. The size of your compiled code, on the other hand, is very important.

If you do have regularly spaced magnets then you could table times from one sense to the next as speed in flash and do little or no calculating for the tabled value or an interpolation between 2 tabled values.

PS at one time I laced my wheels with stainless steel spokes to be a safer when drafting trucks at over 60MPH. Regular wheels come apart somewhere over 70MPH or so I'd been told, I didn't want my day ruined by some minor ding!

Ive been doing some experimenting with the simulator, trying to clock the doSpeedCalc function and ive gathered some unexpected numbers..

Without floats; the speed calculation segment takes about 216 microseconds, but when using floats the time drops to about 64 or 68 microseconds..
So it seems its not that useful to eliminate float calculations..

code, including the clocking feature:

#define USE_FLOAT_MATH
#ifdef USE_FLOAT_MATH

float speed = 0.0;
float tripLength = 0.0;
float cadence = 0.0;

#define writeSpeedSerial() Serial.println(STR_BLANK); Serial.print(STR_SPD_SPEED); Serial.print(speed); Serial.println(STR_SPD_KMH);

#else // end USE_FLOAT_MATH

uint8_t numSpokeMagnets = 1; // used to increase the update frequency, 4x magnets reports a speed variation 4x faster.
uint8_t speed = 0;
uint8_t speedDec = 0;
uint16_t tripLength = 0;
uint8_t cadence = 0;

#define writeSpeedSerial() Serial.println(STR_BLANK); Serial.print(STR_SPD_SPEED); Serial.print(speed); Serial.print(STR_DOT); Serial.print(speedDec); Serial.println(STR_SPD_KMH);

#endif // end !USE_FLOAT_MATH

volatile uint16_t rotCount = 0;
volatile uint32_t rotTime = 0;
uint8_t numRotToMeasure = 3;
volatile uint32_t odometer = 0;
uint32_t lastSpdRep = 0;
uint32_t spdRepInterval = 1500;
uint32_t cirq = 2114; // mm
uint32_t NOW = 0;

uint32_t lastCalcNOW = 0;
uint32_t calcTime = 0;
volatile uint16_t cadRotCount = 0;
volatile uint32_t cadRotTime = 0;

#define SpdPin 2
#define CadPin 3

#ifdef USE_FLOAT_MATH
void doSpeedCalc(void) {
  noInterrupts();
  uint16_t _rotCount = rotCount;
  uint32_t _rotTime = rotTime;
  interrupts();
  if (_rotCount) {
    if (checkElapsed(_rotTime,(3000 * _rotCount))) {
      noInterrupts();
      rotCount = 0;
      rotTime = 0;
      speed = 0.0;
      interrupts();
    }
  }
  if (_rotCount >= numRotToMeasure) {
    lastCalcNOW = micros();
    noInterrupts();
    rotCount = 0;
    rotTime = 0;
    interrupts();
    uint32_t elapsedTime = 0;
    uint32_t distance = (cirq * (_rotCount));
    elapsedTime = (NOW - _rotTime);
    // speed = distance / time
    speed = (((float)distance / (float)elapsedTime) * 3.6);
    calcTime = (micros() - lastCalcNOW) ;
    writeVarSerial("calc ",calcTime," us");
  }
  /// cadence
  noInterrupts();
  uint16_t _cadRotCount = cadRotCount;
  uint32_t _cadRotTime = cadRotTime;
  interrupts();
  if (_cadRotCount) {
    if (checkElapsed(_cadRotTime,(3000 * _cadRotCount))) {
      noInterrupts();
      cadRotCount = 0;
      cadRotTime = 0;
      cadence = 0.0;
      interrupts();
    }
  }
  if (_cadRotCount >= numRotToMeasure) {
    noInterrupts();
    cadRotCount = 0;
    cadRotTime = 0;
    interrupts();
    uint32_t elapsedTime = (NOW - _cadRotTime);
    // cadence = rotations / time
    cadence = ((float(_cadRotCount) / float(elapsedTime)) * 60000);
  }
  if (checkElapsed(lastSpdRep,spdRepInterval)) {
    lastSpdRep = NOW;
    printSpeed();  
  }
}
#else // end USE_FLOAT_MATH
void doSpeedCalc(void) {
  // speed & distance
  noInterrupts();
  uint16_t _rotCount = rotCount;
  uint32_t _rotTime = rotTime;
  interrupts();
  if (_rotCount) {
    if (checkElapsed(_rotTime,(3000 * _rotCount))) {
      noInterrupts();
      rotCount = 0;
      rotTime = 0;
      speed = 0.0;
      interrupts();
    }
  }
  if (_rotCount >= numRotToMeasure) {
    lastCalcNOW = micros();
    noInterrupts();
    rotCount = 0;
    rotTime = 0;
    interrupts();
    uint32_t elapsedTime = 0;
    uint32_t distance = (cirq * _rotCount) * 100;
    elapsedTime = (NOW - _rotTime);
    // speed = distance / time
    uint32_t speedTemp = (distance / elapsedTime) * 36;
    speedTemp = speedTemp / numSpokeMagnets;
    speedTemp = speedTemp / 100;
    speed = speedTemp / 10;
    speedDec = speedTemp % 10;
    calcTime = (micros() - lastCalcNOW) ;
    writeVarSerial("calc ",calcTime," us");
  }
  /// cadence
  noInterrupts();
  uint16_t _cadRotCount = cadRotCount;
  uint32_t _cadRotTime = cadRotTime;
  interrupts();
  if (_cadRotCount) {
    if (checkElapsed(_cadRotTime,(3000 * _cadRotCount))) {
      noInterrupts();
      cadRotCount = 0;
      cadRotTime = 0;
      cadence = 0.0;
      interrupts();
    }
  }
  if (_cadRotCount >= numRotToMeasure) {
    noInterrupts();
    cadRotCount = 0;
    cadRotTime = 0;
    interrupts();
    uint32_t elapsedTime = (NOW - _cadRotTime);
    // cadence = rotations / time
    cadence = (((_cadRotCount * 10000) / elapsedTime) * 6);
  }
  if (checkElapsed(lastSpdRep,spdRepInterval)) {
    lastSpdRep = NOW;
    printSpeed();  
  }
}
#endif // end !USE_FLOAT_MATH

GoForSmoke: i will go over my code and modify the divisions according to your suggestions, it might be the solution for the precision issue im having (differing 0.1km/h or so from expected).
Regarding precision, i need about 2km/h precision; 25km/h is allowed to be between 23km/h and 25km/h, since the only practical reason for measuring the speed is to cut off the e-bike throttle at 25km/h.

But a mechanical coupling is very out of the question, a wheel roller costs energy regardless of how its implemented, and i am a VERY lazy bicyclist. :slight_smile:
Also, a hall sensor/trigger is used in pretty much all bicycle speedometers, witch gives me a pretty strong hint that its the proper way to go.

The interrupts are there so i dont have to keep measuring the sensor pins in my program.
Theyre probably not strictly necessary, but they dont hurt ether (i think).

Regarding that idea of a table instead of calculations, it might be a good idea if the project were ONLY to handle the speed calculation, but its not.
This is just a fraction of the whole project, it will be controlling all vehicle lights, buttons and horns, as well as the e-bike controller.
So im already using the flash memory for storing variables and im working on wear leveling, to shift the stored variables for each write to EEPROM.

There are probably better ways to solve this speed measurement issue but all things considered, i think i may have something thats pretty close to allright. :slight_smile:

By the way; if you really want strong and durable wheels, you might want to "tie" the spokes together.
According to some bike nerds i know, you can use fishing line, spool it around the intersection of two spokes then secure it with superglue.
The more common approach is by using steel wire and solder.

I havent done it myself, i rarely go over 30km/h and never beyond 60km/h, even in downhills, nor do i load my bike with anything more than my own soggy bottom and possibly up to 25-30kg of groceries (all in all, well below 120kg).

wvmarle:
That's just because you put the commands of the macro on a single line - so no proper formatting.

Also the length of code in lines has little relation to the final size of the compiled code in bytes. There are often ways to decrease the compiled code by adding lines of C++ code - I've seen that happening often enough to my own code. Also a single line of code can compile to a single byte, or to a couple hundred bytes, depending on the line.

The number of lines of code has no importance. The size of your compiled code, on the other hand, is very important.

You were arguing human-readability..
And it was in that regard that i wrote the reply.
The compiled code length does not differ by using macros compared to inserting the macroed functions wherever you use them, its just to keep the lines of code down to a minimum.
And human-readability is very much affected by code length..

And you are wrong, a macro should be kept to one line, otherwise you´dd have to use curly brackets to include multiple lines, witch in turn creates a bunch of other problems.

So please stop twisting things just to be "right", if you cant add something constructive to the discussion, then perhaps your energy is better spent elsewhere. :slight_smile:

Gear tooth counters do NOT touch the gear teeth. The movement of the metal tooth in the counter's magnetic field changes the field as the field induces charge in the tooth and a Hall Sensor detects that. A cheap commercial GTC may run about $20 or less.

The more you have being calculated the more cycles you want to save. Table what you got room for, when you compile you know how much you used.

Are you going to switch timing to micros()?

How many magnets are you using? --- feeds into time between reads and practical ways to improve accuracy.

I tightened my spokes with a spoke wrench and kept my wheels true. A trued wheel is stronger. Why in H would I do something so stupid as gluing spokes together? It won't make the wheel stronger unless that wheel is a mess to begin with. Ask a competent mechanical engineer some time.

When I last rode like that, it was in the 80's before I turned 30 (also in the 80's).

xarvox:
Ive been doing some experimenting with the simulator, trying to clock the doSpeedCalc function and ive gathered some unexpected numbers..

Without floats; the speed calculation segment takes about 216 microseconds, but when using floats the time drops to about 64 or 68 microseconds..
So it seems its not that useful to eliminate float calculations..

That's not what these results tell you.

What it does tell you: don't trust simulators to be an accurate representation of the reality. Always do your testing on the real hardware.

GoForSmoke:
Gear tooth counters do NOT touch the gear teeth. The movement of the metal tooth in the counter's magnetic field changes the field as the field induces charge in the tooth and a Hall Sensor detects that. A cheap commercial GTC may run about $20 or less.

a hall sensor costs a dime and my custom PCB´s are already designed with hall sensors in mind.
Also, i am planning on using my old bike computer sensors in case im not happy with the hall triggers ive already purchased.

I was initially concidering using the brake disks as ABS encoder rings, reading the gauss fields leaking thru the cooling holes of the brake disks, but i eventually ditched that idea since it would require me to have a very specific brake disk design, i couldnt mount other brands since the holes would not be in the same locations.

GoForSmoke:
The more you have being calculated the more cycles you want to save. Table what you got room for, when you compile you know how much you used.

Are you going to switch timing to micros()?

That is true indeed.
However, this table solution requires a single, hard-encoded wheel size, witch wouldnt be accurate if i change tires to another dimension (width).
I do have more than one bike and i aim to use this project on them all, so a easily configurable wheel size seems like the better option.

As far as i understand things, 33 rotations per second on the measured wheel (standard 28") will measure 251km/h, giving a average read time of 30,30303 ms per rotation, or 90,9 ms for a set of rotations (calculating average over 3 rotations).
At 10 rotations per second (76km/h), the average read time would be 100ms or 300ms for that set of rotations.

uint32_t speedTemp = ((36 * distance) / elapsedTime);

The code would then be read as (36 * ((2114*3)*100)) / 300, or 22831200 / 300 = 76104.
if we were to add two milliseconds, just to calculate the error difference, our numbers would look like this:
22831200/302 = 75600
22831200/298 = 76614,7651006711
So: 76104 - 75600 = 504
and 76104 - 76614 = -510.
since we´re dividing by 1000 to get the km/h, adding those error differences gives us a maximum error of 1.1km/h, given a millis accuracy of +/-2ms

I grant you that there is room for improvements there, but at the same time, a 1km/h accuracy is not a dealbreaker for me.

GoForSmoke:
How many magnets are you using? --- feeds into time between reads and practical ways to improve accuracy.

Im currently not using any magnets, but the plan is to use 1. I might eventually increase that number to 4 but thats ways down the road, nothing that´ll happen in the near future..

GoForSmoke:
I tightened my spokes with a spoke wrench and kept my wheels true. A trued wheel is stronger. Why in H would I do something so stupid as gluing spokes together? It won't make the wheel stronger unless that wheel is a mess to begin with. Ask a competent mechanical engineer some time.

When I last rode like that, it was in the 80's before I turned 30 (also in the 80's).

A shorter spoke is stronger because of physics. :slight_smile:
And by tying them together, you are effectively using spokes half their actual length.

You would of course have to have the wheel true and strong before you tie the spokes, but according to those who tried, the result is quite dramatic.
As i said earlier, i have never tried it, but if i were to load my wheels with greater forces than they are designed for (more weight and/or higher speed), i would try it.
But i would also spend some time balancing the wheel afterwards, if the goal was high top speed.

Wvmarle: I do understand that a simulator is not a exact representation of reality and IF i were to need exact numbers, i would have used real hardware.
But a simulator is supposed to proximate reality, a result in the sim gives a hint that you would get similar results on real hardware.
If this were not the case, why would anyone use a sim in the first place?
..Quite frankly, it feels like youre just moodily arguing, perhaps because you cant handle being corrected?

I dont have this project running on a actual hardware yet, simply because im not there yet. In other words, i dont have a function generator to simulate wheel rotations in real life, nor do i have the patience to build a circuit merely to measure something that i really dont NEED to know exactly at the moment; i only need a hint.
Once im ready and running on my project hardware, i will be doing the same comparison as i did in the simulator, as well as checking alot of other metrics from the system, but at this very moment, the simulator says floats are 3 times faster.
If you really want to prove me wrong, rig up your own experiment and present some numbers, a mere bickering attitude isnt really constructive...

No exact numbers, just experience, and that's telling enough.

First of all, adding a floating point calculation to a program increases code size by some 500-1000 bytes. That's also giving an indication of the size of the code paths taken to do such calculations, as the Arduino's ATmega processor can only do integer math so the float math has to be done in integer, then converted back to floating point values. In contrast, an integer calculation usually adds just a few bytes.

When working with a stepper I could get no more than about 10-20 thousand steps a second when there was a floating point calculation in the mix when calculating the acceleration and timing of the next step, not enough for the application. That increased to some 100,000 steps per second (ATtiny84a at 8 MHz clock speed) when it was all done with integer math. Major improvement.

In the second case the processor may have spent 1-2% of the time doing the calculations; most of the processor cycles are timer interrupt overhead. I was pushing the limits to the extent that I couldn't use the digitalWrite() and digitalRead() calls either, too slow.

By extension this means that the floating point calculations took about 80% of processor time, while performing only a fraction of the number of calculations.

xarvox:
a hall sensor costs a dime and my custom PCB´s are already designed with hall sensors in mind.

And a gear tooth counter is more than a Hall sensor. For one you don't need a magnet out on a spoke adding to wheel mass needing to be spun up. And BTW, 1 sense per wheel rev is especially a problem at lower speeds.

I was initially concidering using the brake disks as ABS encoder rings, reading the gauss fields leaking thru the cooling holes of the brake disks, but i eventually ditched that idea since it would require me to have a very specific brake disk design, i couldnt mount other brands since the holes would not be in the same locations.

A tooth counter can pick up bolt heads, etc, as well as gear teeth.

As far as i understand things, 33 rotations per second on the measured wheel (standard 28") will measure 251km/h, giving a average read time of 30,30303 ms per rotation, or 90,9 ms for a set of rotations (calculating average over 3 rotations).......................

Please show your math, I get different answers. If the outside dia of the wheel is 28" I get just over 8KPH at 1 RPM.

28" dia, 14" radius = .3556m radius * 2 * 3.1416 = 2.234m circumference * 3.6 = 8.0435KPH for 1 RPM.

A shorter spoke is stronger because of physics. :slight_smile:
And by tying them together, you are effectively using spokes half their actual length.

That's loose rhetorical BS. Each spoke holds tension between the hub and the rim and should be straight. Tie them in the middle and the tension is still the same. Again, talk to a competent mechanical engineer.

You would of course have to have the wheel true and strong before you tie the spokes, but according to those who tried, the result is quite dramatic.

How do they know? Did they run any real tests that don't amount to anecdotes and confirmation bias self congrats?

Are you in school?

I would use a lightweight disk roller with sealed bearings held against the back wheel behind the frame. The drag would be very very small. The roller would move at road speed and have a known diameter so would work on any wheel. The circumference being small you'd get speed without having to go some 2.234m for an update.

I would do that because I know the drag would be negligible. I still have some old toy cars including redline Hot Wheels so yeah I have some idea of what really is and the words "there must be drag" don't stop me from asking "yeah, so HOW MUCH?" since I have experience here and don't spook on empty objections.

If you want to cut way more drag than that disk makes, wear silk riding clothes instead of cotton. Don't count pennies when there's dollars being wasted.

GoForSmoke:
28" dia, 14" radius = .3556m radius * 2 * 3.1416 = 2.234m circumference * 3.6 = 8.0435KPH for 1 RPM.

To translate your math at 1 rpm: 8.0435km/h / 60 rotations(minutes) per hour = 0,1340583333 km or 134,058 metres per rotation.
That would give a wheel diameter of roughly 42 metres..
You sure you did that math yourself?

My calculation looks like this: (using spreadsheet to be able to quickly edit values)
meters per minute = (circumference in millimetres * (rotations per second * 60 ) ) /1000
( (2114 * (160)) / 1000 = (211460) /1000 = 126840 / 1000 = 126,840 m/min )
km/h = (metres per minute * 60) / 1000
( (126,840 * 60) / 1000 = 7610,4 / 1000 = 7.6104km/h )
Do note; im using 1 rotation per second, 1Hz, or 60RPM for my calculation above.

a 28" wheel is not 28 inches in diameter, it depends on what tire you use and so on..
I measured my wheel by rolling it exactly one revolution and measured the distance with a measuring tape.
By doing that i also included the tire pressure in the "equation", since that affects the diameter and therefore rolling distance per rotation.

I guess that you do have a point, that a roller doesnt really make that much of a drag, but if it were really so unnoticeable, why is it practically impossible to purchase a roller speedometer for any vehicle today?
almost all modern cars and most motorcycles are using feedback from the ABS system, bicycles are using hall sensors and magnets, at least since the late 80´s.
In other words, practically every single vehicle with a speedometer that you see on the streets today are using contactless speed detection, many (if not most) of them are using magnets and hall sensors.

I did have a roller speedometer (a completely analog thing, without any batteries) in my youth, but i wouldnt even consider mounting that on a bike today, unless i were to install a stepper motor in the gauge housing and control it from the arduino, just for the retro looks.. :slight_smile:
And the drag from that speedometer was anything but negligible...

Anyhow and at any rate, regardless of what actual drag metrics you can provide for that wheel roller, there is still drag and you havent yet shown any reason why that drag would be better than a hall sensor.

And regarding that tied spoke idea; just drop it and forgive me for bringing it up.
By the way, the only straight spoke is in a radially spoked wheel...
In the normal 3crossed or 4crossed wheel, the spokes are pressed against each other, slightly bending around each other.
But maybe thats "loose rethorical BS" too?
And even the tightest spokes move as the wheel rotates, often rubbing or vibrating against other spokes, so by tying them together, you limit that movement.
In essence; they act as they are shorter.
But still, its just physics, not your subjective (loose rethorical BS) Belief System.

and finally, no im not in school, i finished it almost a quarter of a century ago.. Did you finish yours yet?

wvmarle:
No exact numbers, just experience, and that's telling enough.

First of all, adding a floating point calculation to a program increases code size by some 500-1000 bytes.
...

I checked the arduino IDE output, when NOT using floats, the result is 4050 bytes of program memory, 277 bytes of dynamic memory.
That would be 13% of both types of available memory.

WITH floats, the numbers are 5076 bytes of program memory, 294 bytes of dynamic memory.
And that would be 16% program storage space and 14% of the dynamic memory.

So you are indeed correct, it seems to add about 1000 bytes of progmem and 20 bytes of dynamic mem with floats.

Its not just about the compiled program size tho, i think its more important to keep as low cycle time as possible, witch brings us back to that previous post about the measurements i got from the simulator.

Im not saying that the simulator is telling the truth, the whole truth and nothing but the truth, but i would honestly be quite surprised if the numbers would flip on a real arduino.
.. i would also be quite upset with the simulator.. :wink:

Im not too worried about the speedometer itself tho, i just wanted to try to streamline the program (im learning as im developing it).
Im actually more worried about all of the I2C devices im using, since each of the devices needs some data to/from the arduino at a pretty regular intervals (every 30-40ms) and im guessing that each of these reads/writes takes up more time than that speed calculation does.
That speed calculation will only happen twice every second tops (3rotations/ calculation, two calculations per second equals about 45km/h).

I have both versions in my program and they will be there until im running my code on my hardware, so i can compare the actual time values for them both.
Its as simple as uncomment a initial #define statement and i can live with the extra 60 lines of code in my project file.

In the end, im not doing this project to be done with it and have a working product, im using it as a reason and a tool to learn programming. :slight_smile:

And last, but definitely not least, im sorry for my earlier tone wvmarle, i guess you wouldnt be typing here if you werent trying to help. :slight_smile:

xarvox:
To translate your math at 1 rpm: 8.0435km/h / 60 rotations(minutes) per hour = 0,1340583333 km or 134,058 metres per rotation.
That would give a wheel diameter of roughly 42 metres..
You sure you did that math yourself?

Certainly though I used

1 rev per second in meters times 3600 seconds per hour divided by 1000 to convert meters to kilometers.

I started with 2.234 meters per rev. Wanna see it again?

.3556m radius * 2 * 3.1416 = 2.234m circumference * 3.6 = 8.0435KPH for 1 RPS.

And you turn km/hr into km/min because somehow what was calculated as 3600 revolutions per hour should be evaluated as 60 revolutions in the same time. So no frikkin DUH the answer is going to be garbage!

That would give a wheel diameter of roughly 42 metres..
You sure you did that math yourself?

You did that math, not me.

Please, what margin of error does my 14" radius/2.234m per rev give?

I did finish HS, class of 75 back when school budgets were not sucker prizes.
I do admit to only getting a B in calculus that year but I aced physics, chemistry and biology.
At the same time I got a polytech certificate in drafting and design and later I worked in precision metal fabrication.

I guess that you do have a point, that a roller doesnt really make that much of a drag, but if it were really so unnoticeable, why is it practically impossible to purchase a roller speedometer for any vehicle today?

Perhaps for specific vehicles they don't need a one size fits all disk when they already have so much else?

Not much of a drag? In practical terms it could be none.

me...

28" dia, 14" radius = .3556m radius * 2 * 3.1416 = 2.234m circumference * 3.6 = 8.0435KPH for 1 RPM.

And there's my error, labeling rev per second as RPM. Nope, it's really RPS!

3600 revs * 2.234m = 8.0424km ------ the above had more trailing digits I rounded off while typing.