Manejo de pantallas LCD con Netduino


Las pantallas de cristal líquido (LCD) son una gran opción de dispositivo de salida para mostrar caracteres alfanuméricos en tiempo real. También son muy útiles si su proyecto requiere una interfaz de usuario interactiva para la entrada de datos. Además, son de bajo costo, consumen menos energía que las pantallas LED, y le dan un aspecto más profesional a su proyecto.

Gracias a Ebedded Lab , hoy vamos a explorar cómo conectar un LCD personaje basado HD44780 a Netduino para mostrar caracteres alfanuméricos  sin usar las librerías de Crsytal Liquid , que aun siendo muy potentes  y utiles en ocasiones nos pueden dar algún problema .

Para más detalles técnicos del controlador HD44780, por favor lea la hoja de datos, así como su ejemplo de interfaz con chipKIT.

 

Configuración de Circuito y Teoría

La conexión de un LCD  es realmente simple. El LCD funciona en modo de 4 bits, y por lo tanto los pines 7 a 10 (D0-D3) de la pantalla LCD no han sido utilizados. Los cuatro bits de datos más significativos, D4-D7 (pines 12 a 14), recibe datos LCD / comando a través de pasadores Netduino E / S 7, 5, 3 y 1, respectivamente. Del mismo modo, el Registro Select (R / S) y Enable (E) las líneas de señal de la pantalla LCD son impulsados ​​por Netduino pins E / S 11 y 9, respectivamente. Pasadores LCD 1, 2, 3, 4, 15 y 16 están relacionados con la alimentación, ajuste de contraste y pantalla LED de luz de fondo, y están conectados apropiadamente como se muestra a continuación. Para entender cómo funciona LCD, tengo que apuntar a un documento diferente, ya que se explica mejor por allá. En Interconexión un LCD de caracteres, Raj explica acerca de la comunicación en el modo de 4 bits y también habla sobre los fundamentos de controlador HD44780 LCD. Para más detalles técnicos, consulte Hitachi HD44780U (LCD-II) ficha técnica.

Conexión Con Netduino / Netduino Plus

 Programa en # .NET

Hay dos maneras en que podemos mirar el programa, uno de una manera muy sencilla y la otra manera, por supuesto, más difícil.

En forma más simple no nos preocupamos acerca de lo que hay dentro de la clase LCD (o una biblioteca) y no utilizar algunos de los métodos o propiedades expuestas. Echemos un vistazo a las cosas más simples primero:

  //configuracion del LCD compatible con HD44780 de dos lineas
 var lcdProvider = new GpioLiquidCrystalTransferProvider(
 Pins.GPIO_PIN_D8, // RS
 Pins.GPIO_PIN_D9, // enable
 Pins.GPIO_PIN_D10, // d4
 Pins.GPIO_PIN_D11, // d5
 Pins.GPIO_PIN_D12, // d6
 Pins.GPIO_PIN_D13); // d7
 var lcd = new LiquidCrystal(lcdProvider);

lcd.Clear();

 lcd.Begin(16, 2);
 lcd.SetCursorPosition(0, 0);
 lcd.Write("primera linea");// Print a message to the LCD.
 

 lcd.SetCursorPosition(0, 1);

 lcd.Write("segunda linea");

Como se puede ver que hemos creado una instancia de la clase LCD y luego establecemos algunas propiedades de lo que nos gusta y simplemente llamamos al método  Write. Hay dos métodos escribir, uno mostrará el texto de la primera linea y el otro método mostrará la  segunda.

Ahora vamos a profundizar en la parte compleja. Hay varias bibliotecas por ahí, pero yo escribimos mi propia para entender lo que se cuece dentro. Echemos un vistazo a la sección constructor,

 
 /*-------------------------------------------------------------------------------+
|
| Copyright (c) 2012, Embedded-Lab. All Rights Reserved.
|
| Limited permission is hereby granted to reproduce and modify this 
| copyrighted material provided that this notice is retained 
| in its entirety in any such reproduction or modification.
|
| Author: ANir
| First Version Date: 2012/12/27
+-------------------------------------------------------------------------------*/


using System;
using System.Threading;
using Microsoft.SPOT.Hardware;
using System.Text;

namespace EmbeddedLab.NetduinoPlus.Day2.Display
{
 public class LCD
 {
 #region Constructor
 public LCD(Cpu.Pin rs, Cpu.Pin enable,
 Cpu.Pin d4, Cpu.Pin d5, Cpu.Pin d6, Cpu.Pin d7,
 byte columns, Operational lineSize, int numberOfRows, Operational dotSize)
 {
 RS = new OutputPort(rs, false);
 Enable = new OutputPort(enable, false);
 D4 = new OutputPort(d4, false);
 D5 = new OutputPort(d5, false);
 D6 = new OutputPort(d6, false);
 D7 = new OutputPort(d7, false);

 Columns = columns;
 DotSize = (byte)dotSize;
 NumberOfLines = (byte)lineSize;
 NumberOfRows = numberOfRows;

 Initialize();
 }
 #endregion


 #region Public Methods
 public void Show(string text, int delay, bool newLine)
 {
 if (newLine) dirtyColumns = 0;
 foreach (char textChar in text.ToCharArray())
 {
 ResetLines();
 Show(Encoding.UTF8.GetBytes(textChar.ToString()));
 dirtyColumns += 1;
 Thread.Sleep(delay);
 }
 }

 public void Show(string text)
 {
 string[] splitedText = SplitText(text);
 Show(splitedText);
 }


 public void ClearDisplay()
 {
 SendCommand((byte)Command.Clear);
 currentRow = 0;
 dirtyColumns = 0;
 }
 public void GoHome()
 {
 SendCommand((byte)Command.Home);
 currentRow = 0;
 dirtyColumns = 0;
 }
 public void JumpAt(byte column, byte row)
 {
 if (NumberOfLines == (byte)Operational.DoubleLIne) row = (byte)(row % 4);
 else row = (byte)(row % 2);

 SendCommand((byte)((byte)Command.SetDdRam | (byte)(column + rowAddress[row]))); //0 based index
 }

 public void PushContentToLeft()
 {
 SendCommand(0x18 | 0x00);
 }

 public void PushContentToRight()
 {
 SendCommand(0x18 | 0x04);
 }

 #endregion


 #region Private Methods
 private void Initialize()
 {
 //initialize fields
 isVisible = true;
 showCursor = false;
 isBlinking = false;

 rowAddress = new byte[] { 0x00, 0x40, 0x14, 0x54 };
 firstHalfAddress = new byte[] { 0x10, 0x20, 0x40, 0x80 };
 secondHalfAddress = new byte[] { 0x01, 0x02, 0x04, 0x08 };

 currentRow = 0;
 dirtyColumns = 0;

 Thread.Sleep(50); // must wait for a few milliseconds


 // RS to high = data transfer
 // RS to low = command/instruction transfer
 RS.Write(false);

 // Enable provides a clock function to synchronize data transfer
 Enable.Write(false);


 // Set for 4 bit model
 Write(0x03, secondHalfAddress);
 Thread.Sleep(4);
 Write(0x03, secondHalfAddress);
 Thread.Sleep(4);
 Write(0x03, secondHalfAddress);
 Thread.Sleep(150);
 Write(0x02, secondHalfAddress);


 // Set the LCD properties 
 byte operationalValue = (byte)((byte)Operational.FourBit | (byte)NumberOfLines | (byte)DotSize);
 SendCommand((byte)((byte)Command.Operational | operationalValue));

 UpdateDisplayOptions();

 ClearDisplay();

 byte entranceValue = (byte)Entrance.FromLeft | (byte)Entrance.ShiftDecrement;
 SendCommand((byte)((byte)Command.Entrance | entranceValue));

 }

 private string[] SplitText(string str)
 {
 if (str.Length > Columns * NumberOfRows) str = str.Substring(0, Columns * NumberOfRows);

 int stringArrayCounter = 0;
 dirtyColumns = 0;

 char[] charArray = str.ToCharArray();
 int arraySize = (int)System.Math.Ceiling((double)(str.Length + dirtyColumns) / Columns);
 string[] stringArray = new string[arraySize];

 for (int i = 0; i < charArray.Length; i++)
 {
 if (dirtyColumns < Columns)
 {
 stringArray[stringArrayCounter] = stringArray[stringArrayCounter] + charArray[i];
 dirtyColumns += 1;
 }
 else
 {
 dirtyColumns = 1;
 stringArrayCounter += 1;
 stringArray[stringArrayCounter] = stringArray[stringArrayCounter] + charArray[i];
 }
 }
 return stringArray;
 }


 private void ResetLines()
 {
 if (dirtyColumns == 0) return;
 if (dirtyColumns % Columns == 0)
 {
 currentRow += 1;
 JumpAt((byte)0, (byte)(currentRow));
 }
 }

 private void Write(byte[] data)
 {
 foreach (byte value in data)
 {
 Write(value, firstHalfAddress); // First half
 Write(value, secondHalfAddress); // Second half
 }
 }

 private void Write(byte value, byte[] halfAddress)
 {
 D4.Write((value & halfAddress[0]) > 0);
 D5.Write((value & halfAddress[1]) > 0);
 D6.Write((value & halfAddress[2]) > 0);
 D7.Write((value & halfAddress[3]) > 0);

 Enable.Write(true);
 Enable.Write(false);
 //Debug.Print("Wrote " + value.ToString());
 }

 private void SendCommand(byte value)
 {
 RS.Write(false); // command/instruction transfer
 Write(new byte[] { value });

 Thread.Sleep(5);
 }

 private void UpdateDisplayOptions()
 {
 byte command = (byte)Command.DisplayControl;
 command |= isVisible ? (byte)DisplayControl.ScreenOn : (byte)DisplayControl.ScreenOff;
 command |= showCursor ? (byte)DisplayControl.CursorOn : (byte)DisplayControl.CursorOff;
 command |= isBlinking ? (byte)DisplayControl.BlinkBoxOn : (byte)DisplayControl.BlinkBoxOff;

 SendCommand(command);
 }

 private void Show(string[] splitedText)
 {
 foreach (string text in splitedText)
 {
 JumpAt((byte)0, (byte)(currentRow));
 currentRow += 1;

 Show(Encoding.UTF8.GetBytes(text));
 }
 }

 private void Show(byte[] bytes)
 {
 RS.Write(true);
 Write(bytes);
 }

 #endregion

 #region Public Properties

 public bool IsVisible
 {
 get { return isVisible; }
 set { isVisible = value; UpdateDisplayOptions(); }
 }

 public bool IsBlinking
 {
 get { return isBlinking; }
 set { isBlinking = value; UpdateDisplayOptions(); }
 }

 public bool ShowCursor
 {
 get { return showCursor; }
 set { showCursor = value; UpdateDisplayOptions(); }
 }

 #endregion

 #region Fields
 private OutputPort RS;
 private OutputPort Enable;
 private OutputPort D4;
 private OutputPort D5;
 private OutputPort D6;
 private OutputPort D7;
 private byte Columns;
 private byte DotSize;
 private byte NumberOfLines;
 private byte[] rowAddress;
 private byte[] firstHalfAddress;
 private byte[] secondHalfAddress;
 


 private int currentRow;
 private int dirtyColumns;
 private int NumberOfRows;

 private bool isVisible;
 private bool showCursor;
 private bool isBlinking;
 #endregion


 #region Enums
 public enum Command : byte
 {
 Clear = 0x01,
 Home = 0x02,
 Entrance = 0x04,
 DisplayControl = 0x08,
 Move = 0x10,
 Operational = 0x20,
 SetCgRam = 0x40,
 SetDdRam = 0x80
 }

 public enum Entrance : byte
 {
 FromRight = 0x00,
 FromLeft = 0x02,
 ShiftIncrement = 0x01,
 ShiftDecrement = 0x00
 }

 public enum DisplayControl : byte
 {
 ScreenOn = 0x04,
 ScreenOff = 0x00,
 CursorOn = 0x02,
 CursorOff = 0x00,
 BlinkBoxOn = 0x01,
 BlinkBoxOff = 0x00
 }

 public enum Operational : byte
 {
 Dot5x10 = 0x04,
 Dot5x8 = 0x00,
 SingleLine = 0x00,
 DoubleLIne = 0x08,
 FourBit = 0x00
 }
 #endregion
 }
}

En la sección constructor, que básicamente crea cierta Outport, guarda las propiedades de LCD y luego llama al método Initialize. En este método, fijamos las propiedades visuales, inicializar algunas matrices y luego preparar la pantalla LCD para el modo de comunicación de 4 bits.

 private void Initialize()
 {
 //initialize fields
 isVisible = true;
 showCursor = false;
 isBlinking = false;

 rowAddress = new byte[] { 0x00, 0x40, 0x14, 0x54 };
 firstHalfAddress = new byte[] { 0x10, 0x20, 0x40, 0x80 };
 secondHalfAddress = new byte[] { 0x01, 0x02, 0x04, 0x08 };

 currentRow = 0;
 dirtyColumns = 0;

 Thread.Sleep(50); // must wait for a few milliseconds


 // RS to high = data transfer
 // RS to low = command/instruction transfer
 RS.Write(false);

 // Enable provides a clock function to synchronize data transfer
 Enable.Write(false);


 // Set for 4 bit model
 Write(0x03, secondHalfAddress);
 Thread.Sleep(4);
 Write(0x03, secondHalfAddress);
 Thread.Sleep(4);
 Write(0x03, secondHalfAddress);
 Thread.Sleep(150);
 Write(0x02, secondHalfAddress);


 // Set the LCD properties 
 byte operationalValue = (byte)((byte)Operational.FourBit | (byte)NumberOfLines | (byte)DotSize);
 SendCommand((byte)((byte)Command.Operational | operationalValue));

 UpdateDisplayOptions();

 ClearDisplay();

 byte entranceValue = (byte)Entrance.FromLeft | (byte)Entrance.ShiftDecrement;
 SendCommand((byte)((byte)Command.Entrance | entranceValue));

 }

 

Ahora, echemos un vistazo a los métodos que son críticas para mostrar el texto a la pantalla LCD. El primer método Show nos permite mostrar la letra texto dado por carta como se puede ver el bucle está estructurado para cada carácter en el texto dado.

private void Show(string[] splitedText)
 {
 foreach (string text in splitedText)
 {
 JumpAt((byte)0, (byte)(currentRow));
 currentRow += 1;

 Show(Encoding.UTF8.GetBytes(text));
 }
 }

El segundo método Show muestra básicamente todo el texto a la vez, pero antes de que lo haga algún formato para que el texto aparecerá continuamente. Confía en mí mostrando texto dado de una manera continua es uno de lo difícil que resolví en esta clase LCD.

 private void Show(byte[] bytes)
 {
 RS.Write(true);
 Write(bytes);
 }

Por último, el método que escribe información en la pantalla LCD se realiza bajo el método de escritura. El primer método Write llama al método send Escribir pasa el valor de escritura y la dirección donde enviar la información. El segundo método de escritura básicamente escribe la información en la pantalla LCD.

  private void Write(byte value, byte[] halfAddress)
 {
 D4.Write((value & halfAddress[0]) > 0);
 D5.Write((value & halfAddress[1]) > 0);
 D6.Write((value & halfAddress[2]) > 0);
 D7.Write((value & halfAddress[3]) > 0);

 Enable.Write(true);
 Enable.Write(false);
 //Debug.Print("Wrote " + value.ToString());
 }
 

Producción

Después de conectar un par de cables, al ejecutar su código y visualizar el texto que usted quiere, pone una sonrisa en la cara. Dependiendo del método que está utilizando dará a obtener resultados diferentes Show, se mostrará letra por letra y otra mostrará el texto entero de una vez. Al igual que en el video, al girar el potenciómetro, los cambios de contraste de la pantalla.

 

  public static void Main()
 {
 LCD lcd = new LCD(
 Pins.GPIO_PIN_D8, // RS
 Pins.GPIO_PIN_D9, // Enable
 Pins.GPIO_PIN_D10, // D4
 Pins.GPIO_PIN_D11, // D5
 Pins.GPIO_PIN_D12, // D6
 Pins.GPIO_PIN_D13, // D7
 16, // Number of Columns 
 LCD.Operational.DoubleLIne, // LCD Row Format
 1, // Number of Rows in LCD
 LCD.Operational.Dot5x8); // Dot Size of LCD


 lcd.ShowCursor = true;
 lcd.Show("CRN", 200, true);
 Thread.Sleep(5000); // reading time for the viewer
}

 

Descargas

1) Código C # .NET (archivo Solution)

2) archivo Flitizing

 

 

 

Fuente  aqui

.NET Micro Framework – Usando display alfanumericos LCDs


.NET Micro Framework incluye capacidades gráficas de alcance con las bibliotecas WPF  para algunas tarjetas de desarrollo de alta gama bastante ( Tahoe II , ChipworkX o FEZ Cobra por nombrar algunos)donde se  incluyen gráficos de lujo pantallas TFT, a menudo con soporte a  entrada táctil  también . Pero esto tiene  significativamente mayores costos, y requiere una CPU rápida. Por lo tanto, podría parecer que si está utilizando un tablero mucho más barato como Netduino o uno de la familia FEZ, que éstos están condenados a depender sólo parpadear LEDs. No tanto pues en la mayoría de los escenarios  un display LCD alfanumérico podría ser una alternativa barata (o si tiene mayor presupuesto  usted también podría considerar el uso de una pantalla OLED que también trabaja con Netduino ).

alt Para un proyecto más amplio de este post  vamos a  utilizar un LCD alfanumérico para mostrar todo tipo de información de estado. Prácticamente todos los LCDs populares son controlados por un chipset HD44780 compatible con  interfaz paralela. Hay gran variedad de estas pantallas disponibles en varias combinaciones de luz de fondo y el color del texto. El tamaño común es 16×2 caracteres (16 columnas en 2 filas). Eche un vistazo a la amplia selección de pantallas LCD disponible en Sparkfun

Cuando usted compra un  LCD observe  bien si  ese alimenta a 5V o 3V3 versión. Estoy seguro de que ambos funcionan bien con Netduino (que utiliza la lógica 3V3), pero asegúrese de conectarlos para corregir pin fuente de alimentación.

Estas pantallas LCD son bastante fáciles de conectar  y programar. Si nos fijamos en la imagen de arriba notar la cabecera 16pin en la parte superior de la pantalla. El chipset HD44780 utiliza la interfaz paralela para la comunicación de datos y soporta los modos de 8 bits y 4 bits. En el primer caso se utilizará al menos 10 pines de salida digitales del microcontrolador, en el más tarde al menos 6. El pasador de R / W (escritura y lectura) de bits se puede conectar permanentemente a tierra. Puede encontrar las instrucciones de cableado en los ejemplos para la biblioteca Arduino cristal líquido – que funcionará de la misma manera en la Netduino. Para evitar la soldadura también se puede tratar de utilizar el Teclado LCD Escudo DFRobot para Arduino , del Sr. Kogoro Kotobuki ( kotobuki ), es el diseño de un escudo LCD similar que parece funcionar muy bien con Netduino .

Sin embargo, si tomamos el enfoque directo, la pantalla LCD utilice al menos seis de los pines digitales disponibles. Si utiliza un microcontrolador que expone un número masivo de puertos IO (como FEZ Rhino o próxima FEZ Panda ) esto podría estar bien, pero en caso de Netduino o tableros similares que tiene sólo un puñado de los pines digitales esto puede ser no apto. En este caso es posible que desee para multiplexar la salida de más de un menor número de líneas. En mi anterior post ya os hablamos sobre el uso de registros de desplazamiento para evitar tal situación y en este caso se puede reducir el número de salidas a sólo tres.

Otra alternativa podría ser la de comprar un LCD Habilitado Serial . Por lo general, incluyen pensión hija tan especial en la parte posterior (de ahí llamado «mochila») que tiene una MCU responsable de traducir la comunicación UART a la interfaz de la pantalla LCD. Así que si puedes sacrificar uno de los puertos UART en el Netduino esta podría ser una buena opción, pero por lo general cuesta alrededor de $ más que los LCD regulares 10. También podrás comprar la mochila serie solo, por ejemplo el de Sparkfun o el Kit LCD Serial LCD117 de dispositivos modernos .

Para este proyecto  se va a utilizar el mismo registro 74HC595 tal y como Pavel Bansky demostró cómo hacer esto en dispositivos .NET Micro Framework. En su primer post que muestra cómo conectar LCD con 3 cables usando el registro de desplazamiento 4094, y luego en el segundo puesto , añadió soporte para un I 2 C de 8 bits de ampliación de puertos. He modificado y ampliado su biblioteca para incluir funcionalidad disponible en la biblioteca de Arduino LiquidCrystal .

Pavel introdujo una abstracción agradable en su aplicación para separar las funciones de alto nivel LCD de interfaz de transporte subyacente en clases separadas. Así, el constructor de la clase de nivel superior LiquidCrystal, debe proporcionar una instancia de una clase que implementa la interfaz ILiquidCrystalTransferProvider. Esto permite utilizar esta biblioteca con todos los métodos de comunicación descritas anteriormente. En el código fuente ya se encuentra un GpioLiquidCrystalTransferProvider y Shifter74Hc595LiquidCrystalTransferProvider para el registro 74HC595 turno. Más tarde me voy a añadir soporte para el I 2 C de expansión del puerto. Me dieron una bonita mochila LCD de JeeLabs tienda que utiliza el chip MCP23008 pero no sé aún de lo diferente que es de PCF8574P de Pavel. En preparación para este código de manejo de algunas poco comunes ya que se abstrae de la clase BaseShifterLiquidCrystalTransferProvicer.

Aquí se puede ver el cableado he utilizado:

Wiring 16x2 LCD to Netduino via shift register

Observe que porque esta vez voy a usar interfaz SPI los datos en serie está conectado al pin 11 (MOSI) y reloj pin se conecta al pin 13 (SPCK). Pin 10 se conecta a la clavija del pasador, y en el código que se pasa como Slave Select pin al objeto SPIConfiguration. Ya que vamos a utilizar siete salidas desde el registro de desplazamiento la producción restante se conecta a través 2N2222A transistor para controlar la luz de fondo del LCD. Esto nos permite encender la luz de dentro o fuera del código. Finalmente, el potenciómetro de 10K se utiliza para controlar el contraste de la pantalla.

_MG_1274 Foto de la derecha muestra la placa  para el LCD  Tenga en cuenta que debido a que la junta se monta «al revés» las salidas del registro de desplazamiento a LCD se invierten, pero es fácil corregirlo en el código (Shifter74Hc595LiquidCrystalTransferProvider constructor tiene la opción BitOrder opcional).

A continuación puede encontrar una lista de los métodos expuestos por la clase LiquidCrystal:

  • Comience (columnas, líneas) – Utilice este método para inicializar el LCD. Especifica las dimensiones (anchura y altura) de la pantalla.
  • Borrar () – Borra la pantalla LCD y posiciona el cursor en la esquina superior izquierda.
  • Inicio () – Posiciones el cursor en la parte superior izquierda de la pantalla LCD.
  • SetCursorPosition (columna, fila) – Colocar el cursor del LCD; es decir, establecer la ubicación en la que se mostrará el texto escrito posterior a la pantalla LCD.
  • ScrollDisplayLeft () – Desplaza el contenido de la pantalla (texto y cursor) un espacio a la izquierda.
  • ScrollDisplayRight () – Desplaza el contenido de la pantalla (texto y cursor) un espacio a la derecha.
  • Write (texto) – Escribe un texto a la pantalla LCD.
  • Escribe (buffer, offset, count) – Escribe un número especificado de bytes a la pantalla mediante los datos de una memoria intermedia.
  • WriteByte (datos) – Envía un byte de datos a la pantalla.
  • CreateChar (ubicación, charmap)

Y sus propiedades:

  • Luz de fondo – Enciende la luz de fondo del LCD encendido o apagado.
  • BlinkCursor – mostrar u ocultar el cursor de bloque parpadeante en la posición a la que se escribirá el siguiente carácter.
  • ShowCursor – Mostrar u ocultar el cursor del LCD: un guión bajo (línea) en la posición a la que se escribirá el siguiente carácter.
  • Visible – Activa la pantalla LCD encendida o apagada. Esto restaurará el texto (y cursor) que estaba en la pantalla.
  • Codificación – Obtener o establecer la codificación utilizada para asignar la cadena en códigos de bytes que se envían LCD. UTF8 se utiliza por defecto.

 

Y aquí el código de ejemplo de  “hello world” con un lcd:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public static void Main()
{
    // create the transfer provider
    var lcdProvider = new Shifter74Hc595LiquidCrystalTransferProvider(<span class="skimlinks-unlinked">SPI_Devices.SPI1</span>,
        <span class="skimlinks-unlinked">SecretLabs.NETMF.Hardware.Netduino.Pins.GPIO_PIN_D10</span>);
    // create the LCD interface
    var lcd = new LiquidCrystal(lcdProvider);
    // set up the LCD's number of columns and rows:
    <span class="skimlinks-unlinked">lcd.Begin(16</span>, 2);
    // Print a message to the LCD.
    <span class="skimlinks-unlinked">lcd.Write("hello</span>ld!");
    while (true)
    {
        // set the cursor to column 0, line 1
        lcd.SetCursorPosition(0, 1);
        // print the number of seconds since reset:
        <span class="skimlinks-unlinked">lcd.Write((Utility.GetMachineTime().Ticks</span> / 10000).ToString());
        <span class="skimlinks-unlinked">Thread.Sleep(100</span>);
    }
}

Fuente aqui