All About ESP32 – Part 2

Logging and WiFi Basics

All About ESP32 Parts

Related Articles

The first part of this series was an overview about the ESP32 from a hardware and a software perspectives with explaining the blinking LED application in detail.

As the ESP32 is a such complicated system, this series will deal with some functions or facts as black boxes and then return to exploring these boxes later.

The reader should have a solid understanding of ESP32 System before going to the limitless details. However, in this part we will explore some basic WiFi operations like scanning and connecting to an access point besides learning an introduction about logging via UART, all using ESP-IDF APIs.

 

Basic WiFi Scan

 

This could be the most simple and straight-forward example to get a closer look to WiFi part of ESP32. Let’s save our words and start with writing the code from top to bottom.

First we need to know that WiFi driver uses a non-volatile storage partition in the flash memory  to store WiFi data, thus we need first to initialize the default NVS partition using nvs_flash_init() from the ‘nvs_flash’ library. Another important thing in WiFi operation is the events handler. Because the WiFi has a lot of background processing going on, the developer needs to define a handler to check each new state of the WiFi, this assignment to the event handler function is done using esp_event_loop_init() API function from the ‘esp_event_loop’ library.

Moreover, WiFi doesn’t work alone; it’s related to a stack of other communication layers, so ESP32 does not initialize the WiFi driver only by esp_wifi_init() from ‘esp_wifi’ library, but also using TCP/IP stack called lwIP. lwIP is a well-known open source TCP/IP stack designed for embedded systems.

So we need to include the following libraries:

#include "esp_wifi.h"

#include "esp_event_loop.h"

#include "nvs_flash.h"

We need also to select the working mode of the WiFi: Station or SoftAP besides making the other desired general configurations of the WiFi and the scan operation.

void app_main(void)
{
   nvs_flash_init();

   tcpip_adapter_init();

   esp_event_loop_init(event_handler, NULL);

   wifi_init_config_t conf = WIFI_INIT_CONFIG_DEFAULT();

   esp_wifi_init(&conf);

   esp_wifi_set_mode(WIFI_MODE_STA);

   esp_wifi_start();

   wifi_scan_config_t scanConf;

   scanConf.ssid = NULL;
   scanConf.bssid = NULL;
   scanConf.channel = 0;
   scanConf.show_hidden = 1;
   scanConf.scan_type = WIFI_SCAN_TYPE_ACTIVE;

   esp_wifi_scan_start(&scanConf, 0);
}

Now, let’s plan for an event_handler function. Simply, if the scan is finished then find how many APs are there and print its SSID and RSSI.  Keep in mind that this handler is a function pointer in the end, and called somewhere in the WiFi driver. If you don’t have a clear idea about function pointers, then read this handy introduction. However, when WiFi driver calls the event_handler function it passes all needed details about the event in the form of an argument struct with type system_event_t (.i.e event->event_info.scan_done.number or event->event_id) .

Later, when the scan is done, we should get the stored information about the available APs using esp_wifi_scan_get_ap_records API, and as we don’t know this number, we can perform a dynamic allocation to read the stored info, or we can use fixed length variable; Both options use wifi_ap_record_t type.

Let’s have a look at the event_handler function code:

esp_err_t event_handler(void *ctx, system_event_t *event)
{
   if (event->event_id == SYSTEM_EVENT_SCAN_DONE)

   {

   uint16_t APCnt = event->event_info.scan_done.number;


   if(APCnt == 0) return 0;

   printf("%d AP available\n",APCnt);

   wifi_ap_record_t *list = malloc(sizeof(wifi_ap_record_t)*APCnt);

   printf("\r\n|------------------------------|\r\n");

   esp_wifi_scan_get_ap_records(&APCnt,list);

   printf("        SSID              RSSI\r\n");    


   for (uint8_t i = 0;i < APCnt;i++)
   {

      printf("\t%s\t\t%d\r\n", list[i].ssid, list[i].rssi);

   }

   printf("|------------------------------|\r\n");

   free(list);

   }

   return ESP_OK;

}

Logging Messages

 

This is the first code to print via the console. Expressif brought a special monitor for ESP32 in ESP-IDF called IDF monitor. “make monitor” will make the monitor run. This monitor is not just a common serial monitor, it has some options designed for ESP32 like automatically decoding addresses, pausing the application and reset the application.

Actually, a lot of debugging information is printed on the screen. This is because of ESP runtime logging system available with ESP-IDF. Also logging has levels :

The level can be chosen from Component config->Log output->Default log from “make menuconfig”. Developer can later set the debugging level from esp_log_level_set() function in the code but it can’t be increased beyond what was set by menuconfig.

Each source file has its own TAG name, so esp_log_level_set() has 2 arguments the tag name and level. For example, we can add a tag name in our last example like “scan_main”.

Looking at the image above we can see each line starts with “I” stands for info and the TAG “boot”. There are macros for each level to use.

Talking about logging is mostly for debugging and finding errors, so it’s good to mention also a handy macro called ESP_ERROR_CHECK from the ‘esp_err’ library, which checks the return value from API functions with esp_err_t return type, and if the value is not ESP_OK it prints an error message indicating the line number, source file name and the function name.

#define ESP_ERROR_CHECK(x) do {                                        \
       esp_err_t __err_rc = (x);                                       \
       if (__err_rc != ESP_OK) {                                       \
           _esp_error_check_failed(__err_rc, __FILE__, __LINE__,       \
                                   __ASSERT_FUNC, #x);                 \
       }                                                               \
    } while(0);

So, the next time we use error check macro and ESP logging macros you should have a basic idea about that.

WiFi Connect

 

The next application is to connect ESP32 to a specific AP. We will reuse some parts of the previous code with the same architecture.

In this code, we will include two additional header files, string.h and esp_log.h. The Former because we need to use strcpy function in the code and latter to print some logging information as we mentioned in logging messages section.

#include "esp_wifi.h"

#include "esp_event_loop.h"

#include "nvs_flash.h"

#include "esp_log.h"

#include "string.h"

The app_main() function still the same, almost, except that we need to set configuration for the WiFi with details about the target SSID, password, ..etc using esp_wifi_set_config() API using a variable with wifi_config_t type to store these configuration details. Full details of available options of the configuration can be found in esp_wifi_types.h header file.

static const char *WIFISSID = "SSID";

static const char *WIFIPWD = "PWD123456";

void app_main(void)
{
   nvs_flash_init(); 

   tcpip_adapter_init();
  
   wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();

    ESP_ERROR_CHECK(esp_wifi_init(&cfg)); 

   esp_event_loop_init(event_handler, NULL);  

   wifi_config_t wifi_config;

   strcpy((char *)wifi_config.sta.ssid,(char *)WIFISSID);

   strcpy((char *)wifi_config.sta.password,(char *)WIFIPWD);

   wifi_config.sta.scan_method = WIFI_FAST_SCAN;

   wifi_config.sta.sort_method = WIFI_CONNECT_AP_BY_SIGNAL;

   wifi_config.sta.threshold.rssi = -100;

   wifi_config.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK;

   esp_wifi_set_mode(WIFI_MODE_STA);  

   esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config); 

   esp_wifi_start();

}

wifi_config.sta.ssid & wifi_config.sta.password are char arrays so we need to use strcpy to copy the value to them.

Talking about the last part in the code,the event handler. It quite similar to the one in the Wifi scan code. If you have understood the above explanation well, you should understand this one as well:

static esp_err_t event_handler(void *ctx, system_event_t *event)
{

    switch (event->event_id) {

       case SYSTEM_EVENT_STA_START:

           ESP_LOGI(TAG, "SYSTEM_EVENT_STA_START");

           ESP_ERROR_CHECK(esp_wifi_connect());

           break;


       case SYSTEM_EVENT_STA_GOT_IP:

           ESP_LOGI(TAG, "SYSTEM_EVENT_STA_GOT_IP");

           ESP_LOGI(TAG, "Got IP: %s\n",ip4addr_ntoa(&event->event_info.got_ip.ip_info.ip));

           break;


       case SYSTEM_EVENT_STA_DISCONNECTED:

           ESP_LOGI(TAG, "SYSTEM_EVENT_STA_DISCONNECTED");

           ESP_ERROR_CHECK(esp_wifi_connect());

           break;


       default:

           break;

    }

    return ESP_OK;

}

 

The complete version of this example code is:

 

#include "esp_wifi.h"
#include "esp_event_loop.h"
#include "nvs_flash.h"
#include "esp_log.h"
#include "string.h"

static const char *TAG = "scan";

static const char *WIFISSID = "SSID";
static const char *WIFIPWD = "PWD123456";

static esp_err_t event_handler(void *ctx, system_event_t *event)
{
    switch (event->event_id) {

       case SYSTEM_EVENT_STA_START:

           ESP_LOGI(TAG, "SYSTEM_EVENT_STA_START");

           ESP_ERROR_CHECK(esp_wifi_connect());

           break;

       case SYSTEM_EVENT_STA_GOT_IP:

           ESP_LOGI(TAG, "SYSTEM_EVENT_STA_GOT_IP");

           ESP_LOGI(TAG, "Got IP: %s\n",

                    ip4addr_ntoa(&event->event_info.got_ip.ip_info.ip));

           break;

       case SYSTEM_EVENT_STA_DISCONNECTED:

           ESP_LOGI(TAG, "SYSTEM_EVENT_STA_DISCONNECTED");

           ESP_ERROR_CHECK(esp_wifi_connect());

           break;

       default:

           break;

    }

    return ESP_OK;
}


void app_main(void)
{
   nvs_flash_init();  

   tcpip_adapter_init();  

   wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();

    ESP_ERROR_CHECK(esp_wifi_init(&cfg));

   esp_event_loop_init(event_handler, NULL); 

   wifi_config_t wifi_config;

   strcpy((char *)wifi_config.sta.ssid,(char *)WIFISSID);
   strcpy((char *)wifi_config.sta.password,(char *)WIFIPWD);

   wifi_config.sta.scan_method = WIFI_FAST_SCAN;
   wifi_config.sta.sort_method = WIFI_CONNECT_AP_BY_SIGNAL;
   wifi_config.sta.threshold.rssi = -100;
   wifi_config.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK;

   esp_wifi_set_mode(WIFI_MODE_STA);
 
   esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config); 

   esp_wifi_start();

}

That’s all for now. In the next part, we will move forward with WiFi applications to send and receive data over WiFi, and in a later part we shall have a closer look to the memory map, partitions and basic flash memory operations to have a solid understanding of ESP32 system before going deeper.

All About ESP32 Parts

Related Articles

Exit mobile version