Consulting Services
ArticlesEmbedded C & MCUIoT

All About ESP32 – Part 2

Logging and WiFi Basics

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 :

  • error (lowest)
  • warning
  • info
  • debug
  • verbose (highest)

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.

  • ESP_LOGE for error.
  • ESP_LOGW for warning.
  • ESP_LOGI for info.
  • ESP_LOGD for Debug.
  • ESP_LOGV for verbose.

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.

Yahya Tawil

Embedded Hardware Engineer interested in open hardware and was born in the same year as Linux. Yahya is the editor-in-chief of Atadiat and believes in the importance of sharing free, practical, spam-free and high quality written content with others. His experience with Embedded Systems includes developing firmware with bare-metal C and Arduino, designing PCB&schematic and content creation.

7 Comments

  1. Great series of articles. But here is the thing that didn’t work for me, for some reason.
    These two rows don’t seem to work:
    wifi_config_t wifi_config;
    strcpy((char *)wifi_config.sta.ssid,(char *)WIFISSID);
    strcpy((char *)wifi_config.sta.password,(char *)WIFIPWD);
    Whereas this way worked like a charm:
    wifi_config_t wifi_config = {
    .sta = {
    .ssid = “SSID”,
    .password = “PASS”
    }
    };
    I am completely new to C language, can you please point me out, what is wrong with these two cases ? Thank you.

  2. Hello,
    I am using current stable version of idf and getting this error.
    Same for wifi conectivity and not able to resolve it.

    CODE
    #include “esp_wifi.h”
    #include “esp_event_loop.h”
    #include “nvs_flash.h”

    /*Set the SSID and Password via “make menuconfig”*/
    #define DEFAULT_SSID CONFIG_WIFI_SSID
    #define DEFAULT_PWD CONFIG_WIFI_PASSWORD

    #if CONFIG_WIFI_ALL_CHANNEL_SCAN
    #define DEFAULT_SCAN_METHOD WIFI_ALL_CHANNEL_SCAN
    #elif CONFIG_WIFI_FAST_SCAN
    #define DEFAULT_SCAN_METHOD WIFI_FAST_SCAN
    #else
    #define DEFAULT_SCAN_METHOD WIFI_FAST_SCAN
    #endif /*CONFIG_SCAN_METHOD*/

    #if CONFIG_WIFI_CONNECT_AP_BY_SIGNAL
    #define DEFAULT_SORT_METHOD WIFI_CONNECT_AP_BY_SIGNAL
    #elif CONFIG_WIFI_CONNECT_AP_BY_SECURITY
    #define DEFAULT_SORT_METHOD WIFI_CONNECT_AP_BY_SECURITY
    #else
    #define DEFAULT_SORT_METHOD WIFI_CONNECT_AP_BY_SIGNAL
    #endif /*CONFIG_SORT_METHOD*/

    #if CONFIG_FAST_SCAN_THRESHOLD
    #define DEFAULT_RSSI CONFIG_FAST_SCAN_MINIMUM_SIGNAL
    #if CONFIG_EXAMPLE_OPEN
    #define DEFAULT_AUTHMODE WIFI_AUTH_OPEN
    #elif CONFIG_EXAMPLE_WEP
    #define DEFAULT_AUTHMODE WIFI_AUTH_WEP
    #elif CONFIG_EXAMPLE_WPA
    #define DEFAULT_AUTHMODE WIFI_AUTH_WPA_PSK
    #elif CONFIG_EXAMPLE_WPA2
    #define DEFAULT_AUTHMODE WIFI_AUTH_WPA2_PSK
    #else
    #define DEFAULT_AUTHMODE WIFI_AUTH_OPEN
    #endif
    #else
    #define DEFAULT_RSSI -127
    #define DEFAULT_AUTHMODE WIFI_AUTH_OPEN
    #endif /*CONFIG_FAST_SCAN_THRESHOLD*/

    static const char *TAG = “scan”;

    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 >> After flashing it sucessfully <<—–

    $ make monitor
    MONITOR
    — idf_monitor on COM7 115200 —
    — Quit: Ctrl+] | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H —
    ets Jun 8 2016 00:22:57

    rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
    configsip: 0, SPIWP:0xee
    clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
    mode:DIO, clock div:2
    load:0x3fff0018,len:4
    load:0x3fff001c,len:5832
    load:0x40078000,len:9188
    load:0x40080000,len:6084
    0x40080000: _iram_start at C:/msys32/home/abhin/esp/esp-idf/components/freertos/xtensa_vectors.S:1779

    entry 0x4008032c
    0x4008032c: _KernelExceptionVector at ??:?

    I (28) boot: ESP-IDF v3.1.3 2nd stage bootloader
    I (28) boot: compile time 14:25:10
    I (29) boot: Enabling RNG early entropy source…
    I (33) boot: SPI Speed : 40MHz
    I (37) boot: SPI Mode : DIO
    I (41) boot: SPI Flash Size : 4MB
    I (45) boot: Partition Table:
    I (49) boot: ## Label Usage Type ST Offset Length
    I (56) boot: 0 nvs WiFi data 01 02 00009000 00006000
    I (64) boot: 1 phy_init RF data 01 01 0000f000 00001000
    I (71) boot: 2 factory factory app 00 00 00010000 00100000
    I (79) boot: End of partition table
    I (83) esp_image: segment 0: paddr=0x00010020 vaddr=0x3f400020 size=0x13b24 ( 80676) map
    I (120) esp_image: segment 1: paddr=0x00023b4c vaddr=0x3ffb0000 size=0x030d4 ( 12500) load
    I (125) esp_image: segment 2: paddr=0x00026c28 vaddr=0x40080000 size=0x00400 ( 1024) load
    0x40080000: _iram_start at C:/msys32/home/abhin/esp/esp-idf/components/freertos/xtensa_vectors.S:1779

    I (127) esp_image: segment 3: paddr=0x00027030 vaddr=0x40080400 size=0x08fe0 ( 36832) load
    I (151) esp_image: segment 4: paddr=0x00030018 vaddr=0x400d0018 size=0x6439c (410524) map
    0x400d0018: _stext at ??:?

    I (295) esp_image: segment 5: paddr=0x000943bc vaddr=0x400893e0 size=0x076b8 ( 30392) load
    0x400893e0: coex_timer_ts_start_disalarm at ??:?

    I (318) boot: Loaded app from partition at offset 0x10000
    I (318) boot: Disabling RNG early entropy source…
    I (318) cpu_start: Pro cpu up.
    I (322) cpu_start: Starting app cpu, entry point is 0x40080fdc
    0x40080fdc: call_start_cpu1 at C:/msys32/home/abhin/esp/esp-idf/components/esp32/cpu_start.c:232

    I (0) cpu_start: App cpu up.
    I (332) heap_init: Initializing. RAM available for dynamic allocation:
    I (339) heap_init: At 3FFAE6E0 len 00001920 (6 KiB): DRAM
    I (345) heap_init: At 3FFB90D8 len 00026F28 (155 KiB): DRAM
    I (351) heap_init: At 3FFE0440 len 00003BC0 (14 KiB): D/IRAM
    I (358) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM
    I (364) heap_init: At 40090A98 len 0000F568 (61 KiB): IRAM
    I (370) cpu_start: Pro cpu start user code
    I (53) cpu_start: Starting scheduler on PRO CPU.
    I (0) cpu_start: Starting scheduler on APP CPU.
    I (98) wifi: wifi driver task: 3ffc08a4, prio:23, stack:3584, core=0
    I (98) wifi: wifi firmware version: 71d6725
    I (98) wifi: config NVS flash: enabled
    I (98) wifi: config nano formating: disabled
    I (108) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE
    I (118) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE
    Exception in thread Thread-2:
    Traceback (most recent call last):
    File "C:/msys32/mingw32/lib/python2.7/threading.py", line 801, in __bootstrap_inner
    self.run()
    File "C:/msys32/mingw32/lib/python2.7/threading.py", line 754, in run
    self.__targe
    t(*self.__args, **self.__kwargs)
    File "C:/msys32/home/abhin/esp/esp-idf/tools/idf_monitor.py", line 124, in _run_outer
    self.run()
    File "C:/msys32/home/abhin/esp/esp-idf/tools/idf_monitor.py", line 204, in run
    data = self.serial.read(self.serial.in_waiting or 1)
    File "C:/msys32/mingw32/lib/python2.7/site-packages/serial/serialwin32.py", line 257, in in_waiting
    raise SerialException("ClearCommError failed ({!r})".format(ctypes.WinError()))
    SerialException: ClearCommError failed (WindowsError(5, 'Access is denied.'))

  3. when I use this coding in ESP32 wroom, the device connects with the said but I am not getting any IP allocated for the device.Can anyone help me?

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Back to top button