Lcd skips a digit

Hi all

I'm working on building a homebrew beer, fermentation temperature controller with a fancy lcd display and menu/input system and using the PID library to control the output and maintain a stable temperature.

I'm still very much learning how to write code but feel like i've come a long way. I've come across a lot of problems and with the help of professors google and YouTube i've been able to solve just about all of these problems for myself. So far, it's coming along just swimmingly. However, i've come across one problem that i just can not crack.

I've come up with a way to adjust and enter the PID library values to one decimal place. When i press the up or down button to adjust the value, it increments or decrements by 0.1. The problem i'm having is that when i increment or decrement the value, the value adjusts correctly, but when the value reaches 2.1, the lcd display stalls for 1 increment. In other words, the value adjusts correctly up to 2.2 but the lcd displays 2.1 and then continues .1 behind the actual value all the way up until 9.2. Then it skips 9.3 altogether and displays 9.4. Then the LCD and value remain synched again. When decrementing it does the same thing and skips 9.3 however, at 2.1 it skips 2.0 and goes straight to 1.9.

I just can't figure out why its doing this. :sob:

I'm pretty sure its not the decimal place issue or even incrementing or decrementing the value as the value when displayed on the serial monitor adjusts correctly. I'm pretty it's an issue with actually writing the digit to the LCD. It just doesn't like the numbers 2.1 or 9.3.

As i'm using large custom characters on a 20x4 LCD display, each of the displayed digits are being created individually by resolving the actual value to a single digit and sending it to the appropriate place on the LCD. In order to isolate the number for the decimal place digit, I'm multiplying the value by 10 to remove the decimal place and then using %10 to get the final number. This is the digit that is sent to the LCD in the decimals position.

Is this the cause of this weird behaviour?

Please go easy on pulling my code to pieces. I'm pretty sure the guru's here will cringe when they see my birds nest of a program but by all means, if there is a more efficient way of doing this I'm all ears.

Also, if a video of the problem would be easier to understand i'll load something on youtube in the morning and provide a link.

i've commented the parts of the code i think are relevant to this problem with asterisks **** to try and help.

I tried popping the code here for reference but it exceeded the 9000 character message size limit, even after chopping it down :blush: , so i've attached the .pde file instead.

Thanks

Brew_monitor_Version3.ino (15.7 KB)

spleen:
I'm multiplying the value by 10 to remove the decimal place and then using %10 to get the final number. This is the digit that is sent to the LCD in the decimals position.

Is this the cause of this weird behaviour?

Yes, dealing with floating point numbers the wrong way can lead to weird behaviour.

The main reason is, that the internal representation of float/double is binary and therefore decimal numbers cannot be exactly accurate. Internally each floating point number is a binary representation, not a decimal, so rounding errors may appear.

The solution in your case would be: Multiply by 10, then round to the nearest integer value, then print the number div 10, decimal point, number % 10.

Or use floating point formatting with 'dtostrf' function from the AVR LIBC library.

Thanks Jurs

I hadn't considered the rounding issue. However, the values i'm using are never smaller than 1 decimal place. I understand that there are lots of zero's after that 1 decimal place but it is not broken down any further than that.

I would have thought that multiplying it by 10 to remove the decimal place always resulted in a whole number, albeit followed by a number of zero's. Is this not the case?

Sorry if i'm not just not "getting it". I'm just trying to understand the way the Arduino is processing the "double".

As i am only using values down to 1 decimal place, is there an easier way to do this? Or are floats/doubles the only option?

I'll have a play with dtostrf() after work.

Thanks for your reply.

spleen:
Sorry if i'm not just not "getting it".

Then look through the output generated by this example program:

void setup() {
  Serial.begin(9600);
  Serial.println();
  float f=10.0;
  for (int i=0;i<100;i++)
  {
    int fi=f*10;
    Serial.print(fi/10);Serial.print('.');Serial.print(fi%10);
    Serial.print('\t');
    Serial.println(f,10);
    f=f+0.2;
  }
}

void loop() {
}

The code starts with a float number of 10.0, then adds 0.2 in each step.

On one side the float will be multiplied by 10, then int/10 and int%10 are printed.
On the other side the float will be printed with more digits than its internal accuracy.

Perhaps you can see something happen if you 'forget' explicitly rounding.

Thank you so much Jurs.

That was a graphic demonstration of dp rounding! It has all become clear!

While you were taking the time to explain that to me i had a quick go at using dtostrf(). That certainly achieves what i was after on the serial monitor. Im just having problems getting it to display the stored value on the LCD. I'm assuming this is because it is stored as a string but i need it to be an int for my code to know which number to display.

Is this correct?

I tried appending the value from the dtostrf() array with (byte) to convert it to a byte value but this didn't work.

Sorry to keep asking questions. I was so happy to make it as far as i did without having to resort to the forums but this whole problem has had me scratching my head for 3 days.

spleen:
While you were taking the time to explain that to me i had a quick go at using dtostrf(). That certainly achieves what i was after on the serial monitor. Im just having problems getting it to display the stored value on the LCD. I'm assuming this is because it is stored as a string but i need it to be an int for my code to know which number to display.

Is this correct?

Correct is: the dtostrf() function formats a number to a string (char-array, C-string, null-terminated string).

Don't mix strings up with "String" objects. Although 'String' objects are supported by Arduino, those "dynamic String objects' are pure crap. You hardly can use them for anything useful.

But I doubt that there is a problem showing a string on a LCD.
Perhaps there is a problem showing 'String' objects on a LCD.
They are usually not supported by library code of any kind.
But LCDs surely can easily display strings.

I don't see any problem.

On the other hand: If you need integers, you just need to round correctly. The AVR LIBC provides the lround() function for that, rounding floats to long. If the number is small enough, you can directly cast the result to an integer.

Here is the same example as with my previous posting, but I added use of integer from float using correctly rounding. Output created from correctly rounded integers is in the middle column.

void setup() {
  Serial.begin(9600);
  Serial.println();
  float f=10.0;
  for (int i=0;i<100;i++)
  {
    int fi=f*10;
    Serial.print(fi/10);Serial.print('.');Serial.print(fi%10);
    Serial.print('\t');
    int rounded_fi=lround(f*10);
    Serial.print(rounded_fi/10);Serial.print('.');Serial.print(rounded_fi%10);
    Serial.print('\t');
    Serial.println(f,10);
    f=f+0.2;
  }
}

void loop() {
}

The reason i need an int rather than a string (or ascii) is because i'm using custom large numbers which fill all 4 lines of the LCD so that they can be read from across the room. These are built using custom characters stored in an array and referred to using an integer number, not an ascii character as in the dtostrf() function.

Thats ok you have given me plenty to think about and lots of homework. I really do have to go to work now so will look at it again tonight.

Thank you so much for your help!