Better FEZ 2x16 LCD Shield Driver

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);
            }
        }

You should place the code on http://www.fezzer.com and edit the original post to include a link to it.

I think the HD44780 display-off control code is actually 0x08 not 0x0a as you have it. Give that a try !

Josh: Using both Firefox and Chrome with Fezzer I can’t seem to upload a code example?

Sorry LloydK, I left something commented out. Try again.

Still not working correctly, now it uploads the code to the control on the page BUT when I click submit nothing happens.

Why don’t you guys get on chat so you can try it both in realtime? Just an idea

It still wouldn’t be realtime Gus, it’s 23:30 here and I’m off to bed :wink:

perhaps tomorrow, morning for us and evening for you?