Main Content

Enable Support for LCD Using Custom Arduino Library

R2026b

This example shows how to create an LCD custom Arduino® library and display “Hello World!” on an LCD. For more information on LCDs, see RGB Backlit LCDs.

Connect Arduino to LCD

You can connect an LCD display to your Arduino® Uno board.

Required Hardware

  • An Arduino Uno board

  • 16x2 LCD similar to this device from Sparkfun.

  • 10k potentiometer

  • Breadboard

  • Jumper cables

To access all the library files for this example click on 'Open Example' and download the 'ConnectArduinoToLCDExample/SDKExampleLCD' folder attached with this example.

Connect the LCD as shown in the schematic:

Setup

Make sure that you wire your pins as follows:

LCD PinArduino Pin
1 (VSS)Ground
2 (VDD)5V
3 (V0)Mid pin on potentiometer
4 (RS)D7
5 (R/W)Ground
6 (E)D6
11 (DB4)D5 (PWM)
12 (DB5)D4
13 (DB6)D3 (PWM)
14 (DB7)D2
15 (LED+)5 V
16 (LED-)Ground

When done, your setup looks similar to this:

Breadboard diagram showing an LCD display connected to an Arduino Uno board through a breadboard with wiring for data, power, and ground pins.

Generate Library Template

Use arduinoio.customLibrary.createLibraryTemplate to generate the template files for the custom Arduino library.

Generate the library template.

arduinoio.customLibrary.createLibraryTemplate("LCDAddon")

This creates the folder +arduinoioaddons/+LCDAddonFolder that contains the MATLAB class file LCDAddon.m and the C++ header file src/LCDAddon.h.

Download the third-party LiquidCrystal library required for LCD communication.

arduinoio.customLibrary.downloadLibrary("LiquidCrystal")

Modify C++ Header File

Modify the generated LCDAddon.h header file in the +arduinoioaddons/+LCDAddonFolder/src folder to implement the LCD command handling.

Include LibraryBase.h and the third-party header file LiquidCrystal.h.

#include "LibraryBase.h"
#include "LiquidCrystal.h"

Declare debug strings using const char. Use PROGMEM to store the debug strings to the device flash memory, to save SRAM.

const char MSG_pLCD_CREATE_LCD_SHIELD[]       PROGMEM = "Arduino::pLCD = new LiquidCrystal(%d, %d, %d, %d, %d, %d);\n";
const char MSG_pLCD_INITIALIZE_LCD_SHIELD[]   PROGMEM = "Arduino::pLCD->begin(%d, %d);\n";
const char MSG_pLCD_CLEAR_LCD_SHIELD[]        PROGMEM = "Arduino::pLCD->clear();\n";
const char MSG_pLCD_PRINT[]                   PROGMEM = "Arduino::pLCD->print(%s);\n";
const char MSG_SET_CURSOR_LCD_SHIELD[]        PROGMEM = "Arduino::pLCD->setCursor(%d, %d);\n";
const char MSG_pLCD_DELETE_LCD_SHIELD[]       PROGMEM = "Arduino::delete pLCD;\n";

Define command IDs to match those defined in your MATLAB® class.

#define LCD_CREATE      0x00
#define LCD_INITIALIZE  0x01
#define LCD_CLEAR       0x02
#define LCD_PRINT       0x03
#define LCD_DELETE      0x04

byte cursorRow = 0;

Modify the constructor to define the pointer to the LiquidCrystal object that serves as the handle for controlling an LCD display.

class LCDAddon : public LibraryBase
{
public:
    LiquidCrystal *pLCD;
public:
    LCDAddon(MWArduinoClass& a)
    {
        libName = "LCDAddonFolder/LCDAddon";
        a.registerLibrary(this);
    }

Override the setup method to reset the cursor to the first row on initialization.

    void setup()
    {
        cursorRow = 0;
    }
    

Override commandHandler, and map each command to its corresponding methods. For this example, map the commands:

CaseCommand Description
0x00LCD_CREATEDefine pins used by the LCD
0x01LCD_INITIALIZESet up columns and rows
0x02LCD_CLEAR Clear the LCD screen
0x03LCD_PRINTPrint the message on the LCD
0x04LCD_DELETEDelete the LCD object

Create an LCD object. Initialize the LCD and assign rows and columns of the inputs. Use debugPrint to print trace messages to the MATLAB command line to help you debug code running on the Arduino device.

To understand more about command handlers see Command Handler.

public:
    void commandHandler(byte cmdID, byte* dataIn, unsigned int payloadSize)
    {
        switch(cmdID)
        {
            case LCD_CREATE:  //createLCD
            {
                byte* pinNumbers = new byte [6];
                for (byte i=0; i<6; i=i+1)
                {
                    pinNumbers[i] = dataIn[i];
                }
                createLCDObject(pinNumbers[0],pinNumbers[1],pinNumbers[2],pinNumbers[3],pinNumbers[4],pinNumbers[5]);
                sendResponseMsg(cmdID, 0, 0);
                break;
            }
            case LCD_INITIALIZE:  //initializeLCD
            {
                unsigned int rows = dataIn[0];
                unsigned int cols = dataIn[1];
                initializeLCD(rows,cols);
                clearLCD();
                sendResponseMsg(cmdID, 0, 0);
                break;
            }            

Clear the LCD screen.

case LCD_CLEAR:  //clearLCD
            {
                clearLCD();
                cursorRow = 0;
                setCursor(0, cursorRow);
                sendResponseMsg(cmdID, 0, 0);
                break;
            }   

Create a command that takes an input and prints it on the LCD screen.

            case LCD_PRINT:  //printLCD
            {
                byte* val = {dataIn};
                // last byte is the number of rows initialized
                // last 2nd byte is the number of columns initialized
                char message[payloadSize-1];
                for(byte k=0; k<(payloadSize-2); k=k+1)
                {
                    message[k]=val[k];
                }
                message[payloadSize-2] = '\0';
                
                byte cols = val[payloadSize-2];
                byte rows = val[payloadSize-1];

                if(cursorRow+1 > rows){
                    cursorRow = 0;
                    clearLCD();
                }
                setCursor(0,cursorRow);
                printLCD(message);
                cursorRow++;
                    
                sendResponseMsg(cmdID, 0, 0);
                break;
            }        

Delete the LCD object.

            case LCD_DELETE:  //delete
            {
                deleteLCDobject();
                //reset the cursor position to the first row on deletion
                cursorRow = 0;
                sendResponseMsg(cmdID, 0, 0);
                break;
            }
            default:
            {
                // Do nothing
                break;
            }    

Wrap the LiquidCrystal methods to add debug messages.

public:
	void createLCDObject(unsigned int rs,unsigned int enable,unsigned int d0,unsigned int d1,unsigned int d2,unsigned int d3)
	{
		 pLCD = new LiquidCrystal(rs, enable, d0, d1, d2, d3);
		 debugPrint(MSG_pLCD_CREATE_LCD_SHIELD,rs,enable,d0,d1,d2,d3);
	}
	void initializeLCD(unsigned int cols,unsigned int rows)
	{
		pLCD->begin(cols, rows);
		debugPrint(MSG_pLCD_INITIALIZE_LCD_SHIELD, cols, rows);
	}
	void clearLCD()
	{
		pLCD->clear();
		debugPrint(MSG_pLCD_CLEAR_LCD_SHIELD);
	}
	void printLCD(char message[])
	{
		pLCD->print(message);
		debugPrint(MSG_pLCD_PRINT, message);
	}
	void setCursor(byte column, byte row)
	{
		pLCD->setCursor(column, row);
		debugPrint(MSG_SET_CURSOR_LCD_SHIELD,column,row);
	}
	void deleteLCDobject()
	{
		delete pLCD;
		debugPrint(MSG_pLCD_DELETE_LCD_SHIELD);
	}
};

Modify MATLAB Class

Modify the generated LCDAddon.m class file in the +arduinoioaddons/+LCDAddonFolder folder to implement the LCD functionality.

The template creates a MATLAB class, LCDAddon, that inherits from matlabshared.addon.LibraryBase.

classdef LCDAddon < matlabshared.addon.LibraryBase 

Define custom command IDs for all public methods of the LCD class sent to the server on the board.

    properties(Access = private, Constant = true)
        LCD_CREATE     = hex2dec('00')
        LCD_INITIALIZE = hex2dec('01')
        LCD_CLEAR      = hex2dec('02')
        LCD_PRINT      = hex2dec('03')
        LCD_DELETE     = hex2dec('04')
    end  

Override constant properties in the class to include necessary header and third-party source files.

    properties(Access = protected, Constant = true)
        LibraryName = 'LCDAddonFolder/LCDAddon'
        DependentLibraries = {}
        LibraryHeaderFiles = 'LiquidCrystal/LiquidCrystal.h'
        CppHeaderFile = fullfile(arduinoio.FilePath(mfilename('fullpath')), 'src', 'LCDAddon.h')
        CppClassName = 'LCDAddon'
    end

    properties(Access = private)
        ResourceOwner = 'LCDAddonFolder/LCDAddon';
        Rows
        Columns
    end    
  • DependentLibraries must contain all other libraries you need to use your custom library.

  • LibraryHeaderFiles must be a string, starting with the library name, followed by a slash (/), and then header file name. If you need multiple header files, specify a cell array of strings.

  • CppHeaderFile must be the LCDAddon.h with the full path. Replace LCDAddon.h with your custom file name after you create it, and save it to the src folder in the LCDAddonFolder folder.

  • CppClassName must be the exact class name defined in LCDAddon.h.

Specify command IDs supported by the custom Arduino library for each method you create. Specify the pins that connect to the LCD, and define an error if the resource count is more than 0. In this example, you can add only one LCD, and so you must manage your pin resource count.

    methods(Hidden, Access = public)
     % InputPins is user input and contains the pins that connect the LCD Data Pins and the arduino
        function obj = LCDAddon(parentObj,varargin)
             if(nargin < 7)
                matlabshared.hwsdk.internal.localizedError('MATLAB:narginchk:notEnoughInputs');
             elseif nargin > 7
                matlabshared.hwsdk.internal.localizedError('MATLAB:narginchk:tooManyInputs');
             end  

             try
                p = inputParser;
                addParameter(p, 'RegisterSelectPin',[]);
                addParameter(p, 'EnablePin', []);
                addParameter(p, 'DataPins', []);
                parse(p, varargin{1:end});
             catch e
                 throwAsCaller(e);
             end
            obj.Parent = parentObj;            
            obj.RegisterSelectPin = p.Results.RegisterSelectPin;
            obj.EnablePin = p.Results.EnablePin;
            obj.DataPins = p.Results.DataPins;
            inputPins = [cellstr(obj.RegisterSelectPin) cellstr(obj.EnablePin) obj.DataPins];
            obj.Pins = inputPins;
            count = getResourceCount(obj.Parent,obj.ResourceOwner);
            % Since this example allows implementation of only 1 LCD
            % shield, error out if resource count is more than 0
            if count > 0
                error('You can only have 1 LCD shield');
            end 
            incrementResourceCount(obj.Parent,obj.ResourceOwner);    
            createLCD(obj,inputPins);
        end

Devices such as the Arduino Uno have a limited number of pins and require you to manage the resource allocation. So, you are limited to creating only one LCD object at a time. For more information, see getResourceOwner.

Write a createLCD method to perform the following functions:

  • Reserve the pins on the Arduino connected to the LCD and used by the library. Reserving prevents other functions from using these pins, giving your library exclusive use of the pins.

  • Send commands, including pin data, to the server using sendCommand.

        function createLCD(obj,inputPins)
            try
                cmdID = obj.LCD_CREATE;
                
                for iLoop = inputPins
                    configurePinResource(obj.Parent,iLoop{:},obj.ResourceOwner,'Reserved');
                end
                
                terminals = getTerminalsFromPins(obj.Parent,inputPins);
                sendCommand(obj, obj.LibraryName, cmdID, terminals);
            catch e
                throwAsCaller(e);
            end
        end
    end    

Overload destructor delete method for LCD class to free up the reserved pins and decrement LCD resource count.

    methods(Access = protected)
        function delete(obj)
            try
                parentObj = obj.Parent;
                % Clear the pins that have been configured to the LCD shield
                inputPins = [cellstr(obj.RegisterSelectPin) cellstr(obj.EnablePin) obj.DataPins];
                for iLoop = inputPins
                    configurePinResource(parentObj,iLoop{:},obj.ResourceOwner,'Unset');
                end
                % Decrement the resource count for the LCD
                decrementResourceCount(parentObj, obj.ResourceOwner);
                cmdID = obj.LCD_DELETE;
                inputs = [];
                sendCommand(obj, obj.LibraryName, cmdID, inputs);
            catch
                % Do not throw errors on destroy.
                % This may result from an incomplete construction.
            end
        end  
    end

Initialize the LCD with specific number of columns and rows, and clear the LCD screen.

    methods(Access = public)
        function initializeLCD(obj,varargin)                                    
            p = inputParser;
            p.PartialMatching = true;
            
            addParameter(p, 'Rows', 2);
            addParameter(p, 'Columns', 16);
            parse(p, varargin{:});
            output = p.Results;
            
            obj.Rows = output.Rows;
            obj.Columns = output.Columns;
            inputs = [output.Columns output.Rows];
            
            cmdID = obj.LCD_INITIALIZE;  
            sendCommand(obj, obj.LibraryName, cmdID, inputs);
        end    

Clear the LCD.

        function clearLCD(obj)
            cmdID = obj.LCD_CLEAR;
            inputs = [];
            sendCommand(obj, obj.LibraryName, cmdID, inputs);
        end    

Print the message on the LCD. This example uses a 16x2 LCD screen and cannot print more than 16 characters.

        function printLCD(obj,message)
            cmdID = obj.LCD_PRINT;
            
            if numel(message) > 16
                error('Cannot print more than 16 characters')
            end
            
            inputs = [double(message) obj.Columns obj.Rows];
            sendCommand(obj, obj.LibraryName, cmdID, inputs); 
        end       
    end
end    

Run

Make sure the LCDAddonFolder/LCDAddon library is available.

listArduinoLibraries
ans =

    'Adafruit/MotorShieldV2'
    'LCDAddonFolder/LCDAddon'
    'I2C'
    'SPI'
    'Servo'

Tip

If you do not see your custom Arduino library in the list, see Custom Arduino Library Issues for more information.

Create an arduino object specifying the LCDAddonFolder/LCDAddon library. Set ForceBuildOn to true to reprogram the board.

a = arduino('com5','uno','libraries','LCDAddonFolder/LCDAddon','ForceBuildOn',true);
a =

  arduino with properties:

                    Port: 'COM5'
                   Board: 'Uno'
           AvailablePins: {'D2-D13', 'A0-A5'}
    AvailableDigitalPins: {'D2-D13', 'A0-A5'}
        AvailablePWMPins: {'D3', 'D5-D6', 'D9-D11'}
     AvailableAnalogPins: {'A0-A5'}
      AvailableI2CBusIDs: [0]
             Libraries: {'LCDAddonFolder/LCDAddon'}

Create the LCD object, and specify the pins you configured on the Arduino device.

lcd = addon(a,'LCDAddonFolder/LCDAddon','RegisterSelectPin','D7','EnablePin','D6','DataPins',{'D5','D4','D3','D2'});

Initialize the LCD.

initializeLCD(lcd);

Print a string to the LCD.

printLCD(lcd,'Hello World!');

If you do not see a display on your LCD, try turning the knob on your potentiometer to adjust the contrast.

Clear the LCD.

clearLCD(lcd);

See Also

| | |