Revista Tecnología

Compilando y subiendo los primeros programas al ESP8266. Usando GPIO y UART-

Publicado el 01 septiembre 2017 por Gaspar Fernández Moreno @gaspar_fm

Me ha gustado mucho la facilidad de uso (y sobre todo el precio) de este dispositivo. El ESP8266 puede dotar a nuestros proyectos de la conectividad que necesitaban y así adentrarnos en el Internet de las cosas o IoT.

Lo primero es sentirnos cómodos con el entorno. Así que voy a proponer dos opciones. La primera, multiplataforma será utilizando el mismo entorno Arduino. En este caso, al final del proceso cuando estemos compilando o subiendo el código a nuestro chip seleccionaremos la placa ESP8266 y compilaremos para ella. Podremos conectar el ESP8266 con un adaptador USB-UART o incluso desde el propio Arduino. Para ver cómo debemos conectarlo, visita el post sobre programación del ESP8266.

Instalación en el entorno Arduino

Como dije antes es la opción más cómoda y nos va a funcionar sea cual sea nuestro sistema operativo. De manera muy parecida a como hice en este post sobre la programación de ATtinys, podemos añadir esta URL para gestionar tarjetas en Archivo / Preferencias:

http://arduino.esp8266.com/stable/package_esp8266com_index.json

Compilando subiendo primeros programas ESP8266. Usando GPIO UART-Compilando y subiendo los primeros programas al ESP8266. Usando GPIO y UART-
Tras esto, vamos a Herramientas / Placa / Gestor de tarjetas y buscamos ESP8266. Con lo que debería salirnos algo así:
Compilando subiendo primeros programas ESP8266. Usando GPIO UART-Compilando y subiendo los primeros programas al ESP8266. Usando GPIO y UART-
Seguidamente seleccionamos el módulo y lo instalamos. El software ocupa cerca de 150Mb, entre bibliotecas, programas para compilar y programar, cabeceras y demás, así que tenemos que tener en cuenta el espacio que necesitaremos. Y tardará un rato entre la descarga de los módulos y la configuración.

Con Arduino no es muy duro, ¿eh? En este punto ya podemos crear cualquier programa para nuestro chip y subirlo sin complicación desde el entorno del que estamos acostumbrados. Además, desde el entorno Arduino podemos aprovecharnos de muchas facilidades que nos brinda el lenguaje, por ejemplo el Serial lo podemos programar de forma más parecida a como lo hacemos en un Atmega con las abstracciones que nos proporciona Arduino. El programa ocupará un poco más, ya que tenemos las abstracciones propias de Arduino y algunas bibliotecas y atajos de enlazado muy interesantes. Aunque los compiladores son muy listos a estas alturas y todo lo que subamos estará optimizado en tamaño.

Instalación independiente

Pero claro, no todo es Arduino y su ecosistema. Si de verdad queremos tener algo más de control sobre las biliotecas, la compilación, la programación y demás, nos vendrá realmente bien la instalación manual del software.
He utilizado Linux Mint 18.2 para realizar esta instalación, aunque debería ser muy parecido en Ubuntu, Debian y otras. Los nombres de los paquetes también suelen ser parecidos entre distribuciones (no los mismos, pero al menos se pueden encontrar más o menos fácil).

Instalar dependencias

Antes de nada vamos a ver qué necesitamos para instalar nuestro entorno. Lo primero, unos 4Gb de disco libres, porque vamos a copiar mucho código fuente y documentación.

Tras ello, instalaremos el software necesario que puede venir con nuestra distribución GNU/Linux:

Instalar software

Aunque podemos realizar el proceso de forma manual, vamos a utilizar esp-open-sdk. Aquí voy a poner algunas instrucciones sencillas para instalarlo. Pero podremos personalizar la instalación y hacer algunas cosas más. Leed el manual, que hay cosas muy interesantes, pero si queréis algo rápido:

.... esto tarda un rato .....


Tardará un rato porque tendremos que descargar muchas cosas (a mí me tardó unos 10 minutos más o menos).

Luego, es buena idea incluir en nuestro archivo /home/USUARIO/.profile lo siguiente:

PATH="$HOME/local/opt/stand/esp-open-sdk/xtensa-lx106-elf/bin:$PATH"

En mi caso, yo instalé todos los programas en $HOME/local/opt/stand/esp-open-sdk/ deberás cambiar este directorio por tu directorio de instalación. Esta línea en .profile sirve para que podamos ejecutar las utilidades instaladas en las próximas sesiones estemos donde estemos sin necesidad de hacer export PATH=...

Compilando ejemplos

Ya que tenemos el entorno preparado, vamos a proceder a compilar algunos de los ejemplos que vienen (y algunos otros que me he inventado) para hacer pruebas. Primero, vamos a compilar un programa de led intermitente, un blink para ver que todo funciona bien. Al menos, si vemos el led haciendo intermitencia, es que todo funciona perfectamente. De todas formas, al final del post veremos algunos problemas con los que me he encontrado.

Blinky a mano

Una vez instalamos esp-open-sdk dentro del directorio examples encontramos blinky. El famoso ejemplo de un led intermitente, algo así como un hola mundo, en el que podemos ver si nuestros programas compilan bien y si se suben bien, además, veremos el chip funcionando.
Puede que en futuras versiones no encontremos este ejemplo, o incluso aparezca modificado, aśi que pondré aquí el contenido de blinky.c:

Está pensado para cargarlo en un ESP8266-01 por lo que el led está en el GPIO01, que es el led azul que indica transmisión de datos. Al mismo tiempo ese puerto coincide con la transmisión de datos del puerto serie. Así que, cuando lo ejecutemos, al mismo tiempo que veremos el led parpadeando (si todo funciona bien), si abrimos una ventana serie mientras tenemos el chip enchufado en el ordenador veremos cómo recibimos datos en la consola.

Aunque el ejemplo trae un fichero Makefile, con el que simplemente haciendo make debe funcionar, quiero repasar todo lo necesario para compilar, generar un fichero binario y subir el fichero a nuestro dispositivo:


¿Por qué tanta complicación? Casi todo son bibliotecas, aunque veamos:

  • xtensa-lx106-elf-gcc : Es el compilador, la versión que hemos instalado de gcc.
  • -mlongcalls : A veces, cuando se traduce el código C a ensamblador, hay llamadas a funciones o a direcciones de memoria que no se pueden hacer directamente, porque están en posiciones de memoria lejanas.
  • -o blinky : El fichero binario de destino se llamará sólo blinky.
  • blinky.c : Es nuestro fichero fuente. Esta vez sólo tenemos uno. Aunque cuando tengamos más archivos utilizaremos siempre un Makefle en lugar de pelearnos con todos aquí, aunque podríamos incluir una sere de archivos .c desde aquí.
  • -Teagle.app.v6.ld : Para realizar el linkado vamos a utilizar un script del SDK del ESP8266 llamado eagle.app.v6.ld. Esto podrá variar en el futuro.
  • -nostdlib : No incluiremos la biblioteca estándar de C, ya que el SDK tiene su propia implementación.
  • -Wl,-start-group .... -Wl,-end-group : Lo que empieza por -Wl,... serán argumentos que le pasaremos al linker, es decir, el que asocia las llamadas que hacemos a funciones de biblioteca con las bibliotecas en cuestión. El hecho de utilizar grupos en el linker de GCC evita que tengamos que repetir el linkado de bibliotecas si, por ejemplo hay referencias cruzadas entre las propias bibliotecas. Es decir, que las bibliotecas hagan llamadas a funciones que están en otras bibliotecas por lo que tendríamos que linkarlas de nuevo. Se pueden añadir de nuevo en la línea de GCC, pero queda feo hacerlo.
  • -lmail , -lnet80211 , -lwpa ... : Todas estas serán bibliotecas que encontramos dentro del SDK. Dentro de sdk/lib están todas. Y seguramente para nuestros proyectos futuros utilizaremos alguna de ellas.

esptool.py elf2image blinky


Esto generará la imagen que tenemos que grabar en la memoria flash de nuestro chip. Seguidamente, conectaremos nuestro ESP8266 tal y como lo hicimos en este post para flashear el firmware:

Compilando subiendo primeros programas ESP8266. Usando GPIO UART-Compilando y subiendo los primeros programas al ESP8266. Usando GPIO y UART-

Y ejecutamos:

Tras ello, desconectamos los pins GPIO0 y GPIO2 (que estaban en modo programación). Desconectamos la corriente y la volvemos a conectar. Debemos ver el dispositivo con el led intermitente.

Utilizando C99, C11 y C++

Podemos utilizar también revisiones nuevas del lenguaje de programación C. Sin problema. El caso es que, como todavía quedan resquicios privativos en las bibliotecas del chip y, parece ser, que no se han publicado aún muchas cosas, hay funciones cuya cabecera no está disponible. Así que varias comunidades dedicadas al ESP8266 (y ESP32, que es la siguiente versión), han decidido crear un archivo llamado espmissingincludes.h; se puede buscar por Internet, aunque una de las versiones que más me convence es esta:

Este archivo debemos incluirlo en nuestro archivo .c del programa. Y ahora sí que nos dejará compilar con algo así:

Con lo que podremos utilizar características del lenguaje C mucho más modernas.

¿Qué hay de C++?
Bueno, el SDK del ESP8266 está hecho en C y las funciones que se llamen desde la biblioteca (como por ejemplo user_init(), deben estar enlazadas en C. Por eso, vamos a utilizar:

Así, nuestro blinky.cpp quedaría así:

Y... ¡ya podemos utilizar clases! Bueno, y algunas características de C++, incluso de C++11. la pega es que no podemos utilizar la biblioteca std, ni string, ni map, ni vector... una pena. De todas formas, no es imposible. Es un problema al enlazar la biblioteca estándar de C++, porque necesita funciones de C. Podríamos crear dichas funciones con las herramientas que tenemos o buscar versiones reducidas de las clases que necesitamos (que seguramente ocupen menos en memoria), o utilizar la construcción desde Arduino (aunque he de escudriñar un poco más el proceso que sigue Arduino para incluir la biblioteca de C++)

Un Makefile y estructura genericos para nuestros programas

Después de investigar un tiempo con el dispositivo en mis manos y probar algunas formas de construir fácilmente nuestros programas. Además, hacerlos de una forma independiente del editor o IDE que estemos utilizando. Lo primero será crear una estructura de directorios y archivos lógica y manejable para poder utilizar sin problemas. Muchas de las estructuras y ejemplos las estoy publicando en GitHub por lo que, si no hay ningún post nuevo sobre el tema, aquí encontraréis la última versión.

La estructura básica de un proyecto, es decir, dentro del directorio del proyecto encontraremos:

|
|- user/ (directorio con los ficheros de nuestro programa)
| |- user_main.c (fichero principal del programa)
| |- user_config.h (fichero de configuración (claves, ssid, contraseñas, o todas esas cosas relativas a configuración. Este archivo podrá estar vacío.)
| |_ (resto de ficheros del programa, .c y .h)
|- include/ (ficheros .h interesantes)
| |- espmissingincludes.h (el fichero que vimos antes con los símbolos que no tenía el SDK)
| |- driver/ (includes de drivers)
| |_ (resto de includes, ficheros de definiciones de bibliotecas, etc)
|- driver/ (drivers de dispositivos, tanto software como hardware que usemos en nuestro proyecto)
|- build/ (ficheros compilados, será un directorio interno donde se almacene todo lo que vayamos compilando)
|- firmware/ (ficheros de firmware, listos para ser copiados al chip)
|- Makefile (fichero con las instrucciones para construir el programa)
|- README (fichero de texto con instrucciones, explicaciones y demás)
|- ...

Podremos incluir ficheros como LICENSE (con la licencia), CHANGELOG (con el registro de cambios) o algún otro script más que nos ayude a construir. Así como un directorio lib/ donde podamos copiar todas esas bibliotecas extra que vayamos a utilizar en el proyecto. Pero creo que ésta es una buena estructura básica para empezar.

Con drivers me refiero a todo lo referente al UART, SPI, temporizador, o incluso herramientas de control o incluso programas que obtengan resultados de sensores, hablen con displays, etc.

Ahora os pongo un ejemplo de Makefile:

Para este archivo, en cada proyecto tendremos que cambiar XTENSA_TOOLS_ROOT y SDK_BASE para que apunten a las rutas correctas dentro de nuestro ordenador.

Con este Makefile haremos que con sencillos comandos podamos realizar diferentes tareas:

  • make : Construye el programa (compila, linka y crea los binarios que vamos a flashear).
  • make clean : Limpia los ficheros objeto creados, para volver a construir desde cero.
  • make flash : Sube los binarios al chip.
  • make flash ESPPORT=/dev/ttyUSB0 : Cambia el puerto serie para flashear.

Ejemplo de uso del UART (el serial USB)

Encontramos el ejemplo aquí. Para hacer la comunicación por los puertos RX y TX del UART0 he copiado el driver UART que encontramos en el SDK:

  • sdk/driver_lib/include/driver/uart.h a include/driver/
  • sdk/driver_lib/include/driver/uart_register.h a include/driver/
  • sdk/driver_lib/driver/uart.c a driver/

De esta forma tendremos acceso al UART. Podríamos programarlo nosotros, pero el SDK ya trae una buena forma de hacerlo, y una serie de constantes predefinidas que serán de mucha ayuda. Eso sí, ahora tendremos que hacer algunos cambios en el driver uart.c:

Establecer prioridad de la tarea

Debemos ajustar la prioridad de acuerdo a nuestro programa, encontraremos al principio del archivo algo así:

La directiva uart_recvTaskPrio deberá valer 0, 1, 2 o 3. Intenta que no haya conflictos con las tareas que ya tenga tu programa.

Declaración de la tarea externa

Lo siguiente será crear una declaración de la tarea de recepción. Aunque el driver ya trae una tarea que se lanza cuando se reciben mensajes desde el UART, queremos tener una tarea propia para procesarlos a nuestro gusto. Y esa tarea la definiremos en el código fuente de nuestro programa más adelante. La tarea actual se llama uart_recvTask, así que podemos borrarlo si queremos.

Inicialización de la tarea

En uart_init() debemos llamar a la tarea correcta:

Código del programa

Dejo aquí el código fuente de user_main.c:

Vemos que en este ejemplo desactivo por completo la red inalámbrica (como no la queremos, ahorra energía. También desactivo la depuración por lo que os_printf() no tendría efecto, además, nos quitamos muchos mensajes que envía el dispositivo con el fin de saber lo que está haciendo el chip o por qué ha fallado... no está bien para hacer pruebas, pero sí para producción y, por ejemplo si nos vamos a comunicar mediante este puerto serie con otro chip (al otro chip tal vez no le guste demasiado tener información que no sabe interpretar).

Intérprete de comandos

Vamos a implementar un pequeño intérprete de comandos en el que podamos enviar y recibir texto por el UART y obtengamos un resultado. Como puede ser una respuesta en función de lo pedido. Este ejemplo puede generar varios comandos, cada uno asociado a un callback:

  • HELLO: Responderá "Hello Dude!"
  • REPEAT [loquesea]: Responderá "Repeating: loquesea
  • INFO: Responderá con información sobre algunas variables del sistema como tiempo online, memoria libre, versión del SDK, etc.

Los comandos son ampliables fácilmente, sólo hay que añadir un callback y llamar a add_command(). De todas formas es algo muy sencillo y no creo que sea apto para estar en producción. He creado varios archivos.

utils.h:

utils.c:

La función dtostrf() la he cogido de las bibliotecas de Arduino.

user_main.c:

De todas formas, en la página de GitHub lo encontráis con Makefile y todo.

Problemas y soluciones

Todo puede fallar. Incluso esas cosas que aparentemente funcionan bien pueden romperse sin venir a cuento de la forma más tonta.

Fallos intermitentes recién programado

El principal problema que me he encontrado es la estabilidad de la tensión en los pins de entrada del chip. A veces fluctúa y causa un mal funcionamiento. Parece que no carga bien los programas, o no los ejecuta por completo. La solución fue colocar un condensador entre VCC y GND, así cuando haya un pico de consumo y la fuente no pueda entregar corriente (un Arduino, o una pila) el ESP8266 seguirá funcionando. Y por ejemplo, se puede utilizar más corriente de lo normal cuando se están buscando redes, o se está intentando conectar con alguna WiFi no muy cercana. Sí, podemos limitar la potencia, pero no tiene gracia.

Se reinicia solo de vez en cuando

El otro problema es el Watchdog Timer. Eso es, un detector de que el chip se ha quedado colgado y esto puede suceder si no se le envía una señal al watchdog cada cierto tiempo. Si utilizamos algoritmos muy pesados puede que nos suceda. En proyectos definitivos es muy aconsejable tener el watchdog activo, pero para hacer pruebas, podemos desactivarlo llamando a:

Fallos al ejecutar un programa recién flasheado

En ocasiones, sobre todo si estamos haciendo muchas pruebas, o nos hemos confundido en las direcciones del chip que había que flashear, puede que todo deje de funcionar y que al conectar el chip dé un fallo de depuración, o se vuelva loco a mandar cosas sin sentido por el UART, o se caliente mucho de repente y no haga lo que queremos. Puede que hayamos subido algo mal. Podríamos empezar flasheando de nuevo un firmware oficial y cuando funcione volver a flashear nuestro programa.

También podría interesarte...


Volver a la Portada de Logo Paperblog