ESP32 C++ std::thread tutorial

C++ threads std::thread in ESP-IDF is implemented on top of ESP-pthread component 

The C++ threads created using the standard thread class constructor actually use the under the hood ESP-pthread. On the other hand ESP-pthread is actually using FreeRtos Task 



Now we will go through this example of esp-idf Example to use pthread in esp32 https://github.com/espressif/esp-idf/tree/master/examples/system/cpp_pthread 

The example is written in C++ 


The esp pthread configuration structure looks like 

/** pthread configuration structure that influences pthread creation */
typedef struct {
    size_t stack_size;  ///< The stack size of the pthread
    size_t prio;        ///< The thread's priority
    bool inherit_cfg;   ///< Inherit this configuration further
    const char* thread_name;  ///< The thread name.
    int pin_to_core;    ///< The core id to pin the thread to. Has the same value range as xCoreId argument of xTaskCreatePinnedToCore.
} esp_pthread_cfg_t;


To create a std::thread type thread a esp_pthread configuration is needed 

// Create a thread using default values that can run on any core
auto cfg = esp_pthread_get_default_config();

esp_pthread_set_cfg(&cfg);


auto keyword in C++ 

The auto keyword specifies that the type of the variable that is being declared will be automatically deducted from its initializer . it is available from C++ 11


Here I am explaining the code by going through the debug messages after running the program. 


The example first creates a thread that can run at any core 

std::thread any_core(thread_func_any_core);

I (399) pthread: This thread (with the default name) may run on any core.Core id: 0, prio: 5, minimum free stack: 2124 bytes.


Then it creates a thread to be run on core 0 . this thread’s inherit_cfg is set to true . 

std::thread thread_1(spawn_another_thread);

I (409) Thread 1: Core id: 0, prio: 5, minimum free stack: 2144 bytes.


this thread in turn creates another thread which inherits the same configuration of the thread which created it 

std::thread inherits(thread_func_inherited);

I (419) Thread 1: This is the INHERITING thread with the same parameters as our parent, including name. Core id: 0, prio: 5, minimum free stack: 2172 bytes.


Then a thread is created to be run on core 1 only 

std::thread thread_2(thread_func);

I (439) Thread 2: Core id: 1, prio: 5, minimum free stack: 2160 bytes.


The main() of the app has an infinite loop that prints some information. This infinite loop itself is a thread too 

I (429) main: core id: 0, prio: 1, minimum free stack: 3084 bytes.


All the threads sleeps for 5 second 


The example uses stringstream type object to construct debug messages. the constructed message is printed via the ESP_LOGI() function 


Summary 

The C++ std::thread is a popular way to design multithreaded applications. Application developers are more familiar with this API than FreeRTOS API . So it’s a great decision by espressif to port std::thread for ESP32 . Application developers can port C++ std::thread based applications to ESP32 architecture easily. Also if you want to design a C++ based application with thread functionality this is a good design pattern 


ESP32 MQTT Publish using FreeRtos event group

 In this previous post esp32-mqtt-tutorial we have seen how pub-sub and data receive events works in a simple application. In this post, we are going to do the same but the Design pattern will be different. we will be moving towards an Event-driven coding pattern so that later we can embed other code/features easily without any timing issue 



We will follow the following example code to learn about the mqtt publish 


https://github.com/espressif/esp-idf/tree/master/examples/protocols/mqtt/publish_test


This example uses freertos event groups 


static EventGroupHandle_t mqtt_event_group;


Freertos API is backward compatible 


In freertos-based design we can make a task block waiting for a queue. if the length of the queue is 1 then we say that the task is blocked waiting for a semaphore. semaphore can be binary or counting type. if a binary semaphore is used for managing the access of a shared resource between multiple tasks then the semaphore is called mutex. 


“You may think of an event group as a bit field where every bit is a flag that corresponds to a certain event. The event bit can be set or cleared. The state of the bits is altered to signal the status of a process to other functions.” ref: http://iot-bits.com/freertos-event-groups-quick-reference-notes/


mqtt_event_group = xEventGroupCreate();


Creates a new RTOS event group, and returns a handle by which the newly created event group can be referenced


The example has an infinite loop in the main function, this loop can be assumed as a task meaning  we can make it block waiting for something like semaphore/queue / event group bit 


xEventGroupWaitBits(mqtt_event_group, CONNECTED_BIT, false, true, portMAX_DELAY);


EventBits_t xEventGroupWaitBits(
                      const EventGroupHandle_t xEventGroup,
                      const EventBits_t uxBitsToWaitFor,
                      const BaseType_t xClearOnExit,
                      const BaseType_t xWaitForAllBits,
                      TickType_t xTicksToWait );


xClearOnExit = false, means the bits set in the event group are not altered when the call to xEventGroupWaitBits() returns.


xWaitForAllBits = true means xEventGroupWaitBits() will return when either all the bits set in the value passed as the uxBitsToWaitFor parameter are set in the event group or the specified block time expires. In this example, only one bit (CONNECTED_BIT) is passed.


xTicksToWait = portMAX_DELAY, which means the maximum amount of time to wait for the CONNECTED_BIT to set 


In the example code, the main loop task will wait for the CONNECTED_BIT to be set until portMAX_DELAY expires, after that the code will publish a message at QoS 0 


At the event handler function mqtt_event_handler() when MQTT_EVENT_CONNECTED type event is got, the CONNECTED_BIT  of the event group is set so that the main loop task can publish instantaneously 


xEventGroupSetBits(mqtt_event_group, CONNECTED_BIT);


At each project build/config directory there is sdkconfig.h header file where all the CONFIGURATIONS are listed, these configurations are altered at menuconfig 


I have changed the configurations as the following 


You can see that the publish and subscribe both topics are the same and the broker URL of type TCP has been changed to mqtt.eclipse.org 


Now build and run the example, 

The code waits for user input so you will have to input into the terminal 

[Note: nothing will be visible in the terminal so make sure you type rightly and press enter ] 


"tcp esp32_ 10 10 1"      


I typed the above without quotation mark, 


tcp        = it means that we want to use transport type tcp,
esp32_ = The message unit should be "esp32_"
10         = The pattern repetition number is 10
10         = No of publish is 10
1           = QOS is 1 



After the input, the MQTT client is started and when it gets connected at the event handler the CONNECTED_BIT of the event group gets set, this, in turn, unblocks the main loop task which publishes 10 times. 


When the client was connected it subscribed too. 

ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED");
xEventGroupSetBits(mqtt_event_group, CONNECTED_BIT);
msg_id = esp_mqtt_client_subscribe(client, CONFIG_EXAMPLE_SUBSCIBE_TOPIC, qos_test);


In the event handling function Event Data received is tracked so when all the 10 published message is received by the handler it will print success




ESP32 as a WiFi Station

 In this post, we will go through an example in which the esp32 connects to a wifi softAP as a station 

The reference code is

https://github.com/espressif/esp-idf/tree/master/examples/wifi/getting_started/station 


The SSID and password is defined at the top 

#define EXAMPLE_ESP_WIFI_SSID      CONFIG_ESP_WIFI_SSID
#define EXAMPLE_ESP_WIFI_PASS      CONFIG_ESP_WIFI_PASSWORD

Here the CONFIG_XXXXX macros are in sdkconfig.h file 


This configuration file is actually modified when you use the $idf.py menuconfig command

So set the SSID and password of your router using menuconfig 


The code uses the default event loop to register and handle the WIFI connection related events 


Initialize the underlying TCP/IP stack using the following function

void tcpip_adapter_init(void);


Initialize wifi_init_config_t type configuration variable by putting the default values and then initialize the wifi stack

wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));


Create the default event loop & Register for WIFI events to the default event loop 

ESP_ERROR_CHECK(esp_event_loop_create_default());

……..

ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL));


Set the SSID and Password to the wifi stack 

    wifi_config_t wifi_config = {
        .sta = {
            .ssid = EXAMPLE_ESP_WIFI_SSID,
            .password = EXAMPLE_ESP_WIFI_PASS
        },
    };


Set the wifi stack as Station Mode and start it 


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


Now as the wifi connection events occur it will get hanlded by the handler function 

static void event_handler(void* arg, 

                          esp_event_base_t event_base,
                          int32_t event_id, 

                          void* event_data)
{
………………….

………………..
}


Compile and flash the program 

~/esp/esp-idf/examples/wifi/getting_started/station$ idf.py -p /dev/ttyUSB0 flash monitor


Check the output log 


ESP32 Default Event Loop

ESP-IDF has an Event Loop Library. This library allows you to create an Event Loop and declare Events to which other components of your code can register handlers. A handler is a function that gets executed when a particular event occurs. Following this pattern we can get a taste of Event Driven Programming in general. 

A default event loop is already prepared by the IDF; it is responsible for generating major events like

  • WIFI_EVENT
  • IP_EVENT
  • IP_EVENT_STA_GOT_IP

You only need to call a function that will create the default event loop.

After that, you don’t have to create/run any freertos task to run the default event loop. the default Loop runs internally

You can register to the default event loop for events like WIFI_EVENT and handle the event according to your needs. You can also create your own events and post them to the default event loop. Other code components of yours can register by the events created by you.

In this way, the default event loop can generate/dispatch major system Events as well as user-created ones. I guess This is sufficient for most of the application design, but if you really need an event loop other than the default you can create one, but then you will also have to run it by a freeRtos Task.

To distinguish between default & User event loop the API provided by the esp_event library is slightly different for both the cases

Default Event Loops User Event Loops
esp_event_loop_create_default()                       esp_event_loop_create()
esp_event_loop_delete_default() esp_event_loop_delete()
esp_event_handler_register() esp_event_handler_register_with()
esp_event_handler_unregister() esp_event_handler_unregister_with()
esp_event_post() esp_event_post_to()

EVENTS

An Event consists of 2 identifiers

  • The Event Base
  • The Event ID

There can be multiple event ID under 1 Event Base

The esp_event library has Macros to declare and define events

Now we will follow the following example code to know how to use the default event loop

https://github.com/espressif/esp-idf/tree/master/examples/system/esp_event/default_event_loop

In The example, a timer is made using esp_timer API and 3 event sources are created related to the timer. The events are

  • An event is generated when timer is raised
  • An event is generated when timer period expires
  • An event is generated when the time is stopped

first, declare Event in the event_source.h header file

// event_source.h
// Declare an event base
ESP_EVENT_DECLARE_BASE(TIMER_EVENTS); // declaration of the timer events family

enum {               // declaration of the specific events under the timer event family
    TIMER_EVENT_STARTED,       // raised when the timer is first started
    TIMER_EVENT_EXPIRY,        // raised when a period of the timer has elapsed
    TIMER_EVENT_STOPPED        // raised when the timer has been stopped
};

Then define the event in main.c implementation file

/* Event source periodic timer related definitions */
ESP_EVENT_DEFINE_BASE(TIMER_EVENTS);

Create the default event loop as

// Create the default event loop
ESP_ERROR_CHECK(esp_event_loop_create_default());

To register an Event to the default event loop we have to use the following function

esp_err_t esp_event_handler_register(
esp_event_base_t  event_base,
                  int32_t event_id,
                  esp_event_handler_t event_handler,
                  void* event_handler_arg
                );

For example, to register the event of timer start the event base & Event ID is passed as follows

// Register the specific timer event handlers.
ESP_ERROR_CHECK(esp_event_handler_register(
                                 TIMER_EVENTS, 
                                 TIMER_EVENT_STARTED, 
                                 timer_started_handler, 
                                 NULL));

To Post an Event to the default loop you have to use the following function. Remember you have to post your events to the default loop, default loop will then dispatch the event to the listening subscribers

esp_err_t esp_event_post(
                    esp_event_base_t      event_base,
                    int32_t               event_id,
                    void*                   event_data,
                    size_t                   event_data_size,
                    TickType_t               ticks_to_wait
                    );

So the example code starts the timer and posts this very starting event to the default loop

ESP_ERROR_CHECK(esp_timer_start_periodic(TIMER, TIMER_PERIOD));
ESP_ERROR_CHECK(esp_event_post(
                               TIMER_EVENTS, 
                               TIMER_EVENT_STARTED, 
                               NULL, 
                               0, 
                               portMAX_DELAY)
                               );

As soon as this event is got by the default loop, it dispatches the event to the corresponding handler which was registered before this event

// Handler which executes when the timer started event gets executed by the loop.
static void timer_started_handler(void* handler_args, esp_event_base_t base, int32_t id, void* event_data)
{
    ESP_LOGI(TAG, "%s:%s: timer_started_handler", base, get_id_string(base, id));
}

The handler basically prints a log about the event

So this is how you can use the default event loop to design an event-driven application

Reference

https://github.com/espressif/esp-idf/tree/master/examples/system/esp_event/default_event_loop

ESP32 Timer Tutorial

Embedded Software requires Timer functionality for solving many use cases. ESP-IDF is based on FreeRTOS and we use freertos api to design our application. FreeRTOS do provide software timers, but these timers have limitations such as it's smallest possible interval/period can be at best be qual to the RTOS tick period such as 1ms / 10ms . In addition, the software timer callbacks are dispatched from a low priority task. In order to address these issues we can get help from the ESP32 microcontroller itself. The microcontroller has dedicated hardware Timer peripheral as part of the SoC which does not consume any CPU processing and can execute as a higher priority interrupt

Timer Features

ESP-IDF has esp_timer API using which we can get hold of the internal 32bit hardware timer. Let’s see what it provides.

  • It is a high resolution timer
  • Provides one-shot timer
  • Provides periodic timer
  • Timer callbacks are dispatched from a high priority esp_timer task

it is recommended to make the code in the callback as small as possible, the callback can dispatch another low priority task using a queue

  • Time since boot can be read using esp_timer_get_time()
  • Enabling esp_timer profiling feature in menuconfig cause esp_timer_dump() function to print more information

Code-Walkthrough

Now we will go through this example to understand the usage of esp_timer https://github.com/espressif/esp-idf/tree/master/examples/system/esp_timer

The example code creates a periodic timer & a one-shot timer

To create a esp_timer type timer the following configuration type structure is needed to be instantiated

typedef struct {
   
esp_timer_cb_t callback;                //!< Function to call when timer expires
   
void* arg;                                        //!< Argument to pass to the callback
   
esp_timer_dispatch_t dispatch_method;   //!< Call the callback from task or from ISR
   
const char* name;                       //!< Timer name, used in esp_timer_dump function
}
esp_timer_create_args_t;

So we create one of the type above

    const esp_timer_create_args_t periodic_timer_args = {
           .callback = &periodic_timer_callback,
           .name =
"periodic" /* name is optional, but may help identify the timer when debugging */
   };

Before creating the timer we also need a esp_timer_handle_t type handle with the following structure

struct esp_timer {
   
uint64_t alarm;
   
uint64_t period;
   
union {
       
esp_timer_cb_t callback;
       
uint32_t event_id;
   };

typedef struct esp_timer* esp_timer_handle_t;

So we create a handle out of the structure type above

esp_timer_handle_t periodic_timer;

Now we will use the esp_timer_create() function to create the timer

esp_err_t esp_timer_create(
 
const esp_timer_create_args_t* create_args,            
                         
esp_timer_handle_t* out_handle
                          );

Now as we have the arguments and handler variable instantiated, we will call The create function which is wrapped by ESP_ERROR_CHECK macro function

 ESP_ERROR_CHECK(esp_timer_create(&periodic_timer_args, &periodic_timer));

Now the timer is created but the timer has not started yet . we need to decide in which mode we want to run it either periodic or one-shot . Let’s run the timer in a periodic way so we use the  esp_timer_start_periodic() function.

ESP_ERROR_CHECK(esp_timer_start_periodic(periodic_timer, 500000));

The called function definition is

 /*Timer should not be running when this function is called.
* @param timer timer handle created using esp_timer_create
* @param period timer period, in microseconds
*/

esp_err_t esp_timer_start_periodic(esp_timer_handle_t timer, uint64_t period);

At the esp_timer_create_args_t type periodic_timer_args variable we assigned the callback member to a function named periodic_timer_callback . this is the function which will be called back periodically . we need to implement the callback in our C file but the function signature should be like

typedef void (*esp_timer_cb_t)(void* arg);

Here is our implementation . we have put a debug msg to better visualize the timer in operation.

static void periodic_timer_callback(void* arg)
{
   
int64_t time_since_boot = esp_timer_get_time();
   ESP_LOGI(TAG,
"Periodic timer called, time since boot: %lld us", time_since_boot);
}

This callback function prints the time since boot , periodically

In a similar way the example code creates one-shot type timer and runs it . you can run and see it’s operation.

Additional Note

If your application is a low power device and relies upon esp32 hardware timer then do take note of the following cases.

  • Light sleep does not impact timer period. So after light sleep  esp_timer_get_time() function returns accurate time.
  • But Note during the light sleep the periodic timer & one shot timer callbacks do not execute since the CPU is not running during the light sleep. After waking up the callbacks get immediately fired up .


Reference

https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/system/esp_timer.html# 

ESP32 Logging / Debug Library Usage

ESP32 has a very convenient Logging Library

In Arduino-like projects Normally what we do is that we print out our debug information using the Serial print function.

On the Other hand, the Esp-IDF logging library forces you to print debug/log messages in a constructive way so that later you can easily visualize the logs according to the debug type categories and module groups. If you have experience in Android Application development then you will find this library similar.

ESP-IDF has different Logging functions as listed below

type MACRO type functions Verbosity Level
Error          ESP_LOGE(tag, format, …)         Lowest
Warning ESP_LOGW(tag, format, …)
Info ESP_LOGI(tag, format, …)
Debug ESP_LOGD(tag, format, …)
Verbose ESP_LOGV(tag, format, …) Highest

So what you can do is you can categorize your log messages into the above 5 groups according to your need.

  • If you have an information log to print, use the ESP_LOGI() function.
  • If you have a warning log to print use the ESP_LOGW() function

All the functions expect you to put a TAG of the type character string.

Let's say you have written a Kalman filter class by yourself and in this class, you are going to print different types of debug messages. So before using the macro function to print the logs, create a TAG saying “Kalman''. Then pass this TAG to all of the debug function calls. 

The ESP logging library will print these “Kalman” tagged debug messages with the TAG first so that you can easily distinguish these particular module logs, in addition, you also get the warning/info/error kind of categorization. So if you follow this approach in all of your code modules then ultimately the debug dump gets very convenient to see.

static const char\* TAG ="Kalman";   
ESP_LOGW(TAG,"threshold exceeded %d baud", error \*100);
ESP_LOGI(TAG,"filtering complete");

By default, the CONFIG_LOG_DEFAULT_LEVEL is set to info level,

which means that the logging statements whose verbosity levels are higher than the Info level will be disabled. So if you use ESP_LOGD( ) or ESP_LOGV( ) functions , their message will not get printed

By moving up or down the debug levels in menuconfig you can set the common debug level setting of all debug messages.  Keeping the Debug level at default it is possible to change Debug levels of particular TAG Typed messages at Runtime using the following code 

esp_log_level_set("Kalman", ESP_LOG_ERROR); // enable till ERROR level

This function will set the logging level for the “Kanman” TAGGed module to the ERROR level only. But the Other TAGGed module will follow the default level set by the CONFIG_LOG_DEFAULT_LEVEL macro set via menuconfig (compile-time

Reference

https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/system/log.html

Categories

Pages

Firmware Engineer

My photo
Works on Firmware, Embedded Linux, Smart Metering, RTOS, IoT backend

Contact Form

Name

Email *

Message *

Copyrighted by Hassin. Powered by Blogger.