Help need with 4BitLCD and Serial data

I’m using a 2x16 LCD to try and display some serial data. (I’m using a modified 4BitLCD library)
Being something of a code newbie, I’m somewhat at a loss as to what’s going on here. I’m feeding the Arduino some serial data, in the form of “12.345” (minus the quotes). The data arrives about every second. When I try and display it, I get “2.345XX”, sometimes “2.345XXX” (where X is a solid black block). Somehow just the bottom line of the LCD flashes on an off too

Here’s my sketch code:

#include <LCD4Bit.h> 

LCD4Bit lcd = LCD4Bit(2); 

void setup() { 
  Serial.begin(19200);
  lcd.init();
  lcd.backlightOn();
}


void loop() {  
  byte buffer[16];
  int i;

  
  if (Serial.available()){
    for (i=0;Serial.available();i++)
    {
    buffer[i] = Serial.read();
    }
    lcd.clear();
  lcd.print("Temperature:");
  delay(10);
  lcd.cursorTo(2,0);
  delay(10);
  lcd.print((char*)buffer);
  lcd.print(" F");
  }
}

So right now the display from that code is:
line 1: “Temperature:”
line 2: “2.345XX F”

when it gets the data “12.345”

Help! :-[

(I’ll post my modified 4BitLCD library code too).

Modified 4BitLCD header

#ifndef LCD4Bit_h
#define LCD4Bit_h

#include <inttypes.h>

#define USING_RW false

class LCD4Bit {
public:
      //varibles---------------
      int DB[4];
      int RS;
#if USING_RW
      int RW;
#endif
      int Enable;
      int Backlight;
      
      //functions
      LCD4Bit(int num_lines);
      LCD4Bit(int num_lines,int startingpin);
      LCD4Bit(int num_lines,int startingpin, int backlight);
      void commandWrite(int value);
      void init();
      void print(int value);
      void print(char msg[]);
      void printIn(char msg[]);
      void clear();
      //non-core---------------
      void cursorTo(int line_num, int x);
      void leftScroll(int chars, int delay_time);
      void backlightOn();
      void backlightOff();
      //end of non-core--------

      //4bit only, therefore ideally private but may be needed by user
      void commandWriteNibble(int nibble);
private:
      void pulseEnablePin();
      void pushNibble(int nibble);
      void pushByte(int value);
};

#endif

modified 4BitLCD cpp:

/*
LCD4Bit v0.1 16/Oct/2006 neillzero http://abstractplain.net

What is this?
An arduino library for comms with HD44780-compatible LCD, in 4-bit mode (saves pins)

Sources:
- The original "LiquidCrystal" 8-bit library and tutorial
    http://www.arduino.cc/en/uploads/Tutorial/LiquidCrystal.zip
    http://www.arduino.cc/en/Tutorial/LCDLibrary
- DEM 16216 datasheet http://www.maplin.co.uk/Media/PDFs/N27AZ.pdf
- Massimo's suggested 4-bit code (I took initialization from here) http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1144924220/8
See also:
- glasspusher's code (probably more correct): http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1160586800/0#0

Tested only with a DEM 16216 (maplin "N27AZ" - http://www.maplin.co.uk/Search.aspx?criteria=N27AZ)
If you use this successfully, consider feeding back to the arduino wiki with a note of which LCD it worked on.

Usage:
see the examples folder of this library distribution.

*/

#include "LCD4Bit.h"
extern "C" {
  #include <stdio.h>  //not needed yet
  #include <string.h> //needed for strlen()
  #include <inttypes.h>
  #include "WConstants.h"  //all things wiring / arduino
}

//command bytes for LCD
#define CMD_CLR 0x01
#define CMD_RIGHT 0x1C
#define CMD_LEFT 0x18
#define CMD_HOME 0x02

// --------- PINS -------------------------------------
//is the RW pin of the LCD under our control?  If we're only ever going to write to the LCD, we can use one less microcontroller pin, and just tie the LCD pin to the necessary signal, high or low.
//this stops us sending signals to the RW pin if it isn't being used.




//--------------------------------------------------------

//how many lines has the LCD? (don't change here - specify on calling constructor)
int g_num_lines = 2;

//pulse the Enable pin high (for a microsecond).
//This clocks whatever command or data is in DB4~7 into the LCD controller.
void LCD4Bit::pulseEnablePin(){
  digitalWrite(Enable,LOW);
  delayMicroseconds(1);
  // send a pulse to enable
  digitalWrite(Enable,HIGH);
  delayMicroseconds(1);
  digitalWrite(Enable,LOW);
  delay(1);  // pause 1 ms.  TODO: what delay, if any, is necessary here?
}

//push a nibble of data through the the LCD's DB4~7 pins, clocking with the Enable pin.
//We don't care what RS and RW are, here.
void LCD4Bit::pushNibble(int value){
      int val_nibble= value & 0x0F;  //clean the value.  (unnecessary)

      for (int i=0; i <= 3; i++) {
            digitalWrite(DB[i],val_nibble & 01);
            val_nibble >>= 1;
            }
  pulseEnablePin();
}

//push a byte of data through the LCD's DB4~7 pins, in two steps, clocking each with the enable pin.
void LCD4Bit::pushByte(int value){
  int val_lower = value & 0x0F;
  int val_upper = value >> 4;
  pushNibble(val_upper);
  pushNibble(val_lower);
}


//stuff the library user might call---------------------------------
//constructor.  num_lines must be 1 or 2, currently.
//------------------
LCD4Bit::LCD4Bit (int num_lines=2) {
      g_num_lines = num_lines;
      if (g_num_lines < 1 || g_num_lines > 2)
      {
        g_num_lines = 1;
      }
      //RS, RW and Enable can be set to whatever you like
      RS = 12;
#if USING_RW
      RW = 11;
#endif
      Enable = 2;
      //DB should be an unseparated group of pins  - because of lazy coding in pushNibble()
      DB[0] = 7; //wire these to DB4~7 on LCD.
      DB[1] = 8;
      DB[2] = 9;
      DB[3] = 10;
      Backlight=13;
}

LCD4Bit::LCD4Bit (int num_lines=2, int startingpin=2) {
      g_num_lines = num_lines;
      if (g_num_lines < 1 || g_num_lines > 2)
      {
            g_num_lines = 1;
      }
      Enable = startingpin;
      RS=startingpin+1;
      DB[0] = startingpin+2; 
      DB[1] = startingpin+3;
      DB[2] = startingpin+4;
      DB[3] = startingpin+5;
      Backlight=startingpin+6;
      }

LCD4Bit::LCD4Bit (int num_lines=2, int startingpin=2, int backlight=13) {
      g_num_lines = num_lines;
      if (g_num_lines < 1 || g_num_lines > 2)
      {
            g_num_lines = 1;
      }
      Enable = startingpin;
      RS=startingpin+1;
      DB[0] = startingpin+2; 
      DB[1] = startingpin+3;
      DB[2] = startingpin+4;
      DB[3] = startingpin+5;
      Backlight=backlight;
}
      
      
      

      

void LCD4Bit::commandWriteNibble(int nibble) {
  digitalWrite(RS, LOW);
#if USING_RW
  if (USING_RW) { digitalWrite(RW, LOW); }
#endif
  pushNibble(nibble);
}


void LCD4Bit::commandWrite(int value) {
  digitalWrite(RS, LOW);
#if USING_RW
  digitalWrite(RW, LOW);
#endif
  pushByte(value);
  //TODO: perhaps better to add a delay after EVERY command, here.  many need a delay, apparently.
}




//print the given character at the current cursor position. overwrites, doesn't insert.
void LCD4Bit::print(int value) {
  //set the RS and RW pins to show we're writing data
  digitalWrite(RS, HIGH);
#if USING_RW
      digitalWrite(RW, LOW);
#endif

  //let pushByte worry about the intricacies of Enable, nibble order.
  pushByte(value);
}


//print the given string to the LCD at the current cursor position.  overwrites, doesn't insert.
//While I don't understand why this was named printIn (PRINT IN?) in the original LiquidCrystal library, I've preserved it here to maintain the interchangeability of the two libraries.
void LCD4Bit::printIn(char msg[]) {
  uint8_t i;  //fancy int.  avoids compiler warning when comparing i with strlen()'s uint8_t
  for (i=0;i < strlen(msg);i++){
    print(msg[i]);
  }
}

void LCD4Bit::print(char msg[]) {
      uint8_t i;
      for (i=0;i < strlen(msg);i++){
            print(msg[i]);
      }
}

//send the clear screen command to the LCD
void LCD4Bit::clear(){
  commandWrite(CMD_CLR);
  delay(1);
}

modified cpp, part deux:

// initiatize lcd after a short pause
//while there are hard-coded details here of lines, cursor and blink settings, you can override these original settings after calling .init()
void LCD4Bit::init () {
  pinMode(Enable,OUTPUT);
  pinMode(RS,OUTPUT);
#if USING_RW
 pinMode(RW,OUTPUT);
#endif
  pinMode(DB[0],OUTPUT);
  pinMode(DB[1],OUTPUT);
  pinMode(DB[2],OUTPUT);
  pinMode(DB[3],OUTPUT);
  pinMode(Backlight,OUTPUT);

  delay(50);

  //The first 4 nibbles and timings are not in my DEM16217 SYH datasheet, but apparently are HD44780 standard...
  commandWriteNibble(0x03);
  delay(5);
  commandWriteNibble(0x03);
  delayMicroseconds(100);
  commandWriteNibble(0x03);
  delay(5);

  // needed by the LCDs controller
  //this being 2 sets up 4-bit mode.
  commandWriteNibble(0x02);
  commandWriteNibble(0x02);
  //todo: make configurable by the user of this library.
  //NFXX where
  //N = num lines (0=1 line or 1=2 lines).
  //F= format (number of dots (0=5x7 or 1=5x10)).
  //X=don't care

  int num_lines_ptn = g_num_lines - 1 << 3;
  int dot_format_ptn = 0x00;      //5x7 dots.  0x04 is 5x10

  commandWriteNibble(num_lines_ptn | dot_format_ptn);
  delayMicroseconds(60);

  //The rest of the init is not specific to 4-bit mode.
  //NOTE: we're writing full bytes now, not nibbles.

  // display control:
  // turn display on, cursor off, no blinking
  commandWrite(0x0C);
  delayMicroseconds(60);

  //clear display
  commandWrite(0x01);
  delay(3);

  // entry mode set: 06
  // increment automatically, display shift, entire shift off
  commandWrite(0x06);

  delay(1);//TODO: remove unnecessary delays
}


//non-core stuff --------------------------------------
//move the cursor to the given absolute position.  line numbers start at 1.
//if this is not a 2-line LCD4Bit instance, will always position on first line.
void LCD4Bit::cursorTo(int line_num, int x){
  //first, put cursor home
  commandWrite(CMD_HOME);

  //if we are on a 1-line display, set line_num to 1st line, regardless of given
  if (g_num_lines==1){
    line_num = 1;
  }
  //offset 40 chars in if second line requested
  if (line_num == 2){
    x += 40;
  }
  //advance the cursor to the right according to position. (second line starts at position 40).
  for (int i=0; i<x; i++) {
    commandWrite(0x14);
  }
}

//scroll whole display to left
void LCD4Bit::leftScroll(int num_chars, int delay_time){
  for (int i=0; i<num_chars; i++) {
    commandWrite(CMD_LEFT);
    delay(delay_time);
  }
}
      
void LCD4Bit::backlightOn()
{
      digitalWrite(Backlight,HIGH);      
}
      
void LCD4Bit::backlightOff()
{
      digitalWrite(Backlight,LOW);
}
//Improvements ------------------------------------------------
//Remove the unnecessary delays (e.g. from the end of pulseEnablePin()).
//Allow the user to pass the pins to be used by the LCD in the constructor, and store them as member variables of the class instance.
//-------------------------------------------------------------

Its not clear from your post if the problem is on the received data or the LCD. Did you try to print a hard coded string?

lcd.print("Temperature:"); delay(10); lcd.cursorTo(2,0); delay(10); lcd.print("12.345"); lcd.print(" F");

If that doesn't work, could you highligt the changes you made in the LCD code and perhaps say why you needed to change it

mem, just tried what you suggested. Hard coding it to print "12.345" does work. (but the blinking is still there).

I'll overview what I changed in the 4BitLCD code anyways:

  1. Added some overloading to the constructor (in anticipation for me changing the pins in the future)
  2. Added backlight functionality
  3. Overloaded the print function because printIn was confusing (reads too much like println).

So as far as I can tell the problem is in the received data. The bizarre thing is that it seems to print ok if I print it on the first line.

Arrgh! :(

The XX are probably a carriage return and line feed sent on the serial port.

You can filter them out in your sketch or in the library.

You also should zero terminate the buffer before sending to the LCD,so you could detect the line feed and replace it with zero and ignore all other characters less than 32.

Your buffer should be 17 chars long so it can handle a full line plus the terminating 0 and you should check to make sure that you don’t receive more characters than the buffer can hold.

edit: here is an (untested) example to get you started

void printTemp( char * valueStr){
  lcd.clear();
  lcd.print("Temperature:");
  delay(10);
  lcd.cursorTo(2,0);
  delay(10);  
  lcd.print(valueStr);
}

void loop(){
  
  static char buffer[17];
  static int i;
  while( Serial.available() )
  {            
       char c = Serial.read();
       if(c == 10){   // terminate a line on a line feed (ascii 10)
         buffer[i] = 0;
         printTemp(buffer);
         i=0;
       }
       else if(c >= 32 && i < 16) 
         buffer[i++] = c; // only store printable characters 

   }
}

In lieu of filtering (for now), I’ve done something which should have the same effect: I’m only using the first 6 characters of the serial data (2 digits, a decimal, then 3 more digits). Now things have gotten very strange. The second line now reads:

“2.345X2.345XXX F”
when fed the serial data “12.345”

Here’s the updated sketch code:

#include <LCD4Bit.h> 

LCD4Bit lcd = LCD4Bit(2); 

void setup() { 
  Serial.begin(19200);
  lcd.init();
  lcd.backlightOn();
}


void loop() {  
  char buffer[17];
  char dbuffer[6];
  int i;

  
  if (Serial.available()){
    for (i=0;Serial.available();i++)
    {
      buffer[i] = Serial.read();
    }
    for (i=0;i<6;i++)
    {
      dbuffer[i]=buffer[i];
    }
    lcd.clear();
    lcd.print("Temperature:");
    delay(5);
    lcd.cursorTo(2,0);
    delay(5);
    lcd.print(dbuffer);
    lcd.print(" F");
  }
}

If the serial data is of fixed length then you need to read and all the characters and then discard the the cr/lf otherwise they will appear on the next line.

If you always get 6 printable chars plus the cr/lf you could do this:

if (Serial.available()>= 8){
    for (i=0; i < 6;i++)
    {
      buffer[i] = Serial.read();
    }
    buffer[++i] = 0;
    Serial.read();  // discard the cr and lf
    Serial.read();
    lcd.clear();
    lcd.print("Temperature:");
    delay(5);
    lcd.cursorTo(2,0);
    delay(5);
    lcd.print(buffer);
    lcd.print(" F");
}

but you could get out of sync if there are any odd chars on the serial port

Ok, I’ve solved at least one problem. I figured out how to fix the truncation of the first decimal (the tens place). I think it was reading the serial data too quickly. I added a 1 millisecond delay every time before it chomps then next character in the serial buffer. What I still can’t figure out is why it’s printing is twice now on the second line, and where the solid black blocks are coming from :frowning:

With the serial input “12.345”, the second line now shows:
“12.34512.345XXXX” (where X is a black block).

try the code above

Mem:

I tried your code, and at first it worked (after I sprinkled in a few delays), but now it’s doing even stranger stuff.
It prints correctly for a few times, but a single black block will occasionally be at the end. Then every once and a while, it’s starts shifting/wrapping around it’s 6 characters, filling in with black blocks, until it cycles around again with the readout in the correct position. It shifts left every update cycle. I’m beginning to think my arduino is inhabited by ghosts or something ::slight_smile:

If you want to know what’s producing the serial data, it’s this example code in this post:
http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1217214709

Have a look at the raw serial data coming into the arduino and see if its always 6 characters followed by a cr lf. If not you will need to detect the cr/lf as per the code posted in reply #6 above

edit: Just had a look at the code in yr link, I don't think itoa pads zeros so if the temo is something linke 70.010 you will get 70.01 this will result in the output you are seeing.

as mentioned above, have a look at the data actually coming in. If the data is variable length either add code to pad or detect the cr/lf terminator

I've monitored the raw serial, and it is always 6 characters. It's always two digits, a decimal, then 3 digits.

we both posted at the same time, did you check what happens if the last decimal place is zero?

Forgot to add, I haven't seen it send anything that would put a zero in the thousandth place, it's always something like .689, or .567 The black box still appears with these values :(

is there a way to add padding to itoa for the just-in-case?

we both posted at the same time, did you check what happens if the last decimal place is zero?

Sorry, didn't see this post. I haven't had any data sent that's put a 0 in the last decimal place. The temperature sensor doesn't seem to like to leave the thousandth place 0.

I've got a thought. Would it be better (or even possible?) to send the raw float data type over serial (can you do that? Send the raw bytes that make up the float or double?). I've got to do some math operations on the value anyways (alarm if it goes above or below a temperature), so maybe it'd be better to try and pack the float over serial? Then use my tempToAsciii on the received data?

I don't think there is any advantage to sending the raw floating point.

There is a function I posted at the end of this thread http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1207226548 that pads a floating point value and sends it to the serial port as a string without carriage return line feed.

Forgot to add, I haven't seen it send anything that would put a zero in the thousandth place, it's always something like .689, or .567

Odd, because the readings should vary by well over a hundredth of a degree on each reading, one would expect to see an even distribution of all digits including zero in the last place.

Anyway, if you are using the DS18B20 sensor, its not accurate to more than one decimal place so not much point in displaying anything beyond that.