I’ve been working with the 2x16 LCD shield of late and rewrote some of the original driver into something more useful for me. This version includes scrolling of both lines and the ability to automatically turn off the backlight after a given amount of time.
I might have been overzealous with locking and it can still be improved greatly, for example it will only scroll from right to left at the moment and I’m unsure on how efficient everything is.
Also although I can turn the display on using DISP_ON command, I haven’t figured out how to turn it off using DISP_OFF, the command doesn’t seem valid? Ideally I’d prefer to use on/off instead of the backlight for the dim system
.
Anyhow I thought I’d share!
using System;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using GHIElectronics.NETMF.FEZ;
using GHIElectronics.NETMF.Hardware;
namespace GHIElectronics.NETMF.FEZ
{
public enum LCDKey
{
None,
Up,
Down,
Right,
Left,
Select
}
public static class LCDDriver
{
private static bool has_init;
private const byte DISP_ON = 0xC; // Turn visible LCD on
private const byte DISP_OFF = 0xA; // Turn visible LCD off
private const byte CLR_DISP = 1; // Clear display
private const byte CUR_HOME = 2; // Move cursor home and clear screen memory
private const byte SET_CURSOR = 0x80; // SET_CURSOR + X : Sets cursor position to X
private static OutputPort lcd_rs;
private static OutputPort lcd_e;
private static OutputPort lcd_d4;
private static OutputPort lcd_d5;
private static OutputPort lcd_d6;
private static OutputPort lcd_d7;
private static AnalogIn an_key;
private static OutputPort back_light;
private static object locker;
private static Thread thread;
private static LCDKey current_key;
private static int dim_timeout;
private static int dim_counter;
private static ExtendedTimer dim_timer;
private static LCDLine line_1;
private static LCDLine line_2;
static LCDDriver()
{
has_init = false;
}
#region Methods
public static void Initialize()
{
// If already initialized, return
if (has_init) return;
// Configure LCD
lcd_rs = new OutputPort((Cpu.Pin)FEZ_Pin.Digital.Di8, false);
lcd_e = new OutputPort((Cpu.Pin)FEZ_Pin.Digital.Di9, false);
lcd_d4 = new OutputPort((Cpu.Pin)FEZ_Pin.Digital.Di4, false);
lcd_d5 = new OutputPort((Cpu.Pin)FEZ_Pin.Digital.Di5, false);
lcd_d6 = new OutputPort((Cpu.Pin)FEZ_Pin.Digital.Di6, false);
lcd_d7 = new OutputPort((Cpu.Pin)FEZ_Pin.Digital.Di7, false);
an_key = new AnalogIn((byte)FEZ_Pin.AnalogIn.An0);
back_light = new OutputPort((Cpu.Pin)FEZ_Pin.Digital.Di10, true);
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);
Thread.Sleep(50);
// Set up lock object
locker = new object();
// Set current key
current_key = LCDKey.None;
// Set up dimming
dim_timeout = 0;
dim_counter = 0;
dim_timer = new ExtendedTimer(new TimerCallback(OnDimTimeout),null,Timeout.Infinite,Timeout.Infinite);
// Create and start thread
thread = new Thread(new ThreadStart(ThreadProc));
thread.Start();
// Turn on display and clear
SendCommand(DISP_ON);
SendCommand(CLR_DISP);
// Create line wrappers
line_1 = new LCDLine(0);
line_2 = new LCDLine(1);
// Set has initialized
has_init = true;
}
public static void SendCommand(byte c)
{
lock (locker) {
lcd_rs.Write(false); // Set LCD to data mode
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);
lcd_e.Write(false); // Toggle the enable Pin
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);
lcd_e.Write(false); // Toggle the enable Pin
Thread.Sleep(1);
lcd_rs.Write(true); // Set LCD to data mode
}
}
public static void PutChar(char c)
{
lock (locker) {
byte b = (byte)c;
lcd_d7.Write((b & 0x80) != 0);
lcd_d6.Write((b & 0x40) != 0);
lcd_d5.Write((b & 0x20) != 0);
lcd_d4.Write((b & 0x10) != 0);
lcd_e.Write(true);
lcd_e.Write(false); // Toggle the enable pin
lcd_d7.Write((b & 0x08) != 0);
lcd_d6.Write((b & 0x04) != 0);
lcd_d5.Write((b & 0x02) != 0);
lcd_d4.Write((b & 0x01) != 0);
lcd_e.Write(true);
lcd_e.Write(false); // Toggle the enable pin
}
}
public static void Print(string s)
{
foreach(char c in s) PutChar(c);
}
public static void Clear()
{
SendCommand(CLR_DISP);
}
public static void CursorHome()
{
SendCommand(CUR_HOME);
}
public static void SetCursor(byte row, byte col)
{
SendCommand((byte)(SET_CURSOR | row << 6 | col));
}
public static void DisplayOn()
{
SendCommand(DISP_ON);
}
public static void DisplayOff()
{
SendCommand(DISP_OFF);
}
private static LCDKey GetKey()
{
int value = an_key.Read();
const int ERROR = 15;
if (value > 1024-ERROR) return LCDKey.None;
if (value < 0+ERROR) return LCDKey.Right;
if (value < 129 + ERROR && value > 129 - ERROR) return LCDKey.Up;
if (value < 304 + ERROR && value > 304 - ERROR) return LCDKey.Down;
if (value < 477 + ERROR && value > 477 - ERROR) return LCDKey.Left;
if (value < 720 + ERROR && value > 720 - ERROR) return LCDKey.Select;
return LCDKey.None;
}
public static void BacklightOn()
{
lock (locker) {
back_light.Write(true);
}
}
public static void BacklightOff()
{
lock (locker) {
back_light.Write(false);
}
}
private static void ThreadProc()
{
while (true) {
lock (locker) {
// Get current key
current_key = GetKey();
// If not none, turn on backlight
if (current_key != LCDKey.None) {
BacklightOn();
dim_counter = 0;
}
}
// Sleep for a bit
Thread.Sleep(10);
}
}
private static void OnDimTimeout(object state)
{
lock (locker) {
if (dim_counter >= dim_timeout) {
// Turn backlight off
BacklightOff();
// Reset counter
dim_counter = 0;
} else {
// Increment counter
dim_counter++;
}
}
}
#endregion
#region Properties
public static LCDKey CurrentKey
{
get {
// Get current key
lock (locker) {
return current_key;
}
}
}
public static int DimTimeout
{
get {
lock (locker) {
return dim_timeout;
}
}
set {
lock (locker) {
if (dim_timeout != value) {
dim_timeout = value;
dim_counter = 0;
// Start or stop dim timer depending in timeout
if (dim_timeout == 0) {
dim_timer.Change(Timeout.Infinite,Timeout.Infinite);
} else {
dim_timer.Change(0,1000);
}
}
}
}
}
public static LCDLine Line1
{
get {
return line_1;
}
}
public static LCDLine Line2
{
get {
return line_2;
}
}
#endregion
}
public class LCDLine : IDisposable
{
private object locker;
private int row;
private string text;
private int scroll_interval;
private int scroll_counter;
private string scroll_buffer;
private string last_buffer;
private bool thread_terminate;
private Thread thread;
internal LCDLine(int lcdRow)
{
locker = new object();
row = lcdRow;
text = String.Empty;
scroll_interval = 0;
scroll_counter = 0;
scroll_buffer = String.Empty;
last_buffer = String.Empty;
thread_terminate = false;
thread = new Thread(new ThreadStart(ThreadProc));
thread.Start();
}
#region Methods
public void Dispose()
{
// Flag thread to terminate
lock (locker) {
if (thread != null) thread_terminate = true;
}
}
private void ThreadProc()
{
while (true) {
lock (locker) {
// Check if we should terminate
if (thread_terminate) break;
string buffer;
// Work out what goes into the buffer
if (scroll_interval == 0) {
buffer = text;
} else {
if (scroll_buffer == String.Empty) scroll_buffer = " " + text;
if (scroll_interval == scroll_counter) {
scroll_buffer = scroll_buffer.Substring(1);
scroll_counter = 0;
} else {
scroll_counter += 10;
}
buffer = scroll_buffer;
}
// Pad or cut buffer if it's too big or too small
if (buffer.Length < 16) {
while (buffer.Length < 16) buffer += " ";
} else if (buffer.Length > 16) {
buffer = buffer.Substring(0,16);
}
// Only update the display if buffer has changed
if (buffer != last_buffer) {
LCDDriver.SetCursor((byte)row,0);
LCDDriver.Print(buffer);
last_buffer = buffer;
}
}
// Sleep for a bit
Thread.Sleep(10);
}
// Nullify thread
thread = null;
// Reset termination flag
thread_terminate = false;
}
#endregion
#region Properties
public string Text
{
get {
lock (locker) {
return text;
}
}
set {
lock (locker) {
if (text != value) text = value;
}
}
}
public int ScrollInterval
{
get {
lock (locker) {
return scroll_interval;
}
}
set {
lock (locker) {
if (scroll_interval != value) {
scroll_interval = value;
scroll_counter = 0;
scroll_buffer = String.Empty;
}
}
}
}
#endregion
}
}
Example use:
public static void Main()
{
// Start LCD display
LCDDriver.Initialize();
LCDDriver.DimTimeout = 30; // Turn off backlight after 30 seconds of no input (keys)
LCDDriver.Line1.Text = "Hello Planet Fez!";
LCDDriver.Line1.ScrollInterval = 250; // Scroll every 250ms
while (true) {
string key;
switch (LCDDriver.CurrentKey) {
case LCDKey.Select: key = "Select";
break;
case LCDKey.Up: key = "Up";
break;
case LCDKey.Down: key = "Down";
break;
case LCDKey.Left: key = "Left";
break;
case LCDKey.Right: key = "Right";
break;
default: key = "None";
break;
}
LCDDriver.Line2.Text = "Key: " + key;
Thread.Sleep(1);
}
}