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系列模组二次开发