编程技术分享

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

0%

简介:智能家居环境监控系统**

关注微信公众号,提前获取相关推文

为了具体化我们的讨论,并提供一个实际的应用场景,我们将以一个“智能家居环境监控系统”为例。这个系统将使用ESP32S3R8N8开发板作为核心,实现以下功能:

  1. 温湿度监控:实时采集室内温湿度数据。
  2. 光照强度监控:检测室内光照强度。
  3. PM2.5/PM10 浓度检测:监测空气质量,尤其是PM2.5和PM10的浓度。
  4. 数据无线传输:通过Wi-Fi将采集到的数据传输到云平台或本地服务器。
  5. 本地数据显示:可以通过OLED屏幕或串口将数据本地显示出来。
  6. 远程监控与控制:用户可以通过Web界面或手机App远程查看数据,并可能进行简单的控制(例如,控制一个LED灯的开关状态,作为未来扩展功能的预留)。
  7. OTA 固件升级:支持通过OTA (Over-The-Air) 方式进行固件升级,方便后期维护和功能扩展。

系统开发流程

一个完整的嵌入式系统开发流程通常包括以下几个阶段:

  1. 需求分析
  2. 系统设计
  3. 硬件选型与原理图设计 (已完成,使用ESP32S3R8N8开发板)
  4. 软件设计与编码
  5. 系统集成与测试
  6. 部署与维护
  7. 升级与迭代

我们将重点关注软件设计与编码部分,并结合其他阶段进行说明。

1. 需求分析

对于智能家居环境监控系统,我们已经初步定义了功能需求。现在,我们需要更详细地分析需求,明确系统的性能指标、约束条件和用户期望。

  • 功能性需求

    • 实时性:数据采集和传输应具有一定的实时性,例如,每秒或每几秒更新一次数据。
    • 精度:传感器数据的精度需要满足环境监控的要求。
    • 可靠性:系统应稳定可靠运行,避免数据丢失或错误。
    • 无线通信:稳定可靠的Wi-Fi连接。
    • 本地显示:清晰易懂的数据显示。
    • 远程访问:方便易用的远程监控界面。
    • OTA升级:安全可靠的OTA升级机制。
  • 非功能性需求

    • 功耗:考虑到智能家居应用,功耗需要尽可能低,尤其是在未来可能考虑电池供电的情况下。
    • 成本:使用立创·ESP32S3R8N8开发板,成本相对较低。
    • 可维护性:代码结构清晰,易于维护和升级。
    • 可扩展性:系统架构应易于扩展,方便未来增加新的传感器或功能。
    • 安全性:数据传输和存储需要一定的安全性。
  • 约束条件

    • 硬件平台:立创·ESP32S3R8N8开发板。
    • 开发语言:C语言 (结合ESP-IDF框架)。
    • 开发工具:ESP-IDF 工具链。
    • 时间约束:假设开发周期为X个月。

2. 系统设计

在系统设计阶段,我们需要确定系统的整体架构、模块划分、接口定义以及数据流程。考虑到嵌入式系统的特点和需求,我推荐采用分层架构模块化设计相结合的方式。

2.1 代码设计架构:分层架构与模块化设计

分层架构将系统划分为不同的层次,每一层都有明确的职责,层与层之间通过定义好的接口进行交互。模块化设计则是在每一层内部,将功能分解为独立的模块,提高代码的内聚性和可维护性。

推荐的分层架构如下:

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

    • 封装底层硬件操作,提供统一的硬件接口给上层使用。
    • 包含GPIO驱动、ADC驱动、I2C/SPI驱动、UART驱动、定时器驱动等。
    • 目标是屏蔽硬件差异,方便硬件更换或升级。
  • **板级支持包 (BSP - Board Support Package)**:

    • 基于HAL层,提供针对特定开发板的初始化和配置功能。
    • 包括时钟初始化、外设初始化、中断配置、内存管理等。
    • 为上层提供更高级别的硬件服务。
  • **操作系统层 (OS Layer) / 实时操作系统 (RTOS)**:

    • 如果项目复杂度较高,需要使用RTOS(例如FreeRTOS)。
    • RTOS负责任务调度、内存管理、进程间通信、同步机制等。
    • 提供多任务处理能力,提高系统实时性和效率。
    • 如果项目相对简单,也可以不使用RTOS,采用裸机编程,但对于稍复杂的系统,RTOS能带来显著优势。
  • **中间件层 (Middleware Layer)**:

    • 提供通用的服务和功能组件,例如:
      • 网络协议栈:TCP/IP协议栈、Wi-Fi驱动、MQTT客户端、HTTP客户端等。
      • 数据处理模块:数据解析、数据滤波、数据存储等。
      • 安全模块:加密算法、安全通信协议等。
      • OTA升级模块:固件升级逻辑。
      • 日志管理模块:系统日志记录。
  • **应用层 (Application Layer)**:

    • 实现具体的应用逻辑,例如:
      • 传感器数据采集任务。
      • 数据处理与分析任务。
      • 数据上传任务。
      • 本地显示任务。
      • 远程控制任务。
      • 用户界面 (如果需要)。

模块化设计在每一层都适用。例如,在应用层,我们可以将功能模块化为:

  • 传感器数据采集模块 (Sensor Module):负责从各种传感器读取数据。
  • 数据处理模块 (Data Processing Module):对传感器数据进行处理和分析。
  • 网络通信模块 (Network Module):负责与云平台或服务器进行通信。
  • 本地显示模块 (Display Module):负责在本地显示数据。
  • 系统配置模块 (Configuration Module):负责系统参数的配置和管理。
  • OTA升级模块 (OTAModule):负责固件的OTA升级。

2.2 接口定义

在分层架构和模块化设计中,明确定义各层和各模块之间的接口至关重要。接口应该简洁、清晰、稳定。

  • 层间接口:例如,HAL层提供函数接口给BSP层,BSP层提供服务接口给OS层或中间件层。
  • 模块间接口:例如,传感器数据采集模块提供接口给数据处理模块,数据处理模块提供接口给网络通信模块和本地显示模块。

2.3 数据流程

数据流程描述了数据在系统中如何流动和处理。对于智能家居环境监控系统,主要的数据流程如下:

  1. 传感器数据采集:传感器数据采集模块从各个传感器读取原始数据。
  2. 数据预处理:数据处理模块对原始数据进行滤波、校准、单位转换等预处理。
  3. 数据分析与存储:数据处理模块可能进行简单的数据分析,并将处理后的数据存储在内存或Flash中 (如果需要本地存储历史数据)。
  4. 数据传输:网络通信模块将处理后的数据通过Wi-Fi发送到云平台或本地服务器。
  5. 本地显示:本地显示模块从数据处理模块获取数据,并在OLED屏幕或串口输出显示。
  6. 远程控制指令接收与处理:网络通信模块接收来自云平台或服务器的控制指令,并传递给应用层进行处理。

3. 软件设计与编码 (C代码实现)

接下来,我们将用C代码来实现上述架构,并逐步构建智能家居环境监控系统。由于代码量需求较大,我将分模块、分层次地提供代码示例,并进行详细解释。

3.1 硬件抽象层 (HAL)

HAL层负责直接操作硬件,例如GPIO、ADC、I2C、SPI、UART等。我们将为常用的外设编写HAL驱动。

3.1.1 GPIO 驱动 (hal_gpio.h, hal_gpio.c)

  • hal_gpio.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
#ifndef HAL_GPIO_H
#define HAL_GPIO_H

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

// GPIO 端口定义 (根据ESP32S3R8N8的实际GPIO端口定义)
typedef enum {
GPIO_PIN_0 = 0,
GPIO_PIN_1 = 1,
GPIO_PIN_2 = 2,
// ... 更多GPIO端口定义
GPIO_PIN_MAX
} gpio_pin_t;

// GPIO 方向定义
typedef enum {
GPIO_DIR_INPUT = 0,
GPIO_DIR_OUTPUT = 1
} gpio_dir_t;

// GPIO 电平定义
typedef enum {
GPIO_LEVEL_LOW = 0,
GPIO_LEVEL_HIGH = 1
} gpio_level_t;

// 初始化 GPIO 端口
void hal_gpio_init(gpio_pin_t pin, gpio_dir_t dir);

// 设置 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);

#endif // HAL_GPIO_H
  • hal_gpio.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include "hal_gpio.h"
#include "driver/gpio.h" // ESP-IDF GPIO 驱动头文件

void hal_gpio_init(gpio_pin_t pin, gpio_dir_t dir) {
gpio_config_t io_conf;
io_conf.intr_type = GPIO_INTR_DISABLE; // 禁止中断
io_conf.mode = (dir == GPIO_DIR_OUTPUT) ? GPIO_MODE_OUTPUT : GPIO_MODE_INPUT; // 设置GPIO模式
io_conf.pin_bit_mask = (1ULL << pin); // 设置GPIO引脚掩码
io_conf.pull_down_en = 0; // 禁用下拉
io_conf.pull_up_en = 0; // 禁用上拉
gpio_config(&io_conf);
}

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

gpio_level_t hal_gpio_get_level(gpio_pin_t pin) {
return gpio_get_level(pin);
}

代码解释:

  • hal_gpio.h 定义了GPIO驱动的接口,包括GPIO端口枚举、方向枚举、电平枚举以及初始化、设置输出、读取输入等函数声明。
  • hal_gpio.c 实现了 hal_gpio.h 中声明的接口函数。这里使用了ESP-IDF提供的GPIO驱动库 driver/gpio.hhal_gpio_init() 函数配置GPIO的模式 (输入/输出)、禁用中断和上下拉电阻。hal_gpio_set_level()hal_gpio_get_level() 函数分别用于设置GPIO输出电平和读取GPIO输入电平。

3.1.2 ADC 驱动 (hal_adc.h, hal_adc.c)

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

#include <stdint.h>

// ADC 通道定义 (根据ESP32S3R8N8的实际ADC通道定义)
typedef enum {
ADC_CHANNEL_0 = 0,
ADC_CHANNEL_1 = 1,
// ... 更多ADC通道定义
ADC_CHANNEL_MAX
} adc_channel_t;

// 初始化 ADC
void hal_adc_init(adc_channel_t channel);

// 读取 ADC 原始值
uint32_t hal_adc_read_raw(adc_channel_t channel);

// 将 ADC 原始值转换为电压值 (需要根据实际硬件和参考电压进行校准)
float hal_adc_raw_to_voltage(uint32_t raw_value);

#endif // HAL_ADC_H
  • hal_adc.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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include "hal_adc.h"
#include "driver/adc.h" // ESP-IDF ADC 驱动头文件
#include "esp_adc/adc_cali.h" // ESP-IDF ADC 校准库头文件

#define ADC_ATTEN_DB ADC_ATTEN_DB_11 // ADC衰减系数,11dB衰减,量程为0dB~3.3V
#define ADC_CHANNEL_INPUT ADC1_CHANNEL_0 // 假设使用ADC1通道0

static adc_cali_handle_t adc_cali_handle = NULL; // ADC 校准句柄
static bool do_calibration = true; // 是否进行ADC校准

static bool adc_calibration_init(void) {
adc_cali_scheme_ver_t scheme_ver = ESP_ADC_CALI_SCHEME_VER_LINE_FITTING; // 线性拟合校准方案
esp_err_t ret = ESP_FAIL;
bool calibrated = false;

if (!do_calibration) return false; // 如果不需要校准,直接返回

adc_cali_handle = NULL;
ret = esp_adc_cali_create_scheme_line_fitting(ADC_UNIT_1, // ADC 单元
ADC_ATTEN_DB, // 衰减系数
ADC_BITWIDTH_DEFAULT, // 位宽
&adc_cali_handle); // 校准句柄

if (ret == ESP_OK) {
calibrated = true;
}

if (calibrated) {
ESP_LOGI("ADC", "Calibration success");
} else {
ESP_LOGI("ADC", "Calibration failed!");
}

return calibrated;
}


void hal_adc_init(adc_channel_t channel) {
// 初始化 ADC 单元 (ADC1)
adc_unit_t adc_unit = ADC_UNIT_1;
adc_channel_t adc_chan = ADC_CHANNEL_INPUT; // 默认使用ADC1通道0

adc_oneshot_unit_init_cfg_t init_config1 = {
.unit_id = adc_unit,
.ulp_mode = ADC_ULP_MODE_DISABLE,
};
ESP_ERROR_CHECK(adc_oneshot_unit_init(&init_config1));

// 配置 ADC 通道
adc_oneshot_chan_cfg_t config = {
.atten = ADC_ATTEN_DB, // 衰减系数
.bitwidth = ADC_BITWIDTH_DEFAULT, // 位宽
};
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_unit, adc_chan, &config));

// 初始化 ADC 校准
if (adc_calibration_init()) {
ESP_LOGI("ADC", "ADC calibration initialized");
} else {
ESP_LOGW("ADC", "ADC calibration failed or not supported");
}
}

uint32_t hal_adc_read_raw(adc_channel_t channel) {
uint32_t adc_raw_value = 0;
adc_channel_t adc_chan = ADC_CHANNEL_INPUT; // 默认使用ADC1通道0
ESP_ERROR_CHECK(adc_oneshot_read(ADC_UNIT_1, adc_chan, &adc_raw_value));
return adc_raw_value;
}

float hal_adc_raw_to_voltage(uint32_t raw_value) {
uint32_t voltage = 0;
if (do_calibration) {
ESP_ERROR_CHECK(esp_adc_cali_raw_to_voltage(adc_cali_handle, raw_value, &voltage));
ESP_LOGD("ADC", "cali raw data: %d, voltage: %d mV", raw_value, voltage);
return (float)voltage / 1000.0f; // 转换为电压值 (V)
} else {
// 如果没有校准,需要根据ADC的理论分辨率和参考电压进行估算
// 这里只是一个简单的估算,实际应用中需要根据硬件参数进行调整
return (float)raw_value * 3.3f / 4095.0f; // 假设 12位ADC,参考电压 3.3V
}
}

代码解释:

  • hal_adc.h 定义了ADC驱动的接口,包括ADC通道枚举、初始化函数、读取原始值函数、原始值转换为电压值函数。
  • hal_adc.c 实现了 hal_adc.h 中声明的接口函数。使用了ESP-IDF提供的ADC驱动库 driver/adc.h 和 ADC 校准库 esp_adc/adc_cali.h
  • adc_calibration_init() 函数初始化ADC校准,使用线性拟合校准方案。ADC校准可以提高ADC测量的精度。
  • hal_adc_init() 函数初始化ADC单元和通道,并调用 adc_calibration_init() 初始化校准。
  • hal_adc_read_raw() 函数读取ADC通道的原始值。
  • hal_adc_raw_to_voltage() 函数将ADC原始值转换为电压值。如果启用了校准,则使用校准库进行转换;否则,使用简单的理论公式进行估算。

3.1.3 UART 驱动 (hal_uart.h, hal_uart.c)

  • hal_uart.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
#ifndef HAL_UART_H
#define HAL_UART_H

#include <stdint.h>

// UART 端口定义 (根据ESP32S3R8N8的实际UART端口定义)
typedef enum {
UART_PORT_0 = 0,
UART_PORT_1 = 1,
UART_PORT_2 = 2,
UART_PORT_MAX
} uart_port_t;

// UART 波特率定义
typedef enum {
UART_BAUD_RATE_9600 = 9600,
UART_BAUD_RATE_115200 = 115200,
// ... 更多波特率定义
} uart_baud_rate_t;

// 初始化 UART 端口
void hal_uart_init(uart_port_t port, uart_baud_rate_t baud_rate);

// 发送数据
void hal_uart_send_data(uart_port_t port, const uint8_t *data, uint32_t len);

// 接收数据 (非阻塞方式,需要用户定期轮询)
uint32_t hal_uart_receive_data(uart_port_t port, uint8_t *buffer, uint32_t buffer_size);

#endif // HAL_UART_H
  • hal_uart.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
#include "hal_uart.h"
#include "driver/uart.h" // ESP-IDF UART 驱动头文件
#include "string.h"

void hal_uart_init(uart_port_t port, uart_baud_rate_t baud_rate) {
uart_config_t uart_config = {
.baud_rate = baud_rate,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
.source_clk = UART_SCLK_APB,
};
ESP_ERROR_CHECK(uart_param_config(port, &uart_config));

// 设置 UART 引脚 (根据ESP32S3R8N8的实际UART引脚定义)
gpio_num_t tx_pin = GPIO_NUM_4; // 假设 UART0 TX 引脚为 GPIO4
gpio_num_t rx_pin = GPIO_NUM_5; // 假设 UART0 RX 引脚为 GPIO5
gpio_num_t rts_pin = UART_PIN_NO_CHANGE;
gpio_num_t cts_pin = UART_PIN_NO_CHANGE;
ESP_ERROR_CHECK(uart_set_pin(port, tx_pin, rx_pin, rts_pin, cts_pin));

// 安装 UART 驱动
const int uart_buffer_size = 1024 * 2;
QueueHandle_t uart_queue; // 这里不使用队列,只做简单收发
ESP_ERROR_CHECK(uart_driver_install(port, uart_buffer_size, uart_buffer_size, 0, &uart_queue, 0));
}

void hal_uart_send_data(uart_port_t port, const uint8_t *data, uint32_t len) {
uart_write_bytes(port, (const char *)data, len);
}

uint32_t hal_uart_receive_data(uart_port_t port, uint8_t *buffer, uint32_t buffer_size) {
return uart_read_bytes(port, buffer, buffer_size, 0); // 非阻塞读取,超时时间设置为0
}

代码解释:

  • hal_uart.h 定义了UART驱动的接口,包括UART端口枚举、波特率枚举、初始化函数、发送数据函数、接收数据函数。
  • hal_uart.c 实现了 hal_uart.h 中声明的接口函数。使用了ESP-IDF提供的UART驱动库 driver/uart.h
  • hal_uart_init() 函数配置UART的波特率、数据位、校验位、停止位、流控等参数,设置UART引脚,并安装UART驱动。
  • hal_uart_send_data() 函数发送数据。
  • hal_uart_receive_data() 函数接收数据 (非阻塞方式)。

3.2 板级支持包 (BSP)

BSP层基于HAL层,提供针对立创·ESP32S3R8N8开发板的初始化和配置功能。

  • bsp.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
#ifndef BSP_H
#define BSP_H

#include "hal_gpio.h"
#include "hal_adc.h"
#include "hal_uart.h"

// 开发板上 LED 灯的 GPIO 定义
#define BSP_LED_PIN GPIO_PIN_2

// 开发板上 温湿度传感器 (假设是 DHT11/DHT22) 的 GPIO 定义
#define BSP_DHT_DATA_PIN GPIO_PIN_18

// 开发板上 光照传感器 (假设是 光敏电阻 + ADC) 的 ADC 通道定义
#define BSP_LIGHT_SENSOR_ADC_CHANNEL ADC_CHANNEL_0

// 开发板上 PM2.5/PM10 传感器 (假设是 PMS5003/PMS7003,通过 UART 通信) 的 UART 端口定义
#define BSP_PM_SENSOR_UART_PORT UART_PORT_1

// 初始化 BSP
void bsp_init();

// 控制 LED 灯
void bsp_led_control(gpio_level_t level);

#endif // BSP_H
  • bsp.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
#include "bsp.h"
#include "esp_log.h"

#define TAG "BSP"

void bsp_init() {
ESP_LOGI(TAG, "Initializing BSP...");

// 初始化 GPIO (LED)
hal_gpio_init(BSP_LED_PIN, GPIO_DIR_OUTPUT);
bsp_led_control(GPIO_LEVEL_LOW); // 初始状态熄灭 LED

// 初始化 ADC (光照传感器)
hal_adc_init(BSP_LIGHT_SENSOR_ADC_CHANNEL);

// 初始化 UART (PM2.5/PM10 传感器)
hal_uart_init(BSP_PM_SENSOR_UART_PORT, UART_BAUD_RATE_9600);

ESP_LOGI(TAG, "BSP initialized.");
}

void bsp_led_control(gpio_level_t level) {
hal_gpio_set_level(BSP_LED_PIN, level);
}

代码解释:

  • bsp.h 定义了BSP层的接口,包括开发板上常用外设的GPIO或外设端口定义,以及BSP初始化函数、LED控制函数等。
  • bsp.c 实现了 bsp.h 中声明的接口函数。bsp_init() 函数调用HAL层提供的GPIO、ADC、UART初始化函数,对开发板上的LED、光照传感器、PM2.5/PM10传感器等外设进行初始化。bsp_led_control() 函数通过HAL层提供的GPIO控制函数来控制LED灯的亮灭。

3.3 操作系统层 (OS Layer) - 使用 FreeRTOS

对于智能家居环境监控系统,我们选择使用FreeRTOS来管理任务和资源,提高系统的实时性和效率。

  • FreeRTOS 配置 (freertos_config.h)

ESP-IDF已经集成了FreeRTOS,我们只需要在 freertos_config.h 文件中进行必要的配置,例如任务堆栈大小、优先级、时间片等。通常情况下,ESP-IDF的默认配置已经足够使用,我们只需要根据实际需求进行调整。

  • 任务创建与调度

我们将创建多个FreeRTOS任务来分别负责不同的功能模块,例如:

* **传感器数据采集任务 (task_sensor_data_collect)**:负责周期性地读取各个传感器的数据。
* **数据处理任务 (task_data_process)**:负责对传感器数据进行处理和分析。
* **网络通信任务 (task_network_communication)**:负责与云平台或服务器进行通信。
* **本地显示任务 (task_local_display)**:负责在本地显示数据。

3.4 中间件层 (Middleware Layer)

中间件层提供通用的服务和功能组件。我们将重点实现网络通信模块 (Wi-Fi, MQTT) 和 OTA升级模块。

3.4.1 Wi-Fi 连接模块 (wifi_module.h, wifi_module.c)

  • wifi_module.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
#ifndef WIFI_MODULE_H
#define WIFI_MODULE_H

#include <stdbool.h>

// Wi-Fi 连接状态
typedef enum {
WIFI_STATE_DISCONNECTED = 0,
WIFI_STATE_CONNECTING = 1,
WIFI_STATE_CONNECTED = 2
} wifi_state_t;

// 初始化 Wi-Fi 模块
void wifi_module_init();

// 连接 Wi-Fi AP
void wifi_module_connect_ap(const char *ssid, const char *password);

// 获取 Wi-Fi 连接状态
wifi_state_t wifi_module_get_state();

// 获取 IP 地址
const char* wifi_module_get_ip_address();

#endif // WIFI_MODULE_H
  • wifi_module.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
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
114
115
#include "wifi_module.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "string.h"
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"

#define TAG "WIFI_MODULE"

#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1

static EventGroupHandle_t s_wifi_event_group;
static int s_retry_num = 0;
static wifi_state_t s_wifi_state = WIFI_STATE_DISCONNECTED;
static char s_ip_address[16] = {0}; // 存储 IP 地址

static void event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) {
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
s_wifi_state = WIFI_STATE_CONNECTING;
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
if (s_retry_num < 5) { // 最多重试 5 次
esp_wifi_connect();
s_retry_num++;
ESP_LOGI(TAG, "retry to connect to the AP");
s_wifi_state = WIFI_STATE_CONNECTING;
} else {
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
s_wifi_state = WIFI_STATE_DISCONNECTED;
}
ESP_LOGI(TAG,"connect to the AP fail");
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
strcpy(s_ip_address, ip4addr_ntoa(&event->ip_info.ip)); // 保存 IP 地址
s_retry_num = 0;
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
s_wifi_state = WIFI_STATE_CONNECTED;
}
}

void wifi_module_init() {
s_wifi_event_group = xEventGroupCreate();

ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta();

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

esp_event_handler_instance_t instance_any_id;
esp_event_handler_instance_t instance_got_ip;
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
ESP_EVENT_ANY_ID,
&event_handler,
NULL,
&instance_any_id));
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
IP_EVENT_STA_GOT_IP,
&event_handler,
NULL,
&instance_got_ip));

ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );
ESP_ERROR_CHECK(esp_wifi_start() );

ESP_LOGI(TAG, "wifi_init_sta finished.");

/* 等待连接事件或连接失败事件。事件位在事件处理程序中设置 */
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
pdFALSE,
pdFALSE,
portMAX_DELAY);

/* xEventGroupWaitBits() 返回事件位,我们可以测试哪个位被设置。 */
if (bits & WIFI_CONNECTED_BIT) {
ESP_LOGI(TAG, "connected to ap");
} else if (bits & WIFI_FAIL_BIT) {
ESP_LOGI(TAG, "Failed to connect to ap");
} else {
ESP_LOGE(TAG, "UNEXPECTED EVENT");
}
}

void wifi_module_connect_ap(const char *ssid, const char *password) {
wifi_config_t wifi_config = {
.sta = {
.ssid = "",
.password = "",
.threshold.authmode = WIFI_AUTH_WPA2_PSK,
.pmf_cfg = {
.capable = true,
.required = false
},
},
};
strncpy((char *)wifi_config.sta.ssid, ssid, sizeof(wifi_config.sta.ssid) - 1);
strncpy((char *)wifi_config.sta.password, password, sizeof(wifi_config.sta.password) - 1);

ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) );
ESP_ERROR_CHECK(esp_wifi_connect() );
s_wifi_state = WIFI_STATE_CONNECTING;
}

wifi_state_t wifi_module_get_state() {
return s_wifi_state;
}

const char* wifi_module_get_ip_address() {
return s_ip_address;
}

代码解释:

  • wifi_module.h 定义了Wi-Fi模块的接口,包括Wi-Fi连接状态枚举、初始化函数、连接AP函数、获取连接状态函数、获取IP地址函数。
  • wifi_module.c 实现了 wifi_module.h 中声明的接口函数。使用了ESP-IDF提供的Wi-Fi库 esp_wifi.h 和事件库 esp_event.h
  • event_handler() 函数是Wi-Fi事件处理函数,用于处理Wi-Fi连接事件 (连接成功、连接失败、断开连接) 和IP地址获取事件。使用了FreeRTOS事件组 EventGroupHandle_t 来同步Wi-Fi连接状态。
  • wifi_module_init() 函数初始化Wi-Fi模块,包括创建事件循环、创建默认网络接口、初始化Wi-Fi驱动、注册事件处理函数、设置Wi-Fi模式为STA模式、启动Wi-Fi。
  • wifi_module_connect_ap() 函数连接指定的Wi-Fi AP,需要提供AP的SSID和密码。
  • wifi_module_get_state() 函数获取当前的Wi-Fi连接状态。
  • wifi_module_get_ip_address() 函数获取设备的IP地址。

3.4.2 MQTT 客户端模块 (mqtt_module.h, mqtt_module.c)

  • mqtt_module.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
#ifndef MQTT_MODULE_H
#define MQTT_MODULE_H

#include <stdbool.h>

// MQTT 连接状态
typedef enum {
MQTT_STATE_DISCONNECTED = 0,
MQTT_STATE_CONNECTING = 1,
MQTT_STATE_CONNECTED = 2
} mqtt_state_t;

// 初始化 MQTT 模块
void mqtt_module_init();

// 连接 MQTT Broker
void mqtt_module_connect_broker(const char *uri);

// 发布消息
void mqtt_module_publish_message(const char *topic, const char *payload);

// 订阅主题
void mqtt_module_subscribe_topic(const char *topic);

// 获取 MQTT 连接状态
mqtt_state_t mqtt_module_get_state();

#endif // MQTT_MODULE_H
  • mqtt_module.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
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
#include "mqtt_module.h"
#include "esp_mqtt_client.h"
#include "esp_log.h"
#include "string.h"

#define TAG "MQTT_MODULE"

static esp_mqtt_client_handle_t client = NULL;
static mqtt_state_t s_mqtt_state = MQTT_STATE_DISCONNECTED;

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=%d", 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");
s_mqtt_state = MQTT_STATE_CONNECTED;
// 连接成功后,可以订阅主题
mqtt_module_subscribe_topic("/topic/sensor_control");
break;
case MQTT_EVENT_DISCONNECTED:
ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED");
s_mqtt_state = MQTT_STATE_DISCONNECTED;
break;
case MQTT_EVENT_SUBSCRIBED:
msg_id = event->msg_id;
ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", msg_id);
break;
case MQTT_EVENT_UNSUBSCRIBED:
msg_id = event->msg_id;
ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", msg_id);
break;
case MQTT_EVENT_PUBLISHED:
msg_id = event->msg_id;
ESP_LOGD(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", msg_id);
break;
case MQTT_EVENT_DATA:
ESP_LOGI(TAG, "MQTT_EVENT_DATA");
printf("TOPIC=%.*s\r\n", event->topic_len, event->topic);
printf("DATA=%.*s\r\n", event->data_len, event->data);
// 在这里处理接收到的MQTT消息,例如控制LED灯
if (strncmp(event->topic, "/topic/sensor_control", event->topic_len) == 0) {
if (strncmp(event->data, "LED_ON", event->data_len) == 0) {
bsp_led_control(GPIO_LEVEL_HIGH);
ESP_LOGI(TAG, "LED ON command received");
} else if (strncmp(event->data, "LED_OFF", event->data_len) == 0) {
bsp_led_control(GPIO_LEVEL_LOW);
ESP_LOGI(TAG, "LED OFF command received");
}
}
break;
case MQTT_EVENT_ERROR:
ESP_LOGI(TAG, "MQTT_EVENT_ERROR");
break;
default:
ESP_LOGI(TAG, "Other event id:%d", event->event_id);
break;
}
}

void mqtt_module_init() {
ESP_LOGI(TAG, "Initializing MQTT module...");
// 不需要额外的初始化,连接时会创建客户端实例
ESP_LOGI(TAG, "MQTT module initialized.");
}

void mqtt_module_connect_broker(const char *uri) {
esp_mqtt_client_config_t mqtt_cfg = {
.uri = uri,
};

client = esp_mqtt_client_init(&mqtt_cfg);
esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL);
esp_mqtt_client_start(client);
s_mqtt_state = MQTT_STATE_CONNECTING;
}

void mqtt_module_publish_message(const char *topic, const char *payload) {
if (s_mqtt_state == MQTT_STATE_CONNECTED) {
int msg_id = esp_mqtt_client_publish(client, topic, payload, 0, 1, 0); // QoS1, retain=0
ESP_LOGI(TAG, "sent publish successful, msg_id=%d, topic=%s, payload=%s", msg_id, topic, payload);
} else {
ESP_LOGW(TAG, "MQTT not connected, cannot publish message");
}
}

void mqtt_module_subscribe_topic(const char *topic) {
if (s_mqtt_state == MQTT_STATE_CONNECTED) {
int msg_id = esp_mqtt_client_subscribe(client, topic, 1); // QoS1
ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d, topic=%s", msg_id, topic);
} else {
ESP_LOGW(TAG, "MQTT not connected, cannot subscribe topic");
}
}

mqtt_state_t mqtt_module_get_state() {
return s_mqtt_state;
}

代码解释:

  • mqtt_module.h 定义了MQTT模块的接口,包括MQTT连接状态枚举、初始化函数、连接Broker函数、发布消息函数、订阅主题函数、获取连接状态函数。
  • mqtt_module.c 实现了 mqtt_module.h 中声明的接口函数。使用了ESP-IDF提供的MQTT客户端库 esp_mqtt_client.h
  • mqtt_event_handler() 函数是MQTT事件处理函数,用于处理MQTT连接事件 (连接成功、断开连接)、订阅事件、发布事件、接收数据事件等。
  • mqtt_module_init() 函数初始化MQTT模块 (目前只需要打印日志)。
  • mqtt_module_connect_broker() 函数连接指定的MQTT Broker,需要提供Broker的URI。
  • mqtt_module_publish_message() 函数发布MQTT消息,需要提供主题和消息内容。
  • mqtt_module_subscribe_topic() 函数订阅MQTT主题。
  • mqtt_module_get_state() 函数获取当前的MQTT连接状态。

3.5 应用层 (Application Layer)

应用层实现智能家居环境监控系统的具体应用逻辑。我们将创建多个FreeRTOS任务来分别负责不同的功能模块。

  • 传感器数据采集任务 (task_sensor_data_collect.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
#include "task_sensor_data_collect.h"
#include "bsp.h"
#include "hal_adc.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "dht.h" // 假设使用了DHT驱动库 (需要自行添加或编写)

#define TAG "SENSOR_TASK"

#define DHT_TYPE DHT_TYPE_DHT22 // 假设使用 DHT22 传感器

void task_sensor_data_collect(void *pvParameters) {
float temperature = 0.0f;
float humidity = 0.0f;
uint32_t light_sensor_raw_value = 0;
float light_intensity_voltage = 0.0f;

ESP_LOGI(TAG, "Sensor data collect task started");

while (1) {
// 1. 读取温湿度传感器 (DHT22)
if (dht_read_float_data(DHT_TYPE, BSP_DHT_DATA_PIN, &humidity, &temperature) == ESP_OK) {
ESP_LOGI(TAG, "DHT22: Temperature = %.2f°C, Humidity = %.2f%%", temperature, humidity);
} else {
ESP_LOGE(TAG, "DHT22: Failed to read data");
}

// 2. 读取光照传感器 (光敏电阻 + ADC)
light_sensor_raw_value = hal_adc_read_raw(BSP_LIGHT_SENSOR_ADC_CHANNEL);
light_intensity_voltage = hal_adc_raw_to_voltage(light_sensor_raw_value);
ESP_LOGI(TAG, "Light Sensor: Raw Value = %d, Voltage = %.2fV", light_sensor_raw_value, light_intensity_voltage);

// 3. 读取 PM2.5/PM10 传感器 (PMS5003/PMS7003) - 暂未实现,需要添加 PMS5003/PMS7003 驱动

// 延时一段时间
vTaskDelay(pdMS_TO_TICKS(5000)); // 5秒采集一次数据
}
}
  • 数据处理任务 (task_data_process.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
#include "task_data_process.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "mqtt_module.h"
#include "wifi_module.h"
#include "cJSON.h" // 使用 cJSON 库来生成 JSON 数据 (需要自行添加)

#define TAG "DATA_PROCESS_TASK"

// 数据队列,用于接收传感器数据采集任务发送的数据
QueueHandle_t data_queue;

void task_data_process(void *pvParameters) {
sensor_data_t sensor_data;
char json_payload[256];

ESP_LOGI(TAG, "Data process task started");

data_queue = xQueueCreate(10, sizeof(sensor_data_t)); // 创建数据队列

if (data_queue == NULL) {
ESP_LOGE(TAG, "Failed to create data queue");
vTaskDelete(NULL); // 删除当前任务
return;
}

while (1) {
// 从数据队列接收传感器数据
if (xQueueReceive(data_queue, &sensor_data, portMAX_DELAY) == pdTRUE) {
ESP_LOGI(TAG, "Received sensor data: Temp=%.2f, Hum=%.2f, Light=%.2fV",
sensor_data.temperature, sensor_data.humidity, sensor_data.light_intensity);

// 将传感器数据转换为 JSON 格式
cJSON *root = cJSON_CreateObject();
cJSON_AddNumberToObject(root, "temperature", sensor_data.temperature);
cJSON_AddNumberToObject(root, "humidity", sensor_data.humidity);
cJSON_AddNumberToObject(root, "light_intensity", sensor_data.light_intensity);
char *json_string = cJSON_PrintUnformatted(root); // 使用 PrintUnformatted 减少内存占用
strcpy(json_payload, json_string);
cJSON_Delete(root);
free(json_string); // 释放 cJSON_PrintUnformatted 分配的内存

ESP_LOGI(TAG, "JSON Payload: %s", json_payload);

// 如果 Wi-Fi 和 MQTT 连接都已建立,则发布数据到 MQTT Broker
if (wifi_module_get_state() == WIFI_STATE_CONNECTED && mqtt_module_get_state() == MQTT_STATE_CONNECTED) {
mqtt_module_publish_message("/topic/sensor_data", json_payload);
} else {
ESP_LOGW(TAG, "Wi-Fi or MQTT not connected, cannot publish data");
}
}
}
}
  • 网络通信任务 (task_network_communication.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
#include "task_network_communication.h"
#include "wifi_module.h"
#include "mqtt_module.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

#define TAG "NETWORK_TASK"

#define WIFI_SSID "YOUR_WIFI_SSID" // 修改为你的 Wi-Fi SSID
#define WIFI_PASSWORD "YOUR_WIFI_PASSWORD" // 修改为你的 Wi-Fi 密码
#define MQTT_BROKER_URI "mqtt://your_mqtt_broker_address:1883" // 修改为你的 MQTT Broker 地址

void task_network_communication(void *pvParameters) {
ESP_LOGI(TAG, "Network communication task started");

// 初始化 Wi-Fi 模块
wifi_module_init();

// 连接 Wi-Fi AP
wifi_module_connect_ap(WIFI_SSID, WIFI_PASSWORD);

// 初始化 MQTT 模块
mqtt_module_init();

// 连接 MQTT Broker
mqtt_module_connect_broker(MQTT_BROKER_URI);

while (1) {
// 这里可以添加一些网络状态监测和重连逻辑 (例如,定期检查 Wi-Fi 和 MQTT 连接状态,如果断开则尝试重连)
vTaskDelay(pdMS_TO_TICKS(30000)); // 30秒检查一次网络状态
}
}
  • 主应用程序 (main.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
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "bsp.h"
#include "task_sensor_data_collect.h"
#include "task_data_process.h"
#include "task_network_communication.h"

#define TAG "MAIN"

void app_main(void) {
ESP_LOGI(TAG, "Smart Home Environment Monitoring System");

// 初始化板级支持包 (BSP)
bsp_init();

// 创建传感器数据采集任务
xTaskCreatePinnedToCore(task_sensor_data_collect, "SensorTask", 4096, NULL, 2, NULL, APP_CPU_NUM);

// 创建数据处理任务
xTaskCreatePinnedToCore(task_data_process, "DataProcessTask", 4096, NULL, 3, NULL, APP_CPU_NUM);

// 创建网络通信任务
xTaskCreatePinnedToCore(task_network_communication, "NetworkTask", 4096, NULL, 4, NULL, APP_CPU_NUM);

ESP_LOGI(TAG, "Tasks created.");
}

代码解释:

  • **任务代码 (task_sensor_data_collect.c, task_data_process.c, task_network_communication.c)**:分别实现了传感器数据采集、数据处理、网络通信的功能。使用了FreeRTOS任务和队列进行任务管理和数据传递。
  • **主应用程序 (main.c)**:
    • 初始化BSP。
    • 创建并启动传感器数据采集任务、数据处理任务、网络通信任务。

4. 系统集成与测试

在完成各个模块的编码后,需要进行系统集成和测试,验证系统的功能和性能是否满足需求。

  • 单元测试:针对每个模块进行单元测试,例如HAL驱动、BSP初始化、Wi-Fi模块、MQTT模块等。可以使用ESP-IDF提供的单元测试框架。
  • 集成测试:测试模块之间的接口和协作,例如传感器数据采集模块与数据处理模块之间的交互,数据处理模块与网络通信模块之间的交互。
  • 系统测试:进行全面的系统功能测试和性能测试,例如长时间运行测试、压力测试、边界条件测试等,验证系统的稳定性、可靠性和实时性。
  • 用户验收测试:如果面向用户,需要进行用户验收测试,收集用户反馈,进一步改进系统。

5. 部署与维护

系统测试通过后,可以进行部署。对于智能家居环境监控系统,部署通常是将固件烧录到ESP32S3R8N8开发板,并将其安装在需要监控的环境中。

维护阶段主要包括:

  • 故障排除:处理系统运行过程中出现的故障。
  • 性能优化:根据实际运行情况,进行性能优化。
  • 安全更新:及时更新安全补丁,提高系统安全性。
  • 用户支持:提供用户技术支持。

6. 升级与迭代

为了保持系统的活力和竞争力,需要进行升级与迭代。

  • 功能升级:根据用户需求或市场变化,增加新的功能,例如支持更多的传感器类型、增加本地数据存储、扩展远程控制功能等。
  • **固件升级 (OTA)**:通过OTA固件升级功能,方便远程更新固件,修复Bug、添加新功能。
  • 版本迭代:定期发布新版本,持续改进系统。

总结

这个智能家居环境监控系统项目展示了一个完整的嵌入式系统开发流程,从需求分析到系统实现,再到测试验证和维护升级。我们采用了分层架构与模块化设计相结合的代码架构,提高了代码的可维护性、可扩展性和可靠性。通过具体的C代码示例,展示了HAL层、BSP层、中间件层、应用层的实现,以及FreeRTOS在任务管理中的应用。

这只是一个基础框架,实际项目中还需要根据具体需求进行更详细的设计和开发。例如,PM2.5/PM10 传感器驱动、本地数据显示 (OLED/串口)、更完善的OTA升级模块、云平台对接、用户界面等功能都需要进一步实现。

希望这个详细的说明和代码示例能够帮助你理解嵌入式系统开发,并构建出可靠、高效、可扩展的嵌入式系统平台。 请记住,实践是检验真理的唯一标准,只有通过不断的实践和学习,才能成为一名优秀的嵌入式软件开发工程师。

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