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

为了具体化我们的讨论,并提供一个实际的应用场景,我们将以一个“智能家居环境监控系统”为例。这个系统将使用ESP32S3R8N8开发板作为核心,实现以下功能:
- 温湿度监控:实时采集室内温湿度数据。
- 光照强度监控:检测室内光照强度。
- PM2.5/PM10 浓度检测:监测空气质量,尤其是PM2.5和PM10的浓度。
- 数据无线传输:通过Wi-Fi将采集到的数据传输到云平台或本地服务器。
- 本地数据显示:可以通过OLED屏幕或串口将数据本地显示出来。
- 远程监控与控制:用户可以通过Web界面或手机App远程查看数据,并可能进行简单的控制(例如,控制一个LED灯的开关状态,作为未来扩展功能的预留)。
- OTA 固件升级:支持通过OTA (Over-The-Air) 方式进行固件升级,方便后期维护和功能扩展。
系统开发流程
一个完整的嵌入式系统开发流程通常包括以下几个阶段:
- 需求分析
- 系统设计
- 硬件选型与原理图设计 (已完成,使用ESP32S3R8N8开发板)
- 软件设计与编码
- 系统集成与测试
- 部署与维护
- 升级与迭代
我们将重点关注软件设计与编码部分,并结合其他阶段进行说明。
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 数据流程
数据流程描述了数据在系统中如何流动和处理。对于智能家居环境监控系统,主要的数据流程如下:
- 传感器数据采集:传感器数据采集模块从各个传感器读取原始数据。
- 数据预处理:数据处理模块对原始数据进行滤波、校准、单位转换等预处理。
- 数据分析与存储:数据处理模块可能进行简单的数据分析,并将处理后的数据存储在内存或Flash中 (如果需要本地存储历史数据)。
- 数据传输:网络通信模块将处理后的数据通过Wi-Fi发送到云平台或本地服务器。
- 本地显示:本地显示模块从数据处理模块获取数据,并在OLED屏幕或串口输出显示。
- 远程控制指令接收与处理:网络通信模块接收来自云平台或服务器的控制指令,并传递给应用层进行处理。
3. 软件设计与编码 (C代码实现)
接下来,我们将用C代码来实现上述架构,并逐步构建智能家居环境监控系统。由于代码量需求较大,我将分模块、分层次地提供代码示例,并进行详细解释。
3.1 硬件抽象层 (HAL)
HAL层负责直接操作硬件,例如GPIO、ADC、I2C、SPI、UART等。我们将为常用的外设编写HAL驱动。
3.1.1 GPIO 驱动 (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 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>
typedef enum { GPIO_PIN_0 = 0, GPIO_PIN_1 = 1, GPIO_PIN_2 = 2, GPIO_PIN_MAX } gpio_pin_t;
typedef enum { GPIO_DIR_INPUT = 0, GPIO_DIR_OUTPUT = 1 } gpio_dir_t;
typedef enum { GPIO_LEVEL_LOW = 0, GPIO_LEVEL_HIGH = 1 } gpio_level_t;
void hal_gpio_init(gpio_pin_t pin, gpio_dir_t dir);
void hal_gpio_set_level(gpio_pin_t pin, gpio_level_t level);
gpio_level_t hal_gpio_get_level(gpio_pin_t pin);
#endif
|
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"
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; io_conf.pin_bit_mask = (1ULL << pin); 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.h
。hal_gpio_init()
函数配置GPIO的模式 (输入/输出)、禁用中断和上下拉电阻。hal_gpio_set_level()
和 hal_gpio_get_level()
函数分别用于设置GPIO输出电平和读取GPIO输入电平。
3.1.2 ADC 驱动 (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
| #ifndef HAL_ADC_H #define HAL_ADC_H
#include <stdint.h>
typedef enum { ADC_CHANNEL_0 = 0, ADC_CHANNEL_1 = 1, ADC_CHANNEL_MAX } adc_channel_t;
void hal_adc_init(adc_channel_t channel);
uint32_t hal_adc_read_raw(adc_channel_t channel);
float hal_adc_raw_to_voltage(uint32_t raw_value);
#endif
|
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" #include "esp_adc/adc_cali.h"
#define ADC_ATTEN_DB ADC_ATTEN_DB_11 #define ADC_CHANNEL_INPUT ADC1_CHANNEL_0
static adc_cali_handle_t adc_cali_handle = NULL; static bool do_calibration = true;
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_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_unit_t adc_unit = ADC_UNIT_1; adc_channel_t adc_chan = ADC_CHANNEL_INPUT;
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_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));
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; 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; } else { return (float)raw_value * 3.3f / 4095.0f; } }
|
代码解释:
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)
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>
typedef enum { UART_PORT_0 = 0, UART_PORT_1 = 1, UART_PORT_2 = 2, UART_PORT_MAX } uart_port_t;
typedef enum { UART_BAUD_RATE_9600 = 9600, UART_BAUD_RATE_115200 = 115200, } uart_baud_rate_t;
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
|
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" #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));
gpio_num_t tx_pin = GPIO_NUM_4; gpio_num_t rx_pin = GPIO_NUM_5; 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));
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); }
|
代码解释:
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开发板的初始化和配置功能。
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"
#define BSP_LED_PIN GPIO_PIN_2
#define BSP_DHT_DATA_PIN GPIO_PIN_18
#define BSP_LIGHT_SENSOR_ADC_CHANNEL ADC_CHANNEL_0
#define BSP_PM_SENSOR_UART_PORT UART_PORT_1
void bsp_init();
void bsp_led_control(gpio_level_t level);
#endif
|
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...");
hal_gpio_init(BSP_LED_PIN, GPIO_DIR_OUTPUT); bsp_led_control(GPIO_LEVEL_LOW);
hal_adc_init(BSP_LIGHT_SENSOR_ADC_CHANNEL);
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)
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>
typedef enum { WIFI_STATE_DISCONNECTED = 0, WIFI_STATE_CONNECTING = 1, WIFI_STATE_CONNECTED = 2 } wifi_state_t;
void wifi_module_init();
void wifi_module_connect_ap(const char *ssid, const char *password);
wifi_state_t wifi_module_get_state();
const char* wifi_module_get_ip_address();
#endif
|
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};
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) { 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)); 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);
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)
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>
typedef enum { MQTT_STATE_DISCONNECTED = 0, MQTT_STATE_CONNECTING = 1, MQTT_STATE_CONNECTED = 2 } mqtt_state_t;
void mqtt_module_init();
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_state_t mqtt_module_get_state();
#endif
|
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); 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); 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); 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"
#define TAG "SENSOR_TASK"
#define DHT_TYPE DHT_TYPE_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) { 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"); }
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);
vTaskDelay(pdMS_TO_TICKS(5000)); } }
|
- 数据处理任务 (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"
#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);
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); strcpy(json_payload, json_string); cJSON_Delete(root); free(json_string);
ESP_LOGI(TAG, "JSON Payload: %s", json_payload);
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" #define WIFI_PASSWORD "YOUR_WIFI_PASSWORD" #define MQTT_BROKER_URI "mqtt://your_mqtt_broker_address:1883"
void task_network_communication(void *pvParameters) { ESP_LOGI(TAG, "Network communication task started");
wifi_module_init();
wifi_module_connect_ap(WIFI_SSID, WIFI_PASSWORD);
mqtt_module_init();
mqtt_module_connect_broker(MQTT_BROKER_URI);
while (1) { vTaskDelay(pdMS_TO_TICKS(30000)); } }
|
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_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升级模块、云平台对接、用户界面等功能都需要进一步实现。
希望这个详细的说明和代码示例能够帮助你理解嵌入式系统开发,并构建出可靠、高效、可扩展的嵌入式系统平台。 请记住,实践是检验真理的唯一标准,只有通过不断的实践和学习,才能成为一名优秀的嵌入式软件开发工程师。