编程技术分享

分享编程知识,探讨技术创新

0%

简介:使用ESP-NOW协议传输信号,按下按键开始工作,数据传送完成后进入待机状态,待机电流在25ua以下,使用1000mAh锂电池供电。

好的,作为一名高级嵌入式软件开发工程师,很高兴能和你一起探讨这个基于ESP-NOW协议的低功耗嵌入式产品项目。这个项目涵盖了嵌入式开发的完整流程,从需求分析、架构设计、代码实现到测试验证和维护升级,是一个非常典型的实践案例。
关注微信公众号,提前获取相关推文

项目需求回顾与分析

首先,我们来详细回顾一下项目的核心需求,并进行深入分析,这对于后续的架构设计和代码实现至关重要。

  1. 核心功能:基于ESP-NOW协议的数据传输

    • 协议选择: ESP-NOW协议是乐鑫ESP系列芯片特有的快速、低功耗的无线通信协议,非常适合短距离、低延迟的数据传输场景,尤其是在物联网和可穿戴设备领域应用广泛。它无需传统的Wi-Fi连接过程,配对速度快,功耗相对较低。
    • 数据类型和传输量: 虽然需求中没有明确指出数据类型和传输量,但根据“信号传输”的描述,我们可以假设是传感器数据、控制指令或者少量状态信息。我们需要在代码中预留数据结构设计的空间,并考虑数据传输的效率和可靠性。
    • 传输模式: ESP-NOW支持广播、单播和组播模式。根据项目描述,我们可以假设是设备到设备或者设备到中心节点的单播或广播模式。具体模式的选择需要根据实际应用场景来确定。
  2. 触发机制:按键启动

    • 用户交互: 按键是简单直接的用户交互方式,按下按键启动设备工作,符合直观的操作逻辑。
    • 低功耗考虑: 按键触发可以有效控制设备的工作时间,避免不必要的功耗,尤其是在待机状态下,可以完全关闭无线模块,只在需要工作时才唤醒。
    • 按键类型和检测: 需要选择合适的按键类型(机械按键、触摸按键等),并设计可靠的按键检测电路和软件逻辑,防止误触发和抖动问题。
  3. 低功耗待机:电流 < 25uA

    • 功耗指标: 25uA的待机电流是一个非常严格的低功耗要求,这意味着设备在待机状态下必须尽可能地关闭所有耗电模块,包括主控芯片、无线模块、传感器等。
    • 低功耗模式: ESP32芯片提供了多种低功耗模式,例如Deep-sleep模式,可以实现极低的待机电流。我们需要充分利用这些低功耗模式,并结合硬件设计,才能达到25uA的目标。
    • 唤醒机制: 在Deep-sleep模式下,设备需要可靠的唤醒机制。按键触发就是一个很好的唤醒源,此外,还可以考虑定时器唤醒、外部中断唤醒等。
  4. 电源:1000mAh锂电池供电

    • 续航时间: 1000mAh锂电池的容量适中,需要根据设备的工作模式、工作时长、待机时间等因素来评估续航时间。低功耗设计是延长续航的关键。
    • 电源管理: 需要设计完善的电源管理电路,包括电池充电、电量检测、低电量告警等功能,并考虑电源效率和稳定性。

系统架构设计

基于以上需求分析,我们来设计一个可靠、高效、可扩展的系统架构。考虑到项目的低功耗要求和ESP-NOW协议的特点,我推荐采用分层架构事件驱动架构相结合的设计模式,并引入状态机来管理设备的工作模式。

1. 分层架构 (Layered Architecture)

分层架构将系统划分为多个独立的层,每一层只关注特定的功能,层与层之间通过清晰定义的接口进行交互。这种架构模式具有良好的模块化、可维护性和可扩展性。对于嵌入式系统,典型的分层结构包括:

  • 硬件抽象层 (HAL - Hardware Abstraction Layer):

    • 功能: 封装底层硬件操作,提供统一的API接口给上层使用,屏蔽硬件差异。
    • 模块: GPIO驱动、定时器驱动、SPI驱动、I2C驱动、UART驱动、电源管理驱动等。
    • 优势: 提高代码的可移植性,方便更换硬件平台;简化上层开发,无需关注底层硬件细节。
  • 板级支持包 (BSP - Board Support Package):

    • 功能: 针对特定硬件平台(例如ESP32开发板)的初始化配置和驱动支持。
    • 模块: 系统时钟配置、外设初始化(GPIO、SPI、I2C、UART等)、中断控制器配置、存储器配置、电源管理配置、Wi-Fi/蓝牙初始化、ESP-NOW初始化等。
    • 优势: 将硬件相关的初始化和配置代码集中管理,提高代码的可读性和可维护性;为上层应用提供硬件平台的抽象接口。
  • 通信层 (Communication Layer):

    • 功能: 负责ESP-NOW协议的封装和数据传输。
    • 模块: ESP-NOW初始化、配对管理、数据发送、数据接收、广播/单播/组播管理、数据加密/解密(可选)等。
    • 优势: 将ESP-NOW通信细节封装起来,上层应用只需调用简单的API即可进行数据传输;提高代码的可重用性和可维护性。
  • 应用逻辑层 (Application Logic Layer):

    • 功能: 实现产品的核心业务逻辑,例如按键检测、数据采集、数据处理、状态管理、用户界面(如果需要)等。
    • 模块: 按键检测模块、数据采集模块(如果需要)、数据处理模块、状态机模块、用户界面模块(如果需要)、电源管理策略模块等。
    • 优势: 专注于业务逻辑的实现,与底层硬件和通信细节解耦;提高代码的灵活性和可扩展性。

2. 事件驱动架构 (Event-Driven Architecture)

事件驱动架构的核心思想是系统组件通过事件进行通信,组件之间不直接调用,而是发布和订阅事件。当某个事件发生时,订阅该事件的组件会被触发执行相应的处理逻辑。这种架构模式具有良好的响应性、异步性和低耦合性,非常适合嵌入式系统的实时性和低功耗要求。

  • 事件类型: 按键事件(按下、释放)、ESP-NOW数据接收事件、定时器事件、电量告警事件等。
  • 事件管理器: 负责事件的注册、发布和分发。
  • 事件处理函数: 每个组件注册感兴趣的事件,并提供相应的事件处理函数。当事件发生时,事件管理器调用相应的处理函数。

3. 状态机 (State Machine)

状态机是一种描述对象在不同状态之间转换行为的数学模型,非常适合管理嵌入式系统的工作模式和状态转换。对于本项目,我们可以定义以下状态:

  • STANDBY (待机状态): 设备处于低功耗待机模式,大部分模块处于关闭状态,等待按键触发。
  • INIT (初始化状态): 设备启动后进入初始化状态,进行硬件初始化、ESP-NOW初始化等准备工作。
  • WORKING (工作状态): 设备处于工作模式,进行数据采集、数据传输等操作。
  • TRANS_DONE (传输完成状态): 数据传输完成后进入该状态,准备进入待机或继续工作。

状态之间根据事件进行转换,例如:

  • STANDBY -> INIT: 接收到按键按下事件。
  • INIT -> WORKING: 初始化完成后。
  • WORKING -> TRANS_DONE: 数据传输完成后。
  • TRANS_DONE -> STANDBY: 进入待机模式。
  • WORKING -> STANDBY (超时/错误): 工作超时或发生错误,进入待机模式。

系统架构图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
+-----------------------+
| 应用逻辑层 (Application Logic Layer) |
+-----------------------+
| 状态机模块 (State Machine) |
| 按键检测模块 (Button Detection) |
| 数据处理模块 (Data Processing) |
| 电源管理策略 (Power Management Policy)|
+-----------------------+
| 通信层 (Communication Layer) |
+-----------------------+
| ESP-NOW协议栈 (ESP-NOW Stack) |
+-----------------------+
| 板级支持包 (BSP - Board Support Package) |
+-----------------------+
| ESP32硬件初始化 (ESP32 HW Init) |
| 外设驱动 (Peripheral Drivers) |
| 电源管理驱动 (Power Management Driver)|
| ESP-NOW驱动 (ESP-NOW Driver) |
+-----------------------+
| 硬件抽象层 (HAL - Hardware Abstraction Layer) |
+-----------------------+
| GPIO驱动 (GPIO Driver) |
| 定时器驱动 (Timer Driver) |
| 电源管理硬件接口 (PM HW Interface) |
+-----------------------+
| 硬件 (Hardware) |
+-----------------------+
| ESP32芯片、按键、电池、外围电路 |
+-----------------------+

C代码实现 (基于ESP-IDF框架)

以下是基于ESP-IDF框架的C代码实现,为了清晰明了,代码会尽量模块化,并添加详细的注释。由于篇幅限制,这里只提供核心代码框架和关键模块的实现,完整代码需要根据具体硬件和应用场景进行完善。

(1) HAL层 (hardware_abstraction_layer.h 和 hardware_abstraction_layer.c)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// hardware_abstraction_layer.h
#ifndef HARDWARE_ABSTRACTION_LAYER_H
#define HARDWARE_ABSTRACTION_LAYER_H

#include <stdint.h>
#include <stdbool.h>

// GPIO 驱动接口
typedef enum {
GPIO_PIN_BUTTON = 0, // 按键引脚
GPIO_PIN_LED = 1, // LED指示灯引脚 (可选)
// ... 其他GPIO引脚定义
GPIO_PIN_MAX
} gpio_pin_t;

typedef enum {
GPIO_MODE_INPUT,
GPIO_MODE_OUTPUT
} gpio_mode_t;

typedef enum {
GPIO_LEVEL_LOW,
GPIO_LEVEL_HIGH
} gpio_level_t;

typedef enum {
GPIO_PULLUP_DISABLE,
GPIO_PULLUP_ENABLE,
GPIO_PULLDOWN_DISABLE,
GPIO_PULLDOWN_ENABLE
} gpio_pull_mode_t;

// 初始化GPIO
void hal_gpio_init(gpio_pin_t pin, gpio_mode_t mode, gpio_pull_mode_t pull_mode);
// 设置GPIO输出电平
void hal_gpio_set_level(gpio_pin_t pin, gpio_level_t level);
// 读取GPIO输入电平
gpio_level_t hal_gpio_get_level(gpio_pin_t pin);
// 注册GPIO中断回调函数
void hal_gpio_register_isr(gpio_pin_t pin, void (*isr_handler)(void), void *arg);

// 定时器驱动接口 (简单示例,可根据需求扩展)
typedef enum {
TIMER_ID_1 = 0,
// ... 其他定时器ID
TIMER_ID_MAX
} timer_id_t;

// 初始化定时器
void hal_timer_init(timer_id_t timer_id);
// 设置定时器周期 (微秒)
void hal_timer_set_period_us(timer_id_t timer_id, uint32_t period_us);
// 启动定时器
void hal_timer_start(timer_id_t timer_id);
// 停止定时器
void hal_timer_stop(timer_id_t timer_id);
// 注册定时器中断回调函数
void hal_timer_register_isr(timer_id_t timer_id, void (*isr_handler)(void), void *arg);

// 电源管理硬件接口 (示例,具体实现取决于硬件PMIC)
void hal_pm_enter_deep_sleep(void); // 进入Deep-sleep模式
void hal_pm_wakeup_source_config(void); // 配置唤醒源

#endif // HARDWARE_ABSTRACTION_LAYER_H
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// hardware_abstraction_layer.c
#include "hardware_abstraction_layer.h"
#include "driver/gpio.h"
#include "driver/timer.h"
#include "esp_sleep.h" // ESP32 sleep APIs

// GPIO 驱动实现
void hal_gpio_init(gpio_pin_t pin, gpio_mode_t mode, gpio_pull_mode_t pull_mode) {
gpio_config_t io_conf;
io_conf.intr_type = GPIO_INTR_DISABLE; // 默认禁用中断,需要时再注册
io_conf.pin_bit_mask = (1ULL << pin);
io_conf.mode = (mode == GPIO_MODE_OUTPUT) ? GPIO_MODE_OUTPUT : GPIO_MODE_INPUT;
io_conf.pull_up_en = (pull_mode == GPIO_PULLUP_ENABLE);
io_conf.pull_down_en = (pull_mode == GPIO_PULLDOWN_ENABLE);
gpio_config(&io_conf);
}

void hal_gpio_set_level(gpio_pin_t pin, gpio_level_t level) {
gpio_set_level(pin, (level == GPIO_LEVEL_HIGH));
}

gpio_level_t hal_gpio_get_level(gpio_pin_t pin) {
return gpio_get_level(pin) ? GPIO_LEVEL_HIGH : GPIO_LEVEL_LOW;
}

void hal_gpio_register_isr(gpio_pin_t pin, void (*isr_handler)(void), void *arg) {
gpio_isr_handler_add(pin, isr_handler, arg);
gpio_intr_enable(pin); // 使能中断
}

// 定时器驱动实现 (示例,基于ESP-IDF timer driver)
void hal_timer_init(timer_id_t timer_id) {
timer_config_t config = {
.alarm_en = TIMER_ALARM_DIS,
.counter_en = TIMER_PAUSE,
.counter_dir = TIMER_COUNT_UP,
.auto_reload = TIMER_AUTORELOAD_EN,
.divider = 80 // 80MHz APB_CLK / 80 = 1MHz timer clock
};
timer_init(TIMER_GROUP_0, timer_id, &config);
timer_set_counter_value(TIMER_GROUP_0, timer_id, 0);
}

void hal_timer_set_period_us(timer_id_t timer_id, uint32_t period_us) {
timer_set_alarm_value(TIMER_GROUP_0, timer_id, period_us);
}

void hal_timer_start(timer_id_t timer_id) {
timer_start(TIMER_GROUP_0, timer_id);
}

void hal_timer_stop(timer_id_t timer_id) {
timer_pause(TIMER_GROUP_0, timer_id);
}

void hal_timer_register_isr(timer_id_t timer_id, void (*isr_handler)(void), void *arg) {
timer_isr_callback_add(TIMER_GROUP_0, timer_id, isr_handler, arg, 0);
timer_enable_intr(TIMER_GROUP_0, timer_id);
}

// 电源管理硬件接口实现 (示例,基于ESP32 Deep-sleep)
void hal_pm_enter_deep_sleep(void) {
esp_deep_sleep_start(); // 进入Deep-sleep模式
}

void hal_pm_wakeup_source_config(void) {
// 示例:配置GPIO按键作为唤醒源
gpio_wakeup_enable(GPIO_PIN_BUTTON, GPIO_INTR_LOW_LEVEL); // 低电平触发唤醒
esp_sleep_enable_gpio_wakeup();
}

(2) BSP层 (board_support_package.h 和 board_support_package.c)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// board_support_package.h
#ifndef BOARD_SUPPORT_PACKAGE_H
#define BOARD_SUPPORT_PACKAGE_H

#include "hardware_abstraction_layer.h"
#include "esp_now.h"

// 初始化系统时钟
void bsp_init_system_clock(void);
// 初始化外设 (GPIO, Timer, etc.)
void bsp_init_peripherals(void);
// 初始化电源管理
void bsp_init_power_management(void);
// 初始化ESP-NOW
void bsp_init_espnow(void);

// ESP-NOW 发送数据
esp_err_t bsp_espnow_send_data(const uint8_t *dest_addr, const uint8_t *data, size_t data_len);
// 注册 ESP-NOW 数据接收回调函数
void bsp_espnow_register_recv_cb(esp_now_recv_cb_t cb);
// 获取本机 ESP-NOW MAC 地址
uint8_t *bsp_espnow_get_mac_address(void);

#endif // BOARD_SUPPORT_PACKAGE_H
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
// board_support_package.c
#include "board_support_package.h"
#include "esp_wifi.h"
#include "esp_now.h"
#include "esp_log.h"
#include "string.h"

#define TAG "BSP"

// ESP-NOW 配对密钥 (建议使用更安全的密钥管理方案)
#define ESPNOW_CHANNEL 1
#define ESPNOW_WIFI_IF ESP_IF_WIFI_STA
#define ESPNOW_PMK "YOUR_PMK_KEY"
#define ESPNOW_LMK "YOUR_LMK_KEY"

static esp_now_recv_cb_t g_espnow_recv_cb = NULL;

// ESP-NOW 发送回调函数 (可选,用于处理发送结果)
static void espnow_send_cb(const uint8_t *mac_addr, esp_now_send_status_t status) {
char mac_addr_str[18];
esp_log_mac(mac_addr_str, mac_addr);
ESP_LOGI(TAG, "Send data to %s, status: %s", mac_addr_str, status == ESPNOW_SEND_SUCCESS ? "success" : "fail");
}

// ESP-NOW 接收回调函数 (内部使用,再调用注册的回调)
static void espnow_recv_cb(const esp_now_recv_info_t *recv_info, const uint8_t *data, int len) {
if (g_espnow_recv_cb) {
g_espnow_recv_cb(recv_info, data, len); // 调用注册的回调函数
} else {
ESP_LOGW(TAG, "No ESP-NOW recv callback registered!");
}
}

// 初始化系统时钟 (示例,ESP-IDF默认配置即可)
void bsp_init_system_clock(void) {
// ESP-IDF默认已经配置好系统时钟,这里可以根据需要进行更精细的配置
}

// 初始化外设 (GPIO, Timer, etc.)
void bsp_init_peripherals(void) {
// 初始化按键 GPIO
hal_gpio_init(GPIO_PIN_BUTTON, GPIO_MODE_INPUT, GPIO_PULLUP_ENABLE); // 上拉电阻使能
// 初始化 LED GPIO (可选)
// hal_gpio_init(GPIO_PIN_LED, GPIO_MODE_OUTPUT, GPIO_PULLDOWN_DISABLE);
// 初始化定时器 (如果需要)
// hal_timer_init(TIMER_ID_1);
}

// 初始化电源管理
void bsp_init_power_management(void) {
// 配置唤醒源 (例如GPIO按键)
hal_pm_wakeup_source_config();
}

// 初始化ESP-NOW
void bsp_init_espnow(void) {
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_start());
ESP_ERROR_CHECK(esp_wifi_set_channel(ESPNOW_CHANNEL, WIFI_SECOND_CHAN_NONE));

// 初始化 ESP-NOW
esp_now_init();
esp_now_register_send_cb(espnow_send_cb);
esp_now_register_recv_cb(espnow_recv_cb);
esp_now_set_pmk((uint8_t *)ESPNOW_PMK); // 设置 PMK (可选,提高安全性)

ESP_LOGI(TAG, "ESP-NOW initialized");
}

// ESP-NOW 发送数据
esp_err_t bsp_espnow_send_data(const uint8_t *dest_addr, const uint8_t *data, size_t data_len) {
esp_now_peer_info_t *peer = malloc(sizeof(esp_now_peer_info_t));
if (!peer) {
ESP_LOGE(TAG, "Malloc peer information fail");
return ESP_FAIL;
}
memset(peer, 0, sizeof(esp_now_peer_info_t));
peer->channel = ESPNOW_CHANNEL;
peer->ifidx = ESPNOW_WIFI_IF;
peer->encrypt = false; // 默认不加密,可根据需要开启
memcpy(peer->peer_addr, dest_addr, ESP_NOW_ETH_ALEN);
esp_err_t ret = esp_now_add_peer(peer);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Add peer fail, err: %d", ret);
free(peer);
return ret;
}
free(peer);

ret = esp_now_send(dest_addr, data, data_len);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Send data fail, err: %d", ret);
return ret;
}
return ESP_OK;
}

// 注册 ESP-NOW 数据接收回调函数
void bsp_espnow_register_recv_cb(esp_now_recv_cb_t cb) {
g_espnow_recv_cb = cb;
}

// 获取本机 ESP-NOW MAC 地址
uint8_t *bsp_espnow_get_mac_address(void) {
static uint8_t mac[6];
esp_wifi_get_mac(ESPNOW_WIFI_IF, mac);
return mac;
}

(3) 通信层 (communication_layer.h 和 communication_layer.c)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// communication_layer.h
#ifndef COMMUNICATION_LAYER_H
#define COMMUNICATION_LAYER_H

#include <stdint.h>
#include <stdbool.h>

// 定义数据包结构体 (根据实际数据类型定义)
typedef struct {
uint8_t message_type; // 消息类型
uint32_t timestamp; // 时间戳
uint8_t data[32]; // 数据payload (示例,根据实际数据调整大小)
uint8_t data_len; // 数据长度
} comm_packet_t;

// 初始化通信层
void comm_init(void);
// 发送数据包
esp_err_t comm_send_packet(const uint8_t *dest_mac_addr, const comm_packet_t *packet);
// 注册数据接收回调函数
void comm_register_recv_callback(void (*recv_callback)(const uint8_t *src_mac_addr, const comm_packet_t *packet));

#endif // COMMUNICATION_LAYER_H
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// communication_layer.c
#include "communication_layer.h"
#include "board_support_package.h"
#include "esp_log.h"
#include "string.h"

#define TAG "COMM"

static void (*g_comm_recv_callback)(const uint8_t *src_mac_addr, const comm_packet_t *packet) = NULL;

// ESP-NOW 数据接收回调函数 (适配通信层接口)
static void espnow_data_recv_cb(const esp_now_recv_info_t *recv_info, const uint8_t *data, int len) {
if (g_comm_recv_callback) {
comm_packet_t packet;
if (len > sizeof(comm_packet_t)) {
ESP_LOGE(TAG, "Received data length exceeds packet size!");
return;
}
memcpy(&packet, data, len); // 将接收到的数据拷贝到数据包结构体
g_comm_recv_callback(recv_info->src_addr, &packet); // 调用通信层注册的回调函数
} else {
ESP_LOGW(TAG, "No communication recv callback registered!");
}
}

// 初始化通信层
void comm_init(void) {
bsp_init_espnow(); // 初始化 BSP 层的 ESP-NOW
bsp_espnow_register_recv_cb(espnow_data_recv_cb); // 注册 ESP-NOW 数据接收回调
ESP_LOGI(TAG, "Communication layer initialized");
}

// 发送数据包
esp_err_t comm_send_packet(const uint8_t *dest_mac_addr, const comm_packet_t *packet) {
return bsp_espnow_send_data(dest_mac_addr, (const uint8_t *)packet, sizeof(comm_packet_t));
}

// 注册数据接收回调函数
void comm_register_recv_callback(void (*recv_callback)(const uint8_t *src_mac_addr, const comm_packet_t *packet)) {
g_comm_recv_callback = recv_callback;
}

(4) 应用逻辑层 (application_logic_layer.h 和 application_logic_layer.c)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// application_logic_layer.h
#ifndef APPLICATION_LOGIC_LAYER_H
#define APPLICATION_LOGIC_LAYER_H

#include <stdint.h>
#include <stdbool.h>
#include "communication_layer.h"
#include "hardware_abstraction_layer.h"

// 定义设备状态
typedef enum {
DEVICE_STATE_STANDBY,
DEVICE_STATE_INIT,
DEVICE_STATE_WORKING,
DEVICE_STATE_TRANS_DONE
} device_state_t;

// 初始化应用逻辑层
void app_logic_init(void);
// 处理按键事件
void app_handle_button_event(void);
// 处理数据接收事件
void app_handle_data_recv_event(const uint8_t *src_mac_addr, const comm_packet_t *packet);
// 进入待机状态
void app_enter_standby_state(void);
// 获取当前设备状态
device_state_t app_get_device_state(void);

#endif // APPLICATION_LOGIC_LAYER_H
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
// application_logic_layer.c
#include "application_logic_layer.h"
#include "board_support_package.h"
#include "communication_layer.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h" // For vTaskDelay
#include "freertos/task.h" // For vTaskDelay

#define TAG "APP_LOGIC"

// 目标设备的 MAC 地址 (需要根据实际情况修改)
const uint8_t dest_mac_addr[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; // 广播地址,用于演示

static device_state_t g_device_state = DEVICE_STATE_STANDBY; // 初始状态为待机

// 当前设备状态
device_state_t app_get_device_state(void) {
return g_device_state;
}

// 按键中断处理函数
static void button_isr_handler(void *arg) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
// 这里可以发送一个事件到任务队列,或者直接调用按键事件处理函数
// 为了演示简单,直接调用处理函数,实际应用中建议使用事件队列
app_handle_button_event();
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

// 数据接收回调函数 (适配应用逻辑层接口)
static void communication_data_recv_cb(const uint8_t *src_mac_addr, const comm_packet_t *packet) {
app_handle_data_recv_event(src_mac_addr, packet);
}

// 初始化应用逻辑层
void app_logic_init(void) {
ESP_LOGI(TAG, "Application logic layer initialized");
g_device_state = DEVICE_STATE_INIT; // 进入初始化状态

bsp_init_peripherals(); // 初始化外设
comm_init(); // 初始化通信层

// 注册按键中断
hal_gpio_register_isr(GPIO_PIN_BUTTON, button_isr_handler, NULL);

comm_register_recv_callback(communication_data_recv_cb); // 注册数据接收回调

g_device_state = DEVICE_STATE_STANDBY; // 初始化完成,进入待机状态
ESP_LOGI(TAG, "Device进入待机状态");
}

// 处理按键事件
void app_handle_button_event(void) {
if (app_get_device_state() == DEVICE_STATE_STANDBY) {
ESP_LOGI(TAG, "按键按下,设备开始工作");
g_device_state = DEVICE_STATE_WORKING; // 进入工作状态
// 启动数据传输流程
// ... 在这里编写数据采集、处理和发送的代码 ...

// 示例:发送一个测试数据包
comm_packet_t packet;
packet.message_type = 0x01; // 示例消息类型
packet.timestamp = esp_log_timestamp();
strcpy((char *)packet.data, "Hello ESP-NOW!");
packet.data_len = strlen((char *)packet.data);
comm_send_packet(dest_mac_addr, &packet);

// 模拟数据传输完成
vTaskDelay(pdMS_TO_TICKS(1000)); // 延时 1 秒
g_device_state = DEVICE_STATE_TRANS_DONE; // 进入传输完成状态
ESP_LOGI(TAG, "数据传输完成");
app_enter_standby_state(); // 进入待机状态
} else {
ESP_LOGW(TAG, "按键按下,但设备不在待机状态,忽略");
}
}

// 处理数据接收事件
void app_handle_data_recv_event(const uint8_t *src_mac_addr, const comm_packet_t *packet) {
char mac_str[18];
esp_log_mac(mac_str, src_mac_addr);
ESP_LOGI(TAG, "Received data from %s, type: %d, data: %s", mac_str, packet->message_type, packet->data);
// ... 在这里编写数据处理逻辑 ...
}

// 进入待机状态
void app_enter_standby_state(void) {
if (app_get_device_state() != DEVICE_STATE_STANDBY) {
ESP_LOGI(TAG, "进入待机状态");
g_device_state = DEVICE_STATE_STANDBY;
// 关闭不必要的模块,进入低功耗模式
// ... 在这里编写电源管理代码,例如进入 Deep-sleep ...
hal_pm_enter_deep_sleep(); // 进入 Deep-sleep 模式
}
}

(5) 主函数 (main.c)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// main.c
#include <stdio.h>
#include "application_logic_layer.h"
#include "board_support_package.h"

void app_main(void) {
bsp_init_power_management(); // 初始化电源管理 (配置唤醒源)
app_logic_init(); // 初始化应用逻辑层

// 主循环 (可选,根据实际应用场景决定是否需要主循环)
// 在这个简单示例中,设备主要通过按键中断和 ESP-NOW 事件驱动工作,主循环可以为空
while (1) {
// 可以添加一些周期性任务,例如状态检测、电量检测等
vTaskDelay(pdMS_TO_TICKS(1000)); // 延时 1 秒
}
}

代码结构说明:

  • 分层结构: 代码按照 HAL, BSP, Communication Layer, Application Logic Layer 分层组织,每个层都有对应的头文件和源文件。
  • 模块化设计: 每个层内部又细分为不同的模块,例如 HAL 层的 GPIO 驱动、Timer 驱动,Application Logic Layer 的按键检测模块、状态机模块等。
  • 事件驱动: 按键事件通过中断触发,ESP-NOW 数据接收事件通过回调函数处理,应用逻辑层根据事件进行状态转换和业务逻辑处理。
  • 状态机: 使用 device_state_t 枚举定义设备状态,通过 app_get_device_state() 和状态转换函数管理设备状态。
  • 低功耗考虑:app_enter_standby_state() 函数中预留了进入 Deep-sleep 模式的代码接口,实际应用中需要根据硬件平台和功耗需求进行详细设计。
  • 错误处理和日志: 代码中使用了 ESP_LOGI, ESP_LOGW, ESP_LOGE 等宏进行日志输出,方便调试和错误排查。

关键技术和方法总结:

  1. ESP-NOW协议: 选择ESP-NOW协议作为无线通信方案,利用其快速配对、低延迟、低功耗的特点,满足项目需求。
  2. 分层架构和事件驱动架构: 采用分层架构提高代码的模块化和可维护性,采用事件驱动架构提高系统的响应性和低功耗性能。
  3. 状态机: 使用状态机管理设备的工作模式和状态转换,使系统逻辑清晰、易于理解和维护。
  4. 低功耗设计:
    • Deep-sleep模式: 利用ESP32的Deep-sleep模式实现极低的待机电流。
    • 按键触发唤醒: 使用按键作为唤醒源,避免不必要的功耗。
    • 最小化外设功耗: 在待机状态下,尽可能关闭不必要的外设模块。
    • 优化代码执行效率: 避免长时间的CPU高负荷运行,减少功耗。
  5. 硬件抽象层 (HAL): 通过HAL层屏蔽底层硬件差异,提高代码的可移植性,方便更换硬件平台。
  6. 板级支持包 (BSP): 将硬件相关的初始化和配置代码集中管理,提高代码的可读性和可维护性。
  7. 异步编程和中断处理: 使用中断处理按键事件,使用回调函数处理 ESP-NOW 数据接收事件,提高系统的实时性和响应性。
  8. FreeRTOS (可选): 虽然示例代码中没有显式使用 FreeRTOS 的任务调度,但在实际项目中,如果系统功能更复杂,可以考虑引入 FreeRTOS 操作系统,利用其任务管理、队列、信号量等功能,进一步提高系统的稳定性和可扩展性。

系统开发流程 (简述):

  1. 需求分析: 明确产品的功能需求、性能指标、功耗要求、成本预算等。
  2. 系统设计: 根据需求进行系统架构设计、硬件选型、软件框架设计、通信协议设计、电源管理设计等。
  3. 硬件开发: 原理图设计、PCB layout、样机制作、硬件调试。
  4. 软件开发: 根据系统架构和软件框架,进行代码编写、模块测试、集成测试。
  5. 系统测试和验证: 功能测试、性能测试、功耗测试、可靠性测试、EMC测试等。
  6. 维护和升级: 固件升级、bug修复、功能迭代、性能优化等。

测试验证和维护升级:

  • 测试验证:
    • 功能测试: 验证按键触发、数据传输、待机功能是否正常工作。
    • 性能测试: 测试数据传输速率、响应延迟、通信距离等性能指标。
    • 功耗测试: 使用精密电流表测量待机电流和工作电流,验证是否满足 25uA 的待机电流要求,并评估电池续航时间。
    • 可靠性测试: 进行长时间运行测试、环境温度测试、振动测试等,验证系统的可靠性和稳定性。
  • 维护升级:
    • 固件升级: 预留固件升级接口(例如 OTA 无线升级或 UART 串口升级),方便后续功能迭代和 bug 修复。
    • 远程监控和诊断 (可选): 如果产品需要远程管理,可以考虑增加远程监控和诊断功能,方便问题排查和维护。

总结:

这个基于ESP-NOW协议的低功耗嵌入式产品项目,通过采用分层架构、事件驱动架构和状态机等设计模式,结合ESP32芯片的低功耗特性和ESP-NOW协议的优势,可以构建一个可靠、高效、可扩展的系统平台。代码实现方面,我提供了基于ESP-IDF框架的C代码示例,涵盖了 HAL 层、BSP 层、通信层和应用逻辑层,并详细解释了代码结构和关键技术。

希望这个详细的解答和代码示例能够帮助你理解和实现这个嵌入式项目。 实际的项目开发过程中,还需要根据具体的硬件平台、应用场景和需求进行更详细的设计和实现。

欢迎关注我的其它发布渠道