CAN实验演示

Tutorial: 卓越工程师 Category: 嵌入式 Published: 2026-10-23 11:05:29 Views: 0 Likes: 0 Comments: 0

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 引脚连接。

节点/模块引脚连接说明
ZET6PB6OLED SCLI2C1 时钟线
ZET6PB7OLED SDAI2C1 数据线
ZET6PA12/CAN_TX本端收发器 CTX/TXDMCU 发出的 CAN 位流进入收发器
ZET6PA11/CAN_RX本端收发器 CRX/RXD收发器把总线状态送回 MCU
C8T6PA12/CAN_TX本端收发器 CTX/TXD发送端 CAN 控制器输出
C8T6PA11/CAN_RX本端收发器 CRX/RXD发送端仍需接 RX,以完成仲裁、ACK 和错误检测
C8T6PA5DHT11 DATA单线双向数据;裸传感器需合适的上拉电阻,模块可能已自带
两只收发器CANHCANH ↔ CANH不能与 CANL 交叉
两只收发器CANLCANL ↔ CANL建议与 CANH 一起使用双绞线
两块板/模块GND公共地台架实验建议增加地线,给收发器提供共同参考电位

上电前按以下顺序检查:

  1. 所有设备断电,先核对丝印,不根据导线颜色猜引脚;
  2. 确认 CANH 对 CANH、CANL 对 CANL,且两块板共地;
  3. 两节点分别位于总线两端时,两端各放一个 120 Ω 终端。若模块已带可使能的 120 Ω 电阻,不要再重复并联;
  4. 断电测量 CANH 与 CANL,两个 120 Ω 并联时应约为 60 Ω。读数不是精确 60 Ω 也可能受电路影响,但接近 0 Ω 通常表示短路;
  5. 最后再接电源并上电,观察芯片或稳压器是否异常发热。

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,对应 TI SN65HVD230D,其 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.cCAN 过滤、启动、发送和接收封装
DHT11/dht11.cPA5 单总线读取及 5 字节校验
OLED/oled.cI2C OLED 写命令、写数据和字符显示
01Can_Test.iocZET6 工程的时钟、CAN、I2C 与引脚配置
MDK-ARM/01Can_Test.uvprojxKeil 目标器件、存储区和烧写算法配置

.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;只改编辑器文本而不重新编译,芯片里仍是旧固件。

建议按下面流程分别烧写:

  1. **ZET6 接收端:**把宏设为 1,在 Keil 中确认目标器件为 STM32F103ZE、Flash 为 512 KB、高密度启动文件和烧写算法匹配,然后编译并烧写 ZET6。
  2. **断电换线:**关闭目标板电源后再移动 ST-Link。按 SWDIO、SWCLK、GND、目标电压参考逐根核对,不能因为两块板都是四针接口就假定针序相同。
  3. **C8T6 发送端:**把宏设为 0,把目标器件、启动文件、链接存储范围和 Flash 算法全部切换到 STM32F103C8,再重新编译并烧写 C8T6。
  4. 两块板都恢复正常供电后再观察 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 数值随之改变,说明采集、发送、物理总线、接收和显示链路都在工作。但这只证明数值能够变化,不能证明传感器已经精确校准,也不能覆盖总线错误恢复等测试。

源码还有三项实验性限制需要知道,但本节按要求不改原代码:

  1. DHT11 手册规定采样间隔不得低于 1 s,源码却只延时 100 ms,可能反复得到旧值或读数失败;
  2. DHT_Read() 会返回校验结果,但发送分支没有判断返回值,读取失败时仍可能发送缓冲区中的数据;
  3. 接收过滤器掩码全为 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 和业务数据。这样比反复改代码更容易定位问题。

参考资料