Operaciones básicas sobre un módulo wifi ESP8266 desde Arduino

Operaciones básicas sobre un módulo wifi ESP8266 desde Arduino

Operaciones básicas sobre un módulo wifi ESP8266 desde Arduino

Cuando Espressif lanzó al mercado los primeros módulos wifi con el integrado ESP8266 y el firmware con el que manejarlo usando órdenes AT, lo que nos interesaba a los usuarios era integrarlo en montajes con microcontroladores y los problemas se reducían a conocer la (antes) oscura tabla de órdenes AT del ESP8266, las necesidades de alimentación o la actualización del firmware del ESP8266.

Tabla de contenidos

    Después llegaron rápidamente alternativas para programar el ESP8266 e implementaciones en módulos wifi de formatos muy diferentes que planteaban otras inquietudes: qué módulo wifi ESP8266 elegir en función del alcance de las distintas antenas (incluyendo las externas) o la integración física en nuestros montajes de estos nuevos módulos.

    Seguramente por atender a todos esos cambios puede que no se haya insistido en lo más elemental, la gestión más básica del módulo wifi ESP8266. Aunque en polaridad.es se puede encontrar información sobre el uso del ESP8266 y hay algunas aplicaciones con vocación de explicar de manera genérica el funcionamiento del módulo wifi ESP8266 usando órdenes AT, especialmente en el artículo sobre la librería para hacer consultas HTTP desde Arduino con el módulo wifi ESP8266, las impresiones de los lectores permiten pensar que sería útil añadir algo más de información de base que ayude a los usuarios del ESP8266 a realizar sus propias implementaciones.

    Tratar las operaciones básicas para trabajar con el ESP8266 y proponer soluciones genéricas es un objetivo varias partes muy diferentes; para ayudar a seguir el contenido del artículo, el siguiente índice puede servir de guía:

    Controlar el módulo wifi ESP8266 desde el ordenador por el puerto serie

    Desde una placa Arduino y utilizando su IDE es posible monitorizar el funcionamiento de un módulo wifi ESP8266, enviar las órdenes AT del ESP8266 y ver la respuesta pero resulta mucho más cómodo hacerlo desde un ordenador con una aplicación de tipo terminal.

    Utilizando CuteCom para hacer pruebas con el módulo wifi ESP8266 por el puerto serie

    Dependiendo de qué placa Arduino se utilice, puede que solamente haya disponible un puerto serie hardware, lo que añade una pequeña incomodidad al envío y la recepción. Cambiar la velocidad de las comunicaciones es mucho más cómodo en una aplicación de comunicaciones serie desde un ordenador y algunas placas Arduino (y en algunas circunstancias) no soportan bien las velocidades más altas de las comunicaciones serie, especialmente 115200 baudios, que es la velocidad por defecto de las últimas versiones del firmware.

    Sobre qué programa utilizar para monitorizar el ESP8266 utilizando el puerto serie, hay muchos para elegir según necesidades y preferencias; últimamente estoy utilizando más el clásico CuteCom (el de la captura de pantalla de arriba) porque me resulta muy cómodo para repetir ciertas órdenes AT del módulo wifi ESP8266 en las pruebas de los proyectos.

    Ya se han dado aquí algunas recomendaciones sobre programas que funcionen como consola serie; por ejemplo, al hablar de PuTTY para el control de dispositivos serie UART desde el ordenador. PuTTY, además de ser una aplicación excelente, está disponible para la mayoría de sistemas operativos de escritorio. Además, como PuTTY puede utilizarse para hacer de consola tanto con el puerto serie como con los familia de protocolos de Internet (TCP/IP), incluyendo los que funcionan sobre TLS, se convierte en herramienta habitual que devuelve con creces el (poco) tiempo empleado en configurarla y en acostumbrarse a su uso.

    Utilizando PuTTY para hacer pruebas con el módulo wifi ESP8266 por el puerto serie

    Además del software de comunicaciones serie, para conectar el módulo wifi ESP8266 al puerto USB de un ordenador también hace falta un conversor USB a serie TTL. Igual que en el caso del software, existen varias versiones, desde las que solo se utilizan para convertir el puerto USB en un puerto serie TTL (que se puede conseguir desde un Euro) hasta los que pueden emular diferentes protocolos (como SPI o I2C).

    Igual que un programa que funcione como una consola serie, el hardware para comunicar el ordenador por USB con un circuito lógico (no solo el ESP8266) será una herramienta habitual en el trabajo de un desarrollador de aplicaciones microcontroladas, merece la pena tenerlo en la caja de herramientas cuanto antes y trabajar con el módulo wifi ESP8266 es una excelente ocasión para hacerse con uno.

    Hardware para comunicaciones serie UART USB para monitorizar el módulo wifi ESP8266

    El conversor USB a UART TTL también se puede utilizar para monitorizar el comportamiento de un circuito que use el ESP8266, para hacerlo, se conectan en serie a la entrada de datos (RX) del conversor las salidas que se quieran monitorizar con un diodo rápido (el 1N4148, por ejemplo) y una resistencia (2K2, por ejemplo) en paralelo entre ellos. Un montaje de este tipo funciona como un sniffer serie hardware.

    Ejemplo de sniffer para ESP8266 conectado a Arduino usando un conversor USB UART TTL

    Aunque el sniffer de la imagen de arriba es ciertamente rudimentario (entre otras cosas no dispone de buffer) es suficiente para monitorizar el funcionamiento de un montaje con Arduino y el ESP8266.

    Quitando del esquema anterior el sniffer queda el esquema que muestra cómo conectar un módulo wifi ESP8266 a una placa Arduino. Además de alimentarlo a 3V3, hay que conectar a nivel alto el pin de reset y el de activación del integrado (enable). Por supuesto, el pin RX de uno debe conectar al TX del otro.

    Para simplificar el diagrama anterior, se ha representado una placa Arduino alimentada a 3V3 y de la que se supone una tensión en el puerto serie también de 3V3. Si se utiliza un microcontrolador con un nivel de señal en el puerto serie diferente (normalmente 5 V) será necesario, para no dañar el ESP8266, utilizar un conversor de nivel como los de los esquemas de abajo. Este circuito es el que se encuentra con frecuencia en muchas implementaciones de módulos comerciales listos para usar.

    Conversor de nivel de señal de 5V a 3V3 para módulo wifi ESP8266 y Arduino

    Actualizar el firmware del ESP8266

    Las órdenes AT del ESP8266, su terminación, la velocidad por defecto del módulo… dependen de la versión del firmware del ESP8266. Lo más recomendable es asegurarse de disponer la misma versión en todos los módulos y, de ser posible, que sea la última versión.

    Desafortunadamente, la mayoría de los modelos de módulos wifi ESP8266 solo disponen de 4 Mbit, por lo que no se les puede instalar la versión más reciente. La última versión (oficial) de firmware que puede instalarse en módulos wifi ESP8266 con 4 Mbit (la mayoría) es la 0.9.4 que incluye la versión 0.2 de las órdenes AT del ESP8266.

    En resumen, para actualizar el firmware es necesario:

    1. Descargar la versión correspondiente del firmware. La última versión (oficial) para un módulo que disponga de 4 Mbit de memoria, se encuentra en la carpeta de Espressif de github. En la web de Espressif puede descargarse la versión más reciente del firmware, pero es muy importante verificar que el módulo sobre el que se instala tiene suficiente memoria.

    2. Descargar la última versión de la herramienta de instalación del firmware. Mi favorita es esptool que está escrita en Python, por lo que funciona en cualquier plataforma. Además de descargarse, también se puede instalar con pip install esptool (o pip2 o python -m pip…). Por supuesto, Espressif también ofrece su propia herramienta pero actualmente solo está disponible para Windows.

    3. Preparar los archivos descargados; descomprimirlos en una carpeta accesible y, si procede, hacer ejecutable la herramienta esptool, en mi caso, desde GNU/Linux, con chmod +x esptool

    4. Conectar el módulo al ordenador usando un conversor USB UART TTL que trabaje a 3V3 o utilizar un conversor de nivel si funciona a 5 V. Además de la alimentación, habrá que conectar TX a RX del conversor USB UART TTL, RX a TX, GPIO0 a nivel bajo (GND) y puede que GPIO2 a nivel alto (en mis pruebas ha funcionado tanto conectándolo a nivel algo como desconectado). Si el módulo tiene libre la conexión GPIO15 (como ocurre en el ESP-12) debe conectarse a nivel bajo. RESET, que normalmente estaría a nivel alto durante el funcionamiento, puede dejarse sin conectar o conectar a nivel alto por medio de una resistencia (10K, por ejemplo), ya que antes de empezar la grabación puede ser necesario reiniciar el dispositivo conectándolo a nivel bajo.
      Al alimentar el módulo ya estará disponible para actualizarlo pero, si se muestra un error de conexión, será necesario resetearlo conectando RESET a nivel bajo un instante y dejándolo luego al aire (sin conectar) para el proceso de actualización.
      El módulo tiene picos de consumo de medio amperio (hasta 600 mA, según algunos usuarios) así que es importante utilizar una fuente de alimentación capaz de soportar este consumo, especialmente para actualizar el firmware.

      Conexión módulo wifi ESP8266 ESP-01 a conversor USB UART TTL actualizar firmware

    5. Ejecutar la herramienta para actualizar el firmware. En mi caso, he grabado en la misma carpeta la herramienta y los documentos del firmware en el paso 3, así que ejecuto desde la consola:
      cd ~/Datos/firmwareESP8266 (cambiar a la carpeta que contiene la herramienta y el firmware)
      ./esptool.py --baud 115200 --port /dev/ttyUSB0 write_flash \
      0x00000 ./boot_v1.1.bin \
      0x01000 ./user1.bin \
      0x7C000 ./esp_init_data_default.bin \
      0x7E000 ./blank.bin

      --baud establece la velocidad del ESP8266 (115200 baudios en mi caso) y --port el puerto serie al que se conecta (en mi caso, emulado, el primer USB). Los diferentes documentos que forman el firmware van detrás de write_flash precedidos de la dirección, siendo el documento user1.bin el que contiene la carga útil de la actualización.

      wifi ESP8266 firmware actualización consola esptool captura

    Enviar órdenes al módulo wifi ESP8266

    Para controlar el ESP8266 desde un ordenador habrá que empezar por configurar la aplicación para lo que bastará con ① elegir el puerto al que se conecte el conversor USB UART TTL, algo como /dev/USB0 en GNU/Linux y similares o algo como COM6 en Windows, ② elegir la velocidad a la que esté configurado el ESP8266, seguramente 115200 baudios, ③ establecer 8 bits de datos más uno de parada, sin paridad ni handshake, y ④ configurar el fin de línea, dependiendo del firmware, casi siempre CR+LF.

    Configurar CuteCom para monitorizar el módulo wifi ESP8266 usando un conversor USB UART TTL

    Configurar PuTTY para monitorizar el módulo wifi ESP8266 con un conversor USB UART TTL

    Una vez configurada la aplicación (o, en su caso, almacenada y seleccionada) queda abrir la conexión («open device» y «open», respectivamente, en las capturas de pantalla de los ejemplos arriba con CuteCom y PuTTY) y ya se puede empezar a enviar órdenes al ESP8266.

    Como puede verse en la tabla de órdenes AT del ESP8266, el formato para activar, desactivar, establecer un valor y consultarlo es bastante predecible pero, en general, no es fácil recordarlos todos y es probable que sea necesario tenerla a mano para consultarla.

    La forma de enviar órdenes AT al módulo wifi ESP8266 desde Arduino es muy sencilla: ① configurar las comunicaciones con Serial.begin(115200); (o Serial1, Serial2… en las placas con varios puertos serie hardware) y ② enviar las órdenes usando el formato Serial.print(orden+"\r\n");

    El ejemplo anterior muestra cómo enviar las órdenes AT del módulo wifi ESP8266 desde Arduino. En este caso se ilustra AT+CWJAP, que sirve para conectar a un punto de acceso. Esta orden utiliza como argumentos el identificador del punto de acceso (SSID) y la clave, ambos entre comillas, por lo que se convierten en un objeto Srtring y se encierran entre comillas usando el código de escape (\"). Para terminar la orden se usa \r\n que corresponde con CR y LF.

    Para recordar que el puerto serie no siempre se identifica con Serial (en determinadas placas puede ser Serial1, Serial2…) se ha definido el objeto del puerto usado asignándolo a la macro PUERTO_SERIE. Detectando el tipo de placa usada podría añadirse un poco de inteligencia a la selección del puerto serie; más adelante se repasará cómo puede averiguar el tipo de Arduino. El resto de definiciones son las habituales que permiten «dar nombre» a los valores constantes para evitar repetirlos (y equivocarse) y hacen más sencillo cambiarlos.

    El ejemplo anterior se supone que conecta el módulo wifi ESP8266 al punto de acceso indicado pero ¿Estaba ya conectado anteriormente? ¿Ha funcionado la conexión? Para saberlo, necesitamos «escuchar» lo que dice el ESP8266

    Recibir datos desde el módulo wifi ESP8266

    Conectando al ordenador el sniffer de datos explicado más arriba se puede ver lo que Arduino ha enviado al ESP8266 y la respuesta del mismo. Para leer desde Arduino y procesar en él la información será necesario detectar con Serial.available() si ha llegado algún dato y en tal caso cargarlo con Serial.read(). En el siguiente ejemplo se muestra cómo leer la respuesta de AT+CWJAP?, que informará si hay una conexión con algún punto de acceso.

    Como en una placa Arduino Uno (y en otras) abrir el monitor serie resetea el programa, se puede aprovechar para ver en la consola serie de Arduino la información que envía al ESP8266 como muestra la captura de pantalla de la imagen de abajo.

    Recibir datos del módulo wifi ESP8266 desde Arduino. Ejemplo básico

    Analizar la respuesta enviada por el módulo wifi ESP8266

    Ya se ha visto cómo leer la información que llega a Arduino desde el ESP8266. El problema con el que hay que tratar es que no se sabe cuándo empezará a llegar, cuánto tardará en llegar, qué longitud tendrá… y no es muy eficiente estar esperando a que la respuesta del ESP8266 se reciba sin dejar que el microcontrolador realice otras tareas mientras tanto.

    Una forma sencilla de gestionar esta circunstancia es iterar sobre los datos recibidos buscando respuestas concretas con las que, por ejemplo, activar indicadores (banderas o variables booleanas) que determinarán si se sigue buscando en el texto recibido y qué acciones deben realizarse en función de la información que llega desde el ESP8266. Mientras llega la respuesta el microcontrolador puede dedicarse a otras tareas, por ejemplo, recibir datos de sensores y procesarlos.

    Buscar un texto en la información recibida desde el ESP8266

    Para ir buscando en el texto que llega desde el ESP8266 se puede ir comparando cada letra recibida con la que corresponda al mensaje que se busca. Será necesario usar un contador (o un puntero) que señale a la letra que toque comparar; si el carácter que llega desde el ESP8266 es igual al que se está examinando en el mensaje, el contador avanza, si es diferente se inicializa.

    Para saber que se ha llegado al final se consulta el siguiente carácter del mensaje buscado, que será cero (\0) o se almacena la longitud del mensaje para, comparándola con el contador, saber si se ha terminado de comparar y por tanto el módulo wifi ESP8266 ha enviado el mensaje buscado.

    En el siguiente ejemplo se utiliza la orden AT+CWLAP que devolverá una lista de los puntos de acceso y dentro de ellos se busca uno llamado «wifi polaridad.es». Aunque se ha optado por verificar que el último carácter sea cero, como el buffer solamente almacena el texto buscado y se conoce su longitud, se podría también comprobar si se han recibido tal número de letras correctas. Con un LED conectado al pin 2 se informa de que se ha encontrado el texto que se esperaba.

    En el código del ejemplo anterior también puede verse una forma de elegir el puerto serie dependiendo del tipo de placa Arduino utilizada. En este ejemplo se supone que se dispone de tres tipos de placas para el proyecto: una Arduino Uno, una Arduino Mega 2560 y una Arduino Leonardo. Si se trabaja con una Arduino Uno se usará Serial y en caso contrario Serial1.

    Si se trabaja con una placa Arduino Leonardo se puede usar el mismo método para detener el programa y esperar a que la consola (el puerto serie asociado a Serial) esté disponible.

    Buscar varios textos en la respuesta del ESP8266

    El código del ejemplo anterior sirve para buscar un texto en la información enviada por el ESP8266 pero si la respuesta puede incluir diferente información dependiendo del funcionamiento. Supongamos, para empezar por un caso sencillo en el próximo ejemplo, que el texto enviado por el MCU ESP8266 es OK cuando la operación se realiza correctamente y ERROR en caso contrario, como ocurre con la orden AT+CWJAP?, que sirve para verificar si el módulo wifi ESP8266 ya está conectado a un punto de acceso.

    Esta nueva implementación del mismo método, que busca la coincidencia con varios posibles mensajes, permite elegir entre diferentes acciones en función de la respuesta recibida desde el ESP8266, simplemente encender el LED que corresponda.

    Limitar el tiempo de espera de recepción de una respuesta

    Hasta ahora no se ha hecho referencia a una cuestión relevante: el tiempo de espera máximo (timeout) antes de dar por fracasada una operación. Si por cualquier circunstancia se pierde la conexión con el módulo wifi ESP8266, el módulo con el punto de acceso, el punto de acceso con Internet o, por ejemplo, un hipotético servidor no está disponible, el programa puede quedar bloqueado en un punto esperando indefinidamente, por lo que habrá que articular una respuesta a tales circunstancias. El tiempo de espera máximo podrá configurarse para toda la aplicación, normalmente será más «generoso» en ese caso, o se pueden programar tiempos de espera individuales para cada operación.

    Para comprobar que ha pasado (al menos) cierto intervalo de tiempo se suele restar de la «hora» actual la «hora» del momento en el que inicia la cuenta y se verifica que la diferencia sea mayor que el límite buscado. Esta «hora» no tiene que ser el tiempo real, habitualmente corresponde con el intervalo que ha transcurrido desde que el MCU empieza a contar el tiempo; esto no afecta al programa ya que lo que interesa es tiempo transcurrido y no el absoluto.

    Habitualmente, para comprobar si ha transcurrido cierto intervalo, se utiliza la expresión del tipo:

    La variable milisegundos_al_empezar contiene el valor de millis() de cierto momento de la ejecución desde el que se cronometra, por lo que no es raro que su nombre haga referencia a la palabra «cronómetro». La variable intervalo_de_tiempo contiene la cantidad de milisegundos máxima que hace cierta la expresión anterior, es decir, que representa el timeout; es habitual que sea una constante (o una macro) y, como en el caso anterior, es frecuente que en su nombre aparezca la palabra «TIMEOUT». Si se trabaja con intervalos muy cortos se puede utilizar micros() en lugar de millis() (microsegundos en lugar de milisegundos) aunque es mucho menos habitual y mucho menos preciso.

    Un entero largo en Arduino (unsigned long) ocupa 4 bytes (32 bits), así que el valor mayor que puede representar es 4294967295 (2 elevado a 32 menos uno, por empezar en cero). En una placa Arduino que esté funcionando continuamente el contador de milisegundos se reiniciará (volverá a ser cero) cada 50 días aproximadamente. Al restar con tipos de datos sin signo se reproduce el mismo comportamiento (dar la vuelta al contador) por lo que es viable controlar indefinidamente el timeout.

    El código anterior muestra una implementación muy básica de la limitación del tiempo de espera incorporando las líneas marcadas con respecto de ejemplo que le precede. Como la verificación del timeout se realiza después de procesar los datos que llegan desde el módulo wifi ESP8266, puede darse por buena la operación aunque la recepción tarde más que el tiempo de espera impuesto.

    Ejecutar una operación compleja definida por varias órdenes AT

    Para tener una referencia de ejemplo de la finalidad de la aplicación que explota el módulo wifi ESP8266, supongamos que se trata de almacenar información en una base de datos a la que se accede por medio de un servicio web para mantener un registro de la temperatura. El siguiente código lee un sensor conectado a una entrada analógica cada cierto intervalo de tiempo, calcula el valor medio y, transcurrido un intervalo de tiempo mayor, lo envía al servidor web (al estilo IoT) por medio de una petición HTTP (POST, GET…).

    En este ejemplo de grabación de temperaturas, se accede a un servidor web cada cinco minutos. Aunque la disponibilidad no sea especialmente alta es de esperar que la propuesta funcionara pero si fuera necesaria una frecuencia de grabación mayor habría que implementar otros recursos, por ejemplo, se podría utilizar un buffer de datos que esperan enviarse, para mandar varios cuando el servidor pueda atender y acopiarlos para cuando no esté disponible. Si la frecuencia con la que se necesita grabar los datos fuera aún mayor habría que plantear otro tipo de protocolos como alternativa al HTTP o incluso sustituir TCP por UDP para poder enviar a la velocidad requerida la mayoría de los datos aún a costa de perder alguno.

    Las operaciones que forman la tarea que se trata de realizar para ir enviando la temperatura serían:

    • Reiniciar el módulo wifi
    • Desconectar del punto de acceso actual (por si existiera una conexión por defecto)
    • Establecer la configuración. Para el ejemplo se supone que hay que configurar el modo de conexión (simple) y el papel en las comunicaciones wifi (estación)
    • Conectar al punto de acceso
    • Verificar que la conexión es correcta (en realidad, este es el punto de entrada) Si no hay conexión, empezar el proceso desde el principio
    • Conectar al servidor
    • Enviar la petición HTTP con los datos que deben almacenarse

    El orden de las operaciones no tiene que ser exactamente así (aunque lo sea la operativa) y cada paso puede necesitar varias órdenes AT del ESP8266, por ejemplo, la configuración de la lista anterior necesitaría dos: AT+CIPMUX=0 y AT+CWMODE=1.

    Una estructura de datos para representar operaciones sobre el ESP8266

    En los ejemplos anteriores, aunque de forma muy básica, ya se insinúa una solución genérica al problema: utilizar una estructura de datos que almacene las posibles respuestas y las acciones que deben tomarse en cada caso; enviar una acción, esperar una respuesta y proceder conforme a lo que la respuesta signifique. Como cada operación compleja requerirá varias órdenes AT del ESP8266, la estructura de datos deberá enlazar una operación con otras, siguientes o anteriores, las que corresponda realizar en cada caso en función de la respuesta del ESP8266.

    En los ejemplos anteriores se buscaba un mensaje dentro de la respuesta del ESP8266 y se interpretaba como acierto o error. Además de una recepción (y análisis) de todo el texto recibido, para tener un mínimo genérico es recomendable atender también a la finalización del mensaje o, dicho de otro modo, a la disponibilidad del módulo wifi ESP8266 para recibir nuevas órdenes. De esta manera, el cambio a un estado que podríamos llamar, por ejemplo, «wifi disponible», podría ser recibir el nombre del punto de acceso y recibir el texto ERROR o el texto OK significaría que el ESP8266 ha terminado la respuesta y que se puede enviar ya la siguiente orden AT al ESP8266.

    El código anterior utiliza un vector (operacion) para almacenar el texto de las sucesivas operaciones que forman la tarea completa. Se usa una matriz bidimensional (mensaje) con las tres respuestas que se analizan. Como explicaba más arriba, es necesario buscar los mensajes que representan el final de la respuesta además del mensaje que represente una respuesta correcta o errónea. No todas las operaciones tendrán el mismo número de respuestas posibles; cuando haya menos respuestas se puede utilizar un mensaje vacío que consuma el menor número posible de ciclos en su análisis (aún así no es la forma más óptima). Lógicamente, será necesario que el mínimo de respuestas buscado (tres en el ejemplo) recoja todas las posibilidades de funcionamiento, aunque no sean todas las posibles.

    Al hablar de las posibles respuestas ya se aprecia que este ejemplo no es muy útil para recibir datos con un formato arbitrario desde un módulo wifi ESP8266, pero es que, en el contexto del uso con microcontroladores no es habitual; lo más frecuente es enviar datos datos recabados por los sensores que tienen conectados y/o recibir información sobre qué hacer con los actuadores que controla. Una información muy tasada, que se puede predecir muy bien.

    En la estructura de datos anterior, al igual que se hace para expresar las posibles repuestas que se analizan, para determinar la operación que debe realizarse en cada caso se usa también una matriz bidimensional (siguiente_operacion). En concreto, se ha elegido responder a tres tipos de mensajes: ① un texto arbitrario (LITERAL) para verificar si existe conexión al punto de acceso wifi y al servidor, ② un texto para detectar errores en el proceso (FALLO) y ③ un texto que indica que la operación se ha realizado satisfactoriamente (ACIERTO).

    Por último, hay dos vectores más para establecer el tiempo de espera máximo antes de desistir (timeout) y especificar (configuracion) si la operación termina sin esperar respuesta (ESPERAR_RESPUESTA) y los mensajes que indican el final de la comunicación. Este último vector, para ilustrar un ejemplo de cómo se podría ahorrar memoria memoria, trabaja con los bits de un byte de configuración para indicar los diferentes estados.

    Las primeras órdenes AT del ESP8266 de la estructura de datos esperan siempre respuesta, que puede ser el mensaje de acierto o error. Cuando se produce un error se reinicia el módulo y se vuelve a empezar y si el mensaje indica que la operación es correcta se pasa a la siguiente.

    Cuando se ha conectado con el servidor, el patrón cambia. En este caso es necesario ① enviar la longitud del paquete de datos que se quiere transmitir y ② componer la petición HTTP con un texto fijo más el valor (de la temperatura) que se envía para ser almacenado en el servidor. La preparación de estos datos se realiza en cada envío y es necesario partir en dos (avisar de la longitud) o tres (enviar la petición HTTP) la orden AT del ESP8266. Solamente la última de las partes en las que se divide la operación esperará respuesta.

    En este caso funcionará sin problemas (puede que avisando de que el módulo está ocupado) pero cuando la longitud de los datos sea mayor será necesario partir en trozos más pequeños los bloques de datos e incluso puede que sea necesario implementar una espera, como se hace con la lectura de la temperatura, para dar tiempo el módulo a enviar los datos si llenar su buffer.

    Junto con otras macros que ya se han explicado antes, en el código de ejemplo de arriba se muestra cómo se definen los diferentes estados con los que especificar si se debe esperar respuesta y en su caso qué mensaje indica que ha terminado.

    Como en diferentes puntos del código se enviará una operación (cuando toque enviar la media de la temperatura, si se supera el tiempo de espera de una operación, al terminar correctamente la operación actual…) pero se establece de forma global cómo hacerlo, se ha definido una macro ENVIAR_OPERACION que agrupa los pasos que intervienen en el envío.

    El siguiente es el código del programa principal del ejemplo. La tarea más externa es la que se encarga de ir muestreando la temperatura para calcular la media y, cada cierto lapso de tiempo, se envía al servidor utilizando el módulo wifi ESP8266. Una vez enviada cada operación se analiza la respuesta para determinar cuál es la siguiente o si se ha terminado la tarea de envío de información.

    Lógicamente, sobre el código anterior cabe efectuar varias acciones de optimización pero, como se trata de un ejemplo para entender cómo se puede utilizar el ESP8266 de forma genérica, solamente merece la pena detenerse en algunos aspectos, el primero es la estructura de datos. Parece que lo lógico es utilizar una estructura de datos del lenguaje de programación (struct) para representar la información que se procesa: las órdenes AT del ESP8266 y los mensajes que se analizan.

    Usar una estructura (struct) para almacenar los datos en lugar de las matrices del ejemplo (partiendo de ellas) es trivial y, aunque puede resultar en un código más elegante no supone ninguna mejora en el resultado. La verdadera alternativa que plantea el uso de struct es implementar, como se explica más abajo, longitudes variables en las estructuras que datos «interiores» que son referidas por ellas. De esta forma, por ejemplo, no sería necesario que una operación tuviera un número fijo de respuestas a analizar.

    Este planteamiento sugiere que es la mejor forma de implementar la solución pero el inconveniente es que sería necesario utilizar asignación dinámica de memoria, una práctica de riesgo trabajando con un microcontrolador que exige medir cuidadosamente cuánta memoria se utilizará en tiempo de ejecución, ya que difícilmente el compilador podrá avisarnos de esto y cabe la posibilidad cierta de agotar la memoria (o la pila) con el consecuencias fatales para la ejecución del programa.

    En la línea de optimizar el código, es interesante recordar que, en un programa de este tipo, que utiliza una gran cantidad de texto, se puede ahorrar espacio de la memoria SRAM almacenando las cadenas de texto en la memoria de programa (flash) con la macro F(). En las siguientes capturas de pantalla puede verse la diferente distribución de memoria de programa y dinámica con un uso normal del texto y utilizando la macro F().

    ejemplo de código Arduino que usa texto almacenado en la memoria de programa (flash)
    ejemplo de código Arduino que usa texto en SRAM

    Con respecto a las acciones que se ejecutan según la información que llegue desde el módulo wifi ESP8266, como alternativa a comprobar el mensaje desde el código y realizar una u otra según lo recibido, pueden almacenarse en esta estructura de datos punteros a las funciones que realizan cada tarea en lugar de indicadores de estado (banderas) que avisen de un determinado estado que la aplicación se encarga de gestionar, por ejemplo, dentro del bucle principal.

    El siguiente es un ejemplo de estructuras para almacenar los datos de las peticiones al ESP8266 (el tipo de datos operacion_esp8266) y sus respuestas (el tipo de datos respuesta_esp8266).

    Como la estructura que representa la operación (los datos que se envían al módulo wifi ESP8266) hace referencia a la estructura con la que se definen las respuestas, y la estructura de las respuesta a la estructura de las operaciones, es necesario declarar ambas primero, al definir el nuevo tipo de datos, y después definir su contenido.

    El anterior ejemplo considera que en el programa que lo incluye se ha optado por usar un indicador de estado, que deberá corresponder con una variable accesible desde el código que se encargue de realizar unas u otras operaciones según indique dicho valor. Si en la respuesta del ESP8266 que se analiza se encuentra determinado texto, el estado toma el valor que indica la estructura de la respuesta correspondiente.

    Como se ha dicho antes, otra alternativa, ya sea para sustituir o para complementar a un indicador de estado, sería almacenar en la estructura referencia una función (un puntero) que se llamaría al encontrar cierto texto en la respuesta desde el módulo wifi ESP8266.

    En el ejemplo anterior se ha añadido a la estructura de datos que sirve para procesar la respuesta procedente del módulo wifi ESP8266 un puntero a una (supuesta) función que devuelve un dato de tipo float (podría ser el valor ponderado de una lectura analógica) y a la que se le aportan como argumentos dos bytes (dos unsigned char que podrían ser el pin desde el que se lee la entrada analógica y el que activa el ENABLE de un hipotético integrado).

    En el desarrollo para MCU, al contrario de lo que se ocurre en el estilo de desarrollo para sistemas mayores, no es tan infrecuente utilizar variables globales al definir el comportamiento (global) de la aplicación que controla un montaje, así que no será especialmente raro encontrar este tipo de definiciones como funciones sin parámetros y que no devuelven valores, algo como void (*accion)();

    Si se trabaja con esta forma de representar los datos, usando struct de datos de longitud variable, será necesario asignar de forma dinámica la memoria con malloc() (o new(), si se usan objetos), que utilizará como parámetro la cantidad de memoria asignada y devolverá un puntero al principio de la zona de memoria que se reserva. Con sizeof() sobre el tipo que se almacenan, multiplicado por el número de elementos usados, se puede obtener la cantidad de memoria que se necesita. En las capturas de pantalla de abajo puede verse un ejemplo con y sin usar malloc(); cuidado con la memoria usada por el programa en el primer caso, necesita cargar la librería que contiene esta función.

    Ejemplo de asignación de memoria con malloc en Arduino

    Ejemplo de asignación de texto sin malloc en Arduino

    Si las operaciones sobre el módulo wifi ESP8266 variaran a lo largo de la ejecución del programa será necesario ir liberando la memoria que no se utiliza con free() (o delete(), en el caso de ser objetos). Aunque es razonable esperar que el compilador (GCC) optimizará el programa para evitar el fraccionamiento de la memoria, seguramente el rendimiento no será tan óptimo como trabajar con memoria asignada de forma estática.

    Aunque en este ejemplo (en ambas implementaciones) no tiene mucho sentido, en la línea de generalizar el funcionamiento para poder aplicarlo a otros casos, hay que resaltar que el envío de datos repite siempre el mismo protocolo: avisar de la cantidad de bytes que se enviarán, esperar el indicador (>) y enviar los datos.

    Como en este ejemplo solamente se usa en una ocasión (toda la petición se hace en un paquete), no parece muy útil pero, en general, puede ser necesario para realizar varios envíos en la misma operación, incluyendo los casos en que se deban transmitir cantidades importantes de datos que deberán fragmentarse para no desbordar la memoria del ESP8266.

    Para implementar este comportamiento se pueden utilizar los dos últimos elementos de la conexión de forma que a cada envío se rellenen los datos con los valores que correspondan: en el primer caso el número de bytes enviados y en el segundo la (parte de la) petición que se transmita.

    Para repetir la asignación y el envío de los diferentes elementos que se deban transmitir se pueden almacenar en un vector. Este nuevo vector será el que determine el final de la operación compleja y no la última operación como hasta ahora.

    Publicar comentario

    Puede que te hayas perdido