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.
Pego a continuación el código y luego comento los aspectos más interesantes:
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.
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:
y ejecutar el programa indicando que el enlazado debe ser dinámico. Por ejemplo:
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:
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.
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.
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):
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.
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.
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.
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.





