Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / IoT / Arduino

Print-derived Serial LCD library for Arduino

4.67/5 (8 votes)
1 Jun 2014CPOL6 min read 19K   407  
A new serial LCD library for Arduino with the power of 'Print' class

Introduction

LCD displays are mostly used in Arduino-based systems to show output of your application. They are cheap, widely-available, easy-to-use. Therefore, you can find many kinds of LCD display on the market. One drawback is that it occupies to many pins on your Arduino board. As a solution, serial LCD displays has emerged, thanks to kindhearted manufacturers :)

Background

Arduino IDE comes with a LiquidCrystal library ([3]) for Hitachi-HD44780-based LCDs ([4]). You command the controller by selecting & writing registers according to your needs. In 4-bit usage, you need 6 digital pin of your Arduino out of 14. What a waste.

Image 1

Figure 1: A 2x16 LCD

Some functionality provided by Hitachi-based controller:

  • Positioning cursor
  • Setting cursor type (hide, underline, blink)
  • Scrolling display (to-left, to-right)
  • Display on/off
  • Clear screen

LiquidCrystal library exposes those functions by commanding Hitachi controller. Dedicated pins are specified in the constructor.

Let's see an example:  

#include <Arduino.h>
#include <LiquidCrystal.h>

/*
Pin configuration:
LCD pin  <=>   Arduino Digital
-----------------------------
register select <=> 12
enable <=> 11
data4 <=> 2
data5 <=> 3
data6 <=> 4
data7 <=> 5
*/
LiquidCrystal lcd(12,11,2,3,4,5);

void setup() {
  lcd.begin(16,2); // specify columns & rows count

  lcd.print("that is a test");
  delay(5000);

  lcd.clear(); // clear screen
  
  lcd.setCursor(1,1); // second column of second row
}

void loop() {
  while(1);
}

A LiquidCrystal instance (lcd) is created with a given pin configuration at the beginning of the code. The library uses this pin configuration to send commands and data to LCD.

LiquidCrystal lcd(12,11,2,3,4,5); 

In setup function, LCD is initialized with its columns & rows count. The library uses this information when it sets cursor position.

lcd.begin(16,2); // specify columns & rows count

For example, if you want to move cursor to second column of second row, target cursor position is decided by using values provided in begin function. The next print will start from that position.

lcd.setCursor(1,1); // second column of second row 

I hope this sample is enough to see how to use an LCD with LiquidCrystal library.

Serial LCD

Now, it is time to talk about serial LCD. When it comes to serial LCD, those low-level driving issues are diverted to another microcontroller. You command this microcontroller, and it commands LCD (Hitachi controller). To do this, it provides you a serial interface with an only one line. Information flow is one-way, from Arduino to serial LCD, therefore it needs only one digital pin of your Arduino. You use only the transmit line of HardwareSerial ([7]) or SoftwareSerial ([8]) to communicate with your serial LCD.

Image 2

Figure 2: A serial LCD (connected with SoftwareSerial)

What I have done in this library is to implement communication protocol ([2]) with my specific serial LCD ([1]). My implementation has similar functionality with LiquidCrystal as you may expect since both are based on Hitachi controller.

Commands to serial LCD are composed of 1-byte directives with parameters. To drive serial LCD, you send those commands over serial transmit.

Command: Set backlight
Command byte: 0x80
Parameter: 0x00-0xFF (0-255). corresponds to brightness between 0%-100%

Command: Set baudrate (after reset), default 9600
Command byte: 0x81
Parameters: 0x00-0x0A (0-10), corresponding values 300, 1200, 2400, 2400, 9600, 14400, 19200, 28800, 38400, 57600, 115200

Following commands control LCD's behavior and all are preceded by 0xFE command byte.

Command: Clear screen, set cursor position to 0
Command byte: 0x01 (no parameter)

Command: Move cursor right
Command byte: 0x14 (no parameter)

Command: Move cursor left
Command byte: 0x10 (no parameter)

Command: Scroll display right
Command byte: 0x1C (no parameter)

Command: Display off (backlight doesn't change)
Command byte: 0x08 (no parameter)

Command: Display on
Command byte: 0x0C (no parameter)

Command: Underline cursor
Command byte: 0x0E (no parameter)

Command: Blink cursor
Command byte: 0x0D (no parameter)

Command: Cursor off (if display is ON)
Command byte: 0x0C (no parameter)

Command: Set cursor position
Command byte: 0x80
Parameter: 1-byte position index, starting from 0 (top-left)

Command: Toggle splash screen
Command byte: 0x1E (no parameter)

(I had developed another library ([9]) for serial LCD, but it is not good enough in terms of design & functionality. I prefer to count it as an experimental study.)

Using the code

Here is the class declaration of SerialLcd2:

class SerialLcd2: public Print {
protected:
    // underlying stream connected to serial LCD.
    // can be HardwareSerial or SoftwareSerial
    Stream* _Stream;
public:
    // constructor
    SerialLcd2(Stream* stream): _Stream(stream) { };
    
    // override for Print::write(uint8_t)
    size_t write(uint8_t b) { return this->_Stream->write(b); }; 

    // line feed, same as print('\n');
    void printLine() { this->write(SL_CHR_LF); };
    // back space
    void del() { this->write(SL_CHR_BCK); };

    // direction: SL_CMD_CURSOR_MV_R|SL_CMD_CURSOR_MV_L
    void moveCursor(uint8_t cmd); 
    // cmd = SL_CMD_CURSOR_OFF|SL_CMD_CURSOR_UNDERLINE|SL_CMD_CURSOR_BLINK
    void showCursor(uint8_t cmd);
    // pos: 0..(max chars-1)
    void setCursorPos(uint8_t pos);
    
    // clear screen
    void clr(); 
    // backlight: 0-255
    void setBacklight(uint8_t b); 
    // direction: SL_CMD_SCROLL_R|SL_CMD_SCROLL_L
    void scrollScreen(uint8_t cmd);
    // on if true
    void displayOn(bool);

    // toggle splash screen display. 
    void toggleSplash();
    // set baud rate
    void setBaudRate(uint8_t);
};

What I did here is to implement serial communication protocol and some other handy functions.

Let's see an example.

SerialLcd2* lcd; // pointer for SerialLcd2
Stream* s; // stream to be connected

To connect via hardware serial (Serial), we first need to initialize serial port of Arduino. I haven't changed my LCD's baud rate, therefore baud rate of the connection will be 9600 bps.

Serial.begin(9600);
s = &Serial; // connect to tx(pin 1)

If we want to connect with SoftwareSerial, similar initialization is needed except we have to reserve pins for SoftwareSerial.

SoftwareSerial ss(2,3); // rx=2, tx=3(connection)
ss.begin(9600);
s = &ss;

Then create a SerialLcd2 instance with its serial Stream ([6]).

lcd = new SerialLcd2(s); 

Caution! We should connect Stream's transmit line to the LCD's receive line, which is its only communication line.  

Now, we are ready to use it.

First of all, we can print anything as Print class ([5]) does without any further processing. This is accomplished by overriding virtual write function of Print class since Print class uses it in all print versions.

lcd->print(1); // integer
lcd->print('A'); // char
lcd->print(0.1); // float
lcd->print("string"); // const char*
lcd->print(F("flash string")); // flashmem string

Don't use println, because it doesn't move the cursor to the start of the next line as you may expect. Instead send '\n' directly or use printLine() function. Then, the cursor goes to the next line, or to the first if it is on the last line of LCD.

lcd->print('\n'); // or
lcd->printLine();

One good thing, you can delete last printed character as if you press backspace.

lcd->del();

The rest is about controlling LCD as in LiquidCrystal library of Arduino.

/* screen control */
lcd->clr(); // clear screen

lcd->setBacklight(0); // backlight off
lcd->setBacklight(0xFF); // backlight max

lcd->scrollScreen(SL_CMD_SCROLL_R); // scroll right
lcd->scrollScreen(SL_CMD_SCROLL_L); // scroll left

lcd->displayOn(false); // hides printed text and makes backlight off
lcd->displayOn(true); // shows printed text and makes backlight max

/* cursor control */
lcd->showCursor(SL_CMD_CURSOR_OFF); // cursor off
lcd->showCursor(SL_CMD_CURSOR_UNDERLINE); // underline cursor
lcd->showCursor(SL_CMD_CURSOR_BLINK); // blink cursor

// moves cursor to the first column of the first row
lcd->setCursorPos(0);
// moves cursor to the first column of the second row
// if the total number of columns is 16
lcd->setCursorPos(16);

lcd->moveCursor(SL_CMD_CURSOR_MV_R); // increments cursor position
lcd->moveCursor(SL_CMD_CURSOR_MV_L); // decrements cursor position

/* misc */
lcd->setBaudRate(4); // sets to 9600bps after reset
lcd->toggleSplash(); // toggles splash screen for my specific serial LCD

Some points to mention:

  • In the serial command of LCD, setting display on/off doesn't have any effect on backlight. I have changed this behavior in my implementation. If you call displayOn(false), backlight is also turned off, and vice versa.
  • Setting cursor position is different from the LiquidCrystal library. You pass [column,row] values to LiquidCrystal::setCursor function, and pass [offset-from-topleft] to SerialLcd2::setCursorPos.
  • Baud rate and splash screen toggling don't exist in LiquidCrystal

You can find an example application for the usage of the library in the download section.

Points of Interest

The good thing with this library is that it derives from Print class. Therefore it has the power of Print class. You can print anything as you do with Print, such as formatted numbers or strings from flash memory of Arduino.

Another thing is that it uses HardwareSerial or SoftwareSerial without any difference since it sees both as Stream. Therefore, you can connect receive line of serial LCD to any pin which is configured as serial transmit.

Behind the scenes, serial LCD kit makes use of LiquidCrystal library in its firmware ([10]). This is achieved by the same techniques used in an Arduino board. Serial kit has a ATmega328 MCU and Arduino bootloader is uploaded on it. Therefore, it is possible to upload any code with its serial programming interface. The firmare is just another Arduino code which can be thought as proxy between your Arduino and LCD. You can even change the communication protocol of serial LCD and create your very own. Enjoy!

If you have any idea/suggestion regarding to this implementation, I will happy to hear them.

Reference

[1] Serial LCD kit from Cooking-Hacks.com
[2] Serial communication with serial LCD
[3] Arduino LiquidCrystal library
[4] Hitachi LCD controller
[5] Arduino Print class
[6] Arduino Stream class
[7] Arduino Serial
[8] Arduino SoftwareSerial
[9] Another implementation of mine for SerialLcd
[10] Firmware of serial LCD kit
 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)