CAN实验演示
178.1.1CAN实验演示
#define SEND_LEN 5 /* 每次发送 DHT11 的 5 个原始数据字节。 */
#define ZET6 1 //1代表使用ZET6接收数据的代码 0代表使用C8T6发送数据的代码;这是编译期开关,修改后必须重新编译并烧录。
Can_Filter_Init(); /* 配置 CAN 接收过滤器并启动 CAN;函数内部实现不属于本节展示范围。 */
#if ZET6 /* 只有 ZET6=1 时,下面三条 OLED 初始化语句才参与编译。 */
OLED_Init(); /* 初始化接收端连接的 OLED。 */
OLED_Clear(); /* 清空 OLED 显存,避免残留像素。 */
OLED_ShowString(32, 0, "Can Test",16); /* 在 OLED 顶部显示实验标题。 */
#endif /* ZET6 接收端专用初始化结束。 */
#if ZET6 //1代表使用接收数据的代码 0代表使用发送数据的代码;预处理器只会保留其中一个分支。
uint32_t Recv_ID = 0; /* 保存本次收到的 CAN 标准帧标识符。 */
uint32_t Recv_Len = Can_Recv_Msg(&Recv_ID, Rx_Data); /* 查询 FIFO0;有帧时把 ID、数据和长度取出。 */
if(Recv_Len>0) /* 返回长度大于 0,表示本轮取到了一帧带数据的报文。 */
{ /* 接收成功分支开始。 */
printf("recv id 0x%x:\r\n", Recv_ID); /* 打印收到的标准帧 ID,便于串口观察。 */
printf("recv length %u:\r\n", Recv_Len); /* 打印数据长度 DLC。 */
printf("recv data:"); /* 输出数据列表的提示文字。 */
for(int i=0;i<Recv_Len;i++) /* 从第 0 字节遍历到本帧最后一个有效字节。 */
printf(" %u ",Rx_Data[i]); /* 以无符号十进制打印当前接收字节。 */
printf("\r\n"); /* 一帧数据打印完毕后换行。 */
sprintf(OLED_BUFF,"H:%u T:%u",Rx_Data[0],Rx_Data[2]); /* 用第 0 字节显示湿度整数、第 2 字节显示温度整数。 */
OLED_ShowString(0, 2, OLED_BUFF,16); /* 从 OLED 的第 2 个 16 像素文本行显示温湿度。 */
memset(Rx_Data, 0, 8); /* 清零 8 字节接收缓冲区,避免旧值残留。 */
}else{ /* 本轮没有取到数据长度大于 0 的报文。 */
printf("recv eorro\r\n"); /* 原源码拼写原样保留;这里的“无新帧”不一定是真正的 CAN 错误。 */
} /* 接收结果判断结束。 */
#else /* ZET6=0 时编译 C8T6 发送分支。 */
DHT_Read(); /* 从 PA5 所接 DHT11 读取 5 字节数据;原程序未检查其校验返回值。 */
HAL_Delay(100); /* 原源码延时 100 ms;DHT11 手册要求采样间隔不得低于 1 s。 */
uint32_t ret = Can_Send_Msg(0x12, SEND_LEN, Data); /* 用标准 ID 0x12 发送 Data 中的 5 字节。 */
if(ret == 0) /* 课程封装函数返回 0 表示发送调用成功。 */
{ /* 发送成功分支开始。 */
printf("send data:"); /* 输出发送数据列表的提示文字。 */
for(int i=0;i<SEND_LEN;i++) /* 遍历本次发送的 5 个字节。 */
printf(" %u ",Data[i]); /* 以无符号十进制打印当前发送字节。 */
printf("\r\n"); /* 数据打印结束后换行。 */
printf("send ok\r\n"); /* 打印发送成功提示。 */
}else{ /* 发送封装函数返回非 0。 */
printf("send eorro\r\n"); /* 原源码拼写原样保留,表示发送调用失败。 */
} /* 发送结果判断结束。 */
#endif /* ZET6 接收分支与 C8T6 发送分支选择结束。 */
源码范围说明:上面只摘录本节在
main.c中展示和用于烧写演示的宏、OLED 接收端初始化以及 CAN 收发条件编译分支,并在不改动业务语句的前提下逐行加注释。代码是若干原位置片段,不是可单独编译的完整main.c;Rx_Data、OLED_BUFF和 DHT11 的Data均为工程中已有的全局缓冲区。Can_Filter_Init()、Can_Recv_Msg()、Can_Send_Msg()、DHT11 驱动、OLED 驱动和 HAL 库的内部实现没有在本课展开,因此不强行抄入。
一、知识讲解
1.1 应用场景
CAN 是 Controller Area Network(控制器局域网)的缩写。它最初面向汽车电子,课程列举了马达、空调、车门、仪表盘、车灯、发动机/传动控制、转向、胎压监测和雷达等场景;CAN 的高可靠性、多主通信和差分物理层也使它常见于船舶、医疗设备及工业自动化。
汽车中使用 CAN 的核心动机不是“单片机越多越先进”,而是多个控制单元需要可靠交换数据。若每两个控制器都单独拉线,线束会迅速增加;总线允许多个节点共享一对 CANH/CANL。课程资料中的车载网络图还展示了不同速率、不同用途的网络可通过网关交换数据,因此一辆车并不一定只有一条 CAN 总线。
这节实验把真实车辆网络缩小成两个节点:
DHT11
│ 单总线,PA5
▼
C8T6(采集并组帧)
│ PA12/CAN_TX、PA11/CAN_RX
▼
CAN 收发器 ══ CANH/CANL ══ CAN 收发器
│ PA12/CAN_TX、PA11/CAN_RX
▼
ZET6(接收并解析)
│ PB6/SCL、PB7/SDA
▼
0.96 寸 OLED
STM32 内部的 bxCAN 控制器负责标识符、数据长度、CRC、ACK 等帧级处理;外置 CAN 收发器负责把 MCU 侧的 TX/RX 逻辑电平转换成 CANH/CANL 差分电平。二者职责不同,不能只把两块 MCU 的 TX/RX 直接相连来代替 CAN 总线。
1.2 文档资料
课程指向“专题参考文档/CAN总线入门书”。该资料适合先浏览以下内容:
- 第 3 章的车载网络和应用示例,用于建立“多个控制器共享网络”的整体概念;
- 第 3.2 节的连接图,用于理解
CPU → CAN 控制器 → CAN 收发器 → CANH/CANL; - 第 7.2 节的高速 CAN 物理层图,用于理解差分信号、线形总线以及总线两端的 120 Ω 终端电阻;
- 第 8 章的数据帧、仲裁、位填充与错误处理,作为后续理论课的预习材料。
这份入门书发布较早,适合学习基本概念,但新设计的器件电气参数、布线限制和协议版本仍应以当前标准及所用收发器数据手册为准。本节先掌握实验链路,不必一次背完所有帧字段。
1.3 实验演示
1.3.1 实验框图
C8T6 是发送节点:它从 PA5 读取 DHT11 的 40 位数据,再以标准帧 ID 0x12 发送 5 字节。DHT11 的字节顺序为:
| 字节 | 含义 | 本实验用途 |
|---|---|---|
Data[0] | 湿度整数部分 | 接收端显示为 H |
Data[1] | 湿度小数部分 | 发送但未显示 |
Data[2] | 温度整数部分 | 接收端显示为 T |
Data[3] | 温度小数部分 | 发送但未显示 |
Data[4] | 前 4 字节之和的低 8 位校验值 | 发送,但接收端未再次校验 |
ZET6 是接收节点:它轮询 CAN FIFO0,打印收到的 ID、长度和数据,再把 Rx_Data[0]、Rx_Data[2] 格式化后显示在 OLED 上。课程最终项目会使用 3.5 寸 LCD;这里换成 0.96 寸 OLED,只是为了先用更简单的显示链路证明“采集 → CAN 发送 → CAN 接收 → 显示”已经贯通。
1.3.2 实验连线
原笔记把 CAN 引脚识别成了 PA1/PA2,这是转写错误。配套 .ioc 明确配置 PA11=CAN_RX、PA12=CAN_TX,两块 STM32 节点都按这一组默认 CAN 引脚连接。
| 节点/模块 | 引脚 | 连接 | 说明 |
|---|---|---|---|
| ZET6 | PB6 | OLED SCL | I2C1 时钟线 |
| ZET6 | PB7 | OLED SDA | I2C1 数据线 |
| ZET6 | PA12/CAN_TX | 本端收发器 CTX/TXD | MCU 发出的 CAN 位流进入收发器 |
| ZET6 | PA11/CAN_RX | 本端收发器 CRX/RXD | 收发器把总线状态送回 MCU |
| C8T6 | PA12/CAN_TX | 本端收发器 CTX/TXD | 发送端 CAN 控制器输出 |
| C8T6 | PA11/CAN_RX | 本端收发器 CRX/RXD | 发送端仍需接 RX,以完成仲裁、ACK 和错误检测 |
| C8T6 | PA5 | DHT11 DATA | 单线双向数据;裸传感器需合适的上拉电阻,模块可能已自带 |
| 两只收发器 | CANH | CANH ↔ CANH | 不能与 CANL 交叉 |
| 两只收发器 | CANL | CANL ↔ CANL | 建议与 CANH 一起使用双绞线 |
| 两块板/模块 | GND | 公共地 | 台架实验建议增加地线,给收发器提供共同参考电位 |
上电前按以下顺序检查:
- 所有设备断电,先核对丝印,不根据导线颜色猜引脚;
- 确认 CANH 对 CANH、CANL 对 CANL,且两块板共地;
- 两节点分别位于总线两端时,两端各放一个 120 Ω 终端。若模块已带可使能的 120 Ω 电阻,不要再重复并联;
- 断电测量 CANH 与 CANL,两个 120 Ω 并联时应约为 60 Ω。读数不是精确 60 Ω 也可能受电路影响,但接近 0 Ω 通常表示短路;
- 最后再接电源并上电,观察芯片或稳压器是否异常发热。
CAN 的主干应尽量保持线形,节点用短支线接入。两节点桌面短线实验对布线较宽容,但“短线能通信”不能证明无终端、长支线或星形连接在更高速度和更长距离下也可靠。
1.3.3 硬件清单
本实验需要两块 STM32F103 开发板(ZET6 与 C8T6)、两个高速 CAN 收发器模块、一个四针 I2C OLED、一个 DHT11 温湿度模块、ST-Link、供电线、CANH/CANL 双线以及必要的终端电阻。万用表虽不是程序运行的组成部分,但非常适合在上电前检查短路、供电和终端电阻。
供电是本课最容易照抄出错的地方,必须把“开发板输入电压”和“外设芯片 VCC”分开看:
- 两块开发板可以通过 USB/板载
5V输入供电,再由板上稳压器产生 MCU 所需的 3.3 V;这不代表所有外接模块都应接 5 V。 - 课程实物模块芯片丝印为
VP230,对应 TISN65HVD230D,其 VCC 工作范围是 3.0~3.6 V,因此模块VCC应接稳定 3.3 V。视频“CAN 收发器必须 5 V”的说法不适用于这只模块。 - 如果手中实际芯片是 TJA1050,则它的 VCC 是 4.75~5.25 V。
SN65HVD230与TJA1050不能仅凭模块外形套用同一供电结论,必须看芯片丝印和模块原理图。 - 配套 DHT11 手册给出的供电范围是 3~5.5 V,本实验接 C8T6 的 3.3 V 合理;裸 DHT11 的 DATA 典型应用还使用约 5 kΩ 上拉到 VDD。
- 配套四针 OLED 模块资料给出的 VCC 范围是 3~5.5 V,课程接 5 V 是针对该模块载板,不等于 SSD1306 裸芯片或任意外观相似模块都可直接接 5 V。模块型号不一致时应重新查资料。
1.3.4 网盘资料
课程演示使用 源码/3.1 CAN、LVGL、IAP固件升级专题/uint3code/04Can_DHT11_OLED,即专题代码中的第 4 个工程,而不是前面的基础 CAN 工程。主要文件关系如下:
| 文件/目录 | 作用 |
|---|---|
Core/Src/main.c | 本课展示的宏选择、收发循环和 OLED 显示 |
Canif/canif.c | CAN 过滤、启动、发送和接收封装 |
DHT11/dht11.c | PA5 单总线读取及 5 字节校验 |
OLED/oled.c | I2C OLED 写命令、写数据和字符显示 |
01Can_Test.ioc | ZET6 工程的时钟、CAN、I2C 与引脚配置 |
MDK-ARM/01Can_Test.uvprojx | Keil 目标器件、存储区和烧写算法配置 |
.ioc 中 CAN 时钟来自 36 MHz 的 APB1,预分频为 4,时间段为 1 + 9 + 8 = 18 TQ,所以位率为:
[ \text{CAN bit rate}=\frac{36,\text{MHz}}{4\times(1+9+8)}=500,\text{kbit/s} ]
两个节点必须使用相同位率和兼容的位时序,否则不会因为 CANH/CANL 已经接对就自动通信。
1.3.5 代码烧写
ZET6 是预处理宏,不是运行时变量:
#define ZET6 1:编译 ZET6 接收与 OLED 显示分支,#else发送代码被排除;#define ZET6 0:编译 C8T6 的 DHT11 读取与 CAN 发送分支,接收代码被排除;- 每次改宏后都必须重新 Build,再 Download;只改编辑器文本而不重新编译,芯片里仍是旧固件。
建议按下面流程分别烧写:
- **ZET6 接收端:**把宏设为
1,在 Keil 中确认目标器件为 STM32F103ZE、Flash 为 512 KB、高密度启动文件和烧写算法匹配,然后编译并烧写 ZET6。 - **断电换线:**关闭目标板电源后再移动 ST-Link。按
SWDIO、SWCLK、GND、目标电压参考逐根核对,不能因为两块板都是四针接口就假定针序相同。 - **C8T6 发送端:**把宏设为
0,把目标器件、启动文件、链接存储范围和 Flash 算法全部切换到 STM32F103C8,再重新编译并烧写 C8T6。 - 两块板都恢复正常供电后再观察 OLED 与串口日志。
课程用胶带把四根 ST-Link 线固定成一排,只能在两块目标板接口定义和针序已经逐针确认相同时使用。更可靠的方法是给导线贴上信号标签,或使用有防呆方向的连接器。还要避免 USB 和 ST-Link 同时从不同电源反向供电;不清楚克隆 ST-Link 电源针用途时,先查其说明并测量。
芯片容量需要纠正:STM32F103ZET6 有 512 KB Flash,而 STM32F103C8T6 的官方容量是 64 KB,不是 128 KB。Keil 列表中的 STM32F10x 128kB Flash 是可覆盖该中密度系列地址范围的烧写算法名称,不能据此把 C8T6 写成 128 KB。一个小固件即使能用该算法烧入 ZET6 的低地址区,也不代表 ZET6 的整个 512 KB 配置正确。
更要注意,ZET6 宏只选择业务分支,不会自动修改:
- Keil 的 Device 型号;
IROM起始地址和大小;- 中/高密度启动文件与中断向量;
- 编译宏、链接脚本和 Flash Programming Algorithm;
- CubeMX 的芯片封装和引脚配置。
配套归档的 .uvprojx 本身就是混合状态:Device 显示 STM32F103ZE,但 IROM 只有 0x10000(64 KB),Flash 驱动路径又指向 C8 的 128k 算法,同时工程保留 startup_stm32f103xe.s。它适合还原课程演示,却不应直接当作两个芯片都已严谨配置的模板。实际复现最好在同一工程中建立 ZET6_Recv、C8T6_Send 两个独立 Target,或拆成两个工程,让每个目标保存自己的器件、启动文件、存储区和下载算法。
正常现象应为:OLED 先显示 Can Test,随后出现类似 H:41 T:20 的数值。手指靠近或捂住 DHT11 后,局部温度和湿度可能变化,OLED 数值随之改变,说明采集、发送、物理总线、接收和显示链路都在工作。但这只证明数值能够变化,不能证明传感器已经精确校准,也不能覆盖总线错误恢复等测试。
源码还有三项实验性限制需要知道,但本节按要求不改原代码:
- DHT11 手册规定采样间隔不得低于 1 s,源码却只延时 100 ms,可能反复得到旧值或读数失败;
DHT_Read()会返回校验结果,但发送分支没有判断返回值,读取失败时仍可能发送缓冲区中的数据;- 接收过滤器掩码全为 0,相当于接收所有 ID,主循环也没有验证
Recv_ID==0x12和Recv_Len==5。两节点专用总线上便于演示,接入其他节点后则可能误显示别的报文。
1.3.6 常⻅问题
| 现象 | 优先检查 | 原理说明 |
|---|---|---|
| 烧写成功但程序不自动运行 | Options for Target → Debug → Settings → Flash Download → Reset and Run | 勾选后,下载并校验完成会复位并运行;未勾选时可手动按复位键。截图中的 Pack 是另一个标签页,不是需要取消的“选项”。 |
| C8T6 下载失败 | Device 是否选 C8、算法是否匹配、SWD 四线和供电是否正确 | 只改 ZET6=0 不会自动改变下载目标;改完目标配置后必须重新编译。 |
| 切换板子后 ST-Link 连不上 | 断电核对 SWDIO、SWCLK、GND、目标电压参考和接口方向 | 不同开发板的四针排列可能不同,整排平移最容易把电源与地接反。 |
| OLED 全黑 | VCC/GND、PB6/PB7、模块接口类型和 I2C 地址 | 本工程 HAL 使用的地址参数是 0x78,对应 7 位地址 0x3C 左移一位;不同模块地址可能不同。 |
| OLED 有标题但温湿度不更新 | C8 是否烧了发送分支、PA5/DHT11、CAN 两端位率和接线 | 标题只证明 ZET6 与 OLED 正常,不证明 CAN 已收到数据。 |
串口不断打印 recv eorro | 发送端是否运行、CANH/CANL、共地、终端、位率和收发器供电 | 该文字也会在 FIFO 暂时没有新帧时打印;它不是 HAL 的精确错误码,不能单凭这行判断故障类型。 |
| 能收数据但内容不合理 | 查看接收 ID、DLC 和 5 个字节;检查 DHT11 校验及采样间隔 | 当前显示逻辑只取第 0、2 字节,没有在接收端验证 ID、长度和校验。 |
| 一上电芯片发热或掉线 | 立即断电,重查 VP230/TJA1050 型号与 VCC、GND | 不能用“CAN 模块都要 5 V”作为接线依据。 |
排查时建议从物理层向上进行:先查供电与共地,再查终端和 CANH/CANL,再确认两端 500 kbit/s,最后检查宏、目标配置、ID/DLC 和业务数据。这样比反复改代码更容易定位问题。
参考资料
- 课程配套 CAN总线入门书:核对车载应用、控制器/收发器分层、线形拓扑、差分电平和高速 CAN 两端终端电阻。
- 本节配套 main.c、canif.c、dht11.c、01Can_Test.ioc 与 Keil 工程:核对业务源码、过滤器、位率、引脚和目标配置的实际状态。
- 课程配套 DHT11 模块手册 与 0.96 寸 OLED 新手文档:核对 DHT11 的电源、上拉、数据顺序、最小采样间隔,以及课程四针 OLED 模块的接口与供电范围。
- ST 官方 STM32F103x8/xB 数据手册 与 STM32F103xC/xD/xE 数据手册:核对 C8T6 的 64 KB Flash、ZET6 的 512 KB Flash 以及 CAN 外设资源。
- TI 官方 SN65HVD230 数据手册 与 NXP 官方 TJA1050 数据手册:核对两种常见收发器不同的供电范围。
- Keil 官方 Download Function 说明 与 STM32 Flash 算法列表:核对
Reset and Run的作用以及 128 kB 算法名称的含义。