附完整源码!can项目实战:基于freertos的can协议传递温湿度数据(HAL库)
前言:在工业控制和物联网场景中,温湿度数据的采集与传输是常见需求。本文详细介绍了一个基于STM32微控制器的分布式温湿度监测系统解决方案。该系统采用"从控+主控"的分布式架构设计,通过工业级CAN总线实现设备间通信,同时兼具本地显示和云端数据上传功能,具有高可靠性、灵活扩展的特点。另外,系统设计灵活,可根据需求简化为纯 CAN 通信模式。
一、项目概述
1.1 系统架构
系统采用模块化设计思想,分为两个功能明确的部分,可根据实际应用场景灵活配置:
- 从控设备(CAN-DHT11):负责通过 DHT11 传感器采集温湿度数据,本地 OLED 实时显示,并通过 CAN 总线将数据发送至主控设备。
- 主控设备(CAN-ESP8266-DHT11):通过 CAN 总线接收从控数据,本地 OLED 显示,并通过 ESP8266 模块上传至云端平台。
1.2 核心功能
- 从控设备:DHT11 数据采集、OLED 本地显示、CAN 数据发送、环回测试(自测)。
- 主控设备:CAN 数据接收、OLED 本地显示、ESP8266 云端上传(可选)。
- 灵活性:支持删除 WiFi 功能,仅保留 CAN 总线数据传输与显示。
二、硬件设计
2.1 核心硬件清单
| 设备 | 型号 / 规格 | 作用 |
|---|---|---|
| 主控 MCU | STM32F103(或同系列) | 核心控制、CAN/USART 外设驱动 |
| 从控 MCU | STM32F103(或同系列) | 核心控制、传感器数据采集 |
| 温湿度传感器 | DHT11 | 采集环境温湿度(精度 ±2℃/±5%) |
| 显示屏 | 0.96 寸 OLED(I2C 接口) | 本地数据显示 |
| 无线模块 | ESP8266(WiFi) | 云端数据上传 |
| CAN 收发器 | TJA1050 | CAN 总线电平转换 |
2.2 硬件连接示意图
-
从控设备:
- DHT11 → 任意 GPIO(如 PA5,配置为输入 / 输出切换)
- OLED(I2C) → SCL=PB8,SDA=PB9
- CAN → PA12(CAN_TX)、PA11(CAN_RX)→ 连接 TJA1050
- freertos任务配置→can发送任务,OLED显示任务,dht11读取数据任务(优先级为can>dht11>oled)
- freertos队列配置→OLED读取数据队列,can发送任务读取队列(空间超出的话要增加栈大小)
-
主控设备:
- OLED(I2C) → 同从控(可复用引脚定义)
- ESP8266 → USART2(TX=PA2,RX=PA3)
- CAN → 同从控,通过 TJA1050 连接至 CAN 总线
- freertos任务配置→can接收任务,OLED显示任务,WiFi上传数据任务(优先级can和OLED可以相同,但是都要大于WiFi任务,防止WiFi任务阻塞项目运行)
- freertos队列配置→OLED读取数据队列,WiFi上传数据队列(空间超出的话要增加栈大小)

从控配置:

主控配置:

三、软件设计
3.1 从控设备软件流程(CAN-DHT11)
从控核心任务:数据采集→本地显示→CAN 发送,通过 FreeRTOS 任务与消息队列实现模块解耦。
3.1.1 核心任务设计
-
DHT11 采集任务(DHT11StartTask):
- 初始化 DHT11 传感器,周期性(2s)读取温湿度数据。
- 将数据封装为
DHT11_Data结构体(含温度、湿度、有效性标识)。 - 通过消息队列(
OLEDQueueHandle、CANQueueHandle)将数据分别发送给 OLED 显示任务和 CAN 发送任务。
// 关键代码片段(DHT11_task.c) void DHT11StartTask(void *argument) { DHT11_Init(); // 初始化传感器 DHT11_Data msg = {0}; for(;;) { uint8_t temp = 0, humi = 0; // 读取DHT11数据(带超时重试) if(DHT11_Read_Data(&temp, &humi) == 0) { msg.temperature = (float)temp; msg.humidity = (float)humi; msg.valid = 1; // 标记数据有效 } else { msg.valid = 0; } // 发送数据到OLED和CAN队列 osMessageQueuePut(OLEDQueueHandle, &msg, 0, 0); osMessageQueuePut(CANQueueHandle, &msg, 0, 0); osDelay(2000); // 2s采集一次 } } -
OLED 显示任务(OLEDStartTask):
- 从
OLEDQueueHandle队列获取数据,解析为整数 + 小数部分,在 OLED 第 3 行显示温度、第 4 行显示湿度。
// 关键代码片段(OLED_task.c) void OLEDStartTask(void *argument) { OLED_Init(); DHT11_Data displayData; for(;;) { if(osMessageQueueGet(OLEDQueueHandle, &displayData, NULL, 100) == osOK) { if(displayData.valid) { // 显示温度(如"Temp:25.5C") uint16_t temp_int = (uint16_t)displayData.temperature; uint16_t temp_dec = (uint16_t)((displayData.temperature - temp_int) * 10); OLED_ShowString(3, 1, "Temp:"); OLED_ShowNum(3, 6, temp_int, 2); OLED_ShowString(3, 8, "."); OLED_ShowNum(3, 9, temp_dec, 1); OLED_ShowString(3, 10, "C"); // 湿度显示类似... } } osDelay(10); } } - 从
-
CAN 发送任务(CANStartTask):
- 从
CANQueueHandle队列获取数据,将温湿度打包为 16 位整数(保留 1 位小数),通过 CAN 总线发送(ID=0x123,数据长度 4 字节)。
// 关键代码片段(CAN_task.c) void CANStartTask(void *argument) { CAN_Init(); // 初始化CAN外设 DHT11_Data dhtData; for(;;) { if(osMessageQueueGet(CANQueueHandle, &dhtData, NULL, 100) == osOK) { if(dhtData.valid) { uint16_t temp = (uint16_t)(dhtData.temperature * 10); uint16_t humi = (uint16_t)(dhtData.humidity * 10); uint8_t data[4] = {temp>>8, temp&0xFF, humi>>8, humi&0xFF}; CAN_SendData(0x123, data, 4); // 发送CAN消息 } } osDelay(10); } } - 从
3.1.2 环回测试功能
从控支持 CAN 环回测试(自测模式),无需主控即可验证功能:
- 在
CAN_Config.c中,将 CAN 模式配置为CAN_MODE_LOOPBACK:// CAN初始化时修改模式 hcan.Init.Mode = CAN_MODE_LOOPBACK; // 环回模式 - 环回模式下,从控发送的 CAN 消息会被自身接收,通过 OLED 显示验证数据是否正常。
3.2 主控设备软件流程(CAN-ESP8266-DHT11)
主控核心任务:CAN 接收→本地显示→云端上传,同样通过 FreeRTOS 任务与队列实现。
3.2.1 核心任务设计
-
CAN 接收任务:
- 初始化 CAN 外设,配置过滤器接收从控发送的 ID=0x123 消息。
- 接收中断触发后,解析数据(温度 = 高 2 字节 / 10,湿度 = 低 2 字节 / 10),通过队列发送给 OLED 和 WiFi 任务。
// 关键代码片段(CAN_Config.c) void CAN_ReceiveHandler(CAN_HandleTypeDef *hcan) { CAN_RxHeaderTypeDef header; uint8_t data[8]; DHT11_Data receivedData; if(HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &header, data) == HAL_OK) { if(header.StdId == 0x123 && header.DLC == 4) { uint16_t temp = (data[0]<<8)|data[1]; uint16_t humi = (data[2]<<8)|data[3]; receivedData.temperature = temp / 10.0f; receivedData.humidity = humi / 10.0f; receivedData.valid = 1; // 发送到OLED和WiFi队列 osMessageQueuePut(OLEDQueueHandle, &receivedData, 0, 0); osMessageQueuePut(WiFiQueueHandle, &receivedData, 0, 0); } } } -
OLED 显示任务:与从控逻辑一致,从队列获取数据并显示(可复用从控 OLED 代码)。
#include <stdio.h>
extern osMessageQueueId_t OLEDQueueHandle;
void OLEDStartTask(void *argument)
{
OLED_Init();
DHT11_Data_t displayData;
// printf("OLED Task Started\r\n");
OLED_ShowString(3,1,"Wait...");
for(;;) {
// 从CAN接收队列获取数据(等待最多100ms)
if(osMessageQueueGet(OLEDQueueHandle, &displayData, NULL, 100) == osOK) {
// printf("oled get ok \r\n");
if(displayData.valid) {
// 显示温度
uint16_t temp_int = (uint16_t)displayData.temperature;
uint16_t temp_dec = (uint16_t)((displayData.temperature - temp_int) * 10);
OLED_ShowString(3, 1, "Temp:");
OLED_ShowNum(3, 6, temp_int, 2);
OLED_ShowString(3, 8, ".");
OLED_ShowNum(3, 9, temp_dec, 1);
OLED_ShowString(3, 10, "C");
// 显示湿度
uint16_t humi_int = (uint16_t)displayData.humidity;
uint16_t humi_dec = (uint16_t)((displayData.humidity - humi_int) * 10);
OLED_ShowString(4, 1, "Humi: ");
OLED_ShowNum(4, 6, humi_int, 2);
OLED_ShowString(4, 8, ".");
OLED_ShowNum(4, 9, humi_dec, 1);
OLED_ShowString(4, 10, "%");
printf("OLED Updated: %.1fC, %.1f%%\r\n",
displayData.temperature, displayData.humidity);
} else {
OLED_ShowString(3, 1, "Invalid Data Received!");
osDelay(2000);
OLED_ShowString(3, 1, " "); // 清空行
}
}
osDelay(10);
}
}
-
WiFi 上传任务(WiFiStartTask):
- 初始化 ESP8266 模块(连接 WiFi、MQTT 服务器)。
- 从
WiFiQueueHandle队列获取数据,通过Esp_PUB函数将温湿度上报至云端。
// 关键代码片段(WiFi_task.c) void WiFiStartTask(void *argument) { ESP8266_Init(); // 初始化WiFi和MQTT DHT11_Data displayData; uint32_t lastReportTime = HAL_GetTick(); for(;;) { if(osMessageQueueGet(WiFiQueueHandle, &displayData, NULL, 100) == osOK) { if(displayData.valid && (HAL_GetTick() - lastReportTime > 5000)) { // 每5秒上报一次 Esp_PUB("temp", (uint16_t)displayData.temperature); Esp_PUB("humi", (uint16_t)displayData.humidity); lastReportTime = HAL_GetTick(); } } osDelay(10); } }
3.2.2 简化模式(无云端功能)
若无需云端上传,仅需删除 WiFi 相关代码:
- 不创建
WiFiStartTask任务。 - 注释或删除
ESP8266_Init、Esp_PUB等 WiFi 相关函数。 - 主控仅保留 CAN 接收和 OLED 显示功能。
四、测试验证
4.1 从控环回测试
- 配置 CAN 为环回模式(
CAN_MODE_LOOPBACK)。 - 上电后,从控 OLED 应显示 DHT11 采集的温湿度(如 "Temp:25.5C"、"Humi:60.0%")。
- 串口调试助手可观察到 CAN 发送与接收日志(如 "[TX] Temp:25.5C... [RX] ID:0x123...")。

4.2 主从通信测试
- 从控、主控通过 CAN 总线连接(TJA1050 的 CAN_H/CAN_L 互连,注意终端电阻)。
- 从控配置为正常模式(
CAN_MODE_NORMAL),主控同样配置为正常模式。 - 上电后,主控 OLED 应同步显示从控采集的温湿度,与从控显示一致。

4.3 云端上传测试
- 配置 ESP8266 的 WiFi 信息(SSID、密码)和 MQTT 服务器地址(
Url、username等)。 - 主控上电后,通过 MQTT 客户端工具(如 MQTTX)订阅上报主题,应能收到温湿度数据(如
{"id":"123","params":{"temp":{"value":25}}})。

五、总结与扩展
本系统基于STM32F103C8T6微控制器,通过CAN总线(波特率125kbps)实现了1个主控设备与1个从控设备之间的温湿度数据传输。系统采用FreeRTOS实时操作系统,通过任务队列(如创建了CAN接收队列、OLED显示队列等)实现了模块化设计,各功能模块解耦程度高,代码可维护性强。从控设备特别设计了环回测试模式(通过拨码开关触发),在该模式下设备会自动生成测试数据包并通过CAN总线发送,同时接收自身发送的数据,极大简化了调试流程。主控设备可选配ESP8266 WiFi模块(通过AT指令控制),用户可根据实际需求决定是否启用物联网功能。
扩展方向:
-
多从控设备支持
- 通过分配不同的CAN ID(如0x101、0x102等)区分各从控设备
- 主控设备需增加设备发现与注册机制
- 示例场景:可应用于温室大棚的多点温湿度监测
-
传感器升级方案
- 当前使用DHT11(温度±2℃,湿度±5%精度)
- 可替换为SHT30(温度±0.2℃,湿度±2%精度)
- 需注意I2C接口改造及供电电压调整(SHT30工作电压2.4-5.5V)
-
异常报警功能
- 在main.c中增加阈值检测逻辑
- 触发条件示例:温度>35℃或湿度>80%
- 报警方式:ESP8266发送邮件/短信报警,同时OLED显示警告图标
-
低功耗优化
- 硬件层面:选用低功耗型号STM32L系列
- 软件策略:
- OLED息屏:30秒无操作后关闭显示
- 唤醒方式:板载按键中断唤醒
- 显示刷新:唤醒后立即读取最新传感器数据
代码适配说明:
- FreeRTOS配置需根据具体芯片型号调整堆栈大小
- 示例工程包含完整的Keil MDK项目文件,适合STM32初学者学习CAN总线通信与物联网应用开发,也可直接用于课程设计或毕业设计项目。
完整源码已上传github平台https://github.com/tycrsg/FREERTOS-CAN-DHT11.git
https://github.com/tycrsg/FREERTOS-CAN-DHT11.git
更多推荐
所有评论(0)