ESP8266 y Arduino: Una Mirada Profunda a las Interrupciones por Software

El mundo de la microcontrolación ofrece herramientas cada vez más potentes y accesibles para desarrolladores y aficionados. El ESP8266, con su capacidad de conectividad Wi-Fi integrada, se ha convertido en un componente muy popular, especialmente cuando se programa en el familiar entorno de Arduino. Si bien la transición desde un Arduino convencional al ESP8266 es relativamente sencilla, existen matices y particularidades que merecen una exploración detallada. Esta guía se adentra en la programación del ESP8266 en el entorno Arduino, centrándose en las funciones de tiempo, la gestión de interrupciones y las diferencias clave con respecto a su contraparte, el Arduino.

Funciones de Tiempo: millis(), micros() y delay() en el ESP8266

Las funciones de tiempo millis() y micros(), que miden el tiempo transcurrido desde el inicio del programa en milisegundos y microsegundos respectivamente, funcionan de manera idéntica en el ESP8266 y en un Arduino convencional. Del mismo modo, las funciones delay() y delayMicroseconds() para introducir pausas en la ejecución del código también están disponibles. Sin embargo, el uso de estas funciones en el ESP8266 requiere una consideración especial debido a la naturaleza de su funcionamiento interno, particularmente en lo que respecta a la gestión de la conectividad Wi-Fi y la pila TCP/IP.

Diagrama de flujo comparando millis y micros en Arduino y ESP8266

El Concepto de "Yielding" y su Importancia

Una de las diferencias más significativas entre programar un ESP8266 y un Arduino reside en el concepto de "yielding". El ESP8266 dedica una parte considerable de sus recursos a mantener activa la conexión Wi-Fi y a gestionar la pila TCP/IP. Si estas tareas no se ejecutan con la frecuencia adecuada, el sistema puede experimentar problemas de comunicación, bloqueos e incluso reinicios inesperados.

Para evitar estos inconvenientes, se recomienda encarecidamente limitar cualquier bloqueo en el código a un máximo de 100-200 milisegundos. El ESP8266 cuenta con un mecanismo de Watchdog Timer (WDT) que reiniciará el dispositivo si transcurren 3 segundos sin que se ejecuten las funciones críticas de gestión de red. Incluso con el WDT desactivado, una espera prolongada superior a 8 segundos provocará un reinicio automático del ESP8266.

Las funciones internas del ESP8266, que incluyen las operaciones de red, se ejecutan al final de cada ciclo del loop(), al llamar a la función delay(), o mediante la función yield(). Esta última, desarrollada específicamente para el ESP8266 en el entorno Arduino, puede considerarse un equivalente a delay(0), permitiendo que las tareas de red se ejecuten sin detener por completo el programa.

En resumen, si un proceso en su código requiere más de 100-200 milisegundos para completarse, es crucial modificarlo para que incluya llamadas periódicas a delay() o yield(). Es importante notar que la función delayMicroseconds(), a diferencia de delay(), no invoca a yield(). Por lo tanto, su uso para esperas superiores a aproximadamente 20 milisegundos debe ser evitado para no comprometer la estabilidad de la conexión Wi-Fi.

Conexiones y Hardware: Pines y Periféricos del ESP8266

La asignación de pines en la implementación del ESP8266 para el entorno Arduino, aunque teóricamente correcta, a menudo presenta discrepancias en la práctica debido a la variedad de fabricantes y diseños de placas. La tabla siguiente muestra la correspondencia entre los alias comunes y los números GPIO:

AliasGPIOAliasGPIOAliasGPIO
D0GPIO3D6GPIO12D12GPIO12
D1GPIO1D7GPIO13D13GPIO14
D2GPIO16D8GPIO0D14GPIO4
D3GPIO5D9GPIO2D15GPIO5
D4GPIO4D10GPIO15
D5GPIO14D11GPIO13

Para referirse a un pin, se pueden utilizar ambas designaciones indistintamente. Por ejemplo, digitalWrite(D1, HIGH) y digitalWrite(1, HIGH) deberían ser equivalentes. Sin embargo, es fundamental prestar especial atención al pinout específico de la placa ESP8266 que se esté utilizando, especialmente en placas de origen asiático, para evitar errores de conexión y programación.

Entradas y Salidas Digitales (GPIO)

La configuración de los pines GPIO como entradas o salidas digitales es muy similar a la de un Arduino convencional. La función pinMode(pin, mode) se utiliza para establecer el modo de un GPIO, donde mode puede ser OUTPUT, INPUT o INPUT_PULLUP. El pin GPIO16 (D2) ofrece un modo adicional, INPUT_PULLDOWN_16, aunque su uso es menos común. Por defecto, todos los pines se inicializan como INPUT.

Diagrama de pines GPIO de un ESP8266

Para escribir en una salida digital, se utiliza la función digitalWrite(pin, value), donde value es LOW o HIGH. Para leer el estado de una entrada digital, se emplea la función digitalRead(pin).

Salidas PWM por Software

El ESP8266 no dispone de PWM (Modulación por Ancho de Pulso) por hardware, a diferencia de algunos microcontroladores de Arduino. En su lugar, el PWM se implementa por software, lo que implica un coste computacional adicional pero permite utilizar esta funcionalidad en cualquier pin GPIO.

La función para generar PWM es analogWrite(pin, value). El rango por defecto de value es de 10 bits (0-1023), a diferencia de muchos modelos de Arduino que usan 8 bits (0-255). El rango puede ajustarse hasta 14 bits mediante analogWriteRange(maxValue). La frecuencia por defecto del PWM es de 1kHz, pero puede modificarse con analogWriteFreq(frequency), con límites entre 100Hz y 40kHz.

Es importante destacar que si se desea reutilizar un pin configurado como PWM para una salida digital, es necesario desactivar explícitamente el PWM con analogWrite(pin, 0).

Entrada Analógica (ADC)

El ESP8266 posee una única entrada analógica, designada como A0 en el entorno Arduino. La lectura se realiza con la función analogRead(A0), devolviendo un valor de 10 bits (0-1023) proporcional al voltaje de entrada.

Es crucial tener en cuenta que la entrada analógica del ESP8266 tiene un rango máximo de 1V. Muchas placas incorporan un divisor de voltaje o un amplificador para permitir la lectura de voltajes de hasta 5V. Siempre se debe verificar la tensión máxima admisible del ADC de la placa para evitar dañarlo.

Además de la entrada analógica estándar, el ESP8266 puede medir su propio voltaje de alimentación mediante ESP.getVcc(). Este modo inhabilita temporalmente la entrada analógica A0.

Interrupciones en el ESP8266

El ESP8266 soporta interrupciones en todos sus pines GPIO, a excepción del GPIO16 (D2). El manejo de estas interrupciones es similar al de las interrupciones por hardware en Arduino, utilizando la función attachInterrupt(). Los modos disponibles para disparar la interrupción incluyen RISING, FALLING, CHANGE, ONLOW, ONHIGH, ONLOW_WE, y ONHIGH_WE.

La implementación de interrupciones por software en el ESP8266, aunque conceptualmente similar a Arduino, presenta particularidades. Mientras que en Arduino se utilizan funciones como attachInterrupt(), en el ESP8266 para ciertas interrupciones, como las relacionadas con pines, se emplea un enfoque que puede involucrar métodos como addListener().

Un usuario ha compartido su experiencia al observar que las interrupciones en el ESP8266 son significativamente más rápidas que en un Arduino Uno. Realizando una prueba con un microcontrolador funcionando a 80 MHz, la detección y generación de un evento de interrupción tomó aproximadamente 2.6 µs, en comparación con los 13.4 µs en un Arduino Uno. Se ha observado que la interrupción puede "adelantarse" a la salida, sugiriendo que el cambio en los registros internos del puerto de salida genera la interrupción antes de que el cambio se refleje físicamente en el pin.

Diagrama conceptual de una interrupción

Comparativa con las Interrupciones en Arduino

En el contexto de Arduino, las interrupciones son un mecanismo fundamental para reaccionar a eventos externos sin necesidad de estar constantemente monitorizando pines. Cuando ocurre un evento (como un flanco en un pin o el desbordamiento de un temporizador), el microcontrolador detiene la ejecución del programa principal, ejecuta una rutina de servicio de interrupción (ISR), y luego reanuda el programa principal.

Las interrupciones en Arduino se dividen principalmente en programadas y externas. Las interrupciones externas permiten reaccionar a eventos físicos en pines específicos. Para configurarlas, se utiliza la función attachInterrupt(digitalPinToInterrupt(pin), ISR, mode).

  • digitalPinToInterrupt(pin): Convierte el número de pin físico al número de interrupción interno del microcontrolador.
  • ISR: El nombre de la función que se ejecutará cuando ocurra la interrupción. Las ISR deben ser lo más cortas y rápidas posible, y no pueden utilizar delay() ni devolver valores.
  • mode: Define cuándo se dispara la interrupción (RISING, FALLING, CHANGE).

Es crucial declarar las variables que se utilizan tanto en el bucle principal como dentro de las ISR como volatile. Esta palabra clave indica al compilador que la variable puede cambiar de valor en cualquier momento, forzando su recarga cada vez que se accede a ella.

Interrupciones por Timer en Arduino y su Equivalencia en ESP8266

Mientras que el ESP8266 se enfoca en interrupciones externas y la gestión de red, el concepto de interrupciones por temporizador (Timer) es también relevante en microcontroladores. En Arduino, los temporizadores son circuitos que miden ciclos de reloj para ejecutar tareas de forma periódica. Cada temporizador (Timer0, Timer1, Timer2 en Arduino UNO) tiene un contador que incrementa a una velocidad determinada por un prescalador. Las interrupciones del temporizador se activan cuando el contador alcanza un valor específico.

La velocidad del reloj del microcontrolador (por ejemplo, 16 MHz en Arduino UNO) puede ser dividida mediante un prescalador (1, 8, 64, 256, 1024) para controlar la frecuencia de las interrupciones. La fórmula es: VELOCIDAD TIMER (Hz) = VELOCIDAD RELOJ (16MHz) / PRESCALER.

Si bien la información proporcionada no detalla explícitamente la implementación de interrupciones por timer en el ESP8266 dentro del entorno Arduino, la filosofía subyacente de ejecutar código de forma periódica puede lograrse mediante otras técnicas, como el uso de la función millis() y la gestión del tiempo de manera no bloqueante, o explorando librerías específicas que puedan abstraer la complejidad del hardware de temporización del ESP8266.

Memoria y Optimización: PROGMEM

La macro PROGMEM permite almacenar variables y datos en la memoria Flash en lugar de la memoria RAM dinámica. Esto es particularmente útil para grandes cantidades de datos, como tablas de consulta o cadenas de texto, liberando así la limitada RAM.

Una diferencia clave en el ESP8266 respecto a Arduino es que el equivalente a PROGMEM no realiza un análisis previo para eliminar duplicados de literales. Por ejemplo, si se define const char hello[] PROGMEM = "hello"; dos veces, el literal "hello" se almacenará dos veces en la memoria Flash. Para evitar esto y optimizar el uso de la memoria, se puede emplear la función FPSTR() (Flash Program String Table).

Comunicación: Puertos Serie, I2C y SPI

Puertos Serie (UART)

El ESP8266, al igual que Arduino, utiliza el puerto serie para la comunicación. Las funciones estándar como read(), write(), print(), y println() son compatibles. La principal diferencia radica en que el ESP8266 cuenta con dos puertos serie (UARTs):

  • UART0: Por defecto, utiliza los pines GPIO1 (TX0) y GPIO3 (RX0). Opcionalmente, puede configurarse para usar GPIO15 (TX0) y GPIO13 (RX0) mediante la función Serial.swap().
  • UART1: Asociado a los pines TX1 (GPIO2) y RX2 (GPIO8). Sin embargo, el GPIO8 se utiliza para la memoria externa, por lo que el UART1 solo puede emplearse para enviar datos. Para su uso, se utilizan las funciones precedidas por Serial1 (ej. Serial1.begin()).

Diagrama de comunicación serial en ESP8266

Bus I2C

La comunicación I2C en el ESP8266 es similar a la de Arduino, empleando las mismas funciones. No obstante, el ESP8266 simula la comunicación I2C por software, lo que puede generar una carga de procesamiento adicional. La ventaja es que se pueden utilizar dos pines GPIO cualesquiera para la comunicación I2C. Por defecto, se emplean SDA (GPIO4 / D2) y SCL (GPIO5 / D14), pero estos pueden ser reconfigurados con la función Wire.begin(sda, scl). La velocidad máxima de I2C es de aproximadamente 450 kHz.

Bus SPI

El ESP8266 dispone de un bus SPI por hardware (HSPI) accesible para el usuario. Su funcionamiento y las funciones utilizadas son análogas a las de Arduino. Los pines por defecto son MISO (GPIO12 / D12), MOSI (GPIO13 / D7), CLK (GPIO14 / D13) y SS (GPIO15 / D10). La frecuencia del SPI puede ser modificada hasta 1 MHz con la función SPI.setFrequency(frequency).

Comunicación WiFi

La funcionalidad Wi-Fi es una de las características distintivas del ESP8266. Para acceder a ella, se utiliza la librería ESP8266WiFi, cuyo funcionamiento es comparable a la librería WiFi de Arduino. La inclusión se realiza con #include <ESP8266WiFi.h>. El uso intensivo de esta funcionalidad se abordará en tutoriales posteriores.

Lectura de sensores con LabVIEW y ESP32 | ESP8266 mediante Wifi

Compatibilidad de Librerías de Arduino

Una pregunta recurrente es la compatibilidad de las librerías de Arduino con el ESP8266. En general, las librerías que utilizan exclusivamente código C++ y funciones estándar del entorno Arduino para el manejo de hardware son compatibles. Sin embargo, aquellas que acceden a elementos de bajo nivel del microcontrolador, como registros internos, temporizadores específicos o código ensamblador, pueden no funcionar a menos que hayan sido adaptadas específicamente para el ESP8266.

Afortunadamente, la gran popularidad del ESP8266 ha impulsado a la comunidad a adaptar o desarrollar versiones compatibles de muchas librerías populares. Es fundamental buscar explícitamente las versiones de librerías que indiquen compatibilidad con ESP8266.

En conclusión, si bien la programación del ESP8266 en el entorno Arduino comparte muchas similitudes con la programación de un Arduino convencional, es esencial comprender las diferencias clave relacionadas con la gestión del tiempo y el "yielding", la particularidades del hardware, y la implementación de ciertas funcionalidades como el PWM y las interrupciones. Una cuidadosa atención a estos detalles permitirá aprovechar al máximo las capacidades del ESP8266.

tags: #arduino #esp8266 #interrupcion #por #software