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





sábado, 10 de marzo de 2018

Temperatura ambiente con Raspberry Pi y un DS18B20

En esta entrada vamos a ver cómo utilizar la temperatura medida por un sensor de temperatura (en concreto un DS18B20) dentro de un programa del Raspberry Pi.

Hay muchos dispositivos que pueden utilizarse como sensores de temperatura. Casi todos ellos son analógicos, ofreciendo un voltaje o una corriente dependiente de la temperatura según una fórmula conocida. Por ejemplo, el TMP35 es un dispositivo con tres terminales que, debidamente polarizado, genera 10 mV/ºC entre dos de ellos.

El problema de los dispositivos analógicos es que, para poder utilizar la temperatura dentro de un programa, es necesario realizar la conversión a digital. Al contrario que Arduino, Raspberry Pi no incluye ningún ADC (analog to digital converter: utilizaré las siglas en inglés, porque creo que son universalmente reconocidas), y, por tanto, si utilizásemos un TMP35 tendríamos que utilizar un ADC externo para leer su voltaje de salida y convertirlo a un valor digital.

Sin embargo, el DS18B20 es un dispositivo que lee la temperatura ambiente y la comunica digitalmente utilizando el protocolo 1-Wire. El fabricante indica que la precisión es de 0,5 ºC entre -10ºC y +85ºC, lo que es, en general, mucho más que suficiente para la mayor parte de las aplicaciones. Además el protocolo 1-Wire tiene la ventaja de que utiliza un único cable (de ahí el nombre) para realizar la comunicación. Esto es muy interesante cuando el número de pines disponibles no es muy elevado.

Activando 1-Wire en Raspberry Pi

La forma más fácil de activar el protocolo 1-Wire en un raspberry pi es añadir la línea

dtoverlay=w1-gpio

en el fichero /boot/config.txt y reiniciar el raspberry (más información aquí).

Por defecto el bus 1-Wire queda configurado en el pin 4 de GPIO (que es el pin 7 de WiringPi, y también el pin 7 del cabezal... ¡qué lío!). Este pin se puede cambiar como un parámetro al definir el overlay, como viene explicado en este enlace.

El circuito

En este caso el circuito es muy simple:


Estamos utilizando la configuración más simple, de manera que además del DS18B20 solo necesitamos una resistencia de pull-up de valor 4K7.

Como en la entrada anterior, os muestro a continuación las dos versiones del circuito. Una posible realización en la placa de pruebas:

 
y la real, con sus principales elementos:


Bueno... pensando en seguir experimentando con ello, al final opté por soldarlo en una placa de prototipos, pero la versión del breadboard os debería funcionar sin problema. También he vestido el raspberry pi zero w, pero por dentro es el mismo de la entrada anterior.

Utilizando el bus 1-wire

 

Si todo ha funcionado bien y arrancáis el rpi con esta configuración, el sistema cargará los módulos para controlar el bus 1-wire, y lo hará visible mediante sysfs. Así, podemos ver los dispositivos 1-wire que se han detectado mediante el comando ls /sys/bus/w1/devices:

En el ejemplo solo tenemos el controlador del bus y el DS18B20, cuyo id es 28-000008970cc8. Cada una de estas entradas en un directorio cuyos ficheros permiten consultar los dispositivos.

De hecho, leer la temperatura del dispositivo es tan sencillo como consultar un archivo de texto:


En este caso, la temperatura es 22,875 ºC.

Lectura del bus 1-wire con PI4J

Con PI4J es muy fácil acceder a los valores de los dispositivos que se encuentran en el bus 1-wire. De hecho, como veremos, no solo se pueden leer datos genéricos, sino que se pueden utilizar interfaces especializadas para, por ejemplo, trabajar específicamente con sensores de temperatura.

El siguiente programa lee los valores de los dispositivos 1-wire conectados al bus, primero como dispositivos 1-wire genéricos y, después, como sensores de temperatura:
//Empezamos creando el controlador maestro
//Tenemos que tener activo el overlay del w1-gpio
//dtoverlay=w1-gpio en /boot/config.txt
W1Master controlador = new W1Master();

//Vamos a leer todos los dispositivos
List dispositivos2 = controlador.getDevices();

//Vamos a ver qué nos dice de cada uno
for (W1Device dispositivo : dispositivos2) {

    System.out.println("***LEYENDO DISPOSITIVO***");
    System.out.println(dispositivo.getId());

    try {
System.out.println("Temperatura: " + dispositivo.getValue());
    } catch (IOException ex) {
Logger.getLogger(RPI1WireTemperatura.class.getName()).log(Level.SEVERE, null, ex);
    }

}

//Ahora podemos mirar todos los dispositivos del bus
List dispositivos = controlador.getDevices(TemperatureSensor.class);

//Vamos a ver qué nos dice de cada uno
for (TemperatureSensor dispositivo : dispositivos) {

    System.out.println("***LEYENDO DISPOSITIVO DE TEMPERATURA***");
    System.out.println(dispositivo.getName());

    System.out.println("Temperatura: " + dispositivo.getTemperature());

}

En la siguiente imagen se muestra la salida de este programa:

Cuando el dispositivo se lee de manera genérica, la información que obtenemos es la misma que cuando hemos leído el fichero w1_slave del sysfs.

Cuando utilizamos la interfaz TemperatureSensor, sin embargo, accedemos al valor de una manera mucho más significativa, un double con el valor de la temperatura en grados Celsius.

Como curiosidad, según se detalla en la hoja de especificaciones del DS18B20, la información de temperatura se transmite en dos bytes, según la siguiente disposición:

LSB:
23
22
21
20
2-1
2-2
2-3
2-4

MSB:
Signo
Signo
Signo
Signo
Signo
26
25
24

Donde el signo es 0 para temperaturas positivos y 1 para temperaturas negativas.

Si convertimos a binario el número decimal 21,250, obtenemos 10101,0100, que, convertido al formato del sensor de temperatura es:

LSB: 010101002 = 5416
MSB: 000000012 = 0116

que son, precisamente, los dos primeros bytes de la transmisión cuando leemos el dispositivo de manera genérica (sin utilizar la interfaz TemperaturaSensor).

Por último, indicar que, por defecto, la temperatura se proporciona en grados Celsius. Por supuesto, si la necesitásemos en grados Fahrenheit, siempre la podríamos convertir (clásico ejercicio de programación). Pero también la podemos obtener directamente en esa escala modificando la línea 32 del programa de la siguiente manera:

System.out.println("Temperatura: " + dispositivo.getTemperature(TemperatureScale.FAHRENHEIT));

Además de FAHRENHEIT, en la enumeración TemperatureScale contiene CELSIUS, KELVIN y RANKINE, para las diferentes escalas de temperatura.

Conclusiones


En esta entrada hemos visto como configurar el bus 1-Wire del Raspberry Pi y utilizarlo para leer la temperatura ambiente proporcionada por un DS18B20, tanto utilizando sysfs como PI4J.

Además de Blogger, para llevar a cabo esta entrada he utilizado los programas de código abierto Fritzing y Gimp. Para mostrar la temperatura de la manera que se muestra en la imagen inicial he usado la biblioteca JFiglet con la FIGFont dotmatrix.flf, y la biblioteca lanterna para manejar el terminal en modo pseudográfico.

martes, 20 de febrero de 2018

Control de potencia con RPI(II) : controlando el triac

En la entrada anterior vimos cómo hacer un detector de paso por cero muy simple con el objetivo de sincronizar la señal de control con la de potencia. Ahora vamos a completar el circuito y añadir el código para que el Raspberry Pi controle la potencia entregada a la carga.

Como se dijo en la primera entrada, vamos a utilizar PI4J. Esta API nos facilita la programación de los pines GPIO de nuestro RPI mediante Java. En este caso nos va a resultar útil la posibilidad de definir triggers asociados a eventos en un pin, y cuya implementación está basada en las propias interrupciones hardware, según indica la documentación de PI4J.

Pero antes, conviene revisar en detalle cuál es la funcionalidad que tenemos que implementar. Partimos de un circuito que genera un pulso cada vez que el voltaje en la fuente de alimentación. En nuestro caso (la red nos proporciona 50 Hz), esto significa que tenemos un pulso cada 10 ms. Una vez detectado un pulso, hay que esperar una cierta cantidad de tiempo (en función de la potencia deseada) y activar el triac. El estímulo que active el triac debe desaparecer antes del siguiente paso por cero, para permitir que este deje de conducir. La siguiente imagen ilustra este comportamiento:
Cada vez que la onda (en rojo) se acerca al valor cero, el circuito que vimos en la entrada anterior genera un pulso (en azul en el gráfico). Lo que pretendemos es detectar estos pulsos y, en el momento adecuado, generar algún tipo de estímulo (por ejemplo los pulsos marcados en verde en la imagen anterior) que active la conducción del triac (suponemos que la señal de potencia está conectada a la carga a través de él). El triac seguirá en conducción hasta que la fuente de alimentación vuelva a acercarse a cero. En este momento el triac quedará en reposo hasta que un nuevo pulso lo excite, resultando que la carga recibe una onda del estilo de la que se ve en negro en la figura.

Cuanto más cerca del paso por cero estén los pulsos de control, durante más parte del semiciclo estará en conducción el triac y por tanto la carga disipará más potencia. Y viceversa.

Como ya dijimos, PI4J nos permite definir triggers asociados a un pin de entrada. Las clases que implementa la interfaz GpioPinInput disponen de un método denominado addTrigger para esta tarea. Esto es, podemos asociar un determinado evento (típicamente el cambio de estado en un pin) a la ejecución de un método de un objeto arbitrario (con tal de que implementa la interfaz GpioTrigger). Y, puesto que la implementación utiliza las interrupciones hardware, podemos suponer que la ejecución de dicho método sucede con suficiente rapidez.

Pi4J incorpora de serie algunos GpioTrigger estándar. Desgraciadamente ninguno de ellos se adapta exactamente a nuestras necesidades (un pulso tras un retardo). Sin embargo sí existe uno que genera un pulso de una duración determinada (GpioPulseStateTrigger). Si este pulso lo colocamos en la base de un transistor en emisor común, en el colector los voltajes estarán invertidos. El circuito sería el siguiente (incluido el detector de paso por cero de la entrada anterior):






Aunque necesitemos un circuito un poco más complicado, este enfoque tiene la ventaja de que la corriente para conducir el optotriac se toma del raíl de 5V del RPI, en lugar de los propios pines de GPIO.

El programa

 

El programa que hemos realizado para probar este circuito se invoca con un argumento que deber ser un número entre 0 y 10, y que indica el tiempo de espera entre la detección del pulso de paso por cero y la activación del triac.

Pego a continuación el código y luego comento los aspectos más interesantes:
import com.pi4j.io.gpio.GpioController;
import com.pi4j.io.gpio.GpioFactory;
import com.pi4j.io.gpio.GpioPinDigitalInput;
import com.pi4j.io.gpio.GpioPinDigitalOutput;
import com.pi4j.io.gpio.PinState;
import com.pi4j.io.gpio.RaspiPin;
import com.pi4j.io.gpio.trigger.GpioPulseStateTrigger;
import java.util.logging.Level;
import java.util.logging.Logger;

public class RPIControlDePotencia {

    /**
     * Clase principal
     * @param args los argumentos en línea de comandos
     */
    public static void main(String[] args) {
        
        long tiempo = 5;
        
        //Si hay algún argumento en línea de comandos
        if (args.length >= 1) {
            
            try {
                
                tiempo = Long.parseLong(args[0]);
            
            if (tiempo < 0 || tiempo > 10) {
                
                tiempo = 5;
                
            }
                
            } catch (NumberFormatException e) {
                
                System.out.println(
                      "El argumento debe ser un número entre el 1 y el 10");
                
            }
                        
            System.out.println("Fijando el tiempo de espera a " + tiempo + " ms");
                        
        }

        //Siempre hay que crear un controlador
        final GpioController controladorGpio = GpioFactory.getInstance();
 
        //El pin de entrada es el 25 según la numeración de WiringPi 
        //(el 38 del cabezal del RPI)
        GpioPinDigitalInput entrada = controladorGpio.provisionDigitalInputPin(
                RaspiPin.GPIO_25);

        //El pin de salida es el 6 según la numeración de WiringPi 
        //(el 22 del cabezal del RPI)
        GpioPinDigitalOutput salida = controladorGpio.provisionDigitalOutputPin(
                RaspiPin.GPIO_06,
                PinState.HIGH);
        
        //Añadimos un trigger para indicar que queremos generar un pulso de tiempo ms
        entrada.addTrigger(new GpioPulseStateTrigger(PinState.HIGH,salida,tiempo));

        //Ahora viene el bucle
        //El programa termina al pulsar Ctrl-c
        try {
            
            while(true) {

                Thread.sleep(10000);

            }
            
        } catch (InterruptedException ex) {
            
            Logger.getLogger(RPIPasoPorCero.class.getName()).log(Level.SEVERE, null, ex);
            
        } finally {
            
            controladorGpio.shutdown();
            
        }

    }

}


A partir de la línea 46 comenzamos la parte que nos interesa. En primer lugar creamos un ejemplar de GpioController, que utilizaremos para crear los pines de entrada (línea 50) y de salida (línea 51). Inicializamos este último a HIGH.

Es importante tener en cuenta que la clase RaspiPin contiene las definiciones de los pines siguiendo el esquema de WiringPi. Podéis consultar más detalles en este enlace. En el RPI Zero W, el pin correspondiente a RaspiPin.Gpio_25 es el número 38 del cabezal GPIO, y a RaspiPin.Gpio_06 es el número 22.

La parte importante para lograr nuestro objetivo está en la línea 60: utilizando el método addTrigger del pin de entrada hacemos que cada vez que en él se detecte un cambio a HIGH se genere un pulso, en el pin que hemos definido de salida, de la longitud en ms que hemos indicado como un argumento al invocar el programa. Para ello creamos un objeto de la clase GpioPulseStateTrigger, indicándole al constructor que queremos que el pulso se genere cuando el estado en el pin de entrad pasa a HIGH, que se genere en el pin de salida y que dure el tiempo definido en la invocación del programa. Este objeto es el único argumento de la función addTrigger.

Por último, dejamos que el programa se ejecute en un bucle infinito hasta que sea detenido por el usuario (por ejemplo con Ctrl-c). Una vez definido, el trigger funciona independientemente del programa principal, así que, mientras que no lo detengamos (por ejemplo con el método removeTrigger), o finalice el programa, seguirá funcionando. En este caso no hacemos nada, pero, por supuesto, el hilo principal podría estar realizando otras tareas mientras que, por detrás, el trigger se lanza cada vez que sucede el evento definido.

Compilación, empaquetado y ejecución

Solo quiero hacer un pequeño comentario por si os sucede el mismo problema que a mí. Al ejecutar el programa en el RPI Zero me encontré con el siguiente mensaje de error:

Unable to determine hardware version. I see: Hardware    : BCM2835
,
 - expecting BCM2708 or BCM2709.
If this is a genuine Raspberry Pi then please report this
to projects@drogon.net. If this is not a Raspberry Pi then you
are on your own as wiringPi is designed to support the
Raspberry Pi ONLY.


Se trata de un problema con las versiones modernas del kernel de Linux y versiones previas de la biblioteca WiringPi (más detalles en este enlace o en este). Afortunadamente la solución es sencilla, basta con instalar en el RPI la última versión de la biblioteca WiringPi:

$ sudo apt-get install wiringpi

y ejecutar el programa indicando que el enlazado debe ser dinámico. Por ejemplo:

$ java -Dpi4j.linking=dynamic -jar RPIControlDePotencia.jar 7


Resultados

Hemos realizado una batería de pruebas para comprobar, por un lado, si realmente puede controlarse la potencia con este circuito y este programa y, por otro, como de "fino" puede ser el ajuste.

El circuito es el de los esquemas que hay más arriba, implementado en una placa de pruebas. He tomado las referencias de voltaje de los pines del RPI Zero W. Idealmente, el circuito es el de la siguiente imagen. Respecto a los componentes concretos, tengo que decir que el puente de diodos no es el DB107 (no he encontrado en Fritzing otro modelo). Los dos transistores son BC547 y el optotriac es un MOC3051. Como ya he dicho, el control es con un Raspberry Pi Zero W v1.1 con Raspbian.


y en la realidad es este (un poco más desorganizado):


He medido el voltaje en RLoad (100 Ω), primero sin ningún control de potencia (el triac siempre en conducción: para ello he conectado la resistencia en la base del segundo transistor a tierra), y después pasando distintos valores en línea de comandos al programa RPIControlDePotencia. El voltímetro me proporciona valores medios, máximos y mínimos de voltaje, calculado la potencia como V2/R. He dejado que el programa se ejecute un minuto con cada valor del parámetro que controla la potencia.

En las primeras pruebas he utilizado una resistencia de 59 Ω en la base del transistor que controla el paso por cero, que nos da un pulso de paso por cero de tamaño mínimo con esta fuente de alimentación (recordad la anterior entrada). En la siguiente tabla pueden verse los resultados:

parámetro
Vavg
Vmin
Vmax
Potencia
% Potencia
sin control
3,049 V
3,106 V
3,072 V
93 mW
100%
1
3,024 V
2,520 V
3,064 V
91 mW
98%
2
2,980 V
2,504 V
3,040 V
89 mW
96%
3
2,833 V
2,440 V
2,952 V
80 mW
86%
4
2,559 V
2,240 V
2,808 V
65 mW
70%
5
2,178 V
1,864 V
2,528 V
47 mW
51%
6
1,625 V
1,344 V
2,568 V
26 mW
28%
7
0,871 V
0,712 V
2,960 V
8 mW
9%
8
No medible




9
No medible






Como podéis ver, aunque sí funciona para controlar la potencia, no se puede ajustar con demasiada precisión, ya que la diferencia en la potencia entregada entre un valor del parámetro y el siguiente es muy pequeño en algunos casos y muy grande en otros. Este problema viene dado, al menos en parte, porque al trigger que hemos definido en la línea 60 del programa tenemos que indicarle un número entero de ms. También hay que tener en cuenta que el triac necesita una cierta corriente para mantenerse en conducción y que la señal es sinusoidal y no lineal.

Vamos a intentar mejorar estos resultados (en cuanto a la "finura" del ajuste de potencia), cambiando el ancho del pulso de paso por cero. La idea es que si aumentamos este ancho, el RPI detectará antes el paso por cero en cada semiciclo. Como consecuencia, el triac también entrará en conducción antes durante el semiciclo siguiente, con lo que la potencia total será diferente.
Como sabemos, para modificar el ancho del pulso basta cambiar la resistencia entre el puente de diodos y la base del primer transistor. Hemos elegido un valor mucho más elevado, 8K2. Hemos vuelto a realizar las medidas de voltaje, con los siguientes resultados (solo se incluyen los valores significativos del parámetro):

parámetro
Vavg
Vmin
Vmax
Potencia
% Potencia
sin control
3,049 V
3,106 V
3,072 V
93 mW
100%
3
2,951 V
2,480 V
3,056 V
87 mW
94%
4
2,701 V
2,400 V
2,872 V
73 mW
78%
5
2,267 V
1,936 V
2,576 V
51 mW
55%
6
1,814 V
1,456 V
3,120 V
33 mW
35%
7
1,199 V
0,960 V
2,632 V
14 mW
15%
8
0,634 V
0,392 V
1,752 V
4 mW
4%

En este caso, aunque no se puede considerar ideal, la diferencia de potencia al pasar de un parámetro al siguiente está distribuida más uniformemente.

Conclusiones

Hemos probado un circuito simple y un programa basado en PI4J en un RPI Zero W para controlar la potencia en una carga mediante el control en fase de un triac. Ajustando algunos parámetros hemos conseguido saltos en potencia de aproximadamente un 20% de la potencia total.

Los resultados parecen indicar que en función de la señal que se vaya a controlar habrá que ajustar los componentes para obtener el resultado buscado.

La historia completa...

Antes de terminar, es obligatorio indicar que, como seguramente ya habréis pensado, este circuito no es válido para controlar potencias grandes. En realidad los optotriac como el MOC3051 están pensados para conducir triacs de potencia (por ejemplo, un BTA40). Al añadir este elemento, el circuito (solo la parte de potencia) queda:

Cuando el optotriac entra en conducción, a su vez hace que el triac de potencia conduzca también, momento en que la carga empieza a recibir corriente. Si, como buscamos, queremos controlar un horno de cerámica, debemos irnos a un circuito de este tipo. Pero eso ya es otra historia...

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