Análise de sistema no ESP32

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.

ESP32 reset reason



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.

Informações sobre o WiFi

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.

Conectar a uma rede

#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:

  • WIFI_MODE_STA
  • WIFI_MODE_AP
  • WIFI_MODE_APSTA

Basta passar uma dessas definições na linha:

 ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA));

Código de erros

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_OKsem problemas
ESP_ERR_WIFI_FAILerros internos do WiFi
ESP_ERR_WIFI_NOT_INITfaltou inicializar pelo esp_wifi_init
ESP_ERR_WIFI_ARGargumento inválido
ESP_ERR_WIFI_NO_MEMerro de memória (faltou recurso para alocar?)
ESP_ERR_WIFI_CONN não conseguiu fazer bloking do modo STA ou AP

Listar redes disponíveis

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.

Exiba as suas credenciais

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);
}

Sistema de arquivos

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.

Verificar recursos do sistema

No ESP32 temos um sistema operacional de tempo real e claro, temos uma montanha de recursos. Vamos ver alguns deles.

Tamanho da pilha

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 flashuso por variáveis globais,e espaço livre para variáveis locais.

Onde uma tarefa está sendo executada?

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() 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());
    ...
}

Listar tarefas com vTaskList

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 blockedreadydeletedsuspended. Outra informações que retornam além do estado são a prioridadestacknúmero da tarefa.

Bblocked
RReady
DDeleted
SSuspended 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.

Não tem um ESP32 ainda?

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.

Siga-nos no Do bit Ao Byte no Facebook.

Prefere twitter? @DobitAoByte.

Inscreva-se no nosso canal Do bit Ao Byte Brasil no YouTube.

Nossos grupos:

Arduino BR – https://www.facebook.com/groups/microcontroladorarduinobr/
Raspberry Pi BR – https://www.facebook.com/groups/raspberrybr/
Orange Pi BR – https://www.facebook.com/groups/OrangePiBR/
Odroid BR – https://www.facebook.com/groups/odroidBR/
Sistemas Embarcados BR – https://www.facebook.com/groups/SistemasEmbarcadosBR/
MIPS BR – https://www.facebook.com/groups/MIPSBR/
Do Bit ao Byte – https://www.facebook.com/groups/dobitaobyte/

Próximo post a caminho!

Djames Suhanko

Djames Suhanko é Perito Forense Digital. Já atuou com deployer em sistemas de missão critica em diversos países pelo mundão. Programador Shell, Python, C, C++ e Qt, tendo contato com embarcados ( ora profissionalmente, ora por lazer ) desde 2009.