esp32-idf:低功下耗保持wifi、蓝牙连接

1、前言

esp32芯片的三种低功耗模式:
1、Modem Sleep模式:在此模式下,CPU可以运行,时钟频率可配置。Wi-Fi和Bluetooth LE的基带和射频关闭,但Wi-Fi或Bluetooth LE可以保持连接
2、Light Sleep模式:在此模式下,CPU暂停运行。任何唤醒事件(MAC、主机、RTC定时器或外部中断)都会唤醒芯片。Wi-Fi或Bluetooth LE可以保持连接。
3、Deep Sleep模式:在此模式下,CPU、大部分RAM以及所有由时钟APB_CLK驱动的数字外设都会被断电。唤醒条件满足后,芯片就会从睡眠中被唤醒。

如需保持 Wi-Fi 和 Bluetooth 连接,请启用 Wi-Fi 和 Bluetooth Modem-sleep 模式和自动 Light-sleep 模式(请参阅 电源管理 API)。在这两种模式下,Wi-Fi 和 Bluetooth 驱动程序发出请求时,系统将自动从睡眠中被唤醒,从而保持连接。

2、低功耗保持wifi连接阿里云服务器

采用合宙esp32-c3板子进行实验
在这里插入图片描述
首先连接上开发板,选择对应芯片
在这里插入图片描述
使用低功耗wifi作为模板创建工程
在这里插入图片描述
添加wifi昵称和密码,将listen interval设置为10
listen interval:指工作站两次苏醒之间经历多少个Beacon间隔数。这是工作站与接入点关联时所指定的参数之一。较长时间的聆听间隔可以让工作站关闭传送器较长时间,从而节省电力。
长聆听间隔缺点:1、接入点必须为休眠中的工作站暂存帧,因此较长的聆听间隔对接入点而言,意味着必须准备更多的暂存空间(缓冲区)。
2、增加聆听间隔会延迟帧的传递。如果接入点准备转送数据给到工作站时工作站正处于休眠状态,该帧只要等到工作站苏醒之后再加以传送。在这里插入图片描述
修改成最大省电模式,这个函数有3个参数:
1、WIFI_PS_NONE:在该模式,WIFI不会进入省电模式
2、WIFI_PS_MIN_MODEM:WIFI会在每个DTIM(Delivery Traffic Indication Message)周期唤醒以接收信标
3、WIFI_PS_MIN_MODEM:该模式下,接收信标间隔由Listen_interval决定
有关mqtt连接方法,在乐鑫example中有,此处直接使用
mqtt.c:

在这里插入/* 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); */
        //订阅主题
        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\n", event->topic_len, event->topic);
        printf("DATA=%.*s\r\n", 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(&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();
}
代码片

在mqtt.h文件中写入有关设备连接的信息

#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}&${ProductKey}*/
#define   Aliyun_username   ""
/*使用官网 MQTT_Password 工具生成*/
#define   Aliyun_password   ""

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

在这里插入图片描述
最后在wifi成功连接回调函数中连接mqtt,即当wifi成功连接后,连接阿里云
在这里插入图片描述
beacon interval,信标间隔为102.4ms
在这里插入图片描述
在这里插入图片描述
在阿里云发布消息,能实时接收
在这里插入图片描述
最开始连接wifi时,功耗最大
在这里插入图片描述
连接至WiFi后,休眠电流约为500us,因为要不断唤醒,因此改电流会不断波动

3、低功耗保持蓝牙连接

在这里插入图片描述
用gatt服务器模板创建任务

在这里插入图片描述
勾选Bluetooth modem sleep

在这里插入图片描述
勾选Support for power management
在这里插入图片描述
configEXPECTED_IDLE_TIME_BEFORE_SLEEP:当空闲任务是唯一可运行的任务(其他任务都处于阻塞或挂起态)以及系统处于低功耗模式的时间大于configEXPECTED_IDLE_TIME_BEFORE_SLEEP个时钟节拍时,就会进入低功耗模式,如果你的设备需要在大部分时间内保持低功耗状态,那么你可能需要将这个值设置得相对较小。反之,如果你的设备需要频繁地进行计算和处理任务,那么你可能需要将这个值设置得相对较大,以减少设备进入和退出低功耗模式的次数,从而提高设备的性能。最后保存
在这里插入图片描述
该结构体用于设置广播间隔
在esp_ble_adv_params_t结构体中,.adv_int_min和.adv_int_max是用来设置广播间隔的最小值和最大值的。
广播间隔是0.625ms,则最小广播间隔:0x200.625=320.625=20ms;最大广播间隔:0x40*0.625=40ms
如果希望设备更频繁地广播,可以设置一个较小的广播间隔。反之,如果希望设备更少地广播以节省电力,可以设置一个较大的广播间隔。广播间隔越大,蓝牙信号越弱,连接时间越长,但是越省电最大值为0x2000
这里将其设置为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,
};

在这里插入图片描述
这两个参数是连接间隔,单位为1.25ms,设备连接成功后会更新连接间隔,这里我想让设备连接成功后响应更快,因此我保持这个较小的连接间隔

#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(&pm_config) );
#endif // CONFIG_PM_ENABLE

}

初始化电源管理,包含头文件#include “esp_pm.h”
在这里插入图片描述
待机电流约为7.7ma
连接蓝牙以后:
在这里插入图片描述
在这里插入图片描述
电流为16.5ma,这是因为连接后连接间隔变化了

4、参考文章

安信可经验分享 | WiFi保持连接状态下低功耗的实现,适用于ESP32/ESP32C3/ESP32S3系列模组二次开发
安信可经验分享 | 仅保持蓝牙连接状态下的低功耗的实现,适用于ESP32/ESP32C3/ESP32S3系列模组二次开发

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Sense_long

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值