Some Assembly Required - What’s Playing? Interfacing Your Media with an External LED Screen using Visual Studio 2005 Express


Scott Hanselman

Summary: To kick off his new "Some Assembly Required" column, Scott Hanselman explains how to use Visual C# 2005 Express Edition and the .NET Framework 2.0 to control an LED Display Panel and interface it with Windows Media Player or iTunes to show "What's Playing?"

Interfacing with the World

Welcome to my new MSDN Hobbyist column "Some Assembly Required." This column is all about using C# and the .NET Framework to interface with gadgets and hardware - from your cell phone to your MP3 player, from your ReplayTV to your Windows Media Center PC, from your COM ports to a GPS device.

I'm constantly surprised by how much can be accomplished with so little code as I wander the Web and the Blogosphere. We are truly standing on the shoulders of giants. I recall manually configuring jumpers to get my Sound Blaster off IRQ5, and now I can plug my 256 Meg USB Key directly into the hub on my flat screen LCD with zero configuration. Things have certainly changed.

What's Playing?

When I first read Duncan Mackenzie's article "Coding in the Blue Glow" I was totally digging his CrystalFontz LCD Panel. I knew one day I wanted to write an application to report whatever music I was currently playing. It seems that everyone who's blogging includes a little tagline telling you what they're listening to this instant, why shouldn't I have a backlight LCD informing my officemates?

Our goal is to write a .NET Framework-based application using Visual C# 2005 Express Edition that interfaces with a CrystalFontz XE634 4-line by 20-character or XE632 2-line by 16-character display. Both are USB displays with associated virtual COM ports. These virtual ports are very useful to us because there's no standard way to address a USB device, but programming to a COM port is an easily understood problem for us to solve.

The application will determine the song currently playing in either Windows Media Player or iTunes and output the title, artist name, album and song length to the LCD panel.

Figure 1. The CrystalFontz USB LCD displays have an associated “virtual” COM Port.

After installing the free drivers for the CrystalFontz LCD the device manager shows both the USB device and the virtual COM port. I take note that the virtual COM port is COM3 on my system. I'll want to make that a configurable part of the application.

The Magic that is the new System.IO.Ports Namespace

That column was written in 2003 and Duncan lamented the lack of Serial Port support in the .NET Framework 1.x. It was a major drag and folks, particularly hobbyists, have been complaining about it on the boards for years. Well, complain no more because System.IO.Ports is here in the 2.0 BCL and it's very intuitive to use!

Here is a subset of the methods and properties of the System.IO.Ports.SerialPort class that comes new with the .NET BCL 2.0:

public class SerialPort : System.ComponentModel.Component
public System.IO.Stream BaseStream {get; }
public int BaudRate {get; set; }
public void Close();
public int DataBits {get; set; }
public bool DsrHolding {get; }
public bool DtrEnable {get; set; }
public System.Text.Encoding Encoding {get; set; }
public static string[] GetPortNames();
public static int InfiniteTimeout;
public bool IsOpen {get; }
public void Open();
public System.IO.Ports.Parity Parity {get; set; }
public int Read(char[] buffer, int offset, int count);
public int Read(byte[] buffer, int offset, int count);
public int ReadBufferSize {get; set; }
public int ReadByte();
public int ReadChar();
public string ReadExisting();
public string ReadLine();
public string ReadTo(string value);
public int ReceivedBytesThreshold {get; set; }
public SerialPort(string portName, int baudRate, System.IO.Ports.Parity parity, int dataBits, System.IO.Ports.StopBits stopBits);
public SerialPort(string portName, int baudRate, System.IO.Ports.Parity parity, int dataBits);
public SerialPort(string portName, int baudRate, System.IO.Ports.Parity parity);
public SerialPort(string portName, int baudRate);
public SerialPort(string portName);
public System.IO.Ports.StopBits StopBits {get; set; }
public void Write(byte[] buffer, int offset, int count);
public void Write(char[] buffer, int offset, int count);
public void Write(string str);
public void WriteLine(string str);

Since I'll be writing data (rather than reading it) to the LCD, I suspect I'll be using some of the Write() overloads, as well as Open() and IsOpen. I'll want to read the hardware specification for the LCD Display that I'm using to create an LCDPanel class that models the Display's capabilities. Note: The spec for the 634 I'm using corresponds with hardware v2.0 and firmware v2.0, but the concepts in this article can be applied to any LCD panel with a serial interface.

Reading a Hardware Spec

The CrystalFontz Hardware Specification has a section called "Explanation of Control Functions" that lists each of the commands that can be sent to the LCD Panel. Here are a few sentences that stand out and will drive the implementation:

The Crystalfontz intelligent serial displays will accept “plain ASCII” characters and display them on the screen at the current cursor position. For instance, if you send “Hello World”, the display shows “Hello World”.

That's useful, and it means we can perform a "Hello World" test with the following code.

SerialPort port = new SerialPort("COM3",19200);
port.Write("Hello World");

My LCD Panel is on COM3 as seen in Figure 1, and the specification, as well as the LCD's own boot up screen, indicate that it's running at 19.2k or 19200 bits per second.

Now that I can write out a few things to the LCD Panel, I'll design a class that exposes the useful things about the Panel (what it can do) and hides a reasonable amount of the details (what's hard). After reading the spec, here are the definitions for the LCDPanel Class. Notice that our LCDPanel contains a SerialPort. When the LCDPanel is constructed, the developer can pass in the PortNumber and Baud Rate as well as an enum describing what model of panel it is. We'll open the SerialPort in the constructor and include not only a Close() method, but we'll also make the LCDPanel "IDisposable." This will allow use of the Using() keyword in both C# and VB that will automatically call Dispose (which will in turn call Close).

 public class LCDPanel : IDisposable
        private SerialPort port;
        private LCDType type;

        public LCDPanel(LCDType lcdType, int portNumber, int baud);
        public void Close()       

        public void Write(string text)
        public void Write(string[] lines)
        public void Write(byte[] bytes)
        public void Write(byte aByte)
        public void Write(int anInt)
        public void Write(Command aCommand)
        public void Write(HorizontalGraphStyle aStyle)
        public void WriteToRow(string text, int row)
        public void WriteToRow(string text, int row, TextAlignment alignment)
        public void WriteToRow(string text, int row, TextAlignment alignment, bool clearRow) 

        public int MaxWidth

        public void SetContrast(int level)
        public void SetBacklight(int level)
        public void SetCursorPosition(int column, int row)
        public void ShowHorizontalBarGraph(HorizontalGraphStyle style, int startColumn, int endColumn, int lengthInPixels, int row)
        public void SetMarqueeString(string marquee)
        public void StartMarquee(int row, int pixelShift, int updateSpeed)
        public void StopMarquee()
        public void MoveUp()
        public void MoveDown()
        public void MoveRight()
        public void MoveLeft()

It might look complicated, but for now, just focus on the Write() method overloads. There are seven of them, and three for WriteToRow. The ones that take a string will write out the string to a port, just like we did with our Hello World test earlier. We'll also write out the same text to System.Diagnostics.Trace.Write for debugging purposes. 

public void Write(string text)

In the "Control Functions" section of the CrystalFontz Hardware Specification it lists out each of the commands that the LCD panel understands.

In this manual, for "Binary" data the notation \xxx is used, where xxx is the decimal representation of the number. \000 to \255 cover all possible values for a character.

So, in this specification \000 means a byte "0" and \255 is byte "255." If we're going to be writing out bytes, it's fortunate that System.IO.Ports.SerialPort.Write() has an overload that takes a byte array. We can now add three implement Write() methods for our LCDPanel class, one that takes a byte array, which maps directly to the underlying SerialPort, one that takes a single byte and calls laterally to the byte array overload. A final class takes an int and down-casts it to a byte. Note that in these three functions we're adding a lot of flexibility to the programmer but there's only one call to port.Write in the code. Making smart and thoughtful use of overloads is a great way to add flexibility to a developer-focused API, but adds additional testing burden.

public void Write(byte[] bytes)
    if (bytes == null) throw new ArgumentNullException("bytes");
    port.Write(bytes, 0, bytes.Length);

public void Write(byte aByte)
    Write(new byte[] { aByte });

public void Write(int anInt)

The specification lists out a pile of Commands that can be sent to the LCD Panel. Here are a few from the spec:

\001 Cursor Home
\002 Hide Display
\003 Restore Display
\004 Hide Cursor
\005 Show Underline Cursor
\006 Show Block Cursor
\007 Show Inverting Block Cursor

Looks like it's time for an enum!

public enum Command : byte
    CursorHome = 1,
    HideDisplay = 2,
    RestoreDisplay = 3,
    HideCursor = 4,
    ShowUnderlineCursor = 5,
    ShowBlockCursor = 6,
    ShowInvertingBlockCursor = 7,

Now we can send not only text, but also single byte commands to the LCD Panel. However, some commands are multi-byte, meaning that you send the command byte then a parameter. For example, from the Hardware Specification:

Backlight Control (\014 ; Control N)
Send "Control-N", followed by a byte from 0-100 for the backlight brightness. 0=OFF, 100=ON, intermediate values will vary the brightness. There are a total of 25 possible brightness levels.

We could leave the LCDPanel class with just Write() methods, but for the class to be convenient enough to use often, it'd be nice to have methods to handle complex functions. We have a command enum and enough Write() methods to create a SetBacklight() method that encapsulates the command along with it's param. We'll also throw an ArgumentOutOfRangeException if the level isn't in line with the specification.

public void SetBacklight(int level)
    if (level < 0 || level > 100) throw new ArgumentOutOfRangeException("Backlight Level must be between: 0 = OFF and 100 = ON");

Now, we'll repeat this thinking with each of the commands in the specification that you want to expose in the LCDPanel class.  I've included 95% of the commands that the CrystalFontz 634 supports in this article's sample code.  

Getting Data from Media Player (and iTunes)

Windows Media player includes support for Plug-ins and the one we'll be using is what they call the "Windows Media Player 9 Series Fun Pack," specifically the Blogging Plug-in. This Plug-In works with Media Player 9 and 10. The Blogging Plug-in does two things, first it adds the artist, song, and album name to the Windows Media Player 9 Series title bar, but more importantly it also populates a specific key in the registry with this information. The idea for this Fun Pack came from the fact that bloggers like to announce what music they are listening to when they post to their blog, and the blogging software they use can check for this information and include it in their post.

Since we're not blogging, we're sending the information to an LCD Panel, the Blogger Plug-In might be better named the "Windows Media What's Playing Publisher Plug-In," but no one asks me when they name these things. Anyway, the What's Playing data is published to four registry values under HKCU\Software\Microsoft\MediaPlayer\CurrentMetadata.

We'll create an (EXE) executable that uses our LCDPanel assembly and does the retrieval. We've designed the LCDPanel class to be reusable, so we'll keep it separate from the code that gathers music data. We might want to make other programs that use the LCDPanel later. The Registry is easy to access with the Microsoft.Win32.RegistryKey class.

RegistryKey currentMetadata = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\MediaPlayer\CurrentMetadata", false);
if (currentMetadata != null)
    string newCurrentTitle = currentMetadata.GetValue("Title", "No Title") as string;
   string newCurrentAlbum = currentMetadata.GetValue("Album", "No Album") as string;
    string newCurrentAuthor = currentMetadata.GetValue("Author", "No Author") as string;
    string newDurationString = currentMetadata.GetValue("durationString", "No Duration") as string;

Registry.CurrentUser.OpenSubKey will return null if the key doesn't exist, so it's important to be proactive. GetValue() on the returned RegistryKey is called once for each of the four values. GetValue() is a little less forgiving and takes your preferred default value as its second parameter, so if "Title" isn't available "No Title" will be returned. We'll run this code in a timer, perhaps every two seconds. Since we'll be accessing the registry each time, we'll want to save the strings we retrieved previously and compare them to the new values. There's no need to update the LCD display if the song being played hasn't changed.

As an aside: Personally, I'm more of an iTunes person than a Windows Media person, at least so far. Anyway, one fellow has seen fit to create a Plug-In of his own for iTunes that populates these Windows Media registry keys with what's playing in iTunes! What does that mean for us? It means we won't have to change this What's Playing LCD application at all to work with iTunes. I just dropped hi iTunes Plug-In into Program Files/iTunes/Plug-Ins and was able to retrieve track information from the same registry keys.

Making the Connection

So far we've proven that we can retrieve data from the Registry using either the Windows Media Blogging Plug-In or the iTunes Plug-In, and we can can send text and commands to a CrystalFontz LCD Panel. Now let's tie them together. We'll create a WinForms application that will live in the Tray (that little row of icons down in the TaskBar near the clock.) 

Create a new Windows application in Microsoft Visual C# 2005 Express Edition (or VB if you like) and drag a ContextMenu control and a Timer control onto the form. We'll have the Timer fire every two seconds (2000 ms) to retrieve the data from the registry and write it out to the LCD panel.


AssemblyRequired6Since we want the application to be in the Tray only, we will hide the main form as soon as possible and hook our context menu up to the NotifyIcon control. When someone right clicks on the icon in the Tray, the context menu will appear showing a single menu item "Close." In the Click handler of the Close menu item we'll exit the application.

The tick event handler of the timer control will be called every two seconds when we set it's interval to 2000. Its event handler is our opportunity to check the registry and write our findings out to the LCD panel.  Additionally we'll call the ShowBalloon method to pop a balloon up from the tray.


This application only reports "What's Playing" to the LCD, but the concepts in this article along with the features in Visual C# 2005 Express Edition enable you to create any number of applications to interface with your LCD Screen. Here are some ideas to get you started:

  • Check an RSS Feed and display the latest news.
    • (Some sample code to get you started is hidden in the comments of this application, and the RSS classes you need come with the Visual C# 2005 Express Edition's Screen Saver sample application.)
  • Check your email, either via POP3 or Outlook MAPI, and display today's email.
  • Call Web Service and display the Traffic or Ski Conditions.
  • Display the “Upcoming Recordings” for Media Center.
  • Use System.IO.DriveInfo to display free space on each of your drives.
  • Display the status of all the files being currently downloaded
  • Both CrystalFontz screens support horizontal bar graphs and I've included sample code.

Have fun and have no fear when faced with the words - Some Assembly Required!

Scott Hanselman is the Chief Architect at the Corillian Corporation, an eFinance enabler. He has twelve years experience developing software in C, C++, VB, COM, and most recently in VB.NET and C#. Scott's proud to be a Microsoft RD and MVP. He is co-author of a book on ASP.NET 2.0 with Bill Evjen and company, which will be released later in 2005. His thoughts on the Zen of .NET, Programming and Web Services can be found on his blog at


The Discussion

  • User profile image
    Matney X

    It's too bad you didn't include a finished zip of the program, at the end.  This is EXACTLY what I'm looking for, but I don't have the coding experience, or the programs to do it.

    If you do decide to release this into the world, let me know!  nave of hearts (one word, no spaces or underscores) at hotmail dot com.

  • User profile image

    I think its very bad articale becouse it does not has any sample to learning

  • User profile image

    @Yaman:  It has stuff to learn, it teaches you how to interface with a device you don't have APIs with.

  • User profile image

    Let me see if Scott still has the source laying around

  • User profile image

    how about adding effects when displaying the text... how do you do that?

  • User profile image

    @LED up to you on how to create that.  There has to be some 2D Sprite animation examples out there.

  • User profile image


    I am new at this way. Also i am computer engineering strudent. I want do something about windows embedded systems. Before this, i want to do same project like this.

Comments closed

Comments have been closed since this content was published more than 30 days ago, but if you'd like to continue the conversation, please create a new thread in our Forums, or Contact Us and let us know.