HD44780, 2×16 LCD

So, I started working on my own HD44780 compatible 2×16 LCD module that I can use on my FEZ Spider (and other compatible Gadgeteer Mainboards). I am mentioning that this is “my own” version because GHI Electronics also have two similar modules, one without LCD and one with LCD. I know that I will be reinventing the wheel so to speak, but I decided to push forward since I wanted to get some experience creating my own module and associated driver code for the Gadgeteer platform.

I must admit, that the thought of building my module was a bit intimidating in the beginning, since I do not really understand the .NET Micro Framework in that much detail yet. I searched the GHI forums and also the web and came accross this very helpful and easy to understand article by Pete Brown on the topic of Building a .NET Gadgeteer Compatible Hardware and Software Module: Der BlinkenLED. Really worth-while reading material.

Preparing Microsoft Visual C# 2010 Express
First step is to go to the Gadgeteer Codeplex web site and to download the .Net Gadgeteer Builder Templates. The installer (msi) installs three templates:

  • the .NET Gadgeteer Module: the template for a Microsoft .NET Gadgeteer module, and it should be used if you want to create a new .NET Gadgeteer module for other users to use.
  • the .NET Gadgeteer Mainboard: the template for a Microsoft .NET Gadgeteer mainboard, and it should be used if you want to create the software support for a .NET Gadgeteer mainboard.
  • the .NET Gadgeteer Kit: the template for a Microsoft .NET Gadgeteer kit, and it should be used if you want to create a .NET Gadgeteer kit comprising multiple module(s) and/or mainboard(s).

In my case I used the .NET Gadgeteer Module template.

Project Setup
Here I found Pete Brown’s article Building a .NET Gadgeteer Compatible Hardware and Software Module: Der BlinkenLED the most helpful resource:

Step 1: Start a New Project in Microsoft Visual C# 2010 Express, select the .NET Gadgeteer Module template, and give your project a suitable name.

Step 2: Go to “Project” and then your projects “Properties” (in my case this is “lcd2x16 Properties”). Change the “ManufacturerName” in your GTM.ManufacturerName.YouProjectName (in my case this is “GTM.Zoftworx.lcd2x16″).

Step 3: do a “Quick Replace” across the entire solution (in your Solution Explorer you will see a ReadMe.txt file that explains this in detail)

Now the basic part of your project for the new module is ready.

Circuit
In order to understand how to connect the LCD Display to the Gadgeteer mainboard, it is necessary to understand the Gadgeteer Socket Types. There are quite a number of Socket Types on Gadgeteer and I am using a MC16021E8-SYL display, which is a 2 x 16 LCD with a backlight. The pin connections for this specific display are:

This solution will require 7 digital i/o’s, which means that the best Socket Type to use is the Y Socket.

The LCD to Y-Interface inter-connection will look like this:

I always like to build my attempt of the circuit on breadbord, since I do not need to do any soldering. Here is Breadboard version:

20120505-022601.jpg

One thing to keep in mind is that the microprocessor on the Spider is a 3V3 unit, and the LCD backlight is 5V. So I decided to include a 2N2222A transistor sort the voltage difference problem for me (not shown in the picture above, but shown in one of the pictures below). I am driving the 2N2222A with Pin 5 from the selected Gadgeteer Y-socket.

After testing my hardware setup on the Breadboard, I decided to move it all onto Veroboard so that I can have a more permanent solution. The design on Veroboard if you wish to build one for yourself, looks like this (the X indicate where you have to cut the tracks):

And in reality the final board looks like this:

Software / Module Driver Code
Now, with all the hardware sorted out we can look at the actual software. Let me just admit that the functions are coming from earlier projects, specifications and where I could not get something working, I Googled it and tried all different code snipets until I got it working.

In my software I first define the Socket, Gadgeteer Interfaces and Commands. The different functions have the following purpose (the actual code can be found at the bottom of this post):
public lcd2x16 (int socketNumber)

  • set the socket up
  • define all the pins on the selected socket

 
public void Initialize()

  • the LCD initialization procedure

 
void SendCommand(byte c)

  • ability to send commands to the LCD

 
void PutChar(byte c)

  • sends an ASCII character to the LCD

 
public void PutString(string str)

  • Sends a string of ASCII characters to the LCD

 
public void PutString_Line1(string str)

  • Sends a string of ASCII characters to the Line 1 of the LCD

 
public void PutString_Line2(string str)

  • Sends a string of ASCII characters to the Line 2 of the LCD

 
public void DisplayClear()

  • Clear the contents of the screen

 
public void DisplayBlank()

  • Blank the LCD (without clearing)

 
public void DisplayRestore()

  • Restore the display (with cursor hidden)

 
public void CursorHome()

  • Move cursor home and clear screen memory

 
public void CursorSet(byte row, byte col)

  • Sets cursor position to postion X on the LCD. You have to specify the Line number and Column number.

 
public void GotoLine1()

  • Takes the cursor to first character position on Line 1 of the LCD display

 
public void GotoLine2()

  • Takes the cursor to the first characted position on Line 2 of the LCD display

 
public void BacklightOn()

  • Turn the LCD backlight on

 
public void BacklightOff()

  • Turn the LCD backlight off

 
public void DisplayScrollRight(int numCharacters, int scrollDelay)

  • Scroll display n character(s) right (all lines), at a specified rate

 
public void DisplayScrollLeft(int numCharacters, int scrollDelay)

  • Scroll display n character(s) left (all lines), at a specified rate

 
public void CursorLeft()

  • Move cursor one character left

 
public void CursorRight()

  • Move cursor one character right

 
public void CursorUnderline()

  • Turn on visible underline cursor

 
public void CursorBlinking()

  • Turn on visible blinking-block cursor

 
public void CursorInvisible()

  • Make cursor invisible

Problems I encountered worth mentioning

  • Make sure that when you create a new version of your module, that you update the ModuleSoftwareVersion in the common.wxi file ASWELL as the AssemblyFileVersion and AssemblyInformationalVersion in the AssemblyInfo.cs. If you do not update both, then you will get an error when you try to install the new version of your version which will mean that you first have to manually un-install the module before you can install the new version.
  • With my first attempt I called my module LCD2X16. When you install the module and use it in a design, then when you reference your module from your code, then you will see that the first character of the name is changed to lowercase (lCD2X16). Not 100% sure that it is related, but I just could not get the Socket assigned to my module (when for example I have plugged my lcd1x16 module into the Spider Mainbord on Socket 14, then my code will not correctly pick up that it is actually plugged into Socket 14). I then changed all the naming to lower case (so from LCD2X16 to lcd2x16) and everything worked as it should.

lcd2x16.cs

using System;
using Microsoft.SPOT;
using System.Threading;
using Gadgeteer;
using Gadgeteer.Modules;
using Gadgeteer.Interfaces;
namespace Gadgeteer.Modules.Zoftworx
{

public class lcd2x16 : Gadgeteer.Modules.Module
{
Socket _socket;

Gadgeteer.Interfaces.DigitalOutput _LCD_RS;
Gadgeteer.Interfaces.DigitalOutput _LCD_E;
Gadgeteer.Interfaces.DigitalOutput _BackLight;
Gadgeteer.Interfaces.DigitalOutput _LCD_D4;
Gadgeteer.Interfaces.DigitalOutput _LCD_D5;
Gadgeteer.Interfaces.DigitalOutput _LCD_D6;
Gadgeteer.Interfaces.DigitalOutput _LCD_D7;

const byte DISPLAY_ON = 0xC; // Turn visible LCD on
const byte DISPLAY_CLEAR = 0x01; // Clear display
const byte DISPLAY_BLANK = 0x08; // Blank the display (without clearing)
const byte DISPLAY_RESTORE = 0x0C; // Restore the display (with cursor hidden)
const byte CURSOR_HOME = 0x02; // Move cursor home and clear screen memory
const byte SET_CURSOR = 0x80; // SET_CURSOR + X: Sets cursor position to X
const byte SCROLL_RIGHT = 0x1E; // Scroll display one character right (all lines)
const byte SCROLL_LEFT = 0x18; // Scroll display one character left (all lines)
const byte CURSOR_LEFT = 0x10; // Move cursor one character left
const byte CURSOR_RIGHT = 0x14; // Move cursor one character right
const byte CURSOR_UNDERLINE = 0x0E; // Turn on visible underline cursor
const byte CURSOR_BLINKING = 0x0F; // Turn on visible blinking-block cursor
const byte CURSOR_INVISIBLE = 0x0C; // Make cursor invisible

public lcd2x16 (int socketNumber)
{
// set the socket up
_socket = Socket.GetSocket(socketNumber, true, this, null);

// define all the pins on the selected socket
_LCD_RS = new Gadgeteer.Interfaces.DigitalOutput(_socket, Socket.Pin.Four, false, this);
_LCD_E = new Gadgeteer.Interfaces.DigitalOutput(_socket, Socket.Pin.Three, false, this);
_BackLight = new Gadgeteer.Interfaces.DigitalOutput(_socket, Socket.Pin.Five, false, this);
_LCD_D4 = new Gadgeteer.Interfaces.DigitalOutput(_socket, Socket.Pin.Six, false, this);
_LCD_D5 = new Gadgeteer.Interfaces.DigitalOutput(_socket, Socket.Pin.Seven, false, this);
_LCD_D6 = new Gadgeteer.Interfaces.DigitalOutput(_socket, Socket.Pin.Eight, false, this);
_LCD_D7 = new Gadgeteer.Interfaces.DigitalOutput(_socket, Socket.Pin.Nine, false, this);
}

public void Initialize()
{
// start the LCD initialization procedure
_LCD_RS.Write(false);
Thread.Sleep(50);

_LCD_D7.Write(false);
_LCD_D6.Write(false);
_LCD_D5.Write(true);
_LCD_D4.Write(true);
_LCD_E.Write(true);
_LCD_E.Write(false);
Thread.Sleep(50);

_LCD_D7.Write(false);
_LCD_D6.Write(false);
_LCD_D5.Write(true);
_LCD_D4.Write(true);
_LCD_E.Write(true);
_LCD_E.Write(false);
Thread.Sleep(50);

_LCD_D7.Write(false);
_LCD_D6.Write(false);
_LCD_D5.Write(true);
_LCD_D4.Write(true);
_LCD_E.Write(true);
_LCD_E.Write(false);
Thread.Sleep(50);

_LCD_D7.Write(false);
_LCD_D6.Write(false);
_LCD_D5.Write(true);
_LCD_D4.Write(false);
_LCD_E.Write(true);
_LCD_E.Write(false);

BacklightOff();
SendCommand(DISPLAY_ON);
SendCommand(DISPLAY_CLEAR);
}

void SendCommand(byte c)
{
_LCD_RS.Write(false); //set LCD to data mode on

_LCD_D7.Write((c & 0x80) != 0);
_LCD_D6.Write((c & 0x40) != 0);
_LCD_D5.Write((c & 0x20) != 0);
_LCD_D4.Write((c & 0x10) != 0);
_LCD_E.Write(true); //Toggle the Enable Pin on
_LCD_E.Write(false); //Toggle the Enable Pin off

_LCD_D7.Write((c & 0x08) != 0);
_LCD_D6.Write((c & 0x04) != 0);
_LCD_D5.Write((c & 0x02) != 0);
_LCD_D4.Write((c & 0x01) != 0);
_LCD_E.Write(true); //Toggle the Enable Pin on
_LCD_E.Write(false); //Toggle the Enable Pin off

Thread.Sleep(1);
_LCD_RS.Write(true); //set LCD to data mode off
}

void PutChar(byte c)
{
_LCD_D7.Write((c & 0x80) != 0);
_LCD_D6.Write((c & 0x40) != 0);
_LCD_D5.Write((c & 0x20) != 0);
_LCD_D4.Write((c & 0x10) != 0);
_LCD_E.Write(true); //Toggle the Enable Pin on
_LCD_E.Write(false); //Toggle the Enable Pin off

_LCD_D7.Write((c & 0x08) != 0);
_LCD_D6.Write((c & 0x04) != 0);
_LCD_D5.Write((c & 0x02) != 0);
_LCD_D4.Write((c & 0x01) != 0);
_LCD_E.Write(true); //Toggle the Enable Pin on
_LCD_E.Write(false); //Toggle the Enable Pin off

Thread.Sleep(1);
}

public void PutString(string str)
{
for (int i = 0; i < str.Length; i++)
PutChar((byte)str[i]);
}

public void PutString_Line1(string str)
{
GotoLine1();
PutString(str);
}

public void PutString_Line2(string str)
{
GotoLine2();
PutString(str);
}

public void DisplayClear()
{
SendCommand(DISPLAY_CLEAR);
}

public void DisplayBlank()
{
SendCommand(DISPLAY_BLANK);
}

public void DisplayRestore()
{
SendCommand(DISPLAY_RESTORE);
}

public void CursorHome()
{
SendCommand(CURSOR_HOME);
}

public void CursorSet(byte row, byte col)
{
SendCommand((byte)(SET_CURSOR | row << 6 | col));
}

public void GotoLine1()
{
CursorSet(0, 0);
}

public void GotoLine2()
{
CursorSet(1, 0);
}

public void BacklightOn()
{
_BackLight.Write(false);
}

public void BacklightOff()
{
_BackLight.Write(true);
}

public void DisplayScrollRight(int numCharacters, int scrollDelay)
{
for (int i = 0; i < numCharacters; i++)
{
SendCommand(SCROLL_RIGHT);
Thread.Sleep(scrollDelay);
}
}

{
for (int i = 0; i < numCharacters; i++)
{
SendCommand(SCROLL_LEFT);
Thread.Sleep(scrollDelay);
}
}

public void CursorLeft()
{
SendCommand(CURSOR_LEFT);
}

public void CursorRight()
{
SendCommand(CURSOR_RIGHT);
}

public void CursorUnderline()
{
SendCommand(CURSOR_UNDERLINE);
}

public void CursorBlinking()
{
SendCommand(CURSOR_BLINKING);
}

public void CursorInvisible()
{
SendCommand(CURSOR_INVISIBLE);
}
}
}

That’s it folks – next I will do something similar with a 4×3 Keypad.