lunes, 2 de abril de 2018

Utilizar un LCD mediante el bus I2C con Raspberry Pi y PI4J

Quería decir 'Hello World!'... :-/
Si pensamos utilizar Raspberry Pi para controlar un sistema electrónico más o menos pequeño e independiente, lo más probable es que tengamos que idear un sistema de entrada/salida diferente del teclado/ratón/pantalla.

Es verdad que las funciones de red que incorporan los RPI (que incluyen wifi y bluetooth en el RPI Zero W) facilitan la comunicación con él y por tanto el control del dispositivo. Pero aún así, seguramente tengamos que incluir algún tipo de entrada más cercana al hardware (pulsadores, por ejemplo) y algún tipo de indicador de funcionamiento del sistema (por ejemplo, con leds).

Para mostrar información medianamente detallada, un LCD (Liquid Crystal Display) es una buena opción, económica y versátil. En la actualidad es muy fácil encontrar LCDs basados en el chipset HD44780 de Hitachi (o compatibles con él). Estos dispositivos están orientados a la visualización de caracteres, y, por tanto, pueden usarse cuando la información que se desea mostrar es alfanumérica.

Estos LCD tienen una interfaz de 16 pines, de los cuales 2 son para controlar la luz trasera (si dispone de ella), 2 son para alimentar el dispositivo y 1 para regular el contraste. Esto nos deja 11 pines (!!) para controlar el LCD. Afortunadamente, con la configuración adecuada, es posible hacerlo utilizando solo 6 pines, como seguramente ya sepáis si habéis utilizado alguna vez un LCD con Arduino y la biblioteca LiquidCrystal.

Sin embargo, los pines son un bien escaso y preciado en cualquier sistema y, a veces, reservar 6 pines para controlar una única salida puede no ser posible. En este caso, una solución es utilizar un puerto serie junto con un chip de expansión. La idea es que, en los bytes transmitidos por el puerto serie, cada bit indica el estado deseado de uno de los pines del LCD. El chip que los recibe se encarga de colocar cada bit en el pin de salida correspondiente. Es decir, podemos verlo como un conversor de serie a paralelo:




En nuestro caso vamos a usar el PCF8574, que utiliza el bus I2C para la comunicación serie y sus puertos de salida pueden suministrar suficiente corriente como para conducir un led sin necesidad de resistencias de pull-up.

I2C con Raspberry Pi


El bus I2C permite la comunicación serie entre varios dispositivos donde al menos uno funciona como maestro. Utiliza dos líneas (denominadas SDA y SCL) para la comunicación y requiere la presencia de resistencias de pull-up en cada línea (se basa en drenador abierto). Se puede descargar su especificación en este enlace.

Para activar el bus I2C en RPI Zero W y poder utilizarlo con PI4J es necesario cargar dos módulos: i2c_bcm2835 e i2c_dev. El primero es el módulo principal para cargar el controlador del dispositivo en el RPI; el segundo facilita el acceso a él mediante un dispositivo en /dev. Aunque el segundo no sería estrictamente necesario, sin él las clases de PI4J que vamos a utilizar no pueden acceder al bus, así que debemos activarlo también. Podéis leer la documentación de este módulo en la página correspondiente de kernel.org.

Parece que la mejor forma de activar estos módulos es, por un lado, incluir el parámetro correspondiente en /boot/config.txt:

dtparam=i2c_arm=on

y, además, incluir el módulo i2c_dev en /etc/modules. Aunque puede parecer raro (bastaría con insertar los dos módulos en /etc/modules), de esta manera estamos utilizando la información del árbol de dispositivos (device tree).

Una vez hechas estas operaciones y reiniciado el raspberry, debería existir un archivo /dev/i2c-1. Hemos activado el bus I2C número 1, que está asociado, en el RPI Zero W, con los buses del BCM 2 (SDA) y 3 (SCL), que a su vez, en el cabezal de expansión, son los pines 3 y 5. Como siempre, conviene tener un diagrama a mano (por ejemplo, un vistazo a pinout.xyz) para no tener problemas con las conexiones.


De cara a comprobar el funcionamiento del bus, puede ser útil instalar también el paquete i2c-tools.

PCF8574

 

El PCF8574 es un circuito integrado que permite el control de 8 puertos de entrada/salida a través del bus I2C, permitiendo, por tanto, expandir el número de pines disponibles. Cuando se escribe un byte al circuito, cada bit se coloca en uno de los 8 pines entrada/salida. Y, de la misma manera, al leer bytes del PCF8574 estamos obteniendo el estado de cada pin. Además, incorpora una línea de interrupción para avisar a un controlador remoto el cambio de estado de alguno de los pines de entrada. Para terminar, la dirección del circuito en el bus es configurable, de manera que es posible distinguir hasta 8 de estos dispositivos en un mismo bus I2C.

Es muy fácil encontrar, a buen precio, pequeñas extensiones para Arduino (valdrían también para RPI) que, basándose en este chip, incorporan todo para conectar directamente el LCD (podríamos decir, enchufarlo) y controlarlo con alguna de las bibliotecas disponibles. Sin embargo, yo he preferido utilizar directamente el PCF8574 por varias razones: 
  • Estas extensiones dan por hecho que el LCD tiene una disposición concreta de pines. Quizá sea un problema mío, pero la realidad es que cada modelo diferente de LCD que he comprado tiene una disposición de pines diferente.
  • Están diseñadas para Arduino. Las bibliotecas en RPI (al menos PI4J) son diferentes, y permiten  configurar los pines de extensión como se desee.
  • El circuito es muy sencillo y se puede implementar con muy pocos componentes discretos.
  • Es más divertido y se aprende más de esta manera.

El circuito


Aunque "aparatoso", el circuito es muy simple. Solo tiene una pequeña curiosidad que luego comentaré:


 
  • En primer lugar hemos conectado los pines A0, A1 y A2 del PCF8574 al valor lógico 0, así que su dirección de 7 bits es 0x20 (podéis ver más detalles en la hoja de especificaciones del integrado, que he enlazado más arriba).
  • Hemos elegido alimentar el PCF8574 con 3,3V, la misma tensión de funcionamiento del RPI. Así los dos componentes funcionan con los mismo valores lógicos y no hace falta realizar ningún tipo de conversión ni protección.
  • El bus I2C requiere la presencia de dos resistencias de pull-up en las línea SDA y SCL. El valor 4K7 elegido es relativamente bajo, y seguramente funcionaría con un valor más alto. El límite superior de estas resistencias lo fija la capacidad total del bus.
  • Las conexiones entre el PCF8574 y el LCD son las mínimas para que este último funcione. Dejamos sin conectar los pines de datos de DB0 a DB3 (así que vamos a funcionar en el modo de 4 bits), y tampoco vamos a encender el led de la parte trasera. Todas estas conexiones se utilizan para controlar la lógica del LCD.
  • La entrada V0 del LCD es la referencia de voltaje para el cristal líquido, y, a efectos prácticos, sirve para regular el contraste. Si encendéis el circuito y no veis nada en el LCD, o lo veis todo negro (jeje, aunque no os salga a la primera no es para ponerse así), podéis probar a mover el potenciómetro de ajuste por ver si mejora.
  • ...Y ahora viene la curiosidad, o el truco, o la chapuza, según lo veáis. La lógica del LCD que estoy utilizando necesita una alimentación de 5V. El resto del circuito funciona a 3.3V. Como solo vamos a escribir en el LCD, nunca a leer de él, vamos a conectarle la alimentación a 5V, y a cruzar los dedos para esperar que los niveles lógicos del resto del circuito (el 1 lógico) sean suficientemente altos como para que el LCD los interprete correctamente. ¡En mi caso sí me ha funcionado!

El programa


Y ahora viene la parte del programa con PI4J. Realmente es muy simple, ya que PI4J incorpora clases para trabajar con LCD, y, en particular, la clase I2CLcdDisplay para acceder a ellos mediante el bus I2C. Esta clase supone que al LCD se accede mediante un conversor serie/paralelo y, al crear cada ejemplar, hay que indicar qué pines de dicho dispositivo están conectados a cada línea del LCD.

A continuación muestro un programa para mostrar un mensaje en la primera línea del LCD con una alineación determinada:

public class RPILcd {

    //El bus que vamos a utilizar
    private static final int I2CBUS = 1;

    //La dirección (hardwired) del PCF8574
    private static final int PCF_ADDRESS = PCF8574GpioProvider.PCF8574_0x20;

    /**
     * Imprime en el lcd lo que se le pide
     * con una alineación determinada
     * 
     * Uso :  -clear -> limpiar
     *        [-c|-r] texto -> pinta el texto alineado izquierda, centro o derecha
     * 
     * @param args the command line arguments
     * @throws java.io.IOException
     */
    public static void main(String[] args) throws IOException, Exception {
        
        /**
             * RS -> P1 RW -> P2 E -> P3 D4 -> P4 D5 -> P5 D6 -> P6 D7 -> P7
             *
             * Los demás quedan sueltos
             */
            //Y utilizamos un I2CLcdDisplay
            I2CLcdDisplay lcd = new I2CLcdDisplay(
                    2, //filas
                    16, //columnas
                    I2CBUS, //bus
                    PCF_ADDRESS, //dispositivo
                    0, //bit de la luz posterior
                    1, //bit RS
                    2, //bit RW : (también podría estar conectado directamente a LOW)
                    3, //bit E
                    7, //bit D7
                    6, //bit D6
                    5, //bit D5
                    4 //bit D5
            );

        if (args[0].equals("-clear")) {
            
            lcd.clear();
            
        } else if (args.length == 2) {
            
            if (args[0].equals("-r")) {
                
                lcd.write(0, args[1], LCDTextAlignment.ALIGN_RIGHT);
                
            } else if (args[0].equals("-c")) {
                
                lcd.write(0, args[1], LCDTextAlignment.ALIGN_CENTER);
                
            } else {
                
                lcd.write(0, args[1], LCDTextAlignment.ALIGN_LEFT);
                
            }
            
        } else {
            
            System.out.println("USO: RPILcd [-clear | -r | -c] [texto]");
            
        }
        
    }
}

La clase PCF8574GpioProvider, entre otras funciones, tiene definidas las direcciones dentro del bus I2C que el dispositivo puede tener. En nuestro caso, con los tres bits de dirección conectados a GND, esa dirección es 0x20 hexadecimal en el sistema de direccionamiento de 7 bits del bus.

Por lo demás, como puede verse, la clase I2CLcdDisplay se encarga de todo: solo hay que tener cuidado, al crear el objeto concreto, de que la asignación de pines se corresponda con el cableado.

Aunque aquí hemos dejado que el propio objeto se encargue del cálculo de la posición, por supuesto existen métodos de I2CLdcDisplay para colocar el cursos en una posición determinada y escribir en ella.


Conclusiones


El bus I2C nos permite mostrar en un LCD de texto utilizando solo dos pines GPIO en lugar del mínimo de 6 que serían necesarios si lo fuésemos a hacer conectando directamente el LCD al RPI.

Por supuesto, un aspecto que hay que mejorar es el de los dos niveles de alimentación 3,3V y 5V con los que trabajamos y que hemos trampeado para que funcione. Voy a proponer dos posibles soluciones, que espero poder explorar más adelante:

  • Dejar el PCF8574 en el mundo de 3,3V y utilizar un conversor de nivel entre él y el LCD.
  • Alimentar el PCF8574 con 5V e implementar el bus I2C utilizando dos MOSFET, como propone la nota de aplicación: AN10441 Level shifting techniques in I2C-bus design, de NXP.


Además de Blogger, para llevar a cabo esta entrada he utilizado los programas de código abierto Fritzing y Gimp





No hay comentarios:

Publicar un comentario