Manual
do
Maker
.
com
Tudo vai bem quando está bem. Mas algumas vezes estamos desenvolvendo e nos deparamos com um problema, seja um reset, um dispositivo I2C não encontrado, comunicação na rede etc. O intuito desse artigo é ajudar a descobrir a origem desses problemas.
Apesar de um reset mostrar a razão ao início do boot, a mensagem não é tão clara. Nos exemplos para ESP32 tem um sketch para deixar a mensagem clara (e foi desenvolvido por um brasileiro do Paraná). O nome do sketch é ResetReason e contém o seguinte código:
#include <rom/rtc.h>
#define uS_TO_S_FACTOR 1000000
void print_reset_reason(RESET_REASON reason)
{
switch ( reason)
{
case 1 : Serial.println ("POWERON_RESET");break; /**<1, Vbat power on reset*/
case 3 : Serial.println ("SW_RESET");break; /**<3, Software reset digital core*/
case 4 : Serial.println ("OWDT_RESET");break; /**<4, Legacy watch dog reset digital core*/
case 5 : Serial.println ("DEEPSLEEP_RESET");break; /**<5, Deep Sleep reset digital core*/
case 6 : Serial.println ("SDIO_RESET");break; /**<6, Reset by SLC module, reset digital core*/
case 7 : Serial.println ("TG0WDT_SYS_RESET");break; /**<7, Timer Group0 Watch dog reset digital core*/
case 8 : Serial.println ("TG1WDT_SYS_RESET");break; /**<8, Timer Group1 Watch dog reset digital core*/
case 9 : Serial.println ("RTCWDT_SYS_RESET");break; /**<9, RTC Watch dog Reset digital core*/
case 10 : Serial.println ("INTRUSION_RESET");break; /**<10, Instrusion tested to reset CPU*/
case 11 : Serial.println ("TGWDT_CPU_RESET");break; /**<11, Time Group reset CPU*/
case 12 : Serial.println ("SW_CPU_RESET");break; /**<12, Software reset CPU*/
case 13 : Serial.println ("RTCWDT_CPU_RESET");break; /**<13, RTC Watch dog Reset CPU*/
case 14 : Serial.println ("EXT_CPU_RESET");break; /**<14, for APP CPU, reseted by PRO CPU*/
case 15 : Serial.println ("RTCWDT_BROWN_OUT_RESET");break;/**<15, Reset when the vdd voltage is not stable*/
case 16 : Serial.println ("RTCWDT_RTC_RESET");break; /**<16, RTC Watch dog reset digital core and rtc module*/
default : Serial.println ("NO_MEAN");
}
}
void verbose_print_reset_reason(RESET_REASON reason)
{
switch ( reason)
{
case 1 : Serial.println ("Vbat power on reset");break;
case 3 : Serial.println ("Software reset digital core");break;
case 4 : Serial.println ("Legacy watch dog reset digital core");break;
case 5 : Serial.println ("Deep Sleep reset digital core");break;
case 6 : Serial.println ("Reset by SLC module, reset digital core");break;
case 7 : Serial.println ("Timer Group0 Watch dog reset digital core");break;
case 8 : Serial.println ("Timer Group1 Watch dog reset digital core");break;
case 9 : Serial.println ("RTC Watch dog Reset digital core");break;
case 10 : Serial.println ("Instrusion tested to reset CPU");break;
case 11 : Serial.println ("Time Group reset CPU");break;
case 12 : Serial.println ("Software reset CPU");break;
case 13 : Serial.println ("RTC Watch dog Reset CPU");break;
case 14 : Serial.println ("for APP CPU, reseted by PRO CPU");break;
case 15 : Serial.println ("Reset when the vdd voltage is not stable");break;
case 16 : Serial.println ("RTC Watch dog reset digital core and rtc module");break;
default : Serial.println ("NO_MEAN");
}
}
void setup() {
Serial.begin(115200);
delay(2000);
Serial.println("CPU0 reset reason:");
print_reset_reason(rtc_get_reset_reason(0));
verbose_print_reset_reason(rtc_get_reset_reason(0));
Serial.println("CPU1 reset reason:");
print_reset_reason(rtc_get_reset_reason(1));
verbose_print_reset_reason(rtc_get_reset_reason(1));
// Set ESP32 to go to deep sleep to see a variation
// in the reset reason. Device will sleep for 5 seconds.
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF);
Serial.println("Going to sleep");
esp_deep_sleep(5 * uS_TO_S_FACTOR);
}
void loop() {
}
Com esse sketch, o ESP32 fará deep sleep por 5 segundos para que seja possível ler o código de erro. Basta implementar esse exemplo junto a seu código e no setup, iniciar a serial e em seguida chamar a porção de código acima. Mesmo que haja outras definições no setup, essa deve ser a primeira, porque a razão de reset pode ser justamente suas próprias implementações.
Para obter informações sobre o WiFi, podemos usar pequenas porções de código utilizando os recursos do ESP-IDF. Para configurar uma conexão WiFi no ESP32 já utilizei algumas vezes o código descrito em seguida.
#include "lwip/err.h"
#include "esp_wifi.h"
#include "esp_wpa2.h"
#include "esp_event_loop.h"
#define DEFAULT_SSID "SSID"
#define DEFAULT_PWD "SENHA"
//MANIPULADOR DE EVENTOS
static esp_err_t event_handler(void *ctx, system_event_t *event){
switch(event->event_id) {
case SYSTEM_EVENT_STA_START:
uart_write_bytes(UART_NUM_0, (const char *) "SYSTEM_EVENT_STA_START\n", 29);
ESP_ERROR_CHECK(esp_wifi_connect());
break;
case SYSTEM_EVENT_STA_GOT_IP:
uart_write_bytes(UART_NUM_0, (const char *) "SYSTEM_EVENT_STA_GOT_IP\n", 29);
break;
case SYSTEM_EVENT_STA_DISCONNECTED:
uart_write_bytes(UART_NUM_0, (const char *) "SYSTEM_EVENT_STA_DISCONNECTED: ", 40);
ESP_ERROR_CHECK(esp_wifi_connect());
break;
default:
break;
}
return ESP_OK;
}
//INICIALIZAÇÃO WIFI
static void initialise_wifi(void){
tcpip_adapter_init();
ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL));
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK( esp_wifi_init(&cfg) );
ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK( esp_wifi_start() );
uart_write_bytes(UART_NUM_0, (const char *) "WiFi configurado\n", 20);
}
Os modos configuráveis do WiFi são:
Basta passar uma dessas definições na linha:
ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA));
Para saber o estado da conexão, utilize (por exemplo) a função :
esp_err_t error_code = esp_wifi_get_mode(WIFI_MODE_STA);
Depois, basta testar o código de retorno:
if (error_code == ESP_OK){
uart_write_bytes("tudo certo\n",10);
}
Os códigos de retorno são:
ESP_OK | sem problemas |
ESP_ERR_WIFI_FAIL | erros internos do WiFi |
ESP_ERR_WIFI_NOT_INIT | faltou inicializar pelo esp_wifi_init |
ESP_ERR_WIFI_ARG | argumento inválido |
ESP_ERR_WIFI_NO_MEM | erro de memória (faltou recurso para alocar?) |
ESP_ERR_WIFI_CONN | não conseguiu fazer bloking do modo STA ou AP |
E quando não é possível conectar a uma rede, suponhamos, por sinal ruim? Analisar o alcance é uma ótima opção para quando não se está conseguindo conectar a uma rede. Temos um scanner pronto pra isso, mas foi escrito nativamente para o ESP-IDF. Com pouca modificação, temos o seguinte:
#include "Arduino.h"
#include "esp_wifi.h"
#include "esp_system.h"
#include "esp_event.h"
#include "esp_event_loop.h"
#include "nvs_flash.h"
esp_err_t event_handler(void *ctx, system_event_t *event)
{
if (event->event_id == SYSTEM_EVENT_SCAN_DONE) {
uint16_t apCount = 0;
esp_wifi_scan_get_ap_num(&apCount);
printf("Number of access points found: %d\n",event->event_info.scan_done.number);
if (apCount == 0) {
return ESP_OK;
}
wifi_ap_record_t *list = (wifi_ap_record_t *)malloc(sizeof(wifi_ap_record_t) * apCount);
ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(&apCount, list));
int i;
printf("======================================================================\n");
printf(" SSID | RSSI | AUTH \n");
printf("======================================================================\n");
for (i=0; i<apCount; i++) {
char *authmode;
switch(list[i].authmode) {
case WIFI_AUTH_OPEN:
authmode = "WIFI_AUTH_OPEN";
break;
case WIFI_AUTH_WEP:
authmode = "WIFI_AUTH_WEP";
break;
case WIFI_AUTH_WPA_PSK:
authmode = "WIFI_AUTH_WPA_PSK";
break;
case WIFI_AUTH_WPA2_PSK:
authmode = "WIFI_AUTH_WPA2_PSK";
break;
case WIFI_AUTH_WPA_WPA2_PSK:
authmode = "WIFI_AUTH_WPA_WPA2_PSK";
break;
default:
authmode = "Unknown";
break;
}
printf("%26.26s | % 4d | %22.22s\n",list[i].ssid, list[i].rssi, authmode);
}
free(list);
printf("\n\n");
}
return ESP_OK;
}
void setup(){
nvs_flash_init();
system_init();
tcpip_adapter_init();
ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL));
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_start());
// Let us test a WiFi scan ...
wifi_scan_config_t scanConf = {
.ssid = NULL,
.bssid = NULL,
.channel = 0,
.show_hidden = true
};
}
void loop(){
ESP_ERROR_CHECK(esp_wifi_scan_start(&scanConf, true)); //The true parameter cause the function to block until scan is done
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
Se isso causar reset, simplesmente crie uma task (veja no menu ESP32 como criar tasks) e executá-la no núcleo 0. O scan retorna algo nesse formato:
Nunca usei com os prints, mas eu prefiro diretamente trocar para a serial. Para configurar a UART no ESP32, você pode seguir esse tutorial.
O projeto nativo para ESP-IDF está no github, através desse link.
E pra finalizar, criar uma condicional para exibir suas credenciais de acesso à rede pode ser uma boa ideia. às vezes um erro de digitação pode passar despercebido. Lendo da UART, considere um comando como "cred" que devolva as informações:
#define MY_SSID "abcd"
#define MY_PASSWD "1234"
char command[10] = {0};
...
if (!strcmp(command,"cred")){
uart_write_bytes("SSID: ",6);
uart_write_bytes(MY_SSID,sizeof(MY_SSID));
uart_write_bytes("\n",1);
uart_write_bytes("SENHA: ",7);
uart_write_bytes(MY_PASSWD,sizeof(MY_PASSWD));
uart_write_bytes("\n",1);
}
Como está a tabela de partições do seu ESP32? Está compilando pela IDE do Arduino? Pelo CodeBlocks? Então provavelmente não deve saber responder se a tabela de partições está configurada com suporte a OTA, certo?
Quando compilando pela IDE do Arduino, todo o resultado da compilação é gerado em /tmp (para Linux, não sei pra Windows, sorry). O nome do diretório com o conteúdo da compilação deve ser sempre prefixado com arduino_build_ e sufixado com um valor numérico. Compilei o exemplo do ResetReason para poder mostrar o conteúdo:
Tem sempre mais de uma maneira de obter informações sobre quase qualquer coisa. Se quiser fazer da maneira mais preguiçosa possível, simplesmente execute o comando strings sobre o arquivo de tabela de partições gerado:
strings ResetReason.ino.partitions.bin
Isso deve retornar algo como:
Se você tiver o ESP-IDF (já discorri a respeito nesse artigo), encontrará dentro do diretório esp-idf/components/partition_table/ um programa em Python para gerar o binário, o CSV, e exibir informações sobre uma tabela de partições. Para ver o conteúdo da tabela gerada:
Se a tabela de partições não estiver como pretendido, sugiro dar uma olhada nesse artigo.
No ESP32 temos um sistema operacional de tempo real e claro, temos uma montanha de recursos. Vamos ver alguns deles.
Para pegar a memória livre do sistema, podemos utilizar o recurso esp_get_free_heap_size(). Ex:
void setup() {
Serial.begin(115200);
delay(2000);
Serial.print("Memoria livre: ");
Serial.println(esp_get_free_heap_size());
}
void loop() {
// put your main code here, to run repeatedly:
}
Que deve exibir algo assim na serial:
O ESP32 tem 520KiB de memória SRAM. Com isso você conseguirá ver a alocação de recursos necessária pelo seu programa, mas melhor que isso, poderá descobrir leak de memória, se os recursos forem se esgotando (claro, sem que realmente haja alocação intencional de recursos).
As informações de compilação são muito úteis também. Repare na IDE do Arduino, utilizando apenas Ctrl+R você obterá apenas as mensagens de compilação, que lhe mostrarão informações importantes como uso da flash, uso por variáveis globais,e espaço livre para variáveis locais.
Já discorri a respeito nesse outro artigo. Podemos descobrir em que núcleo uma tarefa está sendo executada utilizando a função xPortGetCoreID() dentro da tarefa. As funções **loop()**e setup() são executadas no núcleo 1, mas mesmo sabendo disso, serve como exemplo:
void setup(){
Serial.begin(115200);
delay(1000);
Serial.print("Executando setup() no nucleo ");
Serial.println(xPortGetCoreID());
...
}
Essa função é exclusivamente para debugging, porque ela desabilita as interrupções do sistema quando em execução. A vTaskList utiliza um ponteiro para um array de char que alocará as informações sobre o estado de uma tarefa, que pode conter os estados blocked, ready, deleted e suspended. Outra informações que retornam além do estado são a prioridade, stack e número da tarefa.
B | blocked |
R | Ready |
D | Deleted |
S | Suspended ou bloqueado sem um timeout |
O formato da função:
void vTaskList(char *pcWriteBuffer);
Um exemplo da utilização:
char tasks_state[600] = {0};
...
void myTask(){
...
}
void setup(){
...
}
void loop(){
...
vTaskList(tasks_state);
...
}
Cada tarefa ocupa uns 40 Bytes. O valor do array deve ser ajustado conforme achar devido.
Vou fazer com esse artigo o mesmo que faço com esse outro sobre Raspberry. Cada vez que eu lembrar ou aprender novos recursos, vou incrementando o artigo.
Caramba, se não tiver um ESP32 depois de 26 artigos escritos aqui no site, você realmente não tem ambição.
A imagem de destaque é da placa de desenvolvimento para ESP32, ela é fantástica, mas tem outros modelos de ESP32 que você encontra na CurtoCircuito através desse link. Inclusive sugiro que, se quiser economizar muito, pegue a WROOM, que está com uma promoção incrível!
Espero que tenha sido uma leitura agradável.
Inscreva-se no nosso canal Manual do Maker no YouTube.
Também estamos no Instagram.
Autor do blog "Do bit Ao Byte / Manual do Maker".
Viciado em embarcados desde 2006.
LinuxUser 158.760, desde 1997.