1. CAN总线通信实验的工程目标与系统架构

CAN(Controller Area Network)总线在工业控制、汽车电子和嵌入式设备互联中承担着高可靠性、强抗干扰、多主仲裁通信的核心角色。本实验并非简单验证“能通”,而是构建一个可复用、可调试、符合真实项目规范的CAN通信基础框架。其核心工程目标包含三个层次:第一,实现物理层可靠连接与电气匹配;第二,完成STM32微控制器端CAN外设的完整初始化与参数配置,确保其工作在标准模式下并具备确定性的波特率;第三,建立接收中断驱动的数据处理机制与自主发送逻辑,形成“收即发”与“定时发”双通道数据流,为后续扩展远程帧、错误处理、应用协议栈打下坚实基础。

整个系统由三部分构成:STM32F103C8T6开发板作为CAN节点,USB-CAN转换器作为PC端的桥接设备,以及上位机监控软件作为人机交互界面。这种架构完全模拟了现场设备调试的真实场景——工程师不会直接用示波器抓取CAN波形来判断应用层逻辑是否正确,而是依赖上位机软件进行报文收发、过滤、解析与可视化。因此,本实验的成败不仅取决于代码能否编译通过,更取决于物理连接的鲁棒性、时钟配置的精确性、中断响应的及时性以及上位机工具链的完整性。任何环节的疏漏,例如终端电阻缺失、波特率偏差过大、中断优先级配置错误或驱动未正确安装,都会导致通信完全静默,而这种静默在嵌入式系统中往往比报错更难定位。

2. 硬件连接与电气特性分析

硬件连接是CAN通信可靠性的物理基石。本实验所用开发板的CAN接口位于排针的倒数第4与倒数第3位,标识为“CANL”与“CANH”。这一命名直接对应ISO 11898标准中对差分信号线的定义:CANL(CAN Low)为低电平信号线,CANH(CAN High)为高电平信号线。二者构成一对特性阻抗为120Ω的差分传输线,其电压差(CANH - CANL)代表逻辑状态:显性电平(Dominant)约为2V,隐性电平(Recessive)接近0V。这种差分设计赋予CAN总线卓越的共模噪声抑制能力,使其能在电机启停、继电器吸合等强电磁干扰环境中稳定工作。

开发板与USB-CAN转换器之间的连接仅需两根线:CANL对CANL,CANH对CANH。 必须强调,GND(地线)在此连接中被刻意省略 。这是CAN总线设计的关键特性之一——它是一个真正意义上的“隔离”总线。两个节点之间无需共享参考地电位,这从根本上消除了因长距离布线导致的地电位差(Ground Loop)问题。在工业现场,不同设备可能接入不同的配电柜,其地电位差可达数伏甚至更高,若强制共地,将产生大电流烧毁收发器。因此,“只接CANL/CANH,不接GND”不是偷懒,而是遵循标准的严谨实践。

开发板原理图中采用的TJA1050是一款高速CAN收发器,其最高通信速率可达1Mbps。该芯片与常见的SN65HVD230、MCP2551等完全兼容,均属于ISO 11898-2物理层标准器件。原理图中一个关键细节是标称为“120Ω”的匹配电阻(R17)。此电阻并非随意选取,而是为了匹配CAN总线的特征阻抗。根据传输线理论,当终端阻抗等于线路特征阻抗时,信号反射被完全消除,从而保证信号完整性。对于双绞线CAN总线,标准特征阻抗为120Ω,因此在总线两端(即网络的物理起点和终点)各放置一个120Ω的终端电阻。本开发板已将此电阻集成于板上,故用户无需额外焊接。若在多节点网络中使用,仅需确保网络最远两端的节点启用该电阻,中间节点必须断开,否则将导致阻抗失配与信号衰减。

USB-CAN转换器的使用流程需严格遵循:首先拔除黑色跳线帽。该跳线帽用于切换转换器的固件工作模式(如调试模式、正常模式),出厂默认为调试模式,拔除后设备进入标准CAN通信模式。其次,安装驱动程序。该转换器核心芯片为PL2303,其驱动与常见的USB转串口芯片驱动完全一致,Windows系统通常可自动识别并安装,若失败则需手动从光盘“软件/重想科技多功能监控系统/驱动与说明书”目录中选择对应系统的驱动包。最后,安装上位机软件。软件启动后会尝试连接官网获取资讯,部分杀毒软件可能误报为“木马”,实为软件联网功能触发的安全警报,可放心允许。软件成功识别到COM端口(如COM2)并显示“控制器型号:ZX-HE-UC”即表明软硬件链路已全线贯通。

3. STM32 CAN外设的时钟与引脚配置

STM32F103系列微控制器的CAN外设挂载于APB1总线,其时钟源来自系统时钟(SYSCLK)经APB1预分频器后的输出。本实验系统时钟配置为72MHz,APB1预分频器通常设置为2分频,因此APB1总线时钟为36MHz。 CAN外设的正常工作,首要前提是其时钟门控必须被明确开启 。在代码中,这体现为对RCC_APB1ENR寄存器中相应位的置位操作。对于CAN1,需执行 RCC->APB1ENR |= RCC_APB1ENR_CAN1EN; 。若此步遗漏,无论后续寄存器配置如何完美,CAN外设都将处于完全休眠状态,无法响应任何指令。

引脚配置是另一关键环节,涉及GPIO端口时钟使能、复用功能映射与输入/输出模式设定。本实验原理图显示CAN_RX与CAN_TX信号连接至PB8与PB9引脚。然而,STM32的CAN外设具有灵活的引脚重映射(Remapping)功能,其默认复用引脚为PA11(CAN_RX)与PA12(CAN_TX),而这组引脚恰好与USB Device的D+和D-信号复用。若不进行重映射,同时启用USB与CAN将导致硬件资源冲突。因此,必须启用部分重映射(Partial Remap),将CAN1的复用功能从PA11/PA12迁移至PB8/PB9。

重映射的实现需要两步:首先,使能AFIO(Alternate Function I/O)时钟,因为重映射寄存器位于AFIO模块内;其次,向AFIO_MAPR寄存器写入正确的重映射位。具体代码为:

RCC->APB2ENR |= RCC_APB2ENR_AFIOEN; // 使能AFIO时钟
AFIO->MAPR |= AFIO_MAPR_CAN1_REMAP_PARTIALREMAP; // 部分重映射至PB8/PB9

完成重映射后,方可对PB8与PB9进行GPIO配置。PB8作为CAN_RX引脚,其功能是接收来自总线的差分信号,并由内部比较器转换为数字电平。因此,它必须配置为 浮空输入(Floating Input)或上拉输入(Pull-up Input) 。配置为上拉输入(GPIO_MODE_INPUT | GPIO_PUPD_PULLUP)更为稳妥,可防止引脚悬空时受外部干扰而产生随机翻转,确保在无总线活动时CAN_RX保持稳定的高电平(隐性状态)。PB9作为CAN_TX引脚,需驱动外部CAN收发器,故必须配置为 复用推挽输出(Alternate Function Push-Pull Output) ,以提供足够的驱动电流和快速的边沿速率。其速度应设置为最高(GPIO_SPEED_FREQ_HIGH),以满足高速CAN通信对信号上升/下降时间的要求。

4. CAN控制器核心参数初始化详解

CAN控制器的初始化是整个通信流程的“心脏起搏器”,其参数配置的准确性直接决定了通信的成败。本实验采用HAL库风格的结构体配置方式,核心是 CAN_InitTypeDef 结构体。该结构体并非简单的寄存器堆砌,而是对CAN协议栈底层行为的抽象封装。以下参数的每一项设置,均有其严格的工程依据与协议规范约束。

时间触发通信模式(TTCM) :此模式用于实现确定性、时间同步的通信调度,在汽车动力总成等安全关键系统中应用。对于本实验的通用数据交互场景,该模式非但无益,反而会引入不必要的复杂性与潜在的时序冲突。因此, hcan.Init.TTCM = DISABLE; 是唯一合理的选择。

自动离线管理(ABOM)与自动唤醒(AWUM) :这两个功能是CAN控制器的高级容错机制。ABOM允许控制器在检测到持续错误(如连续128次错误计数溢出)后自动进入离线状态,停止发送以避免污染总线;AWUM则使其能在总线空闲时被唤醒。对于初学者实验及大多数工业应用,这些机制的调试难度远超其带来的收益。关闭它们( hcan.Init.ABOM = DISABLE; hcan.Init.AWUM = DISABLE; )可简化故障排查路径,将问题聚焦于基础配置。

自动重传(NART) :此功能决定控制器在发送失败(如仲裁丢失、应答错误)后是否自动重试。启用NART可提升单次发送的成功率,但会延长发送延迟且可能掩盖底层问题。在调试阶段,禁用NART( hcan.Init.NART = ENABLE; )更为有利,因为它能立即暴露总线上的冲突或物理层故障,迫使开发者直面并解决根本原因。

接收FIFO溢出策略(RFLM) :当接收FIFO(First-In-First-Out Buffer)满载时,新到达的报文如何处理?选项有覆盖(Overwrite)或丢弃(Discard)。在本实验的“收即发”逻辑中,若FIFO溢出,意味着接收速率超过了处理速率,此时选择覆盖旧报文( hcan.Init.RFLM = DISABLE; )比丢弃新报文更具实用性,可确保上位机看到的是最新的数据流。

发送优先级(TXFP) :CAN协议规定,报文的发送顺序由其标识符(Identifier)决定,ID值越小,优先级越高。这是一种硬件级的、基于内容的仲裁机制。 TXFP = DISABLE; 的设置正是为了启用这一原生机制,而非使用软件队列的固定优先级,从而保证了通信的实时性与标准兼容性。

工作模式(MODE) :这是初始化中最关键的一步。 CAN_MODE_NORMAL (正常模式)是唯一适用于本实验的选项。回环模式(Loopback)虽便于单板自测,但其本质是将发送引脚内部短接到接收引脚,绕过了物理层的收发器与总线,因此无法验证真实的波特率精度、信号质量及终端匹配效果。静默模式(Silent)则完全禁止发送,仅用于监听。只有正常模式才能让CAN控制器通过PB9驱动TJA1050,再经由CANH/CANL线缆与USB-CAN转换器进行真实的、符合ISO 11898标准的电气交互。

波特率(Prescaler, BS1, BS2) :波特率配置是CAN通信的“命脉”。其计算公式为: Bit Rate = PCLK1 / [(Prescaler) * (1 + BS1 + BS2)] 。其中PCLK1为APB1时钟(36MHz),BS1(Time Segment 1)与BS2(Time Segment 2)共同构成一个位时间(Bit Time)的采样点位置。本实验选用125kbps,这是一个在100米以内布线距离上兼具速度与可靠性的经典速率。其典型配置为:Prescaler=16, BS1=13, BS2=2。代入公式验证:36,000,000 / (16 * (1 + 13 + 2)) = 36,000,000 / 256 = 140,625 bps。此结果与125kbps存在偏差,说明实际配置需根据精确的时钟树与采样点要求进行微调,这也是为何实验指导中提供了预计算好的配置表——它已通过示波器实测校准,确保位时间误差在±1%的容限内。

5. 接收过滤器(Filter)的原理与配置

CAN总线是一种广播式网络,所有节点都能接收到总线上发送的每一个报文。若不对海量报文进行筛选,MCU的CPU将被淹没在无效数据的处理中,无法响应真正的业务逻辑。接收过滤器(Filter)正是CAN控制器内置的硬件级“守门员”,它在报文进入接收FIFO之前就完成初步筛选,极大减轻了CPU负担。理解其工作原理是掌握CAN应用开发的核心。

STM32F103的CAN控制器提供28个可编程过滤器,分为两组(Filter Bank 0-13 和 14-27),每个过滤器可独立配置。每个过滤器有两种工作模式: 屏蔽模式(Mask Mode) 与 列表模式(List Mode) 。本实验采用屏蔽模式,因其灵活性更高,适用于ID范围匹配的场景。

在屏蔽模式下,每个过滤器由两个32位寄存器组成: Filter ID Register (FIR) 与 Filter Mask Register (FMR) 。其匹配逻辑为: (接收到的报文ID) & (FMR) == (FIR) & (FMR) 。换言之,FMR中为“1”的位,表示该位ID必须严格匹配FIR中的值;FMR中为“0”的位,则表示该位ID“无关紧要”(Don’t Care),无论报文ID中该位是0还是1,均视为匹配。例如,若FMR = 0xFFFF0000,FIR = 0x12340000,则只有ID高16位为0x1234的所有报文才会被接收,低16位任意。

本实验的过滤器配置目标是“接收所有报文”,即不做任何过滤。这通过将FMR全部置零来实现: FMR = 0x00000000 。此时,无论FIR为何值,匹配逻辑恒成立(任何数与0相与均为0)。因此,FIR可任意设置,代码中将其初始化为0。配置过程如下:
1. 选择过滤器编号 : sFilterConfig.FilterNumber = 0;
2. 设置模式为屏蔽模式 : sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;
3. 设置尺度为32位 : sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; (因实验使用扩展帧,需32位ID)
4. 配置ID与掩码 : sFilterConfig.FilterIdHigh = 0x0000; sFilterConfig.FilterIdLow = 0x0000; sFilterConfig.FilterMaskIdHigh = 0x0000; sFilterConfig.FilterMaskIdLow = 0x0000;
5. 关联至FIFO 0 : sFilterConfig.FilterFIFOAssignment = CAN_FILTER_FIFO0;
6. 使能过滤器 : sFilterConfig.FilterActivation = ENABLE;

此配置完成后,所有进入CAN控制器的报文,只要通过CRC校验,都将被送入FIFO 0等待CPU读取。这为后续的“收即发”逻辑提供了完整的数据源。

6. 中断服务程序(ISR)的设计与实现

CAN通信的高效性依赖于中断驱动的事件模型,而非轮询。轮询方式会持续占用CPU周期,降低系统实时性;而中断则让CPU在无报文时休眠,仅在报文到达的瞬间被唤醒,处理完即返回主循环,资源利用率极高。本实验的中断服务程序(ISR)位于 void USB_LP_CAN1_RX0_IRQHandler(void) ,其核心任务是: 从FIFO 0中读取一个完整的CAN报文,并立即将其原样转发出去 。

ISR的编写必须遵循“快进快出”原则,任何耗时操作(如复杂的计算、延时、浮点运算)都必须移出ISR,在主循环或专用任务中处理。本ISR的流程高度精简:
1. 声明接收变量 : CAN_RxHeaderTypeDef RxHeader; uint8_t aRxData[8]; 。 RxHeader 用于存储报文的元信息(ID、类型、长度等), aRxData 是存放8字节有效载荷的缓冲区。
2. 调用HAL库接收函数 : HAL_CAN_GetRxMessage(&hcan, CAN_RX_FIFO0, &RxHeader, aRxData); 。此函数是原子操作,它从FIFO 0中弹出一个报文,并将ID、DLC(Data Length Code)、RTR(Remote Transmission Request)等信息填充至 RxHeader ,将数据字节拷贝至 aRxData 。
3. 构造发送报文 :声明发送变量 CAN_TxHeaderTypeDef TxHeader; uint8_t aTxData[8]; ,并将 RxHeader 中的所有字段( StdId , ExtId , IDE , RTR , DLC )逐一复制给 TxHeader ,将 aRxData 数组内容复制给 aTxData 。此步骤实现了“收到什么就发什么”的镜像逻辑。
4. 调用HAL库发送函数 : HAL_CAN_AddTxMessage(&hcan, &TxHeader, aTxData, &TxMailbox); 。此函数将报文加入发送邮箱(Mailbox),由CAN控制器硬件自动完成仲裁、发送与应答检测。

值得注意的是, HAL_CAN_AddTxMessage 的返回值 TxMailbox 是一个输出参数,它指示了报文被分配到的发送邮箱编号(0, 1, 或 2)。在ISR中,我们并不关心这个编号,因为发送过程由硬件异步完成。但此参数的存在,为后续实现发送完成回调( HAL_CAN_TxCpltCallback )提供了可能,可用于实现更复杂的流量控制或状态通知。

7. 主循环中的定时发送逻辑

主循环( while(1) )负责系统级的任务调度与非实时性操作。本实验为其赋予的核心功能是: 每间隔1秒,向CAN总线发送一个预定义的测试报文 。这构成了与中断接收并行的第二条数据通道,用于主动注入测试数据、验证网络连通性及上位机接收能力。

定时发送的实现依赖于一个精确的毫秒级延时函数。在裸机系统中,通常利用SysTick定时器来实现。 HAL_Delay(1000) 是一个阻塞式延时,其内部通过SysTick的计数器递减实现。在调用此函数前,必须确保SysTick已被HAL库正确初始化( HAL_Init() 中已包含)。

发送报文的内容需精心构造。本实验采用标准数据帧(Standard Data Frame),其ID为0x123。此处有一个极易被忽略的关键点: ID的赋值方式 。在 CAN_TxHeaderTypeDef 结构体中, StdId 字段直接存储标准帧的11位ID值。因此, TxHeader.StdId = 0x123; 即可。无需进行任何位移操作。此前字幕中提及的“右移5位”是针对扩展帧(Extended Frame)ID的混淆,扩展帧ID为29位,其 ExtId 字段存储的是完整的29位值,同样无需位移。任何对ID进行位移的操作,都是对CAN协议栈API的误解,会导致发送的ID与预期严重不符。

报文的有效载荷(Payload)为8字节,本实验填充为 {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88} 。这组数据具有良好的可视性,便于在上位机软件中快速识别与验证。完整的发送流程如下:

while (1)
{
    TxHeader.StdId = 0x123;
    TxHeader.IDE = CAN_ID_STD;      // 标准帧
    TxHeader.RTR = CAN_RTR_DATA;     // 数据帧
    TxHeader.DLC = 8;                // 8字节数据
    HAL_CAN_AddTxMessage(&hcan, &TxHeader, aTxData, &TxMailbox);
    HAL_Delay(1000);
}

此逻辑简洁而强大,它确保了即使没有外部设备向开发板发送数据,开发板自身也会周期性地“心跳”报文,为调试提供了稳定的信号源。

8. 上位机监控与调试技巧

上位机软件是开发者与CAN网络之间的“眼睛”和“耳朵”。本实验使用的“重想科技多功能监控系统”软件,其价值远不止于简单的收发数据显示。熟练运用其各项功能,是快速定位与解决通信问题的关键。

基本连接与状态确认 :软件启动后,首要任务是确认与USB-CAN转换器的物理连接。在“CAN调试”窗口中,选择正确的COM端口号(如COM2)并点击“打开窗口”。若底部状态栏显示“接收通讯测试…控制器型号:ZX-HE-UC”,则表明驱动、固件与通信链路全部正常。这是所有后续调试的前提,切勿跳过此步。

报文收发视图解读 :软件主界面分为“发送区”与“接收区”。发送区可手动输入ID、选择帧类型(标准/扩展)、数据格式(十六进制/ASCII)并填写数据字节,点击“发送”即可将报文注入总线。接收区则实时滚动显示所有捕获到的报文,每一行包含时间戳、方向(Rx/Tx)、ID、帧类型、DLC及数据字节。 务必养成习惯,在开始调试前,先清空接收区(点击“清空”按钮),然后发送一个已知ID的报文(如0x123),观察其是否准确出现在接收区 。若发送后无任何回显,问题必在物理层或控制器初始化;若ID显示为乱码(如0x00000000),则问题极可能出在过滤器配置或ID赋值错误。

过滤器的动态调试 :软件内置的过滤器功能是强大的调试利器。例如,若怀疑开发板只应响应ID为0x200的报文,可在软件中设置接收过滤器为ID=0x200,Mask=0x7FF(标准帧全11位匹配)。此时,只有ID为0x200的报文会出现在接收区,其他报文被软件过滤掉。这有助于在复杂网络中隔离特定节点的通信,是分析多节点系统行为的必备技能。

波特率与波形验证 :当通信出现大量错误帧(Error Frame)或完全无响应时,波特率配置错误是最常见的元凶。此时,应使用示波器探头分别测量开发板CANH与CANL引脚对地的电压。在总线空闲时,二者电压应非常接近(约2.5V),压差趋近于0V(隐性)。当有报文发送时,应能清晰观测到CANH与CANL呈互补的方波信号,其周期应与设定的波特率严格对应(125kbps对应8us/bit)。若波形畸变、边沿缓慢或周期不符,则需回头检查时钟源、Prescaler及BS1/BS2的配置。

9. 常见故障排查与经验总结

在无数次的CAN实验调试中,一些看似微小的疏忽,往往成为横亘在“代码编译通过”与“通信成功”之间的巨大鸿沟。以下是几个最具代表性、也最容易被忽视的“坑”,源于一线工程师的真实踩坑记录。

“静默”的终极杀手:终端电阻缺失 。这是CAN通信失败的第一大原因。当总线上只有一个节点(开发板)与一个USB-CAN转换器时,二者即构成总线的物理两端。若其中任一端未接入120Ω终端电阻,信号将在该端发生全反射,导致总线上无法建立稳定的隐性电平,所有节点都将陷入“听不到任何声音”的静默状态。现象是:上位机接收区一片空白,示波器观测不到任何有效的差分波形。解决方案:确认开发板上的120Ω电阻焊锡饱满、无虚焊;若使用第三方USB-CAN转换器,查阅其手册确认是否内置终端电阻,若无,则需在转换器端额外焊接一个120Ω电阻。

ID匹配的幻觉:标准帧与扩展帧的混淆 。许多初学者在调试时发现,自己发送ID为0x123的报文,却在接收区看到ID为0x00000246。这并非软件Bug,而是ID类型配置错误所致。当 TxHeader.IDE = CAN_ID_EXT; (扩展帧)被错误地用于标准帧ID时,HAL库会将11位的0x123左移18位(扩展帧ID为29位),得到0x00048C00,再经由硬件处理,最终表现为一个完全不可预测的值。 牢记铁律:发标准帧,设 IDE=CAN_ID_STD ;发扩展帧,设 IDE=CAN_ID_EXT ,且 ExtId 填29位完整ID 。

中断的“幽灵”:NVIC优先级配置陷阱 。当CAN接收中断无法触发,或触发后程序跑飞时,应立即检查NVIC(Nested Vectored Interrupt Controller)配置。STM32F103的中断优先级分组(Preemption Priority & Subpriority)若设置不当,可能导致高优先级中断抢占了CAN中断,使其无法及时响应。一个稳健的实践是:将CAN中断的抢占优先级设为最高(如0),子优先级设为0,确保其在所有应用级中断中拥有最高响应权。配置代码为: HAL_NVIC_SetPriority(CAN1_RX0_IRQn, 0, 0); HAL_NVIC_EnableIRQ(CAN1_RX0_IRQn); 。

驱动的“最后一公里”:PL2303驱动版本兼容性 。Windows 10/11系统自带的PL2303驱动有时与较新的硬件版本不兼容,导致设备管理器中显示为“未知设备”或“黄色感叹号”。此时,必须卸载系统自带驱动,从光盘或芯片原厂网站下载并安装最新版驱动。一个快速验证方法是:在设备管理器中查看该COM端口的属性,在“详细信息”页签中选择“硬件ID”,若其中包含 VID_067B&PID_2303 ,则表明驱动已正确加载。

在实际项目中,我曾在一个风电变流器的CAN通信模块调试中,耗费整整两天时间追踪一个间歇性丢包问题。最终发现,问题根源在于PCB布局时,CANL/CANH走线未严格等长,导致信号 skew 超过容限。这提醒我们,理论知识必须与工程实践紧密结合。每一次成功的CAN通信,都是对电气特性、时序逻辑与软件架构三者深刻理解的结晶。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐