Unidad 3: drivers¶
Sesión 1¶
En esta sesión haremos lo siguiente:
Vamos a comenzar con el análisis de la segunda parte del proyecto de curso:
2_drivers.Ajustes para ejecutarlo.
Estructura del proyecto.
Introducción a la técnica de modelado utilizando máquinas de estado.
Ejercicios¶
Ejercicios 1: ejercicio 6 de la unidad 2¶
Te dejo una posible solución al ejercicio 6 de la unidad 2 para que estudies con detenimiento:
1 #include <stdio.h>
2 #include "freertos/FreeRTOS.h"
3 #include "freertos/task.h"
4 #include "driver/gpio.h"
5 #include "string.h"
6 #include "esp_system.h"
7 #include "esp_spi_flash.h"
8
9
10 #define LED_PIN 5
11 #define BUTTON_PIN 19
12
13
14 void app_main(void)
15 {
16
17 uint8_t portLevel = 1;
18 uint8_t mac[6];
19
20 gpio_pad_select_gpio(LED_PIN);
21 gpio_set_level(LED_PIN, portLevel);
22 gpio_set_direction(LED_PIN, GPIO_MODE_OUTPUT);
23
24
25 gpio_pad_select_gpio(BUTTON_PIN);
26 gpio_set_direction(BUTTON_PIN, GPIO_MODE_INPUT);
27 gpio_pullup_en(BUTTON_PIN);
28
29 esp_chip_info_t chip_info;
30 esp_chip_info(&chip_info);
31 esp_efuse_mac_get_default(mac);
32
33 int c;
34 char message[16];
35 uint8_t counter = 0;
36
37 while(1)
38 {
39 c = getchar();
40 if(c != EOF)
41 {
42 if( '\n' == c)
43 {
44 message[counter] = 0;
45 if ( strncmp (message, "ledOn", strlen("ledOn") ) == 0)
46 {
47 portLevel = 0;
48 gpio_set_level(LED_PIN, portLevel);
49
50 }
51 else if( strncmp (message, "ledOff", strlen("ledOff") ) == 0 )
52 {
53 portLevel = 1;
54 gpio_set_level(LED_PIN, portLevel);
55 }
56 else if( strncmp (message, "readButton", strlen("readButton") ) == 0 )
57 {
58 printf("Button state is: %s\n", gpio_get_level(BUTTON_PIN) ? "UP" : "DOWN" );
59
60 }
61 else if( strncmp (message, "rev", strlen("rev") ) == 0 )
62 {
63 printf("Chip revision: %d\n", chip_info.revision);
64 }
65 else if( strncmp (message, "idf", strlen("idf") ) == 0 )
66 {
67 printf("idf version: %s\n", esp_get_idf_version());
68 }
69 else if( strncmp (message, "bt", strlen("bt") ) == 0 )
70 {
71 printf("Bluetooth support: %s\n", chip_info.features & CHIP_FEATURE_BT ? "si":"no");
72 }
73 else if( strncmp (message, "ble", strlen("ble") ) == 0 )
74 {
75 printf("BLE support: %s\n", chip_info.features & CHIP_FEATURE_BLE ? "si":"no");
76 }
77 else if( strncmp (message, "wifi", strlen("wifi") ) == 0 )
78 {
79 printf("Wifi support: %s\n", chip_info.features & CHIP_FEATURE_WIFI_BGN ? "si":"no");
80 }
81 else if( strncmp (message, "flash", strlen("flash") ) == 0 )
82 {
83 printf("flash size %dMB\n", spi_flash_get_chip_size() / (1024 * 1024)) ;
84 }
85 else if( strncmp (message, "mac", strlen("mac") ) == 0 )
86 {
87
88 printf("mac add: %02x:%02x:%02x:%02x:%02x:%02x\n", mac[0], mac[1],mac[2],mac[3],mac[4],mac[5]);
89 }
90
91
92 counter = 0;
93 }
94 else
95 {
96 if( counter < ( sizeof(message) - 1 ) )
97 {
98 message[counter] = c;
99 counter++;
100 }
101 }
102 }
103 vTaskDelay(100/portTICK_PERIOD_MS);
104 }
105 }
Ejercicio 2: ejercicio 23 de la unidad 2¶
Estudia con mucho cuidado esta solución al ejercicio 23 de la unidad 2. Aquí te presento a manera de introducción una técnica de modelado de software conocida como máquinas de estado.
La siguiente figura muestra un posible modelo de la solución al problema:
Y una posible implementación de la máquina de estados es esta:
1 #include <stdio.h>
2 #include "freertos/FreeRTOS.h"
3 #include "freertos/task.h"
4 #include "driver/gpio.h"
5 #include "string.h"
6 #include "esp_system.h"
7 #include "esp_spi_flash.h"
8
9 int suma(int a, int b);
10 int resta(int a, int b);
11 int multi(int a, int b);
12 char * readSerialString(void);
13
14
15 typedef enum {
16 INIT = 0,
17 WAITING_OP1,
18 WAITING_OP2,
19 WAITING_FUNC,
20 } app_state_t;
21
22
23 void app_main(void)
24 {
25 static app_state_t appState = INIT;
26 static int op1;
27 static int op2;
28
29 while(1)
30 {
31 switch(appState)
32 {
33 case INIT:
34 {
35 printf("Enter op1 as integer: \n");
36 appState = WAITING_OP1;
37 break;
38 }
39
40 case WAITING_OP1:
41 {
42 char *pString = readSerialString();
43 if(pString != NULL)
44 {
45 uint8_t status = sscanf(pString,"%d",&op1);
46 if(status == 1)
47 {
48 appState = WAITING_OP2;
49 printf("%d\n", op1);
50 printf("Enter op2 as integer: \n");
51 }
52 else
53 {
54 printf("Bad op1. Enter an int\n");
55 printf("Enter op1 as integer: \n");
56 }
57 }
58
59 break;
60 }
61
62 case WAITING_OP2:
63 {
64 char *pString = readSerialString();
65 if(pString != NULL)
66 {
67 uint8_t status = sscanf(pString,"%d",&op2);
68 if(status == 1)
69 {
70 appState = WAITING_FUNC;
71 printf("%d\n", op2);
72 printf("Enter +,-,*: \n");
73 }
74 else
75 {
76 printf("Bad op2. Enter an int\n");
77 printf("Enter op2 as integer: \n");
78 }
79 }
80
81 break;
82 }
83
84 case WAITING_FUNC:
85 {
86 char *pString = readSerialString();
87 if(pString != NULL)
88 {
89 char func;
90 uint8_t status = sscanf(pString,"%c",&func);
91 if(status == 1)
92 {
93 printf("%c\n", func);
94
95 int (*pfunction)(int, int) = NULL;
96
97 if('+' == func)
98 {
99 pfunction = suma;
100 }
101 else if('-' == func)
102 {
103 pfunction = resta;
104 }
105 else if ('*' == func)
106 {
107 pfunction = multi;
108 }
109
110 if(pfunction != NULL)
111 {
112 printf("Resultado: %d %c %d = %d\n", op1,func, op2, pfunction(op1,op2));
113 printf("Enter op1 as integer: \n");
114 appState = WAITING_OP1;
115 }
116 else
117 {
118 printf("Bad function\n");
119 printf("Enter +,-,*: \n");
120 }
121 }
122 }
123
124 break;
125 }
126
127 default:
128 {
129 printf("State machine error \n");
130 break;
131 }
132
133 }
134
135 vTaskDelay(100/portTICK_PERIOD_MS);
136 }
137 }
138
139 int suma(int a, int b)
140 {
141 return a + b;
142 }
143
144 int resta(int a, int b)
145 {
146 return a - b;
147 }
148
149 int multi(int a, int b)
150 {
151 return a*b;
152 }
153
154 char * readSerialString(void)
155 {
156 static char message[16];
157 static uint8_t counter = 0;
158 char *returnValue = NULL;
159
160 int c = getchar();
161
162 if (c != EOF)
163 {
164 if ('\n' == c)
165 {
166 message[counter] = 0;
167 returnValue = message;
168 counter = 0;
169 }
170 else
171 {
172 if (counter < (sizeof(message) - 1))
173 {
174 message[counter] = c;
175 counter++;
176 }
177 }
178 }
179 return returnValue;
180 }
Ejercicio 3: estructura de 2_drivers: CMakeLists.txt¶
Hagamos una exploración de partes del proyecto 2_drivers:
En la carpeta main se modifica el archivo CMakeLists.txt para incluir en el proceso de construcción otros archivos .c
set(COMPONENT_SRCS "app_main.c" "app_driver.c" )
Ten en cuenta que la propia carpeta main es UN COMPONENTE, no olvides que una aplicación para el ESP32 utilizando el esp-idf no es más que una colección de componentes con los cuales se genera el ejecutable que grabaremos en la memoria del microcontrolador.
En este caso, en el CMakeLists.txt de main estás indicando que el componente tiene dos archivos .c:
app_main.cyapp_driver.c¿Cómo se transforman los archivos .c de una aplicación a un ejecutable que será almacenado en la memoria del microcontrolador? Esta es una pregunta a la que podrías dedicarle un bueno rato; sin embargo, te cuento rápidamente cómo es el proceso para que podamos seguir avanzando. Mira con detenimiento la siguiente figura que muestra los pasos:
Como puedes ver, el proceso se compone de 4 pasos. Primero, el preprocesador procesa todas las DIRECTIVAS. En la figura, el archivo
archivo.ctiene la directiva#include. Nota que el preprocesador simplemente genera un nuevo archivo intermedio que contiene la información dearchivo.cy el contenidoarchivo2.h. Segundo, el archivo de salida del preprocesador es compilado y se genera código ensamblador, que no es más que una representación simbólica del lenguaje de máquina. Tercero, el archivo se ensambla, es decir, se transforma de lenguaje de máquina simbólico a lo que usualmente denominamos unos y ceros. Mira en la figura de nuevo el contenido del archivo de salida de la fase de ensamblado. Observa esta línea:017d mov.n a7, a1
017des la representación binaria de la instrucción en lenguaje ensambladormov.n a7, a1. Finalmente, el cuarto paso es el enlazado. El enlazador toma TODOS los archivo ensamblados del proyecto, los combina y genera elarchivo_ejecutableque grabaremos en la memoria del microcontrolador.
Volvamos al archivo
CMakeLists.txtdel componentemain. Nota las siguientes líneas:set(JUMPSTART_BOARD "board_esp32_devkitc.h") component_compile_options("-DJUMPSTART_BOARD=\"${JUMPSTART_BOARD}\"")
set(JUMPSTART_BOARD "board_esp32_devkitc.h")crea la constanteJUMPSTART_BOARDde tal manera que en otras partes del archivoCMakeLists.txtpodamos usarJUMPSTART_BOARDen vez deboard_esp32_devkitc.h. Observa queboard_esp32_devkitc.hestá en la carpeta main y contiene información específica del sistema de desarrollo que estamos utilizando como los puertos del pulsador y del LED y cuál es el nivel lógico que produce el pulsador al ser presionado, es decir, cuál es el nivel lógico del pulsador al activarse. En mi caso el LED estará en el pin 5, el pulsador en el pin 19 y el estado activo del pulsador será 0.#define JUMPSTART_BOARD_BUTTON_GPIO 19 #define JUMPSTART_BOARD_BUTTON_ACTIVE_LEVEL 0 #define JUMPSTART_BOARD_OUTPUT_GPIO 5
Nota también la línea
component_compile_options("-DJUMPSTART_BOARD=\"${JUMPSTART_BOARD}\""). Esta información se la pasaremos al COMPILADOR cuando compile el componentemain.Para que entiendas mucho mejor lo anterior te voy a explicar con un ejemplo sencillo. Considera este código:
1#include INCLUDE 2 3void app_main() 4{ 5 int c = suma(VALOR1,VALOR2); 6}
Nota que no estamos indicando en el propio código qué es
INCLUDE,VALOR1yVALOR2. Cuando compilemos este programa tendremos un error. Sin embargo, es posible indicarle al compilador qué valor tendrán esas constantes. Si estuviéramos llamando explícitamente al preprocesador haríamos esto:xtensa-esp32-elf-gcc -DVALOR1=2 -DVALOR2=3 -DINCLUDE=\"archivo2.h\" -E archivo.c
Con este comando le decimos qué valores tendrán
INCLUDE,VALOR1yVALOR2. Una vez preprocesado el archivo tendremos esto:1int suma(int a, int b); 2 3void app_main() 4{ 5 int c = suma(2,3); 6}
Ten presente que al construir el código del componente no tenemos que llamar manualmente al preprocesador porque al hacer
idf.py buildtodo el proceso ocurre de manera automática por nosotros ¡HERMOSO! ¿Ahora vez lo que estamos haciendo concomponent_compile_options("-DJUMPSTART_BOARD=\"${JUMPSTART_BOARD}\"")?Ahora vamos para el archivo CMakeLists.txt en el directorio principal del proyecto:
# The following lines of boilerplate have to be in your project's # CMakeLists in this exact order for cmake to work correctly cmake_minimum_required(VERSION 3.5) set(EXTRA_COMPONENT_DIRS ${CMAKE_CURRENT_LIST_DIR}/../components) include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(2_drivers)
Nota que solo hay una novedad con respecto al proyecto de la unidad anterior:
set(EXTRA_COMPONENT_DIRS ${CMAKE_CURRENT_LIST_DIR}/../components). Este comando permite adicionar componentes extra al proyecto. En este caso, estamos utilizando el componete button que está en la carpetacomponentsubicada en el directorio padre del directorio del proyecto.
Ejercicio 4: configuración de componentes¶
Recuerda que una aplicación para el ESP32 basada en el esp-idf es una combinación de
componentes. En
el proyecto de esta unidad estamos utilizando varios componentes, entre ellos el componente button. ¿Qué es un
componente? Según Espressif, es una pieza modular de código independiente que se compila como una biblioteca
estática y es enlazada, en el proceso de ENLACE, con otros componentes y archivos para generar el archivo ejecutable.
¿Recuerdas la figura del ejercicio anterior donde se ven los pasos de transformación del código fuente al archivo
ejecutable? Pues bien, en el último paso correspondiente al enlazado,
además de los archivos .o el enlazador puede tomar también archivos .a. Los archivos .a son colecciones
de archivos .o denominados bibliotecas estáticas. Ahora, los componentes SE PUEDEN CONFIGURAR. Te muestro cómo.
Considera el siguiente código del componente button definido en button.c:
1#define BUTTON_GLITCH_FILTER_TIME_MS CONFIG_IO_GLITCH_FILTER_TIME_MS
2static const char *TAG = "button";
La constante #define BUTTON_GLITCH_FILTER_TIME_MS está definida como CONFIG_IO_GLITCH_FILTER_TIME_MS; sin embargo,
¿En dónde está definida CONFIG_IO_GLITCH_FILTER_TIME_MS? Esa constante se GENERA en el proceso de construcción
del ejecutable antes de la etapa de PREPROCESADO. Esto se consigue gracias al archivo Kconfig que tienen
aquellos componentes configurables. Por ejemplo, para el caso del componente button este es el archivo Kconfig:
menu "Button"
config IO_GLITCH_FILTER_TIME_MS
int "IO glitch filter timer ms (10~100)"
range 10 100
default 50
endmenu
Al realizar el proceso de construcción del ejecutable, las constantes definidas en los archivos Kconfig de todos
los componentes son generadas y agrupadas en el archivo sdkconfig que queda en el directorio raíz del proyecto.
Esto permite que el desarrollador pueda verificar la configuración de los componentes. Adicionalmente, en la carpeta build/config
se generará el archivo sdkconfig.h con las constantes ya listas para ingresar a la fase de PREPROCESADO. Para configurar
cada componente se ejecuta el siguiente comando:
idf.py menuconfig
Volviendo al componente button. Su archivo Kconfig define varios asuntos:
menu "Button": crea una entrada para configurar el componente button conmenuconfig.config IO_GLITCH_FILTER_TIME_MS: define una constante a configurar del componente.int "IO glitch filter timer ms (10~100)": la constante es tipointy da una descripción.range 10 100: los posibles valores que puede tomar la constante.default 50: es el valor que tendrá por defecto la constante si no se configura.
Luego de realizar la operación idf.py menuconfig así quedan parte de los archivos.
sdkconfig:
...
#
# Button
#
CONFIG_IO_GLITCH_FILTER_TIME_MS=50
# end of Button
...
sdkconfig.h:
...
#define CONFIG_WIFI_PROV_AUTOSTOP_TIMEOUT 30
#define CONFIG_WPA_MBEDTLS_CRYPTO 1
#define CONFIG_IO_GLITCH_FILTER_TIME_MS 50
#define CONFIG_AWS_IOT_MQTT_HOST ""
#define CONFIG_AWS_IOT_MQTT_PORT 8883
...
Ejercicio 5: hardware para el proyecto¶
En este ejercicio te pediré que revises y pruebes que el hardware funciona. ¿Qué necesitas para el proyecto de esta unidad? Solo un LED y un pulsador.
¿Cómo puedes probar que todo está bien conectado? Repasa la unidad anterior. Y toma nota de:
¿Qué valor lógico reporta el pulsador cuando lo presionas?
¿Qué valor lógico escribes en el puerto del LED para encenderlo?
En mi caso, la siguiente figura muestra el montaje que utilizaré para el proyecto de curso:
Mi pulsador reporta 0 al presionarlo y el LED se enciende con 0.
Ejercicio 6: código inicial de la aplicación¶
Observa el código en app_main.c:
1#include <stdio.h>
2#include <freertos/FreeRTOS.h>
3#include <freertos/task.h>
4#include "app_priv.h"
5
6
7void app_main()
8{
9 int i = 0;
10 app_driver_init();
11 while (1)
12 {
13 printf("[%d] Hello world!\n", i);
14 i++;
15 vTaskDelay(5000 / portTICK_PERIOD_MS);
16 }
17}
Nota la línea #include "app_priv.h". Esta línea te permitirá utilizar las funciones públicas
declaradas en app_priv.h. En este caso, la aplicación solo está llamando una de ellas:
app_driver_init().
Ejercicio 7: código del driver¶
El archivo app_priv.h tiene el API (application programming interface) del DRIVER. El driver
es la parte de código específica de la aplicación que interactúa con los puertos de
entrada/salida del ESP32. Como nota personal, la palabra priv en app_priv.h resulta
infortunada porque realmente debería ser pub, de pública, ya que las funciones que están allí
son las que podemos usar desde otros archivos.
1void app_driver_init(void); 2int app_driver_set_state(bool state); 3bool app_driver_get_state(void);
En el archivo app_priv.h nota la directiva del preprocesador #pragma once de la que
puedes leer aquí. Pero esa DIRECTIVA del preprocesador
permite que en un archivo se incluya SOLO una vez la definición de las APIs.
Ahora mira app_driver.c. Este archivo define la funcionalidad de las tres funciones públicas:
void app_driver_init()
{
configure_push_button(JUMPSTART_BOARD_BUTTON_GPIO, push_btn_cb);
/* Configure output */
gpio_config_t io_conf = {
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = 1,
};
io_conf.pin_bit_mask = ((uint64_t)1 << JUMPSTART_BOARD_OUTPUT_GPIO);
/* Configure the GPIO */
set_output_state(true);
gpio_config(&io_conf);
}
int IRAM_ATTR app_driver_set_state(bool state)
{
if(g_output_state != state) {
g_output_state = state;
set_output_state(g_output_state);
}
return ESP_OK;
}
bool app_driver_get_state(void)
{
return g_output_state;
}
app_driver.c tiene otras funciones PRIVADAS que no podrás llamar desde otros archivos. Estas funciones
están marcadas con la palabra reservada STATIC:
static void push_btn_cb(void *arg)
{
app_driver_set_state(!g_output_state);
}
static void configure_push_button(int gpio_num, void (*btn_cb)(void *))
{
button_handle_t btn_handle = iot_button_create(JUMPSTART_BOARD_BUTTON_GPIO, JUMPSTART_BOARD_BUTTON_ACTIVE_LEVEL);
if (btn_handle) {
iot_button_set_evt_cb(btn_handle, BUTTON_CB_SERIAL, btn_cb, "RELEASE");
}
}
static void set_output_state(bool target)
{
gpio_set_level(JUMPSTART_BOARD_OUTPUT_GPIO, target);
}
Nota que en app_driver.c también se declara una variable GLOBAL para todas las funciones definidas
en el archivo, pero PRIVADA para los demás archivos del proyecto, es decir, la variable solo podrá ser utilizada
por las funciones en app_driver.c. Los otros archivos solo podrás acceder a la variable a través de la función
pública bool app_driver_get_state(void)
app_driver.c define una función especial: app_driver_set_state. Nota el atributo IRAM_ATTR.
¿Para qué sirve este atributo? sirve para generar código que permita cargar y ejecutar el código de máquina
de esta función en la memoria RAM. Ten presente que en la mayoría de sistemas embebidos, a diferencia de un computador,
los programas se puede ejecutar directamente desde la memoria flash, es decir, la CPU buscará directamente instrucciones
de esa memoria para luego decodificar y finalmente ejecutarlas. ¿Cuál es la ventaja de cargar las instrucciones en la memoria
RAM? El acceso por parte de la CPU es más rápido a la memoria RAM que a la FLASH. ¿Y por qué no cargamos entonces todo
el código de máquina a la RAM? porque no tenemos tanta. Entonces podemos cargar solo algunas partes del código. En nuestro
caso, cagar app_driver_set_state permitirá acelerar la ejecución de la función en el contexto de una INTERRUPCIÓN.
Las interrupciones son un mecanismo que permite interrumpir el flujo normal de ejecución de un programa en un CPU haciendo
que abandone temporalmente el programa y ejecute otro denominado servicio de atención a interrupción. En sistemas embebidos
se busca que los servicios de atención a interrupción sean rápidos para poder retomar de nuevo el programa principal.
app_driver.c hace uso del componente button. De nuevo, incluyendo el archivo con las definiciones
públicas del componente: #include <iot_button.h>.
Mira la función app_driver_init() que modifiqué ligeramente para considerar las particularidades de mi hardware
o montaje:
void app_driver_init()
{
configure_push_button(JUMPSTART_BOARD_BUTTON_GPIO, push_btn_cb);
/* Configure output */
gpio_config_t io_conf = {
.mode = GPIO_MODE_OUTPUT,
};
io_conf.pin_bit_mask = ((uint64_t)1 << JUMPSTART_BOARD_OUTPUT_GPIO);
/* Configure the GPIO */
g_output_state = 1;
set_output_state(true);
gpio_config(&io_conf);
}
Se hacen dos cosas:
configure_push_button: Crea e inicializa un componente button. Este componente será el encargado de controlar el funcionamiento del pulsador./* Configure output */y/* Configure the GPIO */: configuran el pin de salida que controlará el LED y establecen el valor inicial del LED en 1, es decir, APAGADO en mi hardware.
configure_push_button utiliza el componente button. Para eso se incluye el archivo #include <iot_button.h>:
iot_button_create: crea el botón,
iot_button_set_evt_cb: configura cómo se comunicará el código del componente button con el código de la aplicación. Nota que al componete button le estamos diciendo que al liberar el pulsador luego de ser presionado (
BUTTON_CB_RELEASE) se debe llamar la funciónpush_btn_cbcuya dirección fue guardamos en el punterobtn_cbal momento de llamar la funciónconfigure_push_button. Observa entonces quepush_btn_cbsimplemente cambiará de estado el LED.static void push_btn_cb(void *arg) { app_driver_set_state(!g_output_state); }
Ejercicio 12: RETO 1¶
Con la información que te di hasta ahora te invito a
Analiza y estudia con detenimiento el resto de código del componente button.
Luego realiza una copia al proyecto
2_driversy realiza experimentos que reproduzcan las figuras del ejercicio 9. PERO TEN CUIDADO con el callback serial. NO LO HAGAS AÚN, mira el siguiente reto.
Ejercicio 13: RETO 2¶
Al tratar de reproducir la figura del callback serial vas a encontrar un error en el código del componente. ¿Te animas a corregir el error y a reproducir la figura?
Ejercicio 14: SOLO PARA LOS MÁS CURIOSOS¶
En este curso no tenemos tiempo de estudiar a fondo cada detalle del sistema operativo FreeRTOS; sin embargo, hay un tutorial MUY MUY bueno que pudes hacer una vez termines el curso o antes si tienes mucho tiempo libre.
El tutorial está aquí.
Luego de hacer el tutorial te recomiendo leer este enlace donde el fabricante te cuenta algunas adaptaciones que tuvo que hacerle al FreeRTOS para soportar la arquitectura multicore del ESP32.
Ejercicio 15: anexo, DEBUGGING para aventureros (SIN SOPORTE por parte del Profe)¶
El ESP32 supporta la posibilidad de realizar Debugging utilizando una interfaz JTAG.
¿Qué necesitas para poder hacerlo?
Debes conseguir una intefaz USB a JTAG. Te recomiendo esta.
En este enlace puedes encontrar documentación sobre la interfaz.
Debes tener la aplicación OPENOCD (viene con las herramientas del ESP-IDF).
Debes tener un cliente de GDB (viene con las herramientas del ESP-IDF).
Debes configurar tu entorno de desarrollo (eclipse, visual studio) para que se conecte a tu cliente GDB y puedas depurar en un enterno gráfico tu aplicación. Cabe aclara que también puedes hacer la depuración por consola.
La siguiente figura ilustra cómo están conectados todos los elementos anteriores:
Puedes leer sobre el funcionamiento en este enlace.
En la siguiente figura se ve el diagrama en bloques de la interfaz:
Observa que tienes dos conectores llamados PROG header y JTAG header. PROG lo puedes utilizar para programar un módulo del ESP32 que montes es tu propio hardware utilizando el puerto serial tal cual lo has hecho usando el sistema de desarrollo.
Mira un ejemplo de una aplicación:
En esta figura puedes ver más detalles de la interfaz:
Cuando conectes la interfaz al computador notarás que aparecen dos puertos seriales. COM X será el puerto del JTAG y COM X+1 de PROG.
Para usar PROG debes tener dos jumpers: IO0 y PROG PWR SEL. Con el jumper IO0 puesto la aplicación idf.py (esptool.py) podrá controlar el ESP32 y ponerlo en modo boot. Si remueves el jumper IO0 tendrás que poner el ESP32 en modo boot usando el boton boot: lo presionas y sin soltarlo presionas el botón de RST. Con PROG PWR SEL puedes seleccionar el voltaje que tendrá el pin VDD, en el conector PROG.
¿Cómo puedes conectar tu sistema de desarrollo al conector de JTAG de la interfaz?
Observa esta figura:
Revisa de nuevo con esta tabla:
ESP-PROG |
ESP32 |
|---|---|
TCK |
13 |
TDI |
12 |
TDO |
15 |
TMS |
14 |
GND |
GND |
Pasos preparatorios:
Abre el acceso directo al ESP-IDF desde la terminal.
Navega hasta uno de tus proyectos.
Conecta la interfaz al computador.
Lanza OPENOCD así:
Verifica que openocd reconoce tu interfaz y además el servidor se queda escuchando en el puerto 3333 a la espera de clientes GDB.
Lo siguiente que debes hacer es asegurarte de grabar, como siempre, la aplicación en la memoria del microcontrolador.
En este punto, ya debes tener corriendo el servidor openocd y la aplicación grabada en la memoria flash del ESP32. Ahora solo tienes que conectarte al servidor openocd con el cliente GDB:
idf.py gdbtui
GDB se deberá conectar:
También deberás ver que el servidor acepta la conexión:
Prueba que puedes depurar colocando un breakpoint, ejecutando el programa hasta el breakpoint y luego ejecutando paso a paso:
Una vez todo esto funcione, puedes configurar tu entorno de desarrollo para depurar directamente allí. En este caso ya no tendrías que hacer el paso de lanzar manualmente el cliente GDB ya que esta tarea la haría por ti tu entorno de desarollo.
En este enlace puedes ver cómo depurar en eclipse y por consola. Si estás trabajando con Visual Studio Code te recomiendo que instales y configures la extensión ESP-IDF para Visual Studio Code. Aquí encontrarás toda la información necesaria para configurar la extensión y en este otro enlace la parte relativa al DEBUGGING.
Sesión 2¶
En esta sesión vamos a resolver dudas sobre los ejercicios y escuchar aportes, comentarios y/o experiencias de todos.











