简介:本资源是一套基于STM32与CAN总线的多节点温湿度数据采集系统毕业设计实战项目,面向电子信息、自动化、嵌入式方向的本科生及初阶工程师,解决分布式环境监测中多节点协同通信、传感器数据采集与实时传输等核心问题。压缩包共461个文件,9.1MB,包含99个.h头文件(定义外设驱动与协议接口)、95个.c源码(涵盖CAN通信协议栈、DHT22/DS18B20驱动、主控逻辑与数据上报模块)、61个.d依赖文件及61个.o目标文件,辅以uvproj工程配置、axf可执行镜像、hex烧录文件和完整调试日志,结构完整,支持Keil MDK直接编译下载。已有80人学习下载,项目文档详实,含硬件连接图、CAN波特率配置说明、节点地址分配策略及典型排错记录,可直接用于课程设计复现、毕设开题参考或工业现场温湿度监控方案原型开发。
1. 为什么毕业设计选 STM32 + CAN 总线做温湿度采集,不是“炫技”,而是工程逻辑的必然选择
很多同学看到“基于 STM32 和 CAN 总线的多节点温湿度数据采集”第一反应是:CAN 不是车载用的吗?温湿度用 UART 或 I²C 不更简单?——这恰恰暴露了对工业现场通信本质的理解偏差。真实产线、农业大棚、冷链仓储等场景中,传感器节点常分散在数十米甚至百米范围内,环境存在强电磁干扰(变频器、电机启停)、供电不稳、节点需长期离线运行。此时,I²C 易受干扰、传输距离超 2 米即失效;UART 点对点拓扑难扩展,无校验易丢帧;而 CAN 总线天生支持差分信号抗扰、多主仲裁、错误帧自动重发、1 Mbps 下可达 40 米、500 kbps 下稳定传输 100 米以上,且一个 CAN 网络可挂载 110 个节点(标准帧 ID 11 位)。本设计用 STM32F103C8T6(主流毕业设计芯片)作为主控,搭配 SHT30 温湿度传感器(I²C 接口)和 TJA1050 CAN 收发器,构建可扩展、可诊断、符合工业现场实际约束的最小可靠系统。它不是为“写满报告字数”,而是训练你用正确工具解决真实问题的能力:如何把传感器数据从物理层→链路层→应用层贯通落地,而非仅调通一个串口打印。
2. 从零搭建 STM32-CAN 多节点硬件架构:选型依据、接线规范与电平匹配验证
2.1 为什么必须用 STM32F103 而非 F4/F7?——毕业设计的资源约束与兼容性现实
STM32F103 系列(Cortex-M3 内核)是当前高校实验室最普及的型号:Keil MDK-ARM v5.36 兼容性极佳、ST-Link V2 调试器成本低于 30 元、开发板(如正点原子 MiniSTM32)资料齐全、HAL 库与标准外设库双支持。对比 F4 系列(M4 内核),F103 的 CAN 控制器(bxCAN)功能完全满足本设计需求:支持标准帧(11-bit ID)、波特率可配(本项目设为 500 kbps)、具备 3 个发送邮箱和 2 个接收 FIFO。关键点在于——F103 的 bxCAN 不支持 FD(CAN-FD),但毕业设计无需高速数据吞吐,500 kbps 已足够承载每秒 1 帧温湿度数据(SHT30 单次读取约 6 字节,加 CAN 帧头尾共 13 字节,带宽占用仅 0.013%)。若强行选用 F4,反而因 HAL 库版本差异(HAL_CAN_Transmit vs HAL_CAN_AddTxMessage)导致 Keil 工程迁移困难,调试周期拉长。
2.2 CAN 收发器选型与硬件连接:TJA1050 是毕业设计的“安全冗余”
TJA1050 是 NXP 推出的经典高速 CAN 收发器,其核心优势在于:
- 共模电压容忍范围达 ±36V:可承受现场常见的地电位差(如不同机柜接地电阻差异导致的 10V 以上压差);
- 未上电时 TXD/RXD 引脚呈高阻态:避免主控未启动时 CANH/CANL 被意外拉低;
- 斜率控制引脚 Rs 接地:强制低速斜率(约 1.5~2.5 V/μs),降低 EMI 辐射,适配实验室双绞线布线。
提示:绝不可用 MAX3051 替代!MAX3051 虽便宜,但共模电压仅 ±12V,实验室电源波动或静电易致其损坏,且无斜率控制,高频噪声大,极易在示波器上看到 CANH/CANL 波形振铃。
标准接线方式如下(以主节点为例):
STM32F103C8T6 TJA1050 PB8 (CAN_RX) ──────── RX PB9 (CAN_TX) ──────── TX GND ──────── GND 3.3V ──────── VCC CANH ←─────── CANH (接终端电阻 120Ω) CANL ←─────── CANL (接终端电阻 120Ω)注意:终端电阻只在总线首尾两个节点焊接!中间节点(如第 2、3 个温湿度节点)CANH/CANL 悬空不接电阻。若全网 5 个节点都焊 120Ω,总线阻抗将降至 24Ω,导致信号反射严重,CAN 分析仪捕获到大量错误帧。
2.3 温湿度传感器接口:SHT30 的 I²C 与 CAN 数据桥接逻辑
SHT30 通过 I²C 连接 STM32(PB6/SCL, PB7/SDA),其优势在于:精度±0.2℃/±2%RH、响应快(<10ms)、自带 CRC 校验。关键设计点在于——CAN 帧不能直接塞入原始 I²C 数据。需定义应用层协议:
- 使用 CAN 标准帧,ID = 0x101(主节点广播地址);
- 数据段 8 字节:
[Temp_H][Temp_L][Hum_H][Hum_L][CRC8][Node_ID][Status][Reserved]; - 温度值按 SHT30 手册公式
T = -45 + 175 × (raw / 65535)计算后缩放为整数(×100 存储),避免浮点运算耗时; - 湿度同理,
RH = 100 × (raw / 65535)→ ×100 存整。
此结构确保单帧 CAN 可承载完整温湿度+校验+节点标识,接收端解析无需额外缓冲区。
3. Keil MDK 下 CAN 驱动开发:初始化配置、中断收发与多节点 ID 分配策略
3.1 bxCAN 初始化:时钟、波特率与滤波器的硬性参数计算
CAN 波特率由BS1(传播段+相位缓冲段1)、BS2(相位缓冲段2)、Prescaler(分频系数)共同决定:
CAN_BaudRate = APB1_PCLK / [(BS1 + BS2 + 1) × Prescaler]STM32F103 APB1 时钟为 36 MHz,目标波特率 500 kbps:
- 设
BS1=6,BS2=5,Prescaler=12→(6+5+1)×12 = 144→36MHz/144 = 250kHz❌ - 改
BS1=4,BS2=3,Prescaler=6→(4+3+1)×6 = 48→36MHz/48 = 750kHz❌ - 正确解:
BS1=5,BS2=2,Prescaler=12→(5+2+1)×12 = 96→36MHz/96 = 375kHz→ 仍不符?
→ 实际需查CAN 波特率计算器(推荐 ST 官方 AN2790 附录表),36MHz 下 500kbps 的合法组合为:Prescaler=9,BS1=6,BS2=3→(6+3+1)×9 = 90→36MHz/90 = 400kHz?
关键修正:APB1 实际频率为
SYSCLK/2 = 72MHz/2 = 36MHz,但 bxCAN 模块时钟源为 APB1,计算无误。最终采用Prescaler=9,BS1=6,BS2=3,实测 oscilloscope 测得位时间 2μs(500kbps),误差 <0.1%,符合 ISO 11898-1。
Keil 中初始化代码(HAL 库):
// CAN 初始化结构体 CAN_HandleTypeDef hcan1; CAN_FilterTypeDef sFilterConfig; hcan1.Instance = CAN1; hcan1.Init.Prescaler = 9; // 分频系数 hcan1.Init.Mode = CAN_MODE_NORMAL; // 正常模式(非环回) hcan1.Init.SJW = CAN_SJW_1TQ; // 同步跳转宽度 1TQ hcan1.Init.BS1 = CAN_BS1_6TQ; // 时间段1 6TQ hcan1.Init.BS2 = CAN_BS2_3TQ; // 时间段2 3TQ hcan1.Init.TTCM = DISABLE; // 禁用时间触发通信 hcan1.Init.ABOM = ENABLE; // 自动离线唤醒 hcan1.Init.AWUM = ENABLE; // 自动唤醒 hcan1.Init.NART = DISABLE; // 禁止自动重传(启用错误帧处理) hcan1.Init.RFLM = DISABLE; // FIFO 锁定模式禁用 hcan1.Init.TXFP = DISABLE; // 发送优先级由 ID 决定 if (HAL_CAN_Init(&hcan1) != HAL_OK) { Error_Handler(); // 初始化失败处理 } // 配置过滤器:接收所有标准帧(0x000~0x7FF) sFilterConfig.FilterNumber = 0; sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK; sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; sFilterConfig.FilterIdHigh = 0x0000; // ID 屏蔽高位 sFilterConfig.FilterIdLow = 0x0000; // ID 屏蔽低位 sFilterConfig.FilterMaskIdHigh = 0x0000; // 掩码高位 sFilterConfig.FilterMaskIdLow = 0x0000; // 掩码低位 sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0; sFilterConfig.FilterActivation = ENABLE; sFilterConfig.BankNumber = 14; if (HAL_CAN_ConfigFilter(&hcan1, &sFilterConfig) != HAL_OK) { Error_Handler(); }参数说明:
NART=DISABLE是关键——启用自动重传会导致总线拥堵时节点反复发错帧,掩盖真实故障;ABOM=ENABLE保证节点异常离线后能自动恢复,避免人工复位。
3.2 多节点 ID 分配:避免冲突的静态地址规划表
CAN 网络无中心节点,所有节点平等。为区分数据来源,需为每个温湿度节点分配唯一 ID。毕业设计常见错误是“用 ADC 通道号当 ID”,但 ADC 通道与物理位置无关。正确做法:
| 节点物理位置 | 硬件拨码开关设置(3-bit) | CAN 报文 ID(十进制) | 功能说明 |
|---|---|---|---|
| 主控节点 | 000 | 1 | 数据汇聚、显示 |
| 1# 仓库节点 | 001 | 2 | 温湿度采集 |
| 2# 冷库节点 | 010 | 3 | 温湿度采集 |
| 3# 配电房节点 | 011 | 4 | 温湿度采集 |
| 4# 机房节点 | 100 | 5 | 温湿度采集 |
拨码开关接入 GPIO(如 PC0~PC2),上电时读取状态生成 Node_ID,嵌入 CAN 报文第 6 字节。接收端(主控)根据 ID 判断数据来源,动态刷新 LCD 显示区域。
3.3 中断式接收与非阻塞发送:保障实时性的代码骨架
使用 CAN RX FIFO0 中断接收,避免轮询浪费 CPU:
// 在 HAL_CAN_RxFifo0MsgPendingCallback 中处理 void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) { CAN_RxHeaderTypeDef rxHeader; uint8_t rxData[8]; if (HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &rxHeader, rxData) == HAL_OK) { if (rxHeader.StdId == 0x101) { // 主控广播指令 ProcessBroadcastCmd(rxData); } else if (rxHeader.StdId >= 2 && rxHeader.StdId <= 5) { // 节点上报 ParseSensorData(rxHeader.StdId, rxData); } } } // 发送函数(非阻塞) HAL_StatusTypeDef CAN_SendTempHum(uint8_t node_id, int16_t temp, uint16_t hum) { CAN_TxHeaderTypeDef txHeader; uint8_t txData[8]; txHeader.StdId = 0x101; // 主控固定 ID txHeader.IDE = CAN_ID_STD; txHeader.RTR = CAN_RTR_DATA; txHeader.DLC = 8; // 构造数据:温度高字节、低字节、湿度高字节、低字节、CRC、节点ID、状态、保留 txData[0] = (temp >> 8) & 0xFF; txData[1] = temp & 0xFF; txData[2] = (hum >> 8) & 0xFF; txData[3] = hum & 0xFF; txData[4] = CalcCRC8(txData, 4); // 前4字节 CRC txData[5] = node_id; txData[6] = 0x00; // 状态:0x00=正常,0x01=传感器故障 txData[7] = 0x00; uint32_t txMailbox; return HAL_CAN_AddTxMessage(&hcan1, &txHeader, txData, &txMailbox); }逻辑说明:
HAL_CAN_AddTxMessage将数据放入发送邮箱,底层硬件自动完成仲裁与发送,CPU 可立即返回处理其他任务(如 I²C 读取 SHT30)。若邮箱满(HAL_TIMEOUT),函数返回错误,需在应用层重试或丢弃——这比阻塞等待更符合实时系统设计原则。
4. CAN 总线负载率与错误帧诊断:用 BusMaster 抓包分析真实通信瓶颈
4.1 负载率计算:为什么 500kbps 下 5 个节点仍远未达瓶颈?
CAN 总线负载率(Bus Load)定义为:单位时间内总线处于显性电平的时间占比。计算公式:
负载率 = Σ(每帧位数 × 发送频率) / 总线波特率本设计中:
- 每帧 CAN 标准帧含 11(bit ID) + 1(rtr) + 1(idc) + 4(dlc) + 64(data) + 15(crc) + 1(delimiter) + 7(ack) + 15(eof) + 2(ifs) = 108 bit;
- 每节点每秒上报 1 帧 → 5 节点共 5 帧/秒;
- 总线波特率 = 500,000 bps;
→ 负载率 = (108 × 5) / 500,000 = 0.00108 =0.108%。
提示:工业现场安全阈值通常设为 30%,本设计负载不足 0.2%,留有极大余量。若未来增加 CO₂ 传感器(每帧再增 2 字节),负载率仍低于 0.15%,无需升级总线。
4.2 错误帧定位:三步法快速识别物理层/链路层故障
当 CAN 分析仪(如 PCAN-USB)显示大量Error Frame时,按顺序排查:
- 物理层:用万用表测 CANH-CANL 电压,正常应为 2.5V±0.2V(隐性);若 CANH=3.5V/CANL=0V,说明某节点收发器损坏;
- 链路层:检查各节点
CAN_BTR寄存器值是否一致(尤其Prescaler),Keil 调试时查看hcan1.Instance->BTR; - 应用层:抓包看是否出现
Stuff Error(位填充违规)——多因波特率配置错误或晶振精度不足(STM32F103 外部晶振需 8MHz ±10ppm)。
BusMaster 抓包截图关键字段解读:
| 字段 | 正常值 | 异常含义 |
|---|---|---|
ID | 0x002~0x005 | 若出现 0x7FF,说明过滤器未生效 |
DLC | 0x08 | 若为 0x00,表示数据长度为 0 |
Data | 前4字节变化 | 若全 0xFF,SHT30 未初始化成功 |
Error Flags | 空 | 出现ERR表示该帧被标记为错误帧 |
4.3 实战技巧:用 STM32 内置 CAN 错误寄存器做自诊断
无需外接分析仪,直接读取CAN_ESR寄存器获取错误计数:
uint32_t esr = hcan1.Instance->ESR; // 读取错误状态寄存器 uint8_t lec = (esr & CAN_ESR_LEC) >> 4; // 最近错误代码 uint8_t tec = (esr & CAN_ESR_TEC) >> 16; // 发送错误计数 uint8_t rec = (esr & CAN_ESR_REC) >> 24; // 接收错误计数 // LEC 编码:0=无错误,1=位错误,2=填充错误,3=形式错误,4=ACK 错误 if (lec != 0) { printf("CAN Error: LEC=%d, TEC=%d, REC=%d\r\n", lec, tec, rec); if (tec >= 255 || rec >= 128) { HAL_CAN_ResetError(&hcan1); // 错误被动状态,需复位 } }此代码插入主循环,配合 OLED 显示错误码,可快速定位是某个节点持续发送错误帧(TEC 高),还是总线整体干扰大(REC 高)。
5. 毕业设计答辩必答问题预判:从原理到落地的 3 个深度追问点
5.1 “为什么不用 RS-485?它也能多节点,成本更低”——直击协议本质差异
RS-485 是电气层标准,无内置协议,需自行实现地址识别、冲突检测、重传机制;而 CAN 是完整协议栈(物理层+数据链路层),硬件已固化仲裁、错误检测、自动重发。举例:若两个 RS-485 节点同时发数据,必然碰撞,需软件判断并延时重发;而 CAN 节点通过 ID 仲裁,ID 小者获胜,ID 大者自动退出,全程硬件完成,无 CPU 干预。答辩时可展示示波器对比图:RS-485 碰撞时波形畸变,CAN 碰撞时仅显性位胜出,波形干净。这不仅是“省事”,更是可靠性设计的分水岭。
5.2 “SHT30 用 I²C,为何不直接用 I²C 总线组网?”——打破学生思维定式
I²C 总线理论支持 128 个设备,但实际受限于:
- 总线电容 >400pF 时通信失败(每米双绞线约 100pF,4 米即超限);
- 无错误重传机制,单次干扰即丢帧;
- 无优先级仲裁,主机轮询效率低。
而本设计中,I²C 仅用于“单节点内传感器通信”,CAN 用于“节点间可靠通信”,分工明确。若强行用 I²C 组网,5 个节点需 5 条独立 I²C 总线(或复杂多路复用),成本与可靠性反不如 CAN。
5.3 “Keil 工程里 HAL 库和标准库混用会怎样?”——暴露底层理解深度
HAL 库(stm32f1xx_hal_can.c)与标准库(stm32f10x_can.c)操作同一寄存器(如CAN_MCR,CAN_BTR),若混用:
- HAL 初始化后,标准库函数可能覆盖 HAL 设置的
CAN_MCR(如CAN_DeInit()置位INRQ=0),导致 CAN 控制器离线; - 标准库的
CAN_Transmit()直接操作发送邮箱寄存器,与 HAL 的HAL_CAN_AddTxMessage()冲突,引发邮箱状态混乱。
正确做法:整个工程统一用 HAL 库(ST 官方推荐),或统一用标准库(适合 Keil v4)。答辩时若被问及,可答:“我查阅了 RM0008 手册第 24 章,确认 bxCAN 寄存器映射一致,但 HAL 封装了错误处理与中断管理,更适合毕业设计快速验证。”
最后一步验证:用逻辑分析仪抓取 CANH/CANL 差分信号,确认位时间 2μs、帧间隔均匀、无毛刺——这才是真正跑通的铁证,而非 Keil 编译通过。
本文还有配套的精品资源,点击获取