One of the most fascinating aspects of programming is making things work. And that is not limited to just what you can do with your computer; a much more exciting world exists outside of the computer. In this article I will show you how to add a cool and interesting secondary display to your computer. In particular, you will learn how to connect to a LCD display and use .NET to display information on it.
For this article, I will use the 4×20 Serial LCD (model LK204-25; this LCD is manufactured by Matrix Orbital and sold by Parallax) with Keypad Interface from Parallax ($99.95; see Figure 1). LCD displays have been used in a wide variety of electronic devices. The next time you use your credit card at the shopping mall, take a good look at the terminal (see left of Figure 2); you’ll almost certainly find that it is LCD. Besides being embedded in electronic devices, LCD screens are increasingly getting the attention of modders, who like to embed them in drive bays and use them to display system information (see right of Figure 2).
Most LCD screens in the market mainly come in two types of interfaces: parallel and serial. Technically, all LCD screens use parallel interfaces. However, due to the complexity of wiring and programming, some manufacturers add circuits to convert the parallel interface into a serial one. The end result is fewer wires to connect and a much simpler way of programming.
For this article, I used the serial version of the LCD with the new SerialPort class available in .NET 2.0. This makes programming an application much simpler and allows me to concentrate on exploring the features of the LCD.
Setting Up the LCD Display
The first step is to connect the LCD display to your PC. In order to do so, you need to perform a TTL-to-RS-232 level shifting so that the data can be read via a serial port. One way is to connect the LCD display to Parallax’s RS-232 DCE AppMod (http://www.parallax.com/detail.asp?product_id=29120; $29; see Figure 3).
For this project, however, I chose to use the Javelin Demo Board ($119; see Figure 4) to connect to the LCD display.
Figure 5 shows the wiring on the back of the LCD display. Be careful not to reverse the connection of the +5V and Ground connectors. The Rx connector allows data/instructions to be sent to the LCD display while the Tx connector allows data to be read from the LCD display. In most cases, you can simply connect only the Rx connector since all you want is to send data to the LCD.
Testing the Connections
If you did your connections correctly, you can now power up the LCD display. When connected properly, the screen should look as shown in Figure 7.
Programming the LCD
The 4×20 Serial LCD (LK204-25) display is a text-only LCD. However, its built-in character font (ASCII) includes some extended characters (see Figure 8) as well.
In addition, you can define up to eight custom characters. This is useful for defining your own logo, or smileys. It also supports drawing of vertical and horizontal bar graphs.
Programming the LK204-25 is straightforward. The following sections summarize the things you need to know to program the LK204-25 using the SerialPort class in .NET 2.0.
Issuing Commands
To issue commands to the LCD, such as clearing the screen, send a byte array containing the following: 254, [command]. For example, to clear the screen, the command is 254, 88.
In .NET, you can send the command to the LCD by setting a byte array to:
_data = New Byte() {254, 88}serialPort.Write(_data, 0, _data.Length)
The number 254 and 88 are expressed in decimal. Alternatively, you can also express them in hexadecimal:
_data = New Byte() {&HFE, &H58}serialPort.Write(_data, 0, _data.Length)
You can also express the command using its corresponding ASCII character, in this case ‘X’:
_data = New Byte() {254, Asc("X")}serialPort.Write(_data, 0, _data.Length)
Notice how the character X corresponds to column 5 and row 8 in Figure 8. (Note: The list of commands for the 4×20 Serial LCD can be found at http://www.parallax.com/dl/docs/prod/audiovis/LK204-25-2.x.pdf .)
Displaying Text
To send a string to the display, simply write the string directly to the SerialPort object:
serialPort.Write("Hello!")
Displaying Special Characters
To print out a particular character in the character set (this is especially useful for special characters), use the following format:
_data = New Byte() {&HEF}serialPort.Write(_data, 0, _data.Length)
where EF is the character in column E and row F of the character table.
One noteworthy point is that the characters displayed by my LCD do not match those listed in Figure 8, possibly because some characters were changed in the ROM. Hence, it would be best if you print out the character set of your LCD. You can do so by using the following code snippet:
Dim num As Integer = &H0 '---start from 0 For i As Integer = 0 To 255 _data = New Byte() {num} serialPort.Write(num & " - ") serialPort.Write(_data, 0, _data.Length) serialPort.WriteLine("") num += 1 System.Threading.Thread.Sleep(500) Next
DDisplay the CPU usage
- Display the current time
- Display RSS feeds
Packaging the LCD Functionalities into a Class Library
Before writing the sample application, I want to package all the core functionalities of the LCD display into a class library. Thus, if you have the LK204-25 serial LCD, you can simply program it by calling the relevant methods exposed by the class library.
Using Visual Studio 2005, create a new Windows application and name it C:LCD. Add a new class to the project and name it as LCD.vb
First, define the following enumerated types:
'---used for defining custom characters---Public Enum CustomChar ch0 = 0 ch1 = 1 ch2 = 2 ch3 = 3 ch4 = 4 ch5 = 5 ch6 = 6 ch7 = 7End Enum'---used for setting the direction of horizontal bar graph---Public Enum Direction left = 1 right = 0End Enum
Declare the following constant, member variables and events within the LCD class:
Public Class LCD '---define the width of the bar graph--- Const BARGRAPH_WIDTH As Integer = 20 '---store the values of the bar graph--- Private _values(BARGRAPH_WIDTH) As Integer '---keep track of the current index of the bar graph--- Private _index As Integer = 0 '---data received from the LCD--- Public Event DataFromLCD(ByVal str As String) '----create the serial port--- Private WithEvents serialPort As New IO.Ports.SerialPort '---data to send to the LCD--- Private _data() As Byte '---parameters used by the other functions--- Private _col, _row As Integer Private _c As Integer ' 0 to 7 Private _cBytes() As Byte Private _lengthofVBar As Short Private _lengthofHBar As Short Private _d As Short = 0 '---0 is right 1 is left Private _digit As Short '---0 to 9 Private _minutes As Integer '---0 or a number Private _contrast As Integer '---0 to 255
In the constructor of the LCD class, initialize the array used for storing the values in a bar graph:
'---initialize the bar graph--- Public Sub New() For i As Integer = 0 To BARGRAPH_WIDTH - 1 _values(i) = 0 Next End Sub
Define the Connect() subroutine to open the serial port that is connected to the LCD display. You must set the BaudRate property to 19200, otherwise you may not be able to communicate with the LK204-25 LCD display.
'---connect to the LCD using serial port--- Public Sub Connect(ByVal PortNo As String) If serialPort.IsOpen Then serialPort.Close() End If Try With serialPort .PortName = PortNo .BaudRate = 19200 .Parity = IO.Ports.Parity.None .DataBits = 8 .StopBits = IO.Ports.StopBits.One .Handshake = IO.Ports.Handshake.None End With serialPort.Open() Catch ex As Exception MsgBox(ex.ToString) End Try End Sub
The DataReceived event will be fired when incoming data is received from the serial port. It will in turn fire off the DataFromLCD event to notify the calling routine that there is incoming data from the LCD display:
'---data received from the LCD--- Private Sub DataReceived( _ ByVal sender As Object, _ ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) _ Handles serialPort.DataReceived Dim str As String = serialPort.ReadExisting '---cause the event on the caller to fire--- RaiseEvent DataFromLCD(str) End Sub
The Write() subroutine will send a string to the LCD display via the serial port. It supports delayed sending of data, and hence will send individual characters to the LCD at a delay of 100 milliseconds per character.
'---display text with optional delay--- Public Sub Write(ByVal text As String, ByVal delay As Boolean) If delay Then For i As Integer = 0 To text.Length - 1 serialPort.Write(text(i)) System.Threading.Thread.Sleep(100) Next Else serialPort.Write(text) End If End Sub
To simplify communicating with the LCD, the Command() subroutine will take in a command identifier and send the appropriate commands to the LCD (see Listing 1).
For example, to clear a screen, you would call Me.Command(“X”) and the subroutine will do the work of sending the correct command to the LCD display. Notice that this subroutine is only visible within the class, as you do not want the user to directly call the commands.
To make it easier for users to send the appropriate commands to the LCD, define the subroutines in Listing 2 (their use is self explanatory).
Finally, define the DrawBarGraph() subroutine to draw a moving bar graph:
'---draw a bar graph--- Public Sub DrawBarGraph(ByVal value As Integer) '---assign the value into the array--- If _index > BARGRAPH_WIDTH Then '---shift all the values 1 position up For i As Integer = 1 To BARGRAPH_WIDTH - 1 _values(i) = _values(i + 1) Next _values(BARGRAPH_WIDTH) = value Else _values(_index) = value End If _index += 1 '---draw the bars in the graph For i As Integer = 1 To BARGRAPH_WIDTH Me.DrawVertBarGraph(i, (_values(i) / 100) * 32) Next '---display some text--- Me.SetCursorPosition(1, 7) Me.Write("CPU Usage: " & value.ToString & "% ", False) End Sub
Testing the Library
Now it’s time to test the library you just created. Populate the default Form1 with the following controls (see Figure 11):
- Button controls
- CheckBox control
- Label control
- TrackBar control
In the code-behind of Form1, import the following namespaces:
Imports System.ManagementImports System.NetImports System.IOImports System.XmlImports System.Threading
Declare the following member variables:
Public Class Form1 '---create an instance of the LCD class--- Private WithEvents LCD_Display As New LCD '---true when form is finished loading--- Private FormLoaded As Boolean = False '---threads for asynchronous execution--- Private t1, t2, t3 As Thread
When the form is first loaded, open a connection to the serial port connecting to the LCD display:
Private Sub Form1_Load( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles MyBase.Load FormLoaded = True LCD_Display.Connect("COM3") End Sub
Here, I am assuming that the COM3 is used to connect your PC to the LCD display.
Clear Display
To clear the LCD display, call the ClearDisplay() method of the LCD class:
Private Sub btnClearDisplay_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles btnClearDisplay.Click LCD_Display.ClearDisplay() End Sub
Custom Characters
To define a custom character, first initialize the byte array for the character and then call the DefineCustomChar() method of the LCD class.
Private Sub btnCustomChar_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles btnCustomChar.Click Dim bytes(7) As Byte bytes(0) = &H0 bytes(1) = &HA bytes(2) = &HA bytes(3) = &H0 bytes(4) = &H11 bytes(5) = &HE bytes(6) = &H6 bytes(7) = &H0 '---defines the custom character--- LCD_Display.DefineCustomChar(CustomChar.ch0, bytes) '---display the custom char at a specific location--- LCD_Display.SetCursorPosition(4, 15) LCD_Display.WriteCustomChar(CustomChar.ch0) End Sub
To test that the custom character is defined correctly, you will write the custom character at a specified location. These two tasks are accomplished by the WriteCustomChar() and SetCursorPosition() methods, respectively.
Display CPU Usage Graph
You can obtain the CPU utilization using WMI (Windows Management Instrumentation). Using WMI, you can obtain detailed information about your hardware and devices. The Display_CPU_Usage() subroutine repeatedly queries the CPU utilization rate and then updates the graph:
'---display CPU usage using a graph--- Public Sub Display_CPU_Usage() LCD_Display.ClearDisplay() LCD_Display.InitThickVerticalBarGraph() Dim oQ As ObjectQuery = _ New ObjectQuery("select * from Win32_Processor") Dim searcher As ManagementObjectSearcher = _ New ManagementObjectSearcher(oQ) While True For Each CPU As ManagementObject In searcher.Get() LCD_Display.DrawBarGraph(CPU("LoadPercentage")) Exit For '---skip the next processor--- Next End While End Sub
Notice that in the For Each loop, I have inserted an Exit For statement. This is because I am testing this application on a dual core CPU, and in this case I am only interested in the CPU utilization of the first processor.
Author’s Note: You need to add the System.Management DLL to the project for WMI to work. |
As the Display_CPU_Usage() subroutine runs in an infinite loop, you should not call the function directly from the event hander of the button as this will freeze the UI. Instead, use a separate thread to call it:
Private Sub btnCPUUsage_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles btnCPUUsage.Click t1 = New Thread(AddressOf Display_CPU_Usage) t1.Start() End Sub
Display RSS Feeds
One good use of the LCD display is to display headlines from an RSS feed. The Display_RSS() subroutine retrieves a RSS document and then displays the title of each post on the LCD:
'---display RSS feeds--- Private Sub Display_RSS() Dim req As HttpWebRequest Dim xmlDoc As XmlDocument = Nothing Try LCD_Display.ClearDisplay() '---download the RSS document--- req = _ SendRequest("http://services.devx.com/outgoing/devxfeed.xml", _ "GET") Dim xmlData As String = GetResponse(req) xmlDoc = New XmlDocument() xmlDoc.LoadXml(xmlData) '---Select the title of the document--- Dim titlesNode As XmlNodeList = _ xmlDoc.DocumentElement.SelectNodes("channel/item/title") For i As Integer = 0 To titlesNode.Count - 1 LCD_Display.Write("* " & _ titlesNode(i).InnerText.ToString(), True) LCD_Display.Write(vbCrLf & vbCrLf, True) Next Catch ex As Exception MsgBox(ex.ToString) End Try End Sub
The two supporting functions used by the Display_RSS() subroutine are defined below:
Public Function SendRequest( _ ByVal URI As String, _ ByVal requestType As String) As HttpWebRequest Dim req As HttpWebRequest = Nothing Try '---Creates a HTTP request--- req = HttpWebRequest.Create(URI) req.Method = requestType '---GET or POST--- Catch ex As Exception Throw New Exception("Error") End Try Return req End Function Public Function GetResponse( _ ByVal req As HttpWebRequest) As String Dim body As String = String.Empty Try '---Get a response from server--- Dim resp As HttpWebResponse = req.GetResponse() Dim stream As Stream = resp.GetResponseStream() '---Use a StreamReader to read the response--- Dim reader As StreamReader = _ New StreamReader(stream, System.Text.Encoding.UTF8) body = reader.ReadToEnd() stream.Close() Catch ex As Exception Throw New Exception("Error") End Try Return body End Function
To ensure that displaying the RSS feed does not freeze up the UI, use a thread to invoke it:
Private Sub btnDisplayRSS_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles btnDisplayRSS.Click t2 = New Thread(AddressOf Display_RSS) t2.Start() End Sub
Display Time
The LK204-25 supports large digits and this is useful for displaying information such as time. The Display_Time() subroutine continuously displays the current time and updates it every one second (1000 milliseconds):
'---display the current time--- Public Sub Display_Time() LCD_Display.BlockCursorONOFF(False) LCD_Display.ClearDisplay() LCD_Display.InitLargeDigits() While True Dim hour, minute, second As String hour = Now.Hour.ToString("0#") minute = Now.Minute.ToString("0#") second = Now.Second.ToString("0#") '---display the hour--- LCD_Display.PlaceLargeDigits(1, CInt(hour(0).ToString)) LCD_Display.PlaceLargeDigits(4, CInt(hour(1).ToString)) '---display the minute--- LCD_Display.PlaceLargeDigits(8, CInt(minute(0).ToString)) LCD_Display.PlaceLargeDigits(11, CInt(minute(1).ToString)) '---display the second--- LCD_Display.PlaceLargeDigits(15, CInt(second(0).ToString)) LCD_Display.PlaceLargeDigits(18, CInt(second(1).ToString)) Thread.Sleep(1000) End While End Sub
As usual, to avoid freezing the UI, you should call the subroutine using a separate thread:
Private Sub btnDisplayTime_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles btnDisplayTime.Click t3 = New Thread(AddressOf Display_Time) t3.Start() End Sub
Read Version Number
You can read the firmware version number of the LK204-25 by sending the appropriate command to it. This is a good chance to demonstrate how to read data sent from the LCD display. The is accomplished by the ReadVerNumber() method of the LCD class:
Private Sub btnReadVersionNum_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles btnReadVersionNum.Click LCD_Display.ReadVerNumber() End Sub
When data is received, the DataFromLCDDisplay event handler will fire:
'---when data is received from the LCD--- Public Sub DataFromLCDDisplay( _ ByVal str As String) Handles LCD_Display.DataFromLCD MsgBox(str) End Sub
Author’s Note: For some unknown reason, my LCD display always returns a “T” when queried about its firmware information. |
Set the Contrast and Turn On/Off the Backlight
You can set the contrast of the LCD by moving the TrackBar control:
Private Sub TrackBar1_Scroll( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles TrackBar1.Scroll LCD_Display.SetContrast(TrackBar1.Value) End Sub
To turn on/off the backlight of the LK204-25, check (or uncheck) the chkBacklight control:
Private Sub chkBacklight_CheckedChanged( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles chkBacklight.CheckedChanged If Not FormLoaded Then Exit Sub If chkBacklight.Checked Then LCD_Display.BackLightON(0) Else LCD_Display.BackLightOFF() End If End Sub
Stopping the Threads
To stop the threads running the various functions, code the three Stop buttons as follows:
Private Sub btnStopCPUUsage_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles btnStopCPUUsage.Click t1.Abort() End Sub Private Sub btnStopRSSFeeds_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles btnStopRSSFeeds.Click t2.Abort() End Sub Private Sub btnStopDisplayTime_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles btnStopDisplayTime.Click t3.Abort() End Sub
Testing the Application
Finally, you are now ready to test the LCD. Press F5 to debug the application. Ensure that your serial cable is connected to both the PC and the LCD. Click on the various buttons and observe the output on the screen. You can also view the following videos to see it in action.
The videos show, from left to right, the Timer function, the CPU Usage function, and the Display RSS feeds function. Click each Play button twice to start the video.
In this article, you have seen how you can easily connect an external device such as a LCD display to your computer. More importantly, you have learnt how to use .NET to communicate with these devices, and in this particular case, how the SerialPort class is put into action. I hope you have fun with this project; send me your ideas for using the LCD display! In future articles on DevX I will help you expand the skills learned here to interface .NET applications with external devices in other ways.