Using Floating-Point number

I'm using an ssd1306 OLED Display, connected up to a Mega2560,

http://docs-europe.electrocomponents.com/webdocs/008d/0900766b8008d78a.pdf

Fritzing, the encoder used in this drwing for demonstration :slight_smile:

Currently this is the full code I'm using.
If I move the encoder CW "encoderPos" will display from 0 and counts up, much the same if I start from 0 and move the encoder CCW it will count up but with the - sign next to the number.

If I change "int encoderPos = 0;" to "float encoderPos = 0;"
encoderPos is displayed as 0.0000000000, when I move the encoder CW the number will change from, 0.0000000000 to 1.0000000000 to 2.0000000000 to 3.0000000000 and so on, much the same as if I turn the encoder CCW from 0.0000000000. The number will change from down -0.0000000000 to -1.0000000000 to -2.0000000000 to -3.0000000000 and so on.

How can I display the number like this, starts from 0.000, then moves up like this CW from the encoder to 0.001 to 0.002 ro 0.003 and so on, also the same CWW from 0.000 to -0.001 to 0.002 to 0.003 and so on. Also I would like to scale the counts from the encoder too, I really want to learn how to address the above as I'm very new to this.

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define OLED_DC 48
#define OLED_CS 52
#define OLED_CLK 46
#define OLED_MOSI 44
#define OLED_RESET 50
Adafruit_SSD1306 display(OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS);
#if (SSD1306_LCDHEIGHT != 32)
#endif
enum PinAssignments {
  encoderPinA = 20,
  encoderPinB = 21,
  zeroButton = 42
};
int encoderPos = 0;
int lastReportedPos = 0;
boolean A_set = false;
boolean B_set = false;
void setup() {
  pinMode(encoderPinA, INPUT); 
  pinMode(encoderPinB, INPUT); 
  pinMode(zeroButton, INPUT);
  digitalWrite(encoderPinA, HIGH);
  digitalWrite(encoderPinB, HIGH);
  digitalWrite(zeroButton, HIGH);
  attachInterrupt(2, doEncoderA, CHANGE);
  attachInterrupt(3, doEncoderB, CHANGE);
  display.begin(SSD1306_SWITCHCAPVCC);
  display.display();
  delay(1000);
}
void loop(){ 
  if (lastReportedPos != encoderPos) {
    lastReportedPos = encoderPos;
  }
  if (digitalRead(zeroButton) == LOW)  {
    encoderPos = 0;
  }
  display.display();
  display.clearDisplay();
  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.setCursor(0,0);
  display.print("Linear");
  display.setCursor(0,16);
  display.print("Position");
  display.setCursor(0,40);
  display.setTextSize(3);
  display.print(encoderPos, DEC);
  display.setTextSize(2);
  display.setCursor(0,24);
  display.print("__________");
  display.setTextSize(2);
  display.setCursor(96,47);
  display.print("mm");
  display.setTextSize(2);
  display.setCursor(0,50);
  display.print("__________");
}
void doEncoderA(){
  A_set = digitalRead(encoderPinA) == HIGH;
  encoderPos += (A_set != B_set) ? +1 : -1;
}
void doEncoderB(){
  B_set = digitalRead(encoderPinB) == HIGH;
  encoderPos += (A_set == B_set) ? +1 : -1;
}

How can I display the number like this, starts from 0.000, then moves up like this CW from the encoder to 0.001 to 0.002 ro 0.003

Well, you could divide your numbers by 1000 before you display them, but I wouldn't recommend it.

If you can only ever do discrete steps, why would you want to store the steps as a floating point value?
You may be confusing how the value is stored with the myriad ways it could be displayed.

If you can only ever do discrete steps, why would you want to store the steps as a floating point value?
You may be confusing how the value is stored with the myriad ways it could be displayed.

I am confused,
What are discrete steps, I don't know why, I didn't write it. Most of this code is cut and pasted from the Arduino website to to make it all work.
Still learning the basics... I don't think I have totally grasped the concepts of how the very first parts of code work with the rest of the programme. I see the initial setups before void setup() so void setup() know's what to assign pins as INPUTS and OUTPUTS and then if needed to set, HIGH-LOW, True-False, 1-0, (they all mean the same thing to me). I get very lost as to why very basic things need to be stored. I just want to move the decimal point to change the number from 0 to 0.000 It all seems over complicated, or I'm misinterpreting the basics. Help me see the light! :slight_smile:

Well, you could divide your numbers by 1000 before you display them, but I wouldn't recommend it.

I would see this as, but why would you not recommend it?

encoderPos = encoderPos / 1000;

By discrete, I mean that one "click" of your encoder represents an indivisible increment or decrement - there is no such thing as 0.35 of a "click".
So, your accumulator that counts up or down should be represented by an integer datatype.
It probably needs to retain the sign, so you should have a signed type.
An eight bit ("char") value would be able to record 256 distinct values, from -128 to +127 inclusive.
A sixteen bit ("int") value would be able to record 65536 distinct values from -32 768 to +32 767.
If this isn't enough, use a 32 bit ("long") value, where the range is -2 147 483 648 to + 2 147 483 647.

If your "click" in reality represents a movement of 0.01mm (say), then your accumulator simply records increments of 1/100th of a mm, so +/- 1 count is +/1 0.01mm, but you store the value as an integer.
How you present the value to the user is a different matter.
The same goes if "click" represents 0.01Hz.

Floating point types are not suitable, because you may find that adding small values to very large values does not give you the result you expect.

Thanks, that was a bit of a penny drop moment.

I understand the "click" for each time the A and B pins output and know they need to be counted and stored so the "encoderPos" show's CW and CCW moment as the A and B out's are 90' apart.
"int" is fine... But if move the decimal point like this to 0.000 and int max value i.e is +32767, would it read 3.2767 and start at -3.2767 if encoderPos went over 3.2767.

It probably needs to retain the sign

as in,

sign int encoderPos = 0;

f your "click" in reality represents a movement of 0.01mm (say), then your accumulator simply records increments of 1/100th of a mm, so +/- 1 count is +/1 0.01mm, but you store the value as an integer.
How you present the value to the user is a different matter.
The same goes if "click" represents 0.01Hz.

I did try this but the value stays at 0 regardless encoder movement.

void doEncoderA(){
  A_set = digitalRead(encoderPinA) == HIGH;
  encoderPos += (A_set != B_set) ? +0.1 : -0.1;
}
void doEncoderB(){
  B_set = digitalRead(encoderPinB) == HIGH;
  encoderPos += (A_set == B_set) ? +0.1 : -0.1;
}

Thank you for explaining all this!

encoderPos += (A_set != B_set) ? +0.1 : -0.1;

If "encoderPos" is an integer datatype ("char", "int", "long") then you just wrote the same as encoderPos += (A_set != B_set) ? +0 : -0;

But if move the decimal point like this to 0.000 and int max value i.e is +32767, would it read 3.2767

No, it would read 32.767. That's arithmetic.

encoderPos += (A_set != B_set) ? +0 : -0;

I see why this wouldn't work as no values are created to count?

So how can I achieve a count like this, 0.001 to 0.002 to 0.003 and so on. There must be an easy way?

TronSR:
If I change "int encoderPos = 0;" to "float encoderPos = 0;"
encoderPos is displayed as 0.0000000000, when I move the encoder CW the number will change from, 0.0000000000 to 1.0000000000 to 2.0000000000 to 3.0000000000 and so on, much the same as if I turn the encoder CCW from 0.0000000000. The number will change from down -0.0000000000 to -1.0000000000 to -2.0000000000 to -3.0000000000 and so on.

How can I display the number like this, starts from 0.000, then moves up like this CW from the encoder to 0.001 to 0.002 or 0.003 and so on, also the same CWW from 0.000 to -0.001 to 0.002 to 0.003 and so on. Also I would like to scale the counts from the encoder too, I really want to learn how to address the above as I'm very new to this.

The best way (IMHO) to display information the way you want it is to use sprintf and standard printf formatting characters.

That is, to do the "0.000" type of display, you would do something like this:

        float value; // temp variable
        const char *mask = "Encoder output is: %5.3f\r\n";
        char buffer[32]; // must be large enough for the string
        value = (encoderPos / 1000.0); // convert 1.000 to 0.001
        sprintf(buffer, mask, value); // send formatted string to buffer
        display.print(buffer); // display results

See? The "%5.3f" means "floating point number with 3 digits after the decimal point and at least 5 characters long".

By simply changing the formatting character, you can output your data in any format.

Only problem is, with floating point numbers (only), the Arduino C library is setup to not support floating point numbers. The reason is to save space since floating point can make your sketch about 1.5K larger. But you can install floating point support by "ripping out" the crippled code and replacing it with the floating point version.

Here is a script that will automatically fix your libraries and make a backup of the original:

#!/bin/bash

################################################################################
# fixfp - script to install floating point support for printf and
#         sscanf into the Arduino / gcc-avr "libc.a" library.
#
# last update: 12 Dec 2012
#
# For more information, see this post:
# http://arduino.cc/forum/index.php/topic,124809.msg938573.html#msg938573
################################################################################

STATUS=0

## Exit if libc.a isn't here
test -e libc.a
if [ ${?} -ne 0 ]; then {
        echo "File 'libc.a' not found - exiting"
        exit 0
} fi

test -e libc.a.orig
let STATUS+=${?}

test -e vfprintf_flt.o
let STATUS+=${?}

test -e vfscanf_flt.o
let STATUS+=${?}

if [ $STATUS -eq 0 ]; then {
        echo "Floating point patch already performed - exiting"
        exit 0
} else {
        cp libc.a libc.a.orig
        ar -dv libc.a vfprintf_std.o
        ar -dv libc.a vfscanf_std.o
        ar -xv libprintf_flt.a vfprintf_flt.o
        ar -xv libscanf_flt.a vfscanf_flt.o
        ar -rv libc.a vfprintf_flt.o
        ar -rv libc.a vfscanf_flt.o
        echo "Floating point patch installed."
} fi

Just find where your "libc.a" files are located (usually ./arduino-1.0.3//hardware/tools/avr/lib/avr/lib/) and then run the script.

If you use Windows, then it's easier to use the attached ZIP file to overwrite the libraries. The ZIP file will overwrite your libc.a files with the "patched" versions and save a copy of your originals. You have to find the directory that your libc.a files are in, then unzip the file there (it will patch all of them).

NOTE: This change will enable full floating point support for sprintf and sscanf, but it will make your compiled sketches about 1.5K larger. If the extra size will cause you problems, don't use the patch.

Hope this helps.

floating_point_libc.zip (1.83 MB)

So how can I achieve a count like this, 0.001 to 0.002 to 0.003 and so on. There must be an easy way?

There is, and I've explained it to you.
Store your values as integers, and print them as decimals.

float printEncode = encoderPosA / 1000.0;
Serial.print (printEncode, 3);

Several issue with your posted original code.

Any global variables used inside a ISR function must be made volatile as in:

volatile encoderPos = 0;
int lastReportedPos = 0;

volatile boolean A_set = false;
volatile boolean B_set = false;

Note that by using interrupts for both encoder channels and the CHANGE attribute then an encoder that has mechanical detents will generate four count changes for each click, not a bad thing but if you were expecting to only increment or decrement for each 'click' you will be surprised.

attachInterrupt(2, doEncoderA, CHANGE);
attachInterrupt(3, doEncoderB, CHANGE);

Finally any reference to the variables you use in your main loop that can change via the ISR can cause an 'atomic issue' where you can get the wrong value.

So your code in loop:

void loop(){ 
  if (lastReportedPos != encoderPos) {
    lastReportedPos = encoderPos;
  }
  if (digitalRead(zeroButton) == LOW)  {
    encoderPos = 0;
  }

Should be protected from ISR caused value changes to the encoderPos variable. Thus:

void loop(){ 
  
  if (lastReportedPos != encoderPos) {
    noInterrupts();
    lastReportedPos = encoderPos;
    interrupts();
  }
  if (digitalRead(zeroButton) == LOW)  {
    encoderPos = 0;
  }

Lefty

Ok thanks!

So I best sort this out first before messing about with decimal points, problem is..

volatile encoderPos = 0;

I get this error from adding volatile infront of encoderPos = 0;

MS_Fast2:17: error: ISO C++ forbids declaration of 'encoderPos' with no type

Ahhh I added int and it compiled?

volatile int encoderPos = 0;

TronSR:
Ahhh I added int and it compiled?

volatile int encoderPos = 0;

Yes, I choppped off the int while cuttin and pastin. :wink:

It's still a variable of type int, but the volatile modifier tells the compiler to not store the variable in a register but rather read from SRAM for every referenced its used.

Lefty

Awesome, thanks for clarifying .
Ok all working fine, here is the updated code Lefty, thanks for your support!

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define OLED_DC 48
#define OLED_CS 52
#define OLED_CLK 46
#define OLED_MOSI 44
#define OLED_RESET 50
Adafruit_SSD1306 display(OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS);
#if (SSD1306_LCDHEIGHT != 32)
#endif
enum PinAssignments {
  encoderPinA = 20,
  encoderPinB = 21,
  zeroButton = 42
};
volatile int encoderPos = 0;
int lastReportedPos = 0;
volatile boolean A_set = false;
volatile boolean B_set = false;
int myValue;
int printEncode;
void setup() {
  pinMode(encoderPinA, INPUT); 
  pinMode(encoderPinB, INPUT); 
  pinMode(zeroButton, INPUT);
  digitalWrite(encoderPinA, HIGH);
  digitalWrite(encoderPinB, HIGH);
  digitalWrite(zeroButton, HIGH);
  attachInterrupt(2, doEncoderA, CHANGE);
  attachInterrupt(3, doEncoderB, CHANGE);
  display.begin(SSD1306_SWITCHCAPVCC);
  display.display();
  delay(1000);
}
void loop(){ 
  
  if (lastReportedPos != encoderPos) {
    noInterrupts();
    lastReportedPos = encoderPos;
    interrupts();
  }
  if (digitalRead(zeroButton) == LOW)  {
    encoderPos = 0;
  }
  display.display();
  display.clearDisplay();
  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.setCursor(0,0);
  display.print("Linear");
  display.setCursor(0,16);
  display.print("Position");
  display.setCursor(0,40);
  display.setTextSize(3);
  display.print(encoderPos, DEC);
  display.setTextSize(2);
  display.setCursor(0,24);
  display.print("__________");
  display.setTextSize(2);
  display.setCursor(96,47);
  display.print("mm");
  display.setTextSize(2);
  display.setCursor(0,50);
  display.print("__________"); 
}
void doEncoderA(){
  A_set = digitalRead(encoderPinA) == HIGH;
  encoderPos += (A_set != B_set) ? +1 : -1;
}
void doEncoderB(){
  B_set = digitalRead(encoderPinB) == HIGH;
  encoderPos += (A_set == B_set) ? +1 : -1;
}

AWOL,

I've updated the code, it works fine on serial but not on my display I get 0 where's on Serial Monitor I get a working 0.000 line flowed by 0.001 and so forth when I move the encoder.

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define OLED_DC 48
#define OLED_CS 52
#define OLED_CLK 46
#define OLED_MOSI 44
#define OLED_RESET 50
Adafruit_SSD1306 display(OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS);
#if (SSD1306_LCDHEIGHT != 32)
#endif
enum PinAssignments {
  encoderPinA = 20,
  encoderPinB = 21,
  zeroButton = 42
};
volatile int encoderPos = 0;
int lastReportedPos = 0;
volatile boolean A_set = false;
volatile boolean B_set = false;
int myValue;
int printEncode;
int encoderPosA;
void setup() {
  pinMode(encoderPinA, INPUT); 
  pinMode(encoderPinB, INPUT); 
  pinMode(zeroButton, INPUT);
  digitalWrite(encoderPinA, HIGH);
  digitalWrite(encoderPinB, HIGH);
  digitalWrite(zeroButton, HIGH);
  attachInterrupt(2, doEncoderA, CHANGE);
  attachInterrupt(3, doEncoderB, CHANGE);
  display.begin(SSD1306_SWITCHCAPVCC);
  display.display();
  delay(1000);
  Serial.begin(9600);
}
void loop(){ 

  if (lastReportedPos != encoderPos) {
    noInterrupts();
    lastReportedPos = encoderPos;
    interrupts();
  }
  if (digitalRead(zeroButton) == LOW)  {
    encoderPos = 0;
  }
  display.display();
  display.clearDisplay();
  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.setCursor(0,0);
  display.print("Linear");
  display.setCursor(0,16);
  display.print("Position");
  display.setCursor(0,40);
  display.setTextSize(3);
  display.print(printEncode);
  display.setTextSize(2);
  display.setCursor(0,24);
  display.print("__________");
  display.setTextSize(2);
  display.setCursor(96,47);
  display.print("mm");
  display.setTextSize(2);
  display.setCursor(0,50);
  display.print("__________"); 
  float printEncode = (encoderPos / 1000.0);
  Serial.print (printEncode, 3);
}
void doEncoderA(){
  A_set = digitalRead(encoderPinA) == HIGH;
  encoderPos += (A_set != B_set) ? +1 : -1;
}
void doEncoderB(){
  B_set = digitalRead(encoderPinB) == HIGH;
  encoderPos += (A_set == B_set) ? +1 : -1;
}

I've updated the code, it works fine on serial but not on my display I get 0 where's on Serial Monitor I get a working 0.000 line flowed by 0.001 and so forth when I move the encoder.

Because you are printing one thing to the display and another to the serial port. Print the same thing in both places for more consistent results.

Hey Paul,

Because you are printing one thing to the display and another to the serial port. Print the same thing in both places for more consistent results.

But they are both the same?

display.print(printEncode, 3);
Serial.print (printEncode, 3);

printEncode... :S

int printEncode;
  display.print(printEncode);
  float printEncode = (encoderPos / 1000.0);
  Serial.print (printEncode, 3);

Has anyone EVER recommended that you use the same name for two different variables? Especially two different types?

I didn't think so.

But they are both the same?

No, they are not.

I get you, thanks!