Programar el chip wifi ESP8266 como si fuera un microcontrolador (2/2)

En el anterior artículo explicaba cómo generar el toolchain para poder compilar programas y subirlos a nuestro módulo ESP8266. En este artículo explicaré cómo hacer un programa en C. Para ello me voy a poner un reto: un firmware que cuando pulse un botón o la tecla p, haga una llamada a una página web que muestre la acción realizada. Con este ejemplo vamos a trabajar con el gpio, puerto serie, wifi, temporizadores y eventos. No hay mucha documentación sobre cómo programar el chip esp8266 más allá de los ejemplos de código, foros y páginas web con artículos dedicados al chip y de la SDK de Espressif que en el momento de escribir este artículo es la 1.0.1.

Empezaré por mostrar un vídeo de lo que he hecho para tener claro desde el principio lo que explicaré aquí.

Lo que veis en el vídeo es que primero programo el esp8266 con el firmware de este artículo. Luego abro una terminal al puerto serie del ESP8266. Pulso 3 veces el botón físico y otras 3 veces la tecla p en la terminal para que se envíe por el puerto serie. Estas acciones provocan en el esp8266 la llamada a una página web que lo registra, mientras en otra página web se visualiza casi en tiempo real lo que he hecho. Cuando compiléis vosotros el ejemplo podéis usar la misma página para hacer vuestras pruebas.

Este es el esquema del circuito para el botón (he obviado las conexiones al puerto serie para simplificarlo):

placa

Vamos a ver cómo lo hecho. Lo primero es crear una carpeta llamada proyectos-esp8266 en nuestra carpeta personal, entra dentro de esta y crear otra llamada reto. Dentro de esta copiaremos el fichero Makefile que modificamos en el anterior artículo. Después crearemos una carpeta llamada user y dentro de esta compiaremos los ficheros uart.h y uart_register.h del ejemplo de IoT que trae la SDK:

Dentro de la carpeta user crearemos dos ficheros.

user_config.h

user_main.c

Lo primero que salta a la vista es que no existe una función main eso es porque este chip se programa con una función de inicialización y el resto con eventos/interrupciones.

La función de inicialización es user_init (al final del código), como decía esta función es llamada cuando el módulo se enciende y se encarga de inicializar todo en el esp8266. El modificador de función ICACHE_FLASH_ATTR significa que la función se guarde en la flash en vez de en la RAM y que cuando se vaya a usar sea cacheada en esta última, ahorrando así espacio para poder tener más datos en la memoria. Dentro de esta función hay que destacar:

  • system_init_done_cb: Esta función tiene un parámetro que es la función callback que será llamada cuando termine la inicialización.
  • system_os_task: Esta función define la ejecución, como tarea, de la función callback pasada como primer parámetro. Podemos tener hasta 4 tareas (cada una con una prioridad distinta). Más adelante hablaré de ello.

La función inicializado es llamada cuando ha terminado la inicialización como comentába antes. Dentro de esta función hay que destacar:

  • os_printf: Es como el printf de toda la vida, pero su salida es enviada al puerto serie 0 (el esp8266 tiene un puerto serie con tx y rx y otro con tx sólo para hacer debug).

La función inicializa_uart configura el puerto serie. Dentro de esta función hay que destacar:

  • ETS_UART_INTR_ATTACH: Esta función configura a qué función callback se llamará cuando se reciba un caracter por el puerto serie. El segundo parámetro es una estructura que existe por defecto con información del puerto serie y el buffer de recepción, pero yo no lo usaré.
  • PIN_PULLUP_DIS: Desactiva la configuración de pull-up del pin indicado (La lista de pines está en el fichero eagle_soc.h de la sdk), en este caso del pin TX.
  • PIN_FUNC_SELECT: Configura el pin indicado para comportarse de una manera determinada, en este caso para funcionar como TX del puerto serie.
  • uart_div_modify: Configura la velocidad del puerto serie
  • WRITE_PERI_REG(UART_CONF0(0), (STICK_PARITY_DIS)|(ONE_STOP_BIT << UART_STOP_BIT_NUM_S)| (EIGHT_BITS << UART_BIT_NUM_S)): Configura la comunicación como 8 bits, sin paridad y un bit de stop.
  • SET_PERI_REG_MASK(UART_CONF0(0), UART_RXFIFO_RST|UART_TXFIFO_RST) y CLEAR_PERI_REG_MASK(UART_CONF0(0), UART_RXFIFO_RST|UART_TXFIFO_RST): Limpia la FIFO de TX y RX.
  • WRITE_PERI_REG(UART_CONF1(0), (UartDev.rcv_buff.TrigLvl & UART_RXFIFO_FULL_THRHD) << UART_RXFIFO_FULL_THRHD_S): Configura el disparador FIFO de RX.
  • WRITE_PERI_REG(UART_INT_CLR(0), 0xffff): Borrar el flag de todas las interrupciones del puerto serie.
  • SET_PERI_REG_MASK(UART_INT_ENA(0), UART_RXFIFO_FULL_INT_ENA): Configura la interrupción de recepción del pin RX.
  • ETS_UART_INTR_ENABLE: Activa las interrupciones del puerto serie.

La función inicializa_gpio configura el pin GPIO 2 (el único disponible en el ESP-01, pero en otros módulos hay más pines gpio usables). Dentro de esta función hay que destacar:

  • ETS_GPIO_INTR_DISABLE: Desactiva la interrupción de los pines GPIO.
  • ETS_GPIO_INTR_ATTACH: Esta función configura a que función callback se llamará cuando haya una interrpción en el pin gpio indicado.
  • PIN_PULLUP_EN: Activa la configuración de pull-up del pin indicado.
  • gpio_output_set: Configura el pin gpio 2 como entrada.
  • GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, BIT(2)): Borrar el flag de la interrupcion del pin gpio 2.
  • gpio_pin_intr_state_set: Indica que en el pin gpio 2 sólo debe activarse la interrupción cuando detecta un flanco de bajada (conecta con masa/GND).
  • ETS_GPIO_INTR_ENABLE: Activa las interrupciones gpio.

La función inicializa_wifi configura la conexión a la wifi. Dentro de esta función hay que destacar:

  • wifi_set_opmode: Configura el módulo como estación cliente.
  • wifi_station_set_auto_connect: Indica que cuando se encienda el módulo se conecte automáticamente a la wifi establecida.
  • wifi_station_set_reconnect_policy: Indica que si se pierde la conexión con el punto de acceso, la vuelva a restablecer.
  • os_memcpy(&configuracion.ssid, ssid, 32) y os_memcpy(&configuracion.password, password, 64): Copia el nombre de la red wifi y la contraseña a la estructura del tipo station_config. Sirve para copiar datos de una posición de memoria a otra, sería equiparable al memcpy del C.
  • configuracion.bssid_set = 0: Para que conecte a cualquier wifi que tenga el nombre proporcionado anteriormente. Si hay varias wifis con el mismo nombre (SSID) puedes indicar a qué punto de acceso quieres acceder poniendo aquí su BSSID (su dirección MAC).
  • wifi_station_set_config: Inicializa la wifi con los valores proporcionados en la estructura del tipo station_config.
  • wifi_set_event_handler_cb: Esta función configura a que función callback se llamará cuando haya cambios en la wifi (conexión, desconexión, ip recibida…).

La función callback interrupcion_wifi es llamada por el chip cuando un evento en la wifi es generado. Quizá el más importante es cuando recibimos una IP de nuestro router. En el ejemplo pongo una variable a TRUE para indicar que tiene libertad para procesar las pulsaciones del teclado o del botón.

La función callback interrupcion_gpio es llamada por el chip cuando se ha detectado un evento en los pines GPIO. Dentro de esta función hay que destacar:

  • GPIO_REG_READ(GPIO_STATUS_ADDRESS): Devuelve el estado de los pones gpio y cual ha sido el que ha causado el evento.
  • os_timer_disarm: desactiva un temporizador si este estuviese activo.
  • os_timer_setfn: configura un temporizador indicando a qué función callback llamará cuando expire. Se le puede pasar un parámetro (en este caso NULL).
  • os_timer_arm: Activa el temporizador indicando cuanto tiempo dura en milisegundos y si debe o no repetirse. La idea de usarlo es para evitar los rebotes físicos cuando el botón es pulsado.
  • system_os_post: Indica al chip que, cuando pueda, ejecute la tarea de prioridad 0 que predefinimos en la función user_init con los parámetros 0,0.

La función callback boton_pulsado establece una variable a FALSE para indicar que el tiempo dado para mitigar los rebotes físicos del botón ya ha pasado y de nuevo permite reconocer la pulsación de éste.

La función callback caracter_recibido es llamada por el chip cuando se ha detectado un evento en el puerto serie. Dentro de esta función hay que destacar:

  • READ_PERI_REG(UART_INT_ST(UART0): Lee cual ha sido el motivo de la interrupción.
  • WRITE_PERI_REG(UART_INT_CLR(UART0): UART_RXFIFO_FULL_INT_CLR): Borra el bit de la interrupción correspondiente a carácter recibido.
  • READ_PERI_REG(UART_STATUS(UART0)): Lee el estado del puerto serie.
  • READ_PERI_REG(UART_FIFO(UART0)): Lee el caracter propiamente dicho. El chip ya tiene un buffer donde se pueden almacenar los carácteres recibidos, yo no lo he usado pero puede ser interesante.

La función callback conecta es llamada por el chip como una tarea. Inicia el proceso de envío de datos. Dentro de esta función hay que destacar:

  • os_zalloc: Sirve para reservar memoria, sería equiparable al malloc del C.
  • espconn_gethostbyname: Esta función hace una consulta dns para obtener la ip de un host dado y llamará a una función callback. Usa una estructura del tipo espconn donde se rellenarán todos los datos referentes a la conexión TCP

La función callback encontrado es llamada cuando se ha resuelto un nombre de host. Dentro de esta función hay que destacar:

  • espconn_port: Crea un puerto local para poder enganchar el socket con el puerto remoto 80 (http).
  • espconn_regist_connectcb: Esta función indica al chip que cuando se establece una conexión TCP, debe llamar a la función callback definida.
  • espconn_regist_disconcb: Esta función indica al chip que cuando hay una desconexión, debe llamar a la función callback definida.
  • espconn_regist_recvcb: Esta función indica al chip que cuando se reciben datos desde una conexión TCP, debe llamar a la función callback definida.

La función callback conectado es llamada por el chip cuando se establece una conexión TCP. Dentro de esta función hay que destacar:

  • os_sprintf: Sirve para rellenar un buffer con datos formateados, sería equiparable al sprintf del C.
  • espconn_sent: Envía los datos indicados a través del socket TCP.
  • os_strlen: Devuelve la longitud de una cadena, sería equiparable al strlen del C.

La función callback recibido es llamada por el chip cuando se reciben datos de una conexión TCP. En el ejemplo sólo los muestro directamente. Dentro de esta función hay que destacar:

  • os_free: Sirve para liberar memoria, sería equiparable al free del C.

La función callback desconectado es llamada por el chip cuando se desconecta la conexión TCP. En el ejemplo libero los buffers de memoria reservados previamente.

Quiero comentar es que no soy un experto en el esp8266, por lo que si que me preguntáis algo puede que no lo sepa responder, por ello os recomiendo ir a la página www.esp8266.com donde hay mucha gente que trabaja con el esp8266 y os podrán resolver vuestras dudas mejor que yo. Todo lo que he juntado aquí ha sido fruto de búsquedas por internet, mil y una pruebas con código fuente, etc. Sin embargo lo que aquí he explicado, aún siendo de las partes más importantes de la programación de un ESP8266, no cubre todo lo que se puede hacer con este chip, por lo que una vez te encuentres cómodo con lo aquí expuesto, te animo a que busques por tu cuenta más funcionalidades.

5 pensamientos en “Programar el chip wifi ESP8266 como si fuera un microcontrolador (2/2)

  1. Lisergio

    Buenas, excelente articulo…
    He seguido los ejemplos y todo casi bien… he conseguido cambiar el firmware y cargar algún ejemplo directamente en el esp, pero no encuentro ninguna información de como hacer lo siguiente:
    Quiero comunicar una placa arduino pro mini usando el esp8266, con mi movil usando la app netIO, pero no consigo capturar los mensajes que me manda la app al la ip y al puerto que tengo configurados. quiero controlar un sistema de redes conectado a la placa arduino. Y no consigo mostrar por el puerto serie ( usando putty por ejemplo lo que le llega ( se supone que son cadenas de texto)
    Gracias y perdón por no ser el sitio indicado para la consulta.

    Responder
  2. Fran

    Hola,

    he buscado algun contacto por la web para decir esto pero no lo he encontrado, asi que te dejo el mensaje por aqui:

    La ventana de las cookies no tiene boton para aceptar, con lo cual siempre permanece ahi (uso Firefox actualizado).

    Borra este mensaje cuando te sea conveniente. Muchs gracias por la web!

    Responder
  3. Sphinx

    Muchas gracias SistemasORP por tus artículos. Me he iniciado con el ESP8266 gracias a ellos. Además me vienen muy bien y en linea con lo que quiero hacer con el ESP8266-01.

    He estado buscando e investigando por internet para ampliar información, y quería puntualizar una cosa.
    El ESP8266-01 no solo posee el GPIO2 como I/O pin. También es posible utilizar el GPIO0, lo único es que su utilización es más delicada. Simplemente hay algunas recomendaciones que seguir: Principalmente se recomienda utilizar el GPIO0 como pin de entrada, en lugar de utilizarlo de salida. La razón es que dado que el GPIO0 tiene que estar a 0V. para poner el ESP8266 en modo flash, cuando queramos reprogramarlo, si el pin fuese de salida en lugar de entrada y se diese el caso de que estuviese puesto a nivel alto, estaríamos cortocircuitando el pin al ponerlo a 0V. Sin embargo, si el pin es de entrada no tendríamos este problema.

    Un saludo y gracias de nuevo,
    Sphinx

    Responder
  4. Alvaro

    Hola,
    He estado leyendo sobre el ESP8266, he llegado a tu blog y me parece muy interesante. Estamos realizando un proyecto en el que nos gustaria que participaras viendo que te desenvuelves bien con la electronica(obviamente seria remunerado). Por favor ponte en contacto conmigo, sin ningun compromiso!
    Gracias de antemano!

    Responder

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *