Trouble with floats or something else Idk

I've written an odometer function that increments with millis and a speed value derived from can messages. This works great till the ODO hits a value of 262144, then it stops counting up. As seen below I used float for my variable type as this seemed appropriate for what I needed. I've tried a few different variable types as a test but this is the only one that does the calculations correctly besides the stopping at 262144 thing. Don't mind my horrible code I'm not good at this!!

I had to multiply this decimal because division didn't return a value. I can watch this odomath function keep working in the serial monitor as well as everything else.

float odoMath = TPS * .0001492; //conversion factor for 500 milli sampling

I have tried using ++odo for testing and that goes above the stopping point just fine,
Does anyone have some insight on this issue?

#include <SwitecX12.h>
// CAN receiving
#include <arduino.h>
#include <mcp2515.h>
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 32 // OLED display height, in pixels

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
// The pins for I2C are defined by the Wire-library. 
// On an arduino UNO:       A4(SDA), A5(SCL)
// On an arduino MEGA 2560: 20(SDA), 21(SCL)
// On an arduino LEONARDO:   2(SDA),  3(SCL), ...
#define OLED_RESET     4 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

struct can_frame canMsg;
MCP2515 mcp2515(10); // SPI CS Pin 10

// megasquirt variables
int RPM, TPS, CLT, VSS1;

// can variables
int updatePeriod=20; // update period for stepper update
unsigned long motTime=0; //motorupdate period
unsigned long previousMillis=0; // used by can
int delayPeriod=1000; //used by can
unsigned long odoMillis=0; // used by ODO function
unsigned long odoprev=0; // used by ODO function
int odoDelay=500; // used by ODO function
float ODO = 262142; // used by ODO function
float TPS2 = TPS; //probably not needed



const int STEPS = 315 * 12;
const int A_STEP = 5;
const int A_DIR = 6;
const int RESET = 7;

SwitecX12 motor1(STEPS, A_STEP, A_DIR);


void setup() {
  digitalWrite(RESET, HIGH);
  Serial.begin(115200);
  motor1.zero();  //Switec
  motor1.setPosition(STEPS/2); //Switec
  mcp2515.reset();
  mcp2515.setBitrate(CAN_500KBPS, MCP_8MHZ);
  mcp2515.setNormalMode();
   // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); // Don't proceed, loop forever
  }
}

void loop() {
if (mcp2515.readMessage(&canMsg) == MCP2515::ERROR_OK) {
switch (canMsg.can_id) {
case 1512: // dash broadcasting group
RPM = (float)(word(canMsg.data[2], canMsg.data[3]));
CLT = (float)(word(canMsg.data[4], canMsg.data[5])); CLT = (CLT / 10);
TPS = (float)(word(canMsg.data[6], canMsg.data[7])); TPS = (TPS / 10);
break;
case 1516: // dash broadcasting group added 3/7/22
VSS1 = (float)(word(canMsg.data[0], canMsg.data[33])); VSS1 = (VSS1 / 10);
break;
}
previousMillis=0; // reset no data timer
}
else { // no CAN bus data coming in
unsigned long currentMillis = millis();
if(previousMillis == 0){
previousMillis = currentMillis; // entered no data timer
}
else if(currentMillis - previousMillis > delayPeriod) { // no data timer expired
previousMillis = currentMillis;
RPM = -999;
CLT = -999;
TPS = -999;
}
}
unsigned long updateMillis = millis(); //motor step conversion update time
  int sv= TPS * 22.2; // motor steps conversion, Using tps as a stand in for VSS1
  static bool forward = true;
    if (motTime - updateMillis > updatePeriod){
    motor1.setPosition (sv);//motor position
     motor1.update();
  motTime = updateMillis;
  }
   odoMillis = millis(); //setting odoMillis to current time
  if (odoMillis - odoprev > odoDelay)
{
  
  float odoMath = TPS * .0001492; //conversion factor for 500 milli sampling
  Serial.print ("ODO");
   Serial.println (ODO);
  Serial.println (TPS);
  Serial.print ("odoMath ");
  Serial.println (odoMath, 4);
   odoprev = odoMillis;   
   ODO = odoMath+ODO; //adding the concurrent odometer increments
  display.setRotation(2); //upside down mounting
  display.clearDisplay(); //clear display before new values
  display.setTextSize(2); // Normal 1:1 pixel scale
  display.setTextColor(SSD1306_WHITE);   // Draw white text
  display.setCursor(10,1); // Start at top-left corner
  display.print(ODO,1); //ODO is required TPS for testing
 // display.setTextSize(1);
  //display.print(" Miles");
  display.display();}
 
 
 


}

With a Arduino Uno ?

Have you tried pressing Ctrl+T in the Arduino IDE ?

sorry I guess I left out some info, arduino nano clone using vscode for the ide

int sv = TPS * 22.2;

Opened it in arduino ide. Control+t used to auto format, upload and still the same.. straitened it up a bit though.

Calculation for steps to achieve the degrees I needed. I'm using tps as a stand in on the bench as the vehicle is not present.

Can you post the output from the serial monitor from around the time the problem occurs? May be a combination of the mixture of integer and floats in the calculations, and the fact that a float only has 6 to 7 significant digits. What is the value of odoMath when ODO stops incrementing?

odomath carries on as usual, "tps" is actuated by a pot on an input board to the pcm. the values still change with the pot.

You have hit upon one of the limitations of Arduino floating-point math. When you try adding a very small number (such as 0.0148) to a large number (such as 262144), the large number might be so large that the machine doesn’t “see” the small number, and you get exactly the problem you are describing.

Sure. ++ODO adds 1 to ODO. The number 1 is big enough that even when ODO is at 262144 it can still “see” it, but the decimal 0.0148 is too small for ODO to “see” then.

My solution would be simply not to allow ODO to get too big. I would do something like this:

int ODO_thou = 262; // thousands
float ODO = 144.0; // below 1000

// do ODO math
ODO = odoMath + ODO;

// check for thousand
if (ODO >= 1000.0) {
  // make ODO roll over at 1000
  ODO = ODO - 1000;
  ODO_thou = ODO_thou + 1;
}

Serial.print(“ODO”);
// print ODO thousands
Serial.print(ODO_thou);
// print a zero in front of numbers below 100
if (ODO < 100) Serial.print(“0”);
// print another zero in front of numbers below 10
if (ODO < 10) Serial.print(“0”);
// print ODO low numbers and decimals
Serial.println(ODO);

I hope that this makes sense to you.

1 Like

You can also make ODO a 32 bit unsigned int and let the float ooverflow into that int if it is above 1.

In Arduino UNO/NANO/MEGA, "32-bit unsigned" has the following data type:

unsigned long int
or
unsigned long

Precisesly (this is always true of floating point representations, nothing Arduino-specific :slight_smile:
262144 is 2^18, once the float reaches this value its resolution becomes 1/64 which is larger than 0.0148. Just before that the resolution would be 1/128, which is smaller than 0.0148.

This sort of accumulation of many small values to a large total is always bad news for floats, do the accumulation exactly with integers, then scale to float. So code like this:

unsigned long total = 0 ;

void loop ()
{
  if (whatever)
  {
    total += 1 ;
    float odo = 0.0148 * total ;
    ....
  }
  ...
}

I prefer uint32_t as a type. But you need to #include <stdint.h> for that.
I think that long long and int are not the best practice. Depending on the CPU (and compiler settings?) the size of int and long long may vary. This causes portability problems. Also, using uint8_t makes me aware that my integer needs to be very small. But often that is not at all a problem. You do not expect more days in a month than 255... ... and it saves half the memory compared to int. And it will be faster as well...

In embedded system using ATmega32P MCU of the UNO Board, the program goes into flash as a firmware. Therefore, I am not much worry about the portability of the codes. For the said MCU, the int-type is 2-byte and most of the the users of the UNO Board are very much engaged in typing "unsigned int" instead of "uint16_t". :slight_smile:

Not in the IDE, it's included by default.

And if you move to ESP32?
Or, potentially more troublesome, copy a function from ESP32 to Uno...

Ok great... C++ in xcode always needs this include and that is where I often develop new functions (no upload of sketch needed for testing).

What is so bad or difficult about including a standard library header, anyway? It's a common practice.

It is not difficult nor bad. But for newbies it might cause frustration if the compiler throws errors that the newbie does not understand.... ...it might scare them away...
The xcode compiler often says : "cannot find this or that, did you mean to include ..." So that is a clear hint. But not all compilermessages are that comprehensive". I guess you know that from experience (one missing } may cause multiple errors, and somewhere in that big list a hint of not matching { and }...
Anyway, you say it is in IDE, so it is not a problem at all...

In this situation where re-use of codes are desired, it is required to have portability of codes and int16_t style is to be adopted to ensure16-bit in both platforms.