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.