基于ESP32-S3的多线程工业级温度监测系统
1 项目背景
在工业过程控制、电力监测等场景中,多通道高精度温度采集是实现设备状态监测的关键技术。本设计基于ESP32-S3双核处理器,构建支持8通道K型热电偶(MAX31856)的智能采集系统,通过多线程架构实现数据采集、通信处理、看门狗监控的并行执行,最终达成**±0.5℃精度**、**5Hz采样率**的准工业级性能指标。本文将详细介绍该项目的设计思路、实现过程以及关键技术。
实物效果
软件效果
1.1 系统功能构思
在工业生产和环境监测中,温度是一个关键的参数。为了实现对多个温度点的实时监测,我们设计了一款基于ESP32-S3的8通道高精度温度采集器。该设备采用SPI接口连接多个MAX31856热电偶放大器芯片,能够同时采集8个不同位置的温度数据,并通过Modbus RTU协议将数据实时传输至Modbus Poll或SerialStudio上位机甚至是云端。
1.2 系统框图
本系统设计围绕温度采集需求展开,采用模块化设计流程。首先通过**需求设计**明确精度、采样率等核心指标,指导**硬件设计**选型高精度热电偶传感器与多通道采集电路。**系统设计**阶段规划双核处理器架构,划分硬件驱动层与数据处理层。**软件设计**基于FreeRTOS实现多线程调度,开发SPI通信协议与Modbus数据映射模块。**联合调试**阶段通过示波器验证信号完整性,完成软硬件协同测试。若**测试功能一致性**未达标(如通道干扰超限),则进入**协调系统设计**环节,迭代优化滤波算法或调整PCB布局。通过闭环验证后,最终进行**总结与归纳**,输出稳定性报告与优化指南。整个流程采用V模型开发,形成"设计-验证-迭代"的闭环管理体系,确保8通道数据采集精度达±0.5℃,系统响应时间小于15ms。
2 硬件设计
硬件拓扑
- **传感层**:双SPI总线驱动8路MAX31856(SPI2/SPI3各4路)
- **控制层**:ESP32-S3双核处理器(240MHz主频 + 16MB PSRAM)
- **通信层**:RS485 Modbus RTU + USB-C调试接口
- **电源层**:支持12-24V宽压输入,配备过压/反接保护 (后续加上)
2.2 主要组件
1. **主控芯片**:ESP32-S3,具有强大的处理能力和丰富的外设接口。
2. **温度传感器**:8个MAX31856热电偶放大器芯片,分别通过SPI2和SPI3接口连接。
3. **通信接口**:UART1用于Modbus RTU通信,USB接口用于调试和配置。
4. **其他外设**:LED指示灯、按键等用于状态指示和用户交互。
2.3 电路连接
- **SPI接口**:ESP32-S3的SPI2和SPI3接口分别连接4个MAX31856芯片,每个芯片通过独立的片选(CS)引脚进行控制。
- **UART接口**:UART1的TX和RX引脚分别连接至Modbus主站的RX和TX引脚,波特率设置为9600。
- **电源管理**:采用5V电源供电,通过稳压芯片为ESP32-S3和MAX31856提供稳定的3.3V和5V电源。
硬件实物图:
2.4 主要硬件介绍
ESP32-S3双核处理器:立创参考资料
硬件接口与SPI驱动:立创·ESP32S3R8N8】开源硬件
MAX31856热电偶采集
MAX31856 是一款高精度热电偶信号调理芯片,专为工业级温度测量设计。其核心功能是通过 SPI 接口 将热电偶的微弱模拟信号转换为数字温度值,支持 K/J/N/T/S/E/B/R 等多种热电偶类型,典型测量精度达 ±0.5℃(K型)。采用Platformio的adafruit/Adafruit MAX31856 library@^1.2.7库驱动
3 软件设计
3.1 多线程任务管理
多线程任务划分
| 任务名称 | 核心 | 优先级 | 执行周期 | 功能描述 |
|------------------- |------ |-------- |---------- |------------------------------ |
| collectSPI3Data | Core1 | 1 | 1ms | SPI3总线4通道温度采集 |
| collectSPI2Data | Core0 | 1 | 1ms | SPI2总线4通道温度采集 |
| modbus_rtu_loop | Core1 | 3 | 50ms | Modbus寄存器映射与协议 |
| sendDataToSerial | Core1 | 1 | 200ms | 串口数据格式化输出 |
| watchdogResetTask | Core0 | 1 | 1s | 硬件看门狗喂狗 |
3.1.1 双核负载均衡策略
// 实时数据采集(占用率约60%)
xTaskCreatePinnedToCore(collectSPI3Data, "SPI3", 4096, NULL, 1, NULL, 1);
xTaskCreatePinnedToCore(collectSPI2Data, "SPI2", 4096, NULL, 1, NULL, 0);
// 通信与监控(占用率约30%)
xTaskCreatePinnedToCore(sendDataToSerial, "UART", 2048, NULL, 1, NULL, 1);
xTaskCreatePinnedToCore(watchdogResetTask, "WDT", 2048, NULL, 1, NULL, 0);
- **隔离原则**:将时序敏感的SPI操作与通信任务物理隔离至不同核心
- **优先级倒置预防**:Modbus任务设置更高优先级(Level 3)
为了实现高效的温度数据采集与处理,我们采用了FreeRTOS进行多线程任务管理。主要任务包括:
1. **SPI数据采集任务**:
- `collectSPI3Data`:负责采集SPI3接口连接的4个温度传感器的数据。
- `collectSPI2Data`:负责采集SPI2接口连接的4个温度传感器的数据。
- 这两个任务分别运行在不同的核心上,确保数据采集的实时性和并行性。
void collectSPI3Data(void *parameter) {
while (true) {
static float tempSpi3[4];
tempSpi3[0] = thermocouple_spi3_0.readThermocoupleTemperature();
tempSpi3[1] = thermocouple_spi3_1.readThermocoupleTemperature();
tempSpi3[2] = thermocouple_spi3_2.readThermocoupleTemperature();
tempSpi3[3] = thermocouple_spi3_3.readThermocoupleTemperature();
for (int i = 0; i < 4; i++) {
temperatures_spi3[i] = tempSpi3[i];
}
vTaskDelay(pdMS_TO_TICKS(50)); // 每50ms采集一次
}
}
2. **数据发送任务**:
- `sendDataToSerial`:负责将采集到的温度数据通过串口发送出去。该任务根据配置的发送间隔进行数据发送,并支持通过AT命令动态调整发送间隔和采样精度。
void sendDataToSerial(void *parameter) {
while (true) {
if (ttlsendsignal) {
String output = ""; // 起始标志位
// 拼接SPI3温度数据
for (int i = 0; i < 4; i++) {
output += String(temperatures_spi3[i], (unsigned int)samplingPrecision);
if (i < 3) {
output += ",";
}
}
output += ","; // 分隔符
// 拼接SPI2温度数据
for (int i = 0; i < 4; i++) {
output += String(temperatures_spi2[i], (unsigned int)samplingPrecision);
if (i < 3) {
output += ",";
}
}
Serial.println(output); // 一次性发送数据
}
vTaskDelay(pdMS_TO_TICKS(sendInterval - 1)); // 根据配置的间隔发送数据
}
}
3. **看门狗复位任务**:
- `watchdogResetTask`:负责定期复位看门狗,防止系统因意外情况而重启。
void watchdogResetTask(void* parameter) {
while (true) {
esp_task_wdt_reset(); // 复位看门狗
vTaskDelay(pdMS_TO_TICKS(1000)); // 每秒复位一次
}
}
3.1.2 数据共享与同步
- **无锁设计**:通过`volatile`关键字确保双核间数据可见性
- **批量更新**:50ms周期统一刷新Modbus寄存器,避免寄存器乒乓效应
volatile float temperatures_spi3[4]; // Core1写入
volatile float temperatures_spi2[4]; // Core1写入
void modbus_rtu_loop() {
// Core1读取volatile变量
// 循环处理Modbus数据
mb.task();
for (uint8_t i = 0; i < sensor_num / 2; i++)
{
int16_t scaledValue3 = temperatures_spi3[i] * 10; // 放大10倍保留1位小数
mb.Hreg(i, scaledValue3);
int16_t scaledValue2 = temperatures_spi2[i] * 10; // 放大10倍保留1位小数
mb.Hreg(i+4, scaledValue2);
}
vTaskDelay(pdMS_TO_TICKS(50)); // 每 50ms 采集一次
}
3.1.3 实时性保障机制
- **采集任务**:1ms周期严格时序控制,采用`vTaskDelayUntil`精准调度
- **看门狗分级**:硬件看门狗(10s)防护
- **中断优化**:禁用SPI传输期间的FreeRTOS任务切换
---
3.2 数据交互
3.2.1 Modbus RTU通信
为了实现与上位机或云端的数据交互,我们采用了Modbus RTU协议。通过ModbusSerial库,我们实现了以下功能:
1. **寄存器配置**:
- 将采集到的温度数据存储在Modbus保持寄存器中,地址范围为40001-40008。
void modbus_rtu_init() {
// 初始化Modbus串口
modbusSerial.begin(MODBUS_BAUD, SERIAL_8N1, UART_RX_PIN, UART_TX_PIN);
mb.config(MODBUS_BAUD);
// 注册Modbus保持寄存器
for (uint8_t i = 0; i < sensor_num; i++) {
mb.addHreg(i); // 寄存器地址0-8对应40001-40008
}
}
2. **数据更新**:
- 在主循环中,定期将采集到的温度数据更新到Modbus寄存器中。
void modbus_rtu_loop() {
mb.task();
for (uint8_t i = 0; i < sensor_num / 2; i++) {
int16_t scaledValue3 = temperatures_spi3[i] * 10; // 放大10倍保留1位小数
mb.Hreg(i, scaledValue3);
int16_t scaledValue2 = temperatures_spi2[i] * 10; // 放大10倍保留1位小数
mb.Hreg(i+4, scaledValue2);
}
vTaskDelay(pdMS_TO_TICKS(50)); // 每50ms采集一次
}
3.2.2 AT命令配置
为了方便用户配置传感器参数,我们实现了AT命令集。通过串口接收AT命令,可以动态调整发送间隔、采样精度和滤波方法。
编号 | AT指令 | 数值 | 属性 |
---|---|---|---|
1 | AT= | vor | 启动串口输出功能 |
2 | AT+SEND_INTERVAL= | 200~1000 | 200ms发送间隔 |
3 | AT+SAMPLING_PRECISION= | 1、2、3 | 保留1位小数 |
4 | AT+FILTER= | null | MAX31856自带滤波 |
5 | AT= | xxx | 关闭串口输出 |
void configureSensor(const String& command) {
if (command.startsWith("AT=vor")){
ttlsendsignal = true; // 设置标志位为true,开始串口打印数据
}
else if (command.startsWith("AT+SEND_INTERVAL=")) {
String value = command.substring(17);
value.trim(); // 提取并去除多余字符
sendInterval = value.toInt(); // 设置发送间隔
Serial.println("OK");
} else if (command.startsWith("AT+SAMPLING_PRECISION=")) {
// 设置采样精度(1 表示一位小数,3 表示三位小数)
String value = command.substring(22);
value.trim(); // 提取并去除多余字符
int precision = value.toInt();
if (precision == 1 || precision == 2 || precision == 3) {
samplingPrecision = precision;
Serial.println("OK");
} else {
Serial.println("ERROR: Invalid precision value. Use 1 or 3.");
}
} else if (command.startsWith("AT+FILTER=")) {
// 示例:设置滤波方法(假设支持简单平均滤波)
String value = command.substring(11);
value.trim(); // 提取并去除多余字符
int filterType = value.toInt();
if (filterType == 1) {
// 实现简单的平均滤波逻辑
Serial.println("Filter set to Simple Average");
} else {
Serial.println("Filter set to None");
}
} else {
ttlsendsignal = false;
Serial.println("ERROR: Unknown command");
}
}
4 功能演示
主要展示Modbus RTU通讯与串口数据解包以及AT指令功能!😘😘😘
实物效果,需要下载附件中的platfromio的项目文件,编译并下载。当一切准备好后,
连接ESP32S3 USB数据线和485转USB线后,就可以开始下面的实物实验啦!🤣🤣🤣
4.1 Modbus RTU数据映射表
| 寄存器地址 | 数据类型 | 缩放系数 | 描述 |
|------------ |------------ |---------- |-------------------- |
| 40001 | INT16 | ×10 | SPI3通道1温度值 |
| ... | ... | ... | ... |
| 40004 | INT16 | ×10 | SPI3通道4温度值 |
| 40005 | INT16 | ×10 | SPI2通道1温度值 |
| ... | ... | ... | ... |
| 40008 | INT16 | ×10 | SPI2通道4温度值 |
这里采用Modbus Poll10进行问询通讯,具体配置文件参考附件Modbus Poll配置文件🙌🙌🙌
这里采用9600-8-N-1通讯,超时选择2000ms
这里我配置从机地址是10,然后读8个数据位,
采用03保持寄存器功能码进行问询,下面是读取日志;
Tx:000135-23:18:13.449-0A 03 00 00 00 08 45 77
Rx:000136-23:18:13.527-0A 03 10 00 E4 00 E5 00 E4 00 E3 00 F1 00 FC 00 EF 00 F2 4B 3F
Tx:000137-23:18:13.650-0A 03 00 00 00 08 45 77
Rx:000138-23:18:13.759-0A 03 10 00 E4 00 E3 00 E4 00 E3 00 F0 00 FD 00 EF 00 F2 6F F9
Tx:000139-23:18:13.854-0A 03 00 00 00 08 45 77
Rx:000140-23:18:13.930-0A 03 10 00 E4 00 E3 00 E4 00 E3 00 F0 00 FD 00 EF 00 F2 6F F9
Tx:000141-23:18:14.053-0A 03 00 00 00 08 45 77
Rx:000142-23:18:14.115-0A 03 10 00 E4 00 E3 00 E4 00 E3 00 F0 00 FD 00 EF 00 F2 6F F9
Tx:000143-23:18:14.255-0A 03 00 00 00 08 45 77
Rx:000144-23:18:14.348-0A 03 10 00 E4 00 E3 00 E4 00 E3 00 F0 00 FD 00 EF 00 F2 6F F9
Tx:000145-23:18:14.459-0A 03 00 00 00 08 45 77
Rx:000146-23:18:14.518-0A 03 10 00 E4 00 E4 00 E4 00 E3 00 F0 00 FD 00 F0 00 F3 94 B8
Tx:000147-23:18:14.660-0A 03 00 00 00 08 45 77
Rx:000148-23:18:14.750-0A 03 10 00 E4 00 E4 00 E4 00 E3 00 F0 00 FD 00 F0 00 F3 94 B8
Tx:000149-23:18:14.859-0A 03 00 00 00 08 45 77
Rx:000150-23:18:14.921-0A 03 10 00 E4 00 E4 00 E4 00 E3 00 F0 00 FD 00 F0 00 F3 94 B8
Tx:000151-23:18:15.061-0A 03 00 00 00 08 45 77
Rx:000152-23:18:15.155-0A 03 10 00 E5 00 E4 00 E5 00 E3 00 F0 00 FC 00 F0 00 F3 6C 84
Tx:000153-23:18:15.264-0A 03 00 00 00 08 45 77
Rx:000154-23:18:15.325-0A 03 10 00 E5 00 E4 00 E5 00 E3 00 F0 00 FC 00 F0 00 F3 6C 84
Tx:000155-23:18:15.466-0A 03 00 00 00 08 45 77
Rx:000156-23:18:15.558-0A 03 10 00 E5 00 E4 00 E5 00 E3 00 F0 00 FC 00 F0 00 F3 6C 84
下面是我分别4次触摸不同热电偶温度数据变化,这个是乘10倍的,响应速度挺快,而且测量准确,查看图标数据如下:
4.2 串口AT指令集
这里串口默认启动没有开启打印效果,因此需要“AT=vor”启动打印效果,后续就可以进行设置输出时间间隔、精度设置后续的滤波。
AT=vor # 启用实时数据流(TTL模式)
AT+SEND_INTERVAL=500 # 设置串口发送间隔为500ms
AT+FILTER=1 # 启用滑动平均滤波(窗口大小10)
这里推荐SerialStudio软件进行可视化,具体配置文件参考附件SerialStudio配置文件
串口是115200-8-N-1通讯,选择这个SerialStudio配置的json文件,就可以可视化啦!😘😘😘
我通过逗号分割数据,
23:30:34.585 -> 22.727,22.633,22.820,22.687,24.141,25.273,23.891,24.328
23:30:34.784 -> 22.687,22.680,22.773,22.727,24.086,25.367,23.844,24.266
23:30:34.982 -> 22.687,22.680,22.773,22.727,24.086,25.367,23.844,24.266
23:30:35.181 -> 22.687,22.680,22.773,22.727,24.086,25.367,23.844,24.266
23:30:35.384 -> 22.687,22.680,22.773,22.727,24.141,25.297,23.797,24.336
23:30:35.591 -> 22.750,22.687,22.727,22.687,24.141,25.297,23.797,24.336
23:30:35.795 -> 22.750,22.687,22.727,22.687,24.141,25.297,23.797,24.336
23:30:35.977 -> 22.750,22.687,22.727,22.687,24.094,25.328,23.883,24.359
23:30:36.176 -> 22.641,22.617,22.750,22.727,24.094,25.328,23.883,24.359
23:30:36.377 -> 22.641,22.617,22.750,22.727,24.094,25.328,23.883,24.359
23:30:36.574 -> 22.641,22.617,22.750,22.727,24.188,25.305,23.797,24.445
23:30:36.774 -> 22.711,22.594,22.727,22.703,24.188,25.305,23.797,24.445
23:30:36.973 -> 22.711,22.594,22.727,22.703,24.188,25.305,23.797,24.445
23:30:37.185 -> 22.711,22.594,22.727,22.703,24.188,25.305,23.797,24.445
23:30:37.370 -> 22.703,22.687,22.766,22.727,24.086,25.344,23.891,24.336
23:30:37.570 -> 22.703,22.687,22.766,22.727,24.086,25.344,23.891,24.336
23:30:37.768 -> 22.703,22.687,22.766,22.727,24.086,25.344,23.891,24.336
23:30:37.967 -> 22.664,22.641,22.766,22.727,24.109,25.367,23.883,24.437
23:30:38.166 -> 22.664,22.641,22.766,22.727,24.109,25.367,23.883,24.437
23:30:38.366 -> 22.664,22.641,22.766,22.727,24.109,25.367,23.883,24.437
23:30:38.566 -> 22.664,22.641,22.766,22.727,24.062,25.367,23.891,24.422
23:30:38.764 -> 22.711,22.555,22.773,22.703,24.062,25.367,23.891,24.422
23:30:38.964 -> 22.711,22.555,22.773,22.703,24.062,25.367,23.891,24.422
在测试过程中我触摸热电偶两次,可视化显示如下:
- 测试“AT+SEND_INTERVAL=500”设置采样间隔指令
开启串口打印后,默认200ms发送一次(5Hz),然后发送“AT+SEND_INTERVAL=500”指令后变成500ms发送一次(2Hz)
- 测试“AT+SAMPLING_PRECISION=1”设置采样精度指令
开启串口打印后,默认保留3位小数,然后发送“AT+SAMPLING_PRECISION=1”指令后变成保留1位小数
我上面有数据是nan说明接触不良,重新插拔ok,打印如下
5 性能测试与优化
5.1 关键指标验证
| 测试项 | 实测值 | 工业标准 |
|-----------------|--------------|-------------|
| 采样精度 | ±0.5℃ | ±1℃ |
| 通道间干扰 | <0.3℃ | <0.5℃ |
| Modbus响应时间 | 12ms | <100ms |
5.2 功耗优化空间
- **动态调频**:空闲时段将CPU主频降至80MHz(功耗降低62%)
- **智能休眠**:无Modbus请求时关闭SPI总线供电(节省45mA)
5.3 异常处理机制
- **SPI总线监测**:自动检测MAX31856断线故障(CRC校验异常)
- **温度突变告警**:设置±5℃/s梯度阈值,触发Modbus异常码(0x8000)
- **通信重试策略**:Modbus事务失败后启用指数退避重传
6 项目总结
通过本次M-Design设计竞赛,我开发了一款功能强大的8通道高精度温度采集器。该设备不仅实现了多线程任务管理,还通过Modbus RTU协议实现了高效的数据交互。其主要优势包括:
1. **多线程任务管理**:利用FreeRTOS实现并行数据采集与处理,确保系统的实时性和稳定性。
2. **高效的数据交互**:通过Modbus RTU协议实现与上位机或云端的数据通信,支持动态配置参数。
3. **高精度的温度采集**:采用MAX31856芯片,实现高精度的温度测量。
4. **灵活的AT命令配置**:用户可以通过AT命令动态调整发送间隔、采样精度和滤波方法。
本项目展示了多线程任务管理与数据交互在物联网设备中的重要性,为未来的开发提供了宝贵的经验和参考。
7 未来展望
这是我第八次参加嵌入式相关的网上比赛活动,虽然没有去商城买东西,还是有点小遗憾,争取下次选个得意点的开发板。🤣🤣🤣未来,我们将进一步优化设备的功耗管理和通信稳定性,并探索更多通信协议(如MQTT、LoRa等)的应用,以满足不同场景的需求。同时,我们也将考虑增加更多传感器类型,实现多参数的综合监测。💕💕💕
● 第一次是RT-Thread的[【基于RT-Thread+RA6M4的智能鱼缸系统设计之鱼我所欲也】](https://club.rt-thread.org/ask/article/3edb6751a0ad28a2.html)活动,作品是2022年暑假做的获得第六名,还是比较开心!
● 第二次2023年寒假做的是[【基于MAX7800羽毛板语音控制ESP8266小车】](https://www.eetree.cn/project/detail/1309),成绩还不错第七名,让对小车车的可玩性又近了一步!
● 第三次2023年春做的[【基于腾讯云的CH32V307开发板远程机械臂小车】](https://blog.csdn.net/vor234/article/details/129008908),由于图床引用CSDN导致最后评审没有显示出来,最后获得安慰奖!
● 第四次2023年冬做的[【FastBond2阶段2——基于ESP32C3开发的简易IO调试设备 - 电子森林 (eetree.cn) 】](https://www.eetree.cn/project/detail/2222) ,最终获得三等奖,再接再厉哦!
● 第五次实现了[【基于LicheePi-4A的 人脸识别系统软件设计】](https://vor2345.blog.csdn.net/article/details/134864483),人脸识别系统软件设计和调试全流程,加深了对tkinter GUI设计思路,对LicheePi-4A 国产单板计算机更有信心,最终获得参与奖!
● 第六次实现了2024年寒假练 - 基于xiao ESP32S3 Sense的自动化HA鱼缸设计,探讨如何运用Seeed Xiao ESP32-S3 Cam开发板设计一款自动化、可接入Home Assistant(简称HA)的智能鱼缸系统。最终获得第一名,结实了许多朋友,视野开阔了许多。
● 第七次实现了FastBond3挑战部分-XIAO智能助手,让我关于Platformio C++编程充满期待。搭配国产通义灵码编程插件,编程效率嘎嘎上升,与此同时智能终端有更深刻认识啦!😘😘😘
建议:
- 希望硬禾与国内厂商多多合作定期举办嵌入式开发活动,让更加优质的国产嵌入式生态做大做强;
- 期待硬禾平台推出更多有质量有意义持续性的创客活动!
- 建议硬禾不仅开展线上活动,后期可以尝试线下活动,多多与开发者,企业,高校互动,共同探索内需外患。🤣🤣🤣
非常感谢硬禾举行活动,大家都为这个国内嵌入式生态出一份力,只要努力认真做了都会有所收获,期盼这些作品在将来某一天为构建美好未来贡献一份微博之力!
我后期会持续更新我测评的一系列国内开发板测评,并且会积极参加有质量的玄铁杯活动🛹🛹🛹每天都一点点结合实际需求联动丰富生活,从而实现对外部世界进行充分的感知,尽最大努力认识这个有机与无机的环境,科学地合理地进行创作和发挥效益,然后为人类社会发展贡献一点微薄之力。🤣🤣🤣
🥳🥳🥳再次非常感谢硬禾的相关组织者支持等等🥳🥳🥳期待这一次的成绩哟!
参考文献:
【基于ESP32-S3的4通道ADC数据采集(Modbus Poll教程)】