Estación Meteorológica Inalámbrica: Usando módulos ASK de bajo costo y Arduino
Por Raúl Álvarez Torrico
Esta es una traducción automática (pueden haber algunos errores de traducción) de mi artículo original en inglés publicado por la revista “Circuit Cellar” (#338 de Septiembre, 2018). Puedes ver un extracto del artículo original en este enlace. El código de programa y diagramas aún están en Inglés.
Introducción
En este proyecto presento una estación meteorológica inalámbrica casera para monitorear la temperatura ambiente, la humedad relativa, la velocidad y la dirección del viento, usando Arduino y un par de módulos de radio de modulación por desplazamiento de amplitud (ASK).
El sistema consta de dos nodos inalámbricos en una configuración de comunicación punto a punto: un nodo transmisor con todos los sensores meteorológicos y un nodo receptor con una pantalla para mostrar los datos recibidos. En el nodo de transmisión, tengo un sensor DHT22 de bajo costo para leer la temperatura y la humedad ambiente, un anemómetro para la velocidad del viento y una veleta para detectar la dirección del viento. En el nodo receptor, conecté una pantalla OLED de 0,96" con interfaz SPI para visualizar las lecturas meteorológicas recibidas. Ambos nodos tienen módulos de radio transmisores y receptores ASK respectivamente; el alcance de estos módulos es de alrededor de 50 metros (línea de vista) con una fuente de alimentación de 5VDC. Debido a la poca fiabilidad inherente de las comunicaciones inalámbricas, los datos se transmiten utilizando un protocolo de comunicación muy simple diseñado específicamente para este proyecto. Este protocolo permite la transmisión de datos empaquetados en tramas e implementa direccionamiento básico y verificación de errores de la carga útil; lo que ayuda a determinar, por ejemplo, si una determinada trama recibida se dirige, de hecho, al nodo receptor y si los datos recibidos están libres de errores. Aunque hay algunas bibliotecas Arduino disponibles para la comunicación inalámbrica que utilizan módulos Amplitude Shift Keying que implementan protocolos de comunicación, no utilizo ninguno de ellos, porque uno de los objetivos de este proyecto es mostrar los conceptos básicos sobre cómo implementar un protocolo de comunicación inalámbrica, para aquellos que son nuevos en el tema. Entonces, bajo el capó, este proyecto es una introducción muy básica sobre cómo funcionan los protocolos de comunicaciones inalámbricas utilizando el concepto de empaquetar datos en tramas, y por qué agregar metadatos a ellos es importante para el direccionamiento, la identificación de paquetes y la verificación de errores, entre otras cosas.
Un Pequeño Tributo al Sr. Steve Ciarcia
Cuando tenía diecisiete años (hace décadas atrás), encontré el libro “Ciarcia’s Circuit Cellar Vol. IV” (Taller de Circuitos de Ciarcia Vol. IV) en una biblioteca local en mi ciudad natal, y literalmente cambió mi vida. Hasta ese día, mi objetivo era seguir carrera en ciencias de la computación, pero después de leer el libro del Sr. Ciarcia, me enganché a la electrónica por el resto de mi vida y decidí seguir la carrera de electrónica. Fue entonces que descubrí el apasionante mundo de las computadoras embebidas y la electrónica leyendo los artículos de este libro. Uno de ellos fue “Construye una Estación Meteorológica Computarizada” (este proyecto usó un microordenador Z8-BASIC, todavía me encanta jugar con esos chips retro como Z8 y Z80, de vez en cuando), así que, en un pequeño homenaje a uno de los autores que me influenciaron mucho a una edad temprana, aquí está mi propia versión. Gracias Sr. Ciarcia por inspirarme a seguir este emocionante camino de la electrónica, ¡nunca me arrepentí del camino tomado!
Diagrama de Bloques y Descripción Funcional
Figura 1: Diagrama en bloques
Como se puede ver en la Figura 1, el sistema comprende dos nodos inalámbricos; un transmisor y un receptor, cada uno de ellos basado en una placa Arduino Pro Mini con un microcontrolador ATmega328P. El nodo transmisor es el que tiene los sensores meteorológicos (temperatura, humedad relativa, velocidad del viento y dirección del viento) y también tiene un módulo radio transmisor ASK de bajo costo que funciona en la banda de 433MHz (también se pueden usar módulos de 315MHz). El nodo receptor, por otro lado, tiene su correspondiente módulo radio receptor 433MHz ASK y una pantalla OLED de 0,96”, que muestra los datos meteorológicos recibidos. El nodo transmisor realiza lecturas de todos sus sensores y envía los datos de forma inalámbrica cada período fijo estáticamente definido en el código (es decir, cada pocos segundos). Mientras tanto, el nodo receptor está constantemente escuchando los datos entrantes, y cada vez que recibe una trama de datos válida dirigida a él, extraerá la carga útil que contiene las lecturas de todos los sensores remotos y luego los visualizará en la pantalla OLED. El nodo transmisor envía los datos empaquetados en tramas siguiendo un protocolo muy simple diseñado específicamente para este proyecto. Explicaré este protocolo con más detalle más adelante. Tanto el transmisor como el receptor son módulos de radio ASK de muy bajo costo, como los que se usan para los controles de garaje inalámbricos. ¿Por qué usar estos módulos de radio de "juguete" en lugar de otros mucho más sofisticados y confiables? Bueno, para mí, la respuesta corta es: ¡porque se puede hacer! Siempre me han gustado los enfoques minimalistas y tiendo a llevar las cosas al extremo, para ver cuánto se puede lograr con poco.
Hardware Para el Nodo Transmisor
Como se dijo anteriormente, el Nodo Transmisor se basa en un Arduino Pro Mini con un microcontrolador ATmega328P y un módulo transmisor de 433MHz Amplitude Shift Keying; el pin de entrada del transmisor está conectado al pin de salida D3 en la placa Arduino. También tiene un sensor de temperatura y humedad relativa DHT22 conectado al pin de entrada D4, un anemómetro de copa con salida de voltaje analógico conectado al pin de entrada analógico A0 y una veleta con salida de voltaje analógico conectado al pin de entrada A1 en el Arduino. Construí tanto el anemómetro como la veleta usando piezas y materiales reciclados (consulta las referencias para obtener instrucciones sobre cómo construir el tuyo), pero también se puede cualquier anemómetro y veleta disponibles en el mercado si tienen salidas analógicas. La Figura 2 muestra el diagrama del circuito para el nodo de transmisión, y la Figura 3 es una imagen de los componentes ensamblados.
Figura 2. Diagrama de circuito nodo transmisor
Figura 3. Nodo transmisor en proceso de ensamblaje
Firmware Para el Nodo Transmisor
Como tal vez ya lo sabes, la plataforma Arduino utiliza C/C ++ como lenguajes de programación, por lo que todo el código de programación para este proyecto está en una combinación de esos lenguajes. Leer datos del sensor DHT22 con un Arduino es muy sencillo utilizando la biblioteca DHT que está disponible gratuitamente en Internet. Esta biblioteca contiene toda la implementación de código de bajo nivel para interactuar con el sensor utilizando el protocolo de 1 cable (single wire), y una vez que la biblioteca se configura en código, solo es cuestión de llamar a las funciones de la biblioteca para leer los valores de temperatura y humedad como números flotantes, e. g .: f_temperature = dht.readTemperature () para leer la temperatura, y f_humidity = dht.readHumidity () para leer la humedad. Antes de enviar estos dos valores, multiplico ambos por 100 y los convierto en enteros. Este pequeño "truco" me ahorra el envío de cuatro bytes adicionales (porque en Arduino, las variables tipo “float” son variables de 4 bytes, pero los enteros son solo variables de 2 bytes). Según sus especificaciones, el sensor DHT22 puede leer temperaturas entre -40°C a 80°C y humedad relativa de 0% a 100%; entonces, en ambos casos, un valor multiplicado por 100 no cae fuera del rango de un entero con signo de 16 bits (que está entre -32768 y 32767). Entonces, termino enviando un total de cuatro bytes para temperatura y humedad en lugar de ocho (sin ninguna pérdida significativa de precisión) y, además, ella trama de datos final es más corta, lo que generalmente es algo bueno. El anemómetro y la veleta, ambos tienen salidas analógicas de 0 a 5VDC. Se leen fácilmente con la función de Arduino analogRead(); en ambos casos, se lee un valor entre 0 y 1023 (porque el convertidor de analógico a digital presente en el ATmega328P tiene una resolución de 10 bits: 2 ^ 10 = 1024). Después de leer los valores, se aplica una función de conversión o mapeo a ambos valores para obtener una velocidad del viento válida en kilómetros por hora y una de las ocho direcciones cardinales válidas para el viento (es decir, Norte, Este, Sud, Oeste, Noreste, Sudeste, Sudoeste, Noreste). Para el anemómetro, la siguiente función de mapeo ha sido usada para obtener la velocidad del viento: wind_speed = adc_reading / 2.25. Esta función de mapeo se obtuvo del procedimiento de calibración del anemómetro; entonces este valor se multiplica por 100 y se envía como un entero sin signo de 16 bits (lo que permite representar velocidades de 0 a 655.35 km / h, rango que es más que suficiente). La veleta genera ocho voltajes posibles entre 0 ~ 5V, correspondientes a ocho divisores de voltaje en su circuito (uno para cada dirección cardinal), y sus valores digitales correspondientes también estarán entre 0 y 1023.
Figura 4. Tabla de conversión de voltage a valor digital
La Figura 4 muestra una tabla con los voltajes y los valores digitales correspondientes calculados para cada dirección cardinal. No entraré en detalles sobre cómo construir y calibrar el anemómetro casero y la veleta, pero estoy dejando referencias a tutoriales en línea sobre estos temas. Para almacenar la dirección del viento, utilizo un tipo de datos "byte" de 8 bits (con un rango entre 0 y 255) para codificar las ocho direcciones cardinales como números en el rango 1 ~ 8. Estoy usando la biblioteca Arduino SoftwareSerial (una biblioteca de comunicaciones en serie de "bit-banging" de software) para enviar los datos de forma inalámbrica a 1200 baudios (el módulo serial UART de hardware en ambos Arduinos se usa sólo para fines de programación y depuración). Entonces, hay una función Transmit(). En el código del Nodo Transmisor que envía la trama de datos un byte a la vez, con una serie de wlessSerial.write() llamadas de función, en el orden especificado por el protocolo (ver Figura 5). Más información sobre la trama de datos del protocolo más adelante. Los módulos de radio de bajo costo tienen una velocidad de transmisión de trabajo máxima de aproximadamente 4800 baudios (2400 baudios de manera más realista), pero estoy usando 1200 baudios porque en las comunicaciones inalámbricas en general, las velocidades de transmisión más bajas ayudan a alcanzar distancias más largas. Con 1200 baudios y una fuente de alimentación de 5V para el módulo transmisor, obtuve un alcance de aproximadamente 30 metros (probé en casa, con dos paredes de ladrillo y un techo de por medio). Sin embargo, el módulo transmisor puede aceptar una fuente de alimentación de hasta 12VDC, por lo que al aumentar el voltaje de 5VDC a 12VDC (solo para el módulo transmisor, por supuesto, porque la placa Arduino funciona con solo 5VDC), el rango se puede aumentar mucho más, sin disminuir la velocidad en baudios. Mejores antenas también podrían ayudar a extender el alcance, estoy usando solo antenas helicoidales regulares.
Figura 5. Trama del protocolo de comunicación
Hardware Para el Nodo Receptor
El nodo receptor se basa también en un Arduino Pro Mini y tiene su correspondiente receptor de radio 433MHz Amplitude Shift Keying; cuyo pin de salida de datos está conectado al pin de entrada D2 en el Arduino. El módulo de pantalla OLED utiliza un circuito integrado SSD1306 128x64 Dot Matrix OLED, que se comunica mediante SPI. La Figura 6 muestra el diagrama del circuito para el nodo receptor y la Figura 7 muestra sus componentes ensamblados.
Figura 6. Diagrama de circuito nodo receptor
Figura 7. Nodo receptor en proceso de ensamblaje
Firmware Para el Nodo Receptor
La biblioteca SoftwareSerial también se usa en este nodo para recibir los datos de forma inalámbrica. El código de programación correspondiente para este nodo tiene una función Receive(), Que sondea continuamente el búfer SoftwareSerial utilizando la función wlessSerial.available() para verificar que al menos un número de bytes mayor que HEADER_SIZE de una trama de datos está presente. Si ese es el caso, procederá a leer desde el búfer utilizando la función wlessSerial.read(), Intentando al principio encontrar el START_BYTE (todos los bytes de sincronización anteriores serán ignorados, pues su único propósito es estimular los circuitos de radio receptores). Si tiene éxito, leerá el siguiente byte, que con mucha probabilidad será SOURCE_ADDRESS_BYTE, Y luego leerá el siguiente byte, que de acuerdo con nuestro protocolo debería ser DEST_ADDRESS_BYTE. En este punto, si la dirección de destino no coincide con su dirección asignada, el nodo de recepción cancelará la recepción de esta trama en particular y comenzará nuevamente desde el principio, tratando de encontrar otro START_BYTE y repetir el mismo proceso. Si la trama fue, de hecho, dirigido a él, procederá a leer el resto. Entonces, leerá el siguiente byte, que debería ser la ID de trama. A continuación, leerá el byte de longitud de carga útil y luego continuará leyendo toda la carga por delante, hasta el último byte contenido en la trama, que es el byte de suma de verificación (checksum). En este punto, también calculará el local_checksum Y lo comparará con remote_checksum (recibido como el último byte en la trama), y solo si ambos son iguales, se procederá a almacenar la temperatura recibida, la humedad, la velocidad del viento y los datos de dirección del viento, que a su vez, se mostrarán más adelante en la pantalla OLED. Por lo tanto, debe observarse que solo en este punto, cuando el “checksum” se verifica sin errores, los valores climáticos actuales en el sistema cambian. Por otro lado, si hay un error de suma de verificación, se supone que los datos recibidos estaban dañados; los valores del clima no se actualizarán y los datos recibidos se descartarán; en cuyo caso, los valores persistentes de una trama anterior recibida correctamente permanecen como los últimos datos remotos válidos. El punto de rocío (es decir, la temperatura a la que debe enfriarse el aire para saturarse con vapor) también se calcula utilizando los valores de temperatura y humedad, y luego se muestra también en la pantalla. Finalmente, para la interfaz con la pantalla OLED, estoy usando la biblioteca 'u8g2' disponible gratuitamente. Es fácil de usar y admite texto y gráficos para varios tipos de pantallas OLED.
El Protocolo
Como se dijo anteriormente, las comunicaciones inalámbricas son inherentemente poco confiables debido a algunos factores, como el ruido electromagnético y la interferencia, entre otras cosas. Por esa razón, protocolos de comunicación se utilizan para mejorar la confiabilidad, proporcionando también sincronización, seguridad, autenticación, direccionamiento, señalización y detección de errores en las comunicaciones digitales de datos. Por ese motivo, estoy usando un protocolo de comunicación inalámbrica muy simple diseñado específicamente para esta aplicación. En este protocolo, se define una trama de datos que empaquetará todos los datos de la aplicación y contendrá también algunos metadatos. La Figura 5 muestra la estructura de esta trama de datos. Describamos cada byte que es parte de la trama y su significado. En primer lugar, la parte de la carga útil (“payload”) contiene todos los datos de la aplicación que nos interesan (i. e., las lecturas del sensor); el resto de la trama es básicamente lo que se conoce como 'metadatos' (es decir, 'datos sobre los datos') y contiene información significativa sobre los datos en sí, que, como veremos más adelante, es fundamental para lograr confiabilidad, direccionamiento y señalización, identificación y detección de errores. El byte SYNC (conocido también como "preámbulo") es el primer byte en la trama. Dependiendo de cómo se defina un protocolo, en algunos casos no se consideraría parte de la trama como tal, pero por simplicidad, lo pondré como parte de ella. Se utiliza un valor hexadecimal de 0xAA, porque su valor binario es B10101010 y este número binario, cuando se transmite como una señal digital genera una onda cuadrada con un ciclo de trabajo del 50%; lo que a su vez es una excelente señal de sincronización entre el transmisor y el receptor. En algunas circunstancias (y algunos protocolos) se necesita más de un SYNC o byte de preámbulo (por ejemplo, Ethernet usa siete bytes de preámbulo), pero en mi caso, funciona perfectamente con solo un byte de preámbulo; aunque podría enviarse más de uno, en cuyo caso el nodo receptor ignorará todos los bytes adicionales del preámbulo y debería recibir el resto de la trama sin problemas. El byte START se usa para marcar el comienzo "real" de la trama. Su valor elegido es uno que normalmente tiene la intención de romper el patrón seguido por el(los) byte(s) de preámbulo. Ethernet, por ejemplo (no es un protocolo inalámbrico, por supuesto, pero los protocolos de comunicación por cable e inalámbricos son muy similares en general), utiliza el byte 0xD5 (que en binario es: B11010101. ¿Detectas el patrón de ruptura?), pero yo estoy tomando prestado el mío de la trama de la capa de aplicación XBee-ZigBee, que es 0x7E (que en binario será: B01111110. ¿Puedes ver otro patrón de ruptura 'diferente' en cierto sentido?) Ahora, el byte SCR_ADDR (Dirección de origen) contiene, como podrás sospechar, la dirección de 8 bits asignada al Nodo Transmisor, y la DST_ADDR (Dirección de destino) por otro lado, contendrá la dirección del destinatario, que en nuestro caso será nuestro Nodo Receptor. Para las dos direcciones elegí, casi al azar, dos valores entre 0x01 y 0xFE (en algunos protocolos, 0x00 y 0xFF se considerarían direcciones "especiales" o reservadas). Siguiendo esos dos bytes, vemos a continuación el byte de ID. Este byte es un entero sin signo de 8 bits que se utiliza, como su nombre lo indica, para identificar tramas de forma exclusiva. En este protocolo, se utilizará para etiquetar con una misma ID uno o más tramas que contengan la misma carga útil (es decir, tramas que contengan lecturas de sensores tomadas en el mismo instante de tiempo); lo cual será el caso cuando se retransmita la misma trama dos veces o más. Y transmitiremos la misma trama al menos dos veces, para mejorar la probabilidad de que los datos se reciban en el otro extremo del canal de comunicación; porque nuestro protocolo, por ser tan simple, no implementa mecanismos para garantizar la recepción de tramas en el destinatario previsto; como lo hacen otros protocolos más sofisticados; utilizando, por ejemplo, intercambios complejos de sincronización o apretones de manos (“handshakes”) de reconocimiento, y retransmisiones más inteligentes. En nuestro caso, simplemente transmitiremos "tontamente" cada trama al menos dos veces consecutivas; cuando el nodo receptor recibe dos (o más) tramas con la misma ID, sabe que estas tramas son iguales. El siguiente byte en el protocolo es el byte LENGTH. Este byte contiene la longitud de la carga útil o bytes de datos, que comprende todas las lecturas del sensor. Dos bytes para la temperatura, dos para la humedad, otros dos para la velocidad del viento y un byte para la dirección del viento nos dan un total de siete bytes para el PAYLOAD, que siguen a continuación en la estructura de la trama. El último byte en la trama después de la carga útil es el byte CHECKSUM. Para calcular esta suma de verificación en el nodo de transmisión, realizamos una suma aritmética de 8 bits de todos los bytes de carga útil, truncando el resultado en caso de que exceda los 8 bits de longitud; luego restamos esta suma de 0xFF, y ese es el valor para enviar en la trama como suma de verificación. En el nodo receptor, debemos comprobar que la suma de verificación recibida y una suma de verificación calculada localmente tengan el mismo valor; para esto, realizamos una suma aritmética de 8 bits de todos los bytes de carga útil, incluida la suma de verificación recibida (último byte en la trama) y la suma será igual a 0xFF si no hubo corrupción de datos. Sin embargo, este algoritmo de suma de verificación (que es una variación de la llamada suma de verificación de 'suma modular') no es perfecto para detectar todos los posibles errores de corrupción de datos, porque solo detectará “volteos” de un solo bit o un número impar de ellos, y no detectará errores más complejos que implican, por ejemplo, volteos de dos bits en la misma posición en dos bytes diferentes, o el intercambio de dos o más bytes; errores que de todos modos tienen una probabilidad relativamente baja de ocurrir, pero sin embargo pueden ocurrir. Los protocolos más sofisticados utilizan mejores algoritmos de verificación (especialmente en las capas inferiores de comunicación), como la suma de verificación de Fletcher, Adler-32 o Cyclic Redundancy Check (CRC). Pero estos algoritmos son mucho más costosos computacionalmente que una simple suma de verificación modular; aunque podrían implementarse en un microcontrolador ATmega328P, pueden ralentizar otras tareas que se ejecutan en él (y tenemos varias). En nuestro caso, podemos permitirnos perder una trama de vez en cuando, lo que puede ocurrir, pero no con mucha frecuencia. De todos modos, estará cubierto en cierta medida por las transmisiones dobles. Al final, no afectará gravemente nuestra aplicación, porque para este tipo de monitoreo del clima no es crítico si perdemos una lectura de vez en cuando.
Conclusiones y Mejoras Futuras
Figura 8. Nodo transmisor terminado
Figura 9. Nodo receptor terminado
La Figura 8 muestra el prototipo terminado del nodo transmisor y la Figura 9 muestra el prototipo del nodo receptor. Hice muchas pruebas informales en casa, cada una de ellas duró unas pocas horas y el sistema funcionó bastante bien. El nodo transmisor con todos los sensores estaba ubicado en la parte superior del techo de mi casa y el nodo receptor estaba a aproximadamente 20 metros (un par de paredes de ladrillo y un techo de por medio). Verifiqué visualmente que ocasionalmente se perdería una trama, digamos uno de cada cien (la mayoría de las veces, incluso menos que eso). Por lo tanto, estoy muy contento con el rendimiento general del sistema. La pantalla OLED es un poco lenta porque estoy usando comunicación SPI por software con la librería ‘u8g2’, pero no está nada mal. Por alguna razón, no pude lograr que funcionara el SPI de hardware del ATmega328P con la biblioteca u8g2. En el futuro, estoy pensando en agregar un sensor de presión y un medidor de lluvia al sistema. Además, se puede instalar más de un nodo receptor en varios lugares de la casa (es decir, en los dormitorios, la cocina, la sala de estar, el garaje, etc.), convirtiendo la configuración punto a punto en una topología de red en estrella. Al "mezclar" el hardware y el código de la parte de comunicación por radio del nodo de transmisión y el nodo de recepción, es posible hacer nodos repetidores (es decir, nodos que reciben una trama y solo la re-transmitan) para extender el alcance de la red. Este tipo de nodo puede servir como un puente entre un transmisor y un receptor que están fuera del alcance. Otra mejora interesante sería agregar un módulo WiFi al nodo de transmisión y luego implementar un cliente HTTP para cargar las lecturas del sensor a una plataforma IoT en la nube, o tal vez un servidor HTTP para leer datos meteorológicos desde un dispositivo móvil a través de la red inalámbrica de área local (WLAN). El acceso WiFi a Internet también se puede utilizar para consultar sitios como www.wunderground.com, obtener pronósticos meteorológicos para nuestra ubicación y transmitir esa información también al nodo receptor. Por supuesto, en ese caso se necesitaría una pantalla mucho más grande, ¡quizás una de color!
-----------------------------
Materiales
Descarga el código de este proyecto en este enlace.
Suscríbete a Nuestro Boletín de Noticias
Para recibir anuncios de otros talleres cursos similares.