1. 项目概述:双核架构下的智慧农业系统实战
作为一名在嵌入式领域摸爬滚打多年的开发者,我始终认为真正的技术成长来自于综合性项目实战。今天要分享的这套基于STM32F103和ESP32-S3双核架构的智慧大棚系统,正是我近年来遇到的难得的"全栈式"嵌入式学习案例。它不仅覆盖了从裸机开发到RTOS、GUI设计再到物联网通信的完整技术链条,更通过真实的农业场景演示了多芯片协同工作的工程实践。
这个项目的核心价值在于:用一套硬件平台(STM32F103+ESP32-S3开发板)实现了传统嵌入式开发中需要多个阶段才能掌握的技能融合。STM32F103作为经典的Cortex-M3内核MCU,负责底层设备驱动和实时控制;而搭载Xtensa LX7双核处理器的ESP32-S3则凭借其强大的计算能力和内置WiFi/BLE模块,完美胜任LVGL图形界面渲染和物联网通信任务。两者通过UART串口进行数据交互,构建起一个典型的"控制核心+应用处理器"的现代嵌入式系统架构。
提示:对于刚接触双核开发的工程师,建议先分别掌握STM32和ESP32的独立开发,再学习两者间的通信协议。这种分阶段的学习方式能有效降低认知负荷。
2. 系统架构设计与技术选型
2.1 硬件平台解析
项目采用的华清远见AIoT开发板在设计上颇具巧思:
- STM32F103C8T6:72MHz主频,64KB Flash,20KB RAM,包含USART、SPI、I2C等丰富外设
- ESP32-S3-WROOM-1:240MHz双核处理器,384KB ROM,512KB SRAM,支持2.4GHz WiFi和BLE 5.0
- 扩展资源:2.8寸TFT LCD(320x240)、温湿度传感器、直流风扇模块、用户按键等
这种组合既保留了STM32在实时控制方面的可靠性,又发挥了ESP32在无线通信和图形处理上的优势。特别值得注意的是,开发板通过排针将两个MCU的UART3(STM32)与UART0(ESP32)直接相连,省去了额外的电平转换电路,使得开发者可以专注于应用层协议的实现。
2.2 软件架构分层
系统采用典型的三层架构设计:
-
驱动层(STM32):
- FreeRTOS实时任务调度
- DHT11温湿度传感器驱动
- PWM风扇控制
- 串口通信协议封装
-
应用层(ESP32):
- LVGL图形界面(版本8.3)
- WiFi连接管理
- MQTT客户端实现
- 串口数据解析与转发
-
云端服务:
- 微信小程序作为控制终端
- EMQX开源MQTT broker
- JSON格式数据交换
这种分层设计使得各模块职责清晰,便于团队协作开发和后期维护。例如当需要更换温湿度传感器型号时,只需修改STM32侧的驱动代码,不会影响上层业务逻辑。
3. 核心功能实现细节
3.1 环境数据采集与处理
STM32侧通过单总线协议与DHT11传感器通信,关键实现步骤如下:
c复制// DHT11数据读取函数示例
void DHT11_ReadData(DHT11_Data *data) {
GPIO_InitTypeDef GPIO_InitStruct = {0};
// 配置IO口为输出模式
GPIO_InitStruct.Pin = DHT11_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
HAL_GPIO_Init(DHT11_PORT, &GPIO_InitStruct);
// 发送开始信号(拉低18ms)
HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_RESET);
delay_ms(18);
HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_SET);
// 切换为输入模式等待响应
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
HAL_GPIO_Init(DHT11_PORT, &GPIO_InitStruct);
// 检测DHT11响应信号...
// 接收40位数据...
// 校验数据有效性
if(data->humi_int + data->humi_deci + data->temp_int + data->temp_deci == data->check)
return SUCCESS;
else
return ERROR;
}
数据采集后,通过FreeRTOS的消息队列发送给串口通信任务:
c复制void vSensorTask(void *pvParameters) {
DHT11_Data sensor_data;
while(1) {
if(DHT11_ReadData(&sensor_data) == SUCCESS) {
xQueueSend(xSensorQueue, &sensor_data, portMAX_DELAY);
}
vTaskDelay(pdMS_TO_TICKS(2000)); // 每2秒采集一次
}
}
3.2 双核通信协议设计
STM32与ESP32之间采用自定义的串口通信协议,帧格式如下:
| 字节位置 | 内容 | 说明 |
|---|---|---|
| 0 | 0xFF | 帧头 |
| 1 | 0x03 | 数据长度 |
| 2 | 命令字 | 0x01:温湿度 0x02:风扇控制 |
| 3-5 | 数据 | 根据命令字变化 |
| 6 | 校验和 | 前面所有字节的异或值 |
例如温湿度数据帧示例:
FF 03 01 25 50 00 D3 表示温度25℃,湿度50%
在STM32端使用DMA+空闲中断实现高效串口接收:
c复制void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size) {
if(huart == &huart3) {
// 解析接收到的数据帧
if(rx_buffer[0] == 0xFF && check_xor(rx_buffer, rx_buffer[1]+2)) {
process_esp32_command(rx_buffer);
}
// 重新启动DMA接收
HAL_UARTEx_ReceiveToIdle_DMA(&huart3, rx_buffer, RX_BUFFER_SIZE);
}
}
3.3 LVGL界面开发要点
ESP32侧的LVGL界面开发有几个关键注意事项:
-
内存管理:
- 在
lv_conf.h中合理配置LV_MEM_SIZE(建议≥32KB) - 使用
lv_mem_monitor()监控内存使用情况 - 避免频繁创建/删除对象,尽量复用
- 在
-
温湿度显示面板实现:
c复制lv_obj_t * create_temp_panel(lv_obj_t * parent) {
lv_obj_t * panel = lv_obj_create(parent);
lv_obj_set_size(panel, 150, 100);
lv_obj_t * label = lv_label_create(panel);
lv_label_set_text(label, "温度");
lv_obj_align(label, LV_ALIGN_TOP_LEFT, 10, 10);
temp_value = lv_label_create(panel);
lv_label_set_text(temp_value, "0℃");
lv_obj_align(temp_value, LV_ALIGN_BOTTOM_LEFT, 10, -10);
return panel;
}
- 触摸事件处理:
c复制static void fan_btn_event_cb(lv_event_t * e) {
lv_obj_t * btn = lv_event_get_target(e);
uint8_t fan_speed = (uint8_t)lv_event_get_user_data(e);
// 发送控制命令给STM32
uint8_t cmd[] = {0xFF, 0x01, 0x02, fan_speed, 0x00 ^ 0x02 ^ fan_speed};
uart_write_bytes(UART_NUM_2, cmd, sizeof(cmd));
// 更新UI状态
lv_obj_add_state(btn, LV_STATE_CHECKED);
}
4. 物联网通信实现
4.1 WiFi连接管理
ESP32的WiFi连接采用以下健壮性设计:
- 自动保存最近3个成功连接的AP信息
- 信号强度低于-75dBm时自动切换AP
- 连接超时30秒后进入配网模式
关键实现代码:
cpp复制void wifi_connect(const char *ssid, const char *pass) {
WiFi.begin(ssid, pass);
int retry = 0;
while(WiFi.status() != WL_CONNECTED && retry < 30) {
vTaskDelay(1000 / portTICK_PERIOD_MS);
retry++;
}
if(WiFi.status() == WL_CONNECTED) {
save_wifi_config(ssid, pass); // 保存配置
} else {
start_smart_config(); // 启动智能配网
}
}
4.2 MQTT客户端实现
使用ESP-IDF内置的MQTT客户端,关键配置参数:
c复制esp_mqtt_client_config_t mqtt_cfg = {
.broker = {
.address.uri = "mqtt://broker.emqx.io",
.verification.certificate = emqx_cert_pem
},
.credentials = {
.username = "device_001",
.authentication.password = "123456"
},
.session = {
.keepalive = 60,
.disable_clean_session = true
}
};
消息处理逻辑:
c复制static void mqtt_event_handler(void *args, esp_event_base_t base,
int32_t event_id, void *event_data) {
esp_mqtt_event_handle_t event = event_data;
switch(event->event_id) {
case MQTT_EVENT_CONNECTED:
esp_mqtt_client_subscribe(client, "farm/control", 1);
break;
case MQTT_EVENT_DATA:
if(strncmp(event->topic, "farm/control", event->topic_len) == 0) {
process_control_message(event->data, event->data_len);
}
break;
}
}
5. 项目调试与优化经验
5.1 常见问题排查
-
串口通信不稳定:
- 检查波特率一致性(本项目使用115200bps)
- 确保地线连接良好
- 添加适当的延时(特别是STM32启动阶段)
-
LVGL界面卡顿:
- 使用
lv_timer_handler()在FreeRTOS任务中定期调用 - 降低界面刷新频率(建议30-60FPS)
- 使用
LV_DISP_FLUSH_PARTIAL模式减少刷屏区域
- 使用
-
MQTT频繁断开:
- 增加心跳间隔(keepalive)
- 检查WiFi信号强度
- 启用MQTT的遗嘱消息(LWT)便于诊断
5.2 性能优化技巧
-
双核任务分配原则:
- STM32侧重:传感器采样、实时控制、协议解析
- ESP32侧重:界面渲染、网络通信、数据存储
-
内存优化:
c复制// ESP32侧配置 #define LV_MEM_SIZE (48*1024) #define LV_DISP_DEF_REFR_PERIOD 30 -
电源管理:
- 非活跃期降低STM32主频(72MHz→36MHz)
- 使用ESP32的light sleep模式
- 风扇控制采用PWM软启动
6. 项目扩展方向
基于现有框架,还可以进一步扩展:
- 增加土壤湿度监测:添加ADC接口的土壤湿度传感器
- 光照自动调节:集成BH1750光照传感器+LED补光灯
- 数据持久化存储:利用ESP32的SPIFFS存储历史数据
- AI病虫害识别:通过ESP32-S3的AI加速器运行轻量级模型
我在实际部署中发现,当系统连续运行72小时后,ESP32的内存碎片化会导致LVGL运行效率下降约15%。解决方法是定期(每24小时)重启LVGL任务,或者采用内存池分配策略。这个细节在大多数教程中都不会提及,但对于产品化部署却非常关键。