esp32-idf: Maintain wifi and Bluetooth connections at low power consumption

Write the directory title here

    • 1 Introduction
    • 2. Low power consumption to maintain wifi connection to Alibaba Cloud server
    • 3. Maintain Bluetooth connection with low power consumption
    • 4. Reference articles

1. Foreword

Three low power consumption modes of esp32 chip:
1. Modem Sleep mode: In this mode, the CPU can run and the clock frequency is configurable. Wi-Fi and Bluetooth LE’s baseband and radio are turned off, but Wi-Fi or Bluetooth LE can stay connected
2. Light Sleep mode: In this mode, the CPU suspends operation. Any wake-up event (MAC, host, RTC timer or external interrupt) will wake up the chip. Wi-Fi or Bluetooth LE can stay connected.
3. Deep Sleep mode: In this mode, the CPU, most of the RAM, and all digital peripherals driven by the clock APB_CLK will be powered off. After the wake-up conditions are met, the chip will be woken up from sleep.

To maintain Wi-Fi and Bluetooth connections, enable Wi-Fi and Bluetooth Modem-sleep mode and automatic Light-sleep mode (see Power Management API). In both modes, the system will automatically wake from sleep when requested by the Wi-Fi and Bluetooth drivers, thus maintaining connectivity.

2. Low power consumption to maintain wifi connection to Alibaba Cloud server

Use Hezhou esp32-c3 board for experiments

First connect the development board and select the corresponding chip

Create a project using low-power wifi as a template

Add wifi nickname and password, set listen interval to 10
listen interval: refers to the number of Beacon intervals experienced between two wake-ups of the workstation. This is one of the parameters specified when a workstation associates with an access point. Longer listening intervals allow the workstation to turn off the transmitter for a longer period of time, thus saving power.
Disadvantages of long listening intervals: 1. The access point must buffer frames for sleeping workstations, so a longer listening interval means that more buffering space must be prepared for the access point. (buffer).
2. Increasing the listening interval will delay the delivery of frames. If the access point is preparing to forward data to the workstation while the workstation is sleeping, the frame only needs to wait until the workstation wakes up before transmitting.
Modify to maximum power saving mode. This function has 3 parameters:
1. WIFI_PS_NONE: In this mode, WIFI will not enter power saving mode
2. WIFI_PS_MIN_MODEM: WIFI will wake up in every DTIM (Delivery Traffic Indication Message) cycle to receive beacons
3. WIFI_PS_MIN_MODEM: In this mode, the receiving beacon interval is determined by Listen_interval
Regarding the mqtt connection method, it is available in Espressif Systems example and is used directly here.
mqtt.c:

Insert /* here MQTT (over TCP) Example

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/

#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include "esp_wifi.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "esp_event.h"
#include "esp_netif.h"
//#include "protocol_examples_common.h"

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/queue.h"

#include "lwip/sockets.h"
#include "lwip/dns.h"
#include "lwip/netdb.h"

#include "esp_log.h"
#include "mqtt_client.h"
#include "mqtt.h"

#include "cJSON.h"

static const char *TAG = "MQTT_EXAMPLE";

static void log_error_if_nonzero(const char *message, int error_code)
{<!-- -->
    if (error_code != 0) {<!-- -->
        ESP_LOGE(TAG, "Last error %s: 0x%x", message, error_code);
    }
}

/*
 * @brief Event handler registered to receive MQTT events
 *
 * This function is called by the MQTT client event loop.
 *
 * @param handler_args user data registered to the event.
 * @param base Event base for the handler(always MQTT Base in this example).
 * @param event_id The id for the received event.
 * @param event_data The data for the event, esp_mqtt_event_handle_t.
 */
char mqtt_publish_data3[] = "hello";
static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
{<!-- -->
 // ESP_LOGD(TAG, "Event dispatched from event loop base=%s, event_id=%u", base, event_id);
    esp_mqtt_event_handle_t event = event_data;
    esp_mqtt_client_handle_t client = event->client;
    int msg_id;
    switch ((esp_mqtt_event_id_t)event_id) {<!-- -->
    case MQTT_EVENT_CONNECTED:
        ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED");
/* msg_id = esp_mqtt_client_publish(client, "/topic/qos1", "data_3", 0, 1, 0);
        ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id);

        msg_id = esp_mqtt_client_subscribe(client, "/topic/qos0", 0);
        ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id);

        msg_id = esp_mqtt_client_subscribe(client, "/topic/qos1", 1);
        ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id);

        msg_id = esp_mqtt_client_unsubscribe(client, "/topic/qos1");
        ESP_LOGI(TAG, "sent unsubscribe successful, msg_id=%d", msg_id); */
        //Subscribe to topic
        msg_id = esp_mqtt_client_subscribe(client, AliyunSubscribeTopic_user_get, 0);
        ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id);
        break;
    case MQTT_EVENT_DISCONNECTED:
        ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED");
        break;

/* case MQTT_EVENT_SUBSCRIBED:
        ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id);
        msg_id = esp_mqtt_client_publish(client, AliyunSubscribeTopic_user_update, mqtt_publish_data3, strlen(mqtt_publish_data3), 0, 0);
        ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id);
        break; */
    case MQTT_EVENT_UNSUBSCRIBED:
        ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id);
        break;
    case MQTT_EVENT_PUBLISHED:
        ESP_LOGI(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id);
        break;
    case MQTT_EVENT_DATA:
        ESP_LOGI(TAG, "MQTT_EVENT_DATA");
/* msg_id = esp_mqtt_client_publish(client, AliyunSubscribeTopic_user_update, event->data, event->data_len, 0, 0);
        ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id); */
        printf("TOPIC=%.*s\r\\
", event->topic_len, event->topic);
        printf("DATA=%.*s\r\\
", event->data_len, event->data);
        break;
    case MQTT_EVENT_ERROR:
        ESP_LOGI(TAG, "MQTT_EVENT_ERROR");
        if (event->error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT) {<!-- -->
            log_error_if_nonzero("reported from esp-tls", event->error_handle->esp_tls_last_esp_err);
            log_error_if_nonzero("reported from tls stack", event->error_handle->esp_tls_stack_err);
            log_error_if_nonzero("captured as transport's socket errno", event->error_handle->esp_transport_sock_errno);
            ESP_LOGI(TAG, "Last errno string (%s)", strerror(event->error_handle->esp_transport_sock_errno));

        }
        break;
    default:
        ESP_LOGI(TAG, "Other event id:%d", event->event_id);
        break;
    }
}

esp_mqtt_client_handle_t client=NULL;
static void user_mqtt_app_start(void)
{<!-- -->
    esp_mqtt_client_config_t mqtt_cfg = {<!-- -->
.broker.address.hostname = Aliyun_host,
.broker.address.port = Aliyun_port,
        .broker.address.transport = MQTT_TRANSPORT_OVER_TCP,
.credentials.client_id = Aliyun_client_id,
.credentials.username = Aliyun_username,
.credentials.authentication.password = Aliyun_password,

    };

    client = esp_mqtt_client_init( & amp;mqtt_cfg);
    esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, client);
    esp_mqtt_client_start(client);
}


void mqtt_connect(void)
{<!-- -->
    ESP_LOGI(TAG, "[APP] Startup..");
  // ESP_LOGI(TAG, "[APP] Free memory: %u bytes", esp_get_free_heap_size());
    ESP_LOGI(TAG, "[APP] IDF version: %s", esp_get_idf_version());

    esp_log_level_set("*", ESP_LOG_INFO);
    esp_log_level_set("mqtt_client", ESP_LOG_VERBOSE);
    esp_log_level_set("MQTT_EXAMPLE", ESP_LOG_VERBOSE);
    esp_log_level_set("TRANSPORT_BASE", ESP_LOG_VERBOSE);
    esp_log_level_set("esp-tls", ESP_LOG_VERBOSE);
    esp_log_level_set("TRANSPORT", ESP_LOG_VERBOSE);
    esp_log_level_set("outbox", ESP_LOG_VERBOSE);

 // ESP_ERROR_CHECK(nvs_flash_init());
   // ESP_ERROR_CHECK(esp_netif_init());
   // ESP_ERROR_CHECK(esp_event_loop_create_default());

    /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
     * Read "Establishing Wi-Fi or Ethernet Connection" section in
     * examples/protocols/README.md for more information about this function.
     */
    //ESP_ERROR_CHECK(example_connect());

    user_mqtt_app_start();
}
code piece

Write information about the device connection in the mqtt.h file

#ifndef MQTT_H
#define MQTT_H

/*Broker Address:${YourProductKey}.iot-as-mqtt.${YourRegionId}.aliyuncs.com*/
#define Aliyun_host "iot-06z00a7kp9v3vq5.mqtt.iothub.aliyuncs.com"
#define Aliyun_port 1883
/*Client ID: ${ClientID}|securemode=${Mode},signmethod=${SignMethod}|*/
#define Aliyun_client_id ""
/*User Name: ${DeviceName} & amp;${ProductKey}*/
#define Aliyun_username ""
/*Generate using the official MQTT_Password tool*/
#define Aliyun_password ""

#define AliyunSubscribeTopic_user_get ""
#define AliyunSubscribeTopic_user_update ""
void mqtt_connect(void);
#endif


Finally, connect mqtt in the wifi successful connection callback function, that is, when wifi is successfully connected, connect to Alibaba Cloud

beacon interval, beacon interval is 102.4ms


Publish messages on Alibaba Cloud and receive them in real time

When initially connected to wifi, the power consumption is the highest

After connecting to WiFi, the sleep current is about 500us. Because it needs to be constantly woken up, the current will continue to fluctuate.

3. Maintain Bluetooth connection with low power consumption


Create tasks using gatt server template


Check Bluetooth modem sleep


Check Support for power management

configEXPECTED_IDLE_TIME_BEFORE_SLEEP: When the idle task is the only runnable task (other tasks are blocked or suspended) and the system is in low power mode for more than configEXPECTED_IDLE_TIME_BEFORE_SLEEP clock ticks, it will enter low power. Consumption mode, if your device needs to remain in a low-power state most of the time, then you may want to set this value relatively small. On the other hand, if your device requires frequent computing and processing tasks, then you may need to set this value relatively large to reduce the number of times the device enters and exits low-power mode, thereby improving the performance of the device. last saved

This structure is used to set the broadcast interval
In the esp_ble_adv_params_t structure, .adv_int_min and .adv_int_max are used to set the minimum and maximum values of the broadcast interval.
The broadcast interval is 0.625ms, then the minimum broadcast interval: 0x200.625=320.625=20ms; the maximum broadcast interval: 0x40*0.625=40ms
If you want the device to broadcast more frequently, you can set a smaller broadcast interval. Conversely, if you want the device to broadcast less often to save power, you can set a larger broadcast interval. The larger the broadcast interval, the weaker the Bluetooth signal, the longer the connection time, but the more power is saved The maximum value is 0x2000
Here it is set to 0x1000:

static esp_ble_adv_params_t adv_params = {<!-- -->
    .adv_int_min = 0x1000,
    .adv_int_max = 0x1000,
    .adv_type = ADV_TYPE_IND,
    .own_addr_type = BLE_ADDR_TYPE_PUBLIC,
    //.peer_addr =
    //.peer_addr_type =
    .channel_map = ADV_CHNL_ALL,
    .adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
};


These two parameters are the connection interval, the unit is 1.25ms. The connection interval will be updated after the device is successfully connected. Here I want the device to respond faster after the device is successfully connected, so I keep this smaller connection interval.

#define CONFIG_EXAMPLE_MAX_CPU_FREQ_MHZ 80
#define CONFIG_EXAMPLE_MIN_CPU_FREQ_MHZ 10

void power_save_init(void)
{<!-- -->
    #if CONFIG_PM_ENABLE
    // Configure dynamic frequency scaling:
    // maximum and minimum frequencies are set in sdkconfig,
    // automatic light sleep is enabled if tickless idle support is enabled.
#if CONFIG_IDF_TARGET_ESP32
    esp_pm_config_esp32_t pm_config = {<!-- -->
#elif CONFIG_IDF_TARGET_ESP32C3
    esp_pm_config_esp32c3_t pm_config = {<!-- -->
#elif CONFIG_IDF_TARGET_ESP32S3
    esp_pm_config_esp32s3_t pm_config = {<!-- -->
#endif
            .max_freq_mhz = CONFIG_EXAMPLE_MAX_CPU_FREQ_MHZ,
            .min_freq_mhz = CONFIG_EXAMPLE_MIN_CPU_FREQ_MHZ,
#if CONFIG_FREERTOS_USE_TICKLESS_IDLE
            .light_sleep_enable = true
#endif
    };
    ESP_ERROR_CHECK( esp_pm_configure( & amp;pm_config) );
#endif // CONFIG_PM_ENABLE

}

Initialize power management, including the header file #include “esp_pm.h”

Standby current is about 7.7ma
After connecting to Bluetooth:


The current is 16.5ma, this is because the connection interval changes after connection

4. Reference articles

Anxinke experience sharing | Implementation of low power consumption while WiFi remains connected, suitable for secondary development of ESP32/ESP32C3/ESP32S3 series modules
Anxinke experience sharing | Implementation of low power consumption only while maintaining Bluetooth connection, suitable for secondary development of ESP32/ESP32C3/ESP32S3 series modules