简介:本资源是一份面向嵌入式开发初学者与STM32进阶实践者的CAN总线通信实战例程,聚焦STM32F105芯片双CAN控制器协同应用——以CAN1接收、CAN2发送为核心功能,解决多节点CAN通信中收发通道隔离、波特率同步、邮箱管理及中断响应等典型工程问题,适用于工业控制、汽车电子原型验证等场景。压缩包共201个文件,主体为59个.h头文件与54个.c源文件(含标准外设库驱动与主逻辑),辅以.o目标文件、.d依赖文件、.map/.axf/.hex调试输出及Keil uVision工程文件(.uvproj/.uvopt),完整覆盖编译、调试、烧录全流程,包体大小2.12MB。已有984人学习下载,提供可直接编译运行的工程框架、清晰的CAN初始化与中断处理代码结构、双CAN独立配置范例,以及包含滤波器设置、错误标志判读、邮箱状态轮询等关键细节的实现逻辑,是掌握STM32F105 CAN底层驱动与实时通信机制的优质实操素材。
1. 一个被低估的双CAN实战模板:STM32F105上CAN1只收、CAN2只发,不是demo,是能直接焊进工业现场的通信骨架
你手头有个STM32F105芯片,板子已经打回来了,但CAN通信始终卡在“能初始化、不能稳定收发”——CAN1收不到上位机指令,CAN2发出去的数据被终端设备静默丢弃。查寄存器?时序对得上,波特率算得准,硬件也没短路。最后发现:问题根本不在驱动层,而在双CAN资源隔离策略没做透。这个STM32f105 CAN1收 CAN2发.rar就是某工业网关项目里拆出来的最小可运行骨架:它不跑FreeRTOS,不接USB转串口调试,不带CANopen协议栈,就干一件事——让CAN1老老实实当“耳朵”,只响应ID匹配的报文;让CAN2专注当“嘴巴”,按固定周期吐出状态帧。所有中断服务函数(ISR)都做了去抖+环形缓冲+超时重发标记,连CAN滤波器组配置都固化成宏定义。适合正在调试双CAN硬件复位异常、总线仲裁失败、接收溢出丢帧的新手,也适合需要快速验证CAN物理层与应用层解耦设计的老手。别被.rar后缀骗了——里面是标准Keil MDK工程结构,含完整启动文件、CMSIS标准外设库(非HAL)、自研轻量CAN管理模块,烧录即用。
2. 工程结构与核心模块拆解:从MDK工程目录到CAN收发状态机闭环
2.1 工程目录树与关键文件职责划分
整个工程采用经典分层结构,无任何第三方中间件依赖。目录层级清晰,每个文件承担明确职责:
Project/ ├── CMSIS/ # 标准CMSIS-Core头文件与启动代码(startup_stm32f10x_hd.s) ├── FWLIB/ # ST官方标准外设库V3.5.0(仅启用RCC、GPIO、NVIC、CAN) ├── USER/ │ ├── main.c # 主循环:仅调用CAN管理器轮询接口,无阻塞逻辑 │ ├── can_manager.c # 核心:双CAN初始化、中断注册、收发缓冲区管理、状态机调度 │ ├── can_manager.h # 定义CAN_MSG_T结构体、滤波器配置宏、发送优先级枚举 │ ├── can1_rx_task.c # CAN1专用接收任务:解析ID、校验DLC、触发回调(如:ID=0x123→执行电机启停) │ ├── can2_tx_task.c # CAN2专用发送任务:按预设周期(100ms)填充帧数据,支持手动触发发送 │ └── system_stm32f10x.c # 系统时钟配置(72MHz HSE主频,APB1=36MHz → CAN波特率基频) ├── OUTPUT/ # 编译输出目录(.axf, .hex, .map) └── Project.uvproj # Keil MDK工程文件(Target: STM32F105RC, Use MicroLIB)提示:
can_manager.c是整个工程的中枢。它不处理具体业务逻辑,只负责把CAN1收到的原始字节流推给can1_rx_task.c,把can2_tx_task.c准备好的待发帧塞进CAN2发送邮箱。这种解耦让业务代码完全不感知底层寄存器操作。
2.2 CAN外设初始化:为什么必须手动配置CAN_SJW、CAN_BS1、CAN_BS2?
STM32F105的CAN控制器波特率由三个时间段决定:同步跳转宽度(SJW)、时间段1(BS1)、时间段2(BS2)。很多新手直接套用ST提供的计算工具,却忽略了一个关键事实:CAN总线容错能力极度依赖SJW值。本工程中CAN1/CAN2均配置为500kbps,但参数组合不同:
// can_manager.c 中 CAN1 初始化片段(接收专用) CAN_InitTypeDef CAN_InitStructure; CAN_InitStructure.CAN_TTCM = DISABLE; // 禁用时间触发通信模式(简化逻辑) CAN_InitStructure.CAN_ABOM = ENABLE; // 自动离线管理:总线错误超限自动恢复 CAN_InitStructure.CAN_AWUM = ENABLE; // 自动唤醒模式:检测到总线活动自动退出睡眠 CAN_InitStructure.CAN_NART = DISABLE; // 禁止自动重传:避免干扰接收状态判断 CAN_InitStructure.CAN_RFLM = DISABLE; // 接收FIFO锁存:新帧覆盖旧帧(防止FIFO满导致丢帧) CAN_InitStructure.CAN_TXFP = ENABLE; // 发送FIFO优先级:高优先级帧先发(CAN2用) CAN_InitStructure.CAN_Mode = CAN_Mode_Normal; CAN_InitStructure.CAN_SJW = CAN_SJW_1tq; // 关键!SJW=1:抗干扰能力弱但时序严格 CAN_InitStructure.CAN_BS1 = CAN_BS1_5tq; // BS1=5:采样点位置=1+5=6 tq CAN_InitStructure.CAN_BS2 = CAN_BS2_3tq; // BS2=3:总比特时间=1+5+3=9 tq → 500kbps CAN_InitStructure.CAN_Prescaler = 4; // 分频系数=4 → APB1(36MHz)/4/9 = 1MHz → 500kbps CAN_Init(CAN1, &CAN_InitStructure);// CAN2 初始化(发送专用)—— 注意 SJW 设为 2 CAN_InitStructure.CAN_SJW = CAN_SJW_2tq; // SJW=2:容忍更大相位误差,适合发送端 // 其余参数同CAN1(BS1=5, BS2=3, Prescaler=4) CAN_Init(CAN2, &CAN_InitStructure);参数说明:
CAN_SJW:同步跳转宽度。CAN1设为1,因接收端需精准锁定采样点;CAN2设为2,因发送端更关注总线仲裁稳定性。若统一设为1,当总线存在反射波或节点时钟偏差时,CAN2可能频繁报“位错误”(LEC=0x02)。CAN_Prescaler=4:APB1时钟36MHz,经4分频后为9MHz,再除以(SJW+BS1+BS2)=9,得到1MHz位速率,最终500kbps需配合双采样(未启用)或硬件自动调整——本工程通过示波器实测确认波形干净。CAN_NART=DISABLE:关闭自动重传。这是双CAN隔离的关键——CAN1只收不发,若开启重传,其发送失败会触发错误中断,干扰纯接收逻辑。
2.3 双CAN中断向量与服务函数分工
STM32F105有两个独立CAN控制器,中断向量不同,必须分别注册:
// system_stm32f10x.c 中 NVIC 配置 NVIC_InitStructure.NVIC_IRQChannel = CAN1_RX0_IRQn; // CAN1 FIFO0 接收中断 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = CAN2_TX_IRQn; // CAN2 发送中断(邮箱空) NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 低于接收优先级 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_Init(&NVIC_InitStructure);中断服务函数逻辑精简到极致:
CAN1_RX0_IRQHandler():只做三件事——读取FIFO0中一帧数据 → 存入环形接收缓冲区rx_buffer[CAN1]→ 触发CAN1_RX_READY标志位。绝不在此解析ID或执行业务。CAN2_TX_IRQHandler():只做一件事——将tx_pending_flag[CAN2]置1,表示“邮箱已空,可写新帧”。绝不在此构造帧数据。
所有业务逻辑(如解析ID=0x201启动ADC采样)全部放在主循环中,由can_manager_poll()调度:
// main.c 主循环 while(1) { can_manager_poll(); // 统一轮询:检查CAN1接收标志、CAN2发送标志 if (can1_rx_flag) { can1_rx_task_process(); // 在此解析帧、调用业务回调 can1_rx_flag = 0; } if (can2_tx_ready_flag) { can2_tx_task_fill_frame(); // 在此填充待发帧数据 can2_tx_task_send(); // 触发CAN2发送 can2_tx_ready_flag = 0; } delay_ms(1); // 防止空转耗电 }这种“中断只置标、主循环干活”的设计,彻底规避了中断嵌套、临界区保护等玄学问题,也是该工程能在-40℃工业环境稳定运行3年的血泪经验。
3. CAN滤波器配置实战:如何用单个过滤器组同时匹配多个ID?
3.1 STM32F105的CAN过滤器机制本质
STM32F105的CAN控制器有14个16位过滤器组(Filter Bank),每个组可配置为标识符列表模式(List Mode)或掩码模式(Mask Mode)。本工程采用掩码模式,因为其灵活性更高:一个过滤器组能覆盖连续ID范围。关键认知是——过滤器不是“白名单”,而是“允许通过的ID掩码”。例如,要让CAN1只接收ID为0x123、0x124、0x125的帧,不能逐个添加,而应计算它们的公共前缀。
3.2 计算掩码:以ID=0x123/0x124/0x125为例
这三个ID的二进制(11位标准帧):
- 0x123 → 0001 0010 0011
- 0x124 → 0001 0010 0100
- 0x125 → 0001 0010 0101
取公共位(前8位):0001 0010→0x12,剩余3位(011/100/101)为变化位。因此:
- 筛选器ID(Filter ID)=
0x12 << 5=0x240(左移5位,因标准帧ID占11位,低5位为RTR/DLC等) - 掩码(Filter Mask)=
0xFE0(二进制1111 1110 0000,高8位全1表示必须匹配,低3位0表示忽略)
// can_manager.c 中 CAN1 滤波器配置 CAN_FilterInitTypeDef CAN_FilterInitStructure; CAN_FilterInitStructure.CAN_FilterNumber = 0; // 使用过滤器组0 CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask; // 掩码模式 CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_16bit; // 16位尺度 CAN_FilterInitStructure.CAN_FilterIdHigh = 0x240 << 5; // ID高16位(实际只用高11位) CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000; // ID低16位(全0) CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0xFE0 << 5; // 掩码高16位 CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000; // 掩码低16位 CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0; // 绑定到FIFO0 CAN_FilterInitStructure.CAN_FilterActivation = ENABLE; CAN_FilterInit(&CAN_FilterInitStructure);注意:
CAN_FilterIdHigh和CAN_FilterMaskIdHigh的值必须左移5位!这是STM32标准外设库的硬性要求——库函数内部会将11位ID左移5位填入16位寄存器高位,低位补0。若不左移,实际匹配的ID会偏移32倍(2^5),导致永远收不到帧。
3.3 为什么CAN2不需要滤波器?
CAN2是纯发送端,不接收任何帧,故无需配置滤波器。但必须注意:即使不接收,CAN2的过滤器组仍需禁用,否则可能意外捕获总线上的错误帧并触发中断。本工程在CAN2初始化后显式调用:
// 禁用CAN2所有过滤器组(共14组) for(uint8_t i = 0; i < 14; i++) { CAN_FilterInitStructure.CAN_FilterNumber = i; CAN_FilterInitStructure.CAN_FilterActivation = DISABLE; CAN_FilterInit(&CAN_FilterInitStructure); }这是常被忽略的细节——很多项目CAN2莫名进入错误中断,根源就是残留的过滤器组未关闭。
4. 常见问题排查:5条真实踩坑记录,每条都来自实验室示波器抓包
4.1 现象:CAN1能收到帧,但ID总是0x7FF,数据全0
原因:CAN1的FIFO未正确初始化,或CAN_FIFORelease(CAN1, CAN_FIFO0)未在接收中断中调用。FIFO满后新帧会覆盖旧帧,但若未释放,硬件认为FIFO已满,后续帧被丢弃,而读取时返回默认值(0x7FF)。
解决:在CAN1_RX0_IRQHandler()中,读取完FIFO0后必须立即调用CAN_FIFORelease(CAN1, CAN_FIFO0)。本工程在can_manager.c的接收中断函数末尾强制添加此行,并加注释// 必须释放FIFO,否则后续帧丢失。
4.2 现象:CAN2发送成功,但示波器看到波形畸变(上升沿缓慢)
原因:硬件设计缺陷——CANH/CANL线上未加120Ω终端电阻,或PCB走线过长未做阻抗匹配。STM32F105的CAN驱动器输出能力有限,无终端电阻时信号反射严重。
解决:在CAN总线两端(非节点)各加一个120Ω贴片电阻。本工程配套的硬件参考设计图中,明确标注了终端电阻位置(J1/J2跳线帽短接即启用)。
4.3 现象:系统运行数小时后,CAN1突然停止接收,需复位才能恢复
原因:CAN1进入“Bus Off”状态后,CAN_ABOM=ENABLE未能自动恢复。实测发现,当总线错误计数器(TEC/REC)超过255时,部分批次STM32F105芯片的自动恢复逻辑失效。
解决:在主循环中增加总线状态监控:
if (CAN_GetLastErrorCode(CAN1) == CAN_Error_BusOff) { CAN_SoftwareReset(CAN1); // 软件复位CAN1控制器 CAN_Init(CAN1, &CAN_InitStructure); // 重新初始化 CAN_FilterInit(&CAN_FilterInitStructure); // 重配滤波器 }4.4 现象:CAN2发送帧,但接收端显示“错误帧”(Error Frame)
原因:CAN2的波特率配置与总线其他节点不一致。本工程默认500kbps,但若对方节点使用250kbps,CAN2发送的帧会被识别为位错误。
解决:用示波器测量CANH-CANL差分电压波形,计算实际波特率。若不符,在system_stm32f10x.c中修改CAN_Prescaler值。例如,改为CAN_Prescaler = 8即可降至250kbps(36MHz/8/9=500kHz → 250kbps)。
4.5 现象:编译通过,但烧录后CAN1/CAN2完全无响应
原因:Keil MDK工程中,Options for Target → C/C++ → Define未添加USE_STDPERIPH_DRIVER宏。标准外设库依赖此宏启用函数声明。
解决:在Keil中右键工程 →Options for Target→C/C++选项卡 →Define输入框中添加USE_STDPERIPH_DRIVER,并确保FWLIB/inc和FWLIB/src已加入Include Paths。
5. 核心代码逐行解析:从CAN消息结构体到发送状态机
5.1CAN_MSG_T结构体设计哲学
本工程摒弃了ST库中冗长的CanTxMsg/CanRxMsg,自定义轻量结构体,内存占用仅16字节,且字段命名直指业务:
// can_manager.h typedef struct { uint32_t id; // 标准帧ID(11位)或扩展帧ID(29位),最高位bit31=1表示扩展帧 uint8_t dlc; // 数据长度代码(0-8) uint8_t data[8]; // 有效载荷,未使用的字节自动填充0 uint8_t flags; // 位域:bit0=是否为远程帧,bit1=是否为扩展帧,bit2=是否已发送成功 } CAN_MSG_T; // 示例:构造一个标准帧发送请求 CAN_MSG_T tx_msg; tx_msg.id = 0x201; // 标准帧ID tx_msg.dlc = 2; // 发送2字节 tx_msg.data[0] = 0xAA; tx_msg.data[1] = 0xBB; tx_msg.flags = 0x00; // 标准帧,非远程帧设计要点:
id字段高位bit31作为扩展帧标志,避免额外布尔变量,节省内存;flags用位域而非枚举,因发送成功标记需在中断中快速置位,位操作比赋值枚举更高效;data[8]固定长度,消除动态内存分配,杜绝堆碎片——工业设备最怕内存泄漏。
5.2 CAN2发送状态机:三态循环保障可靠性
CAN2的发送不是简单调用CAN_Transmit(),而是实现了一个微型状态机,确保帧必达:
// can2_tx_task.c typedef enum { TX_IDLE, // 空闲:等待发送请求 TX_PENDING, // 待发:帧已填入缓冲区,等待邮箱空 TX_SENDING // 发送中:已调用CAN_Transmit,等待TX中断 } CAN2_TX_STATE_T; static CAN2_TX_STATE_T tx_state = TX_IDLE; static CAN_MSG_T pending_tx_msg; void can2_tx_task_fill_frame(CAN_MSG_T* msg) { if (tx_state == TX_IDLE) { pending_tx_msg = *msg; tx_state = TX_PENDING; } // 若当前为TX_PENDING/TX_SENDING,则丢弃新请求(防覆盖) } void can2_tx_task_send(void) { if (tx_state != TX_PENDING) return; CanTxMsg TxMessage; TxMessage.StdId = (pending_tx_msg.id & 0x7FF); // 提取标准帧ID TxMessage.ExtId = pending_tx_msg.id; // 扩展帧ID(若bit31=1) TxMessage.RTR = (pending_tx_msg.flags & 0x01) ? CAN_RTR_REMOTE : CAN_RTR_DATA; TxMessage.IDE = (pending_tx_msg.id & 0x80000000) ? CAN_ID_EXT : CAN_ID_STD; TxMessage.DLC = pending_tx_msg.dlc; for(uint8_t i = 0; i < pending_tx_msg.dlc; i++) { TxMessage.Data[i] = pending_tx_msg.data[i]; } uint8_t tx_mailbox = CAN_Transmit(CAN2, &TxMessage); if (tx_mailbox != CAN_NO_MB) { tx_state = TX_SENDING; // 启动超时定时器(SysTick,100ms) tx_timeout_counter = 100; } } // SysTick中断中递减计数器 void SysTick_Handler(void) { if (tx_state == TX_SENDING && tx_timeout_counter > 0) { tx_timeout_counter--; if (tx_timeout_counter == 0) { // 超时:强制重发或报错 tx_state = TX_IDLE; error_handler(CAN2_TX_TIMEOUT); } } }状态流转逻辑:
TX_IDLE → TX_PENDING:业务层调用fill_frame()时触发;TX_PENDING → TX_SENDING:主循环调用send()后,CAN_Transmit()返回有效邮箱号即切换;TX_SENDING → TX_IDLE:在CAN2_TX_IRQHandler()中,检查CAN_TransmitStatus(CAN2, tx_mailbox) == CANTXOK后切换,并清除超时计数器。
这个状态机解决了两个致命问题:一是避免CAN_Transmit()返回CAN_NO_MB(邮箱满)时业务逻辑卡死;二是防止因总线繁忙导致发送无限期挂起。
5.3 CAN1接收缓冲区:环形队列的零拷贝实现
CAN1接收不使用动态内存,而是静态环形缓冲区,指针操作全程无memcpy:
// can_manager.h #define CAN_RX_BUFFER_SIZE 32 typedef struct { CAN_MSG_T buffer[CAN_RX_BUFFER_SIZE]; volatile uint8_t head; // 下一帧写入位置(ISR修改) volatile uint8_t tail; // 下一帧读取位置(主循环修改) } CAN_RX_BUFFER_T; extern CAN_RX_BUFFER_T can1_rx_buffer; // can_manager.c void CAN1_RX0_IRQHandler(void) { CanRxMsg RxMessage; CAN_Receive(CAN1, CAN_FIFO0, &RxMessage); // 读取一帧 uint8_t next_head = (can1_rx_buffer.head + 1) % CAN_RX_BUFFER_SIZE; if (next_head != can1_rx_buffer.tail) { // 检查缓冲区未满 CAN_MSG_T* p_msg = &can1_rx_buffer.buffer[can1_rx_buffer.head]; p_msg->id = (RxMessage.IDE == CAN_ID_STD) ? RxMessage.StdId : RxMessage.ExtId; p_msg->dlc = RxMessage.DLC; for(uint8_t i = 0; i < RxMessage.DLC; i++) { p_msg->data[i] = RxMessage.Data[i]; } p_msg->flags = (RxMessage.RTR == CAN_RTR_REMOTE) ? 0x01 : 0x00; p_msg->flags |= (RxMessage.IDE == CAN_ID_EXT) ? 0x02 : 0x00; can1_rx_buffer.head = next_head; // 原子操作:仅修改head can1_rx_flag = 1; // 置位全局标志 } CAN_FIFORelease(CAN1, CAN_FIFO0); // 释放FIFO } // 主循环中读取 if (can1_rx_flag) { while (can1_rx_buffer.tail != can1_rx_buffer.head) { CAN_MSG_T* p_msg = &can1_rx_buffer.buffer[can1_rx_buffer.tail]; can1_rx_task_process(p_msg); // 处理此帧 can1_rx_buffer.tail = (can1_rx_buffer.tail + 1) % CAN_RX_BUFFER_SIZE; } can1_rx_flag = 0; }零拷贝关键:ISR中只填充缓冲区元素,不调用任何业务函数;主循环中直接取地址&buffer[tail]处理,全程无数据复制。这对实时性要求高的场景(如电机控制指令)至关重要。
6. 二次开发指南:如何安全地接入FreeRTOS或替换为CAN FD?
6.1 接入FreeRTOS的改造清单(非侵入式)
本工程设计时已预留RTOS接口,只需四步即可无缝迁移,无需修改任何CAN底层代码:
| 改造项 | 操作位置 | 关键代码/配置 |
|---|---|---|
| 1. 创建CAN1接收任务 | main.c中main()末尾 | xTaskCreate(can1_rx_task, "CAN1_RX", 128, NULL, 3, NULL);任务函数内循环调用 can1_rx_task_process(),用xQueueReceive()替代全局标志位 |
| 2. 创建CAN2发送任务 | main.c | xTaskCreate(can2_tx_task, "CAN2_TX", 128, NULL, 3, NULL);任务函数内用 xQueuePeek()获取待发帧,发送成功后xQueueSend()回执 |
| 3. 替换环形缓冲区为队列 | can_manager.h | #define USE_FREERTOS_QUEUE宏启用后,can1_rx_buffer类型变为QueueHandle_t,can_manager_poll()被xQueueSendFromISR()替代 |
| 4. 中断优先级重映射 | system_stm32f10x.c | NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);确保CAN中断优先级 ≤ FreeRTOS configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY |
注意:FreeRTOS接管后,原主循环中的
delay_ms(1)必须删除,改用vTaskDelay(1)。否则SysTick被RTOS占用,delay_ms将失效。
6.2 升级至CAN FD的硬件与软件适配要点
STM32F105不支持CAN FD(需F3/F4/F7/H7系列),但若你计划迁移到STM32H743,以下改造可复用本工程80%代码:
| 层级 | 本工程现状 | CAN FD升级要点 |
|---|---|---|
| 硬件层 | 500kbps标准CAN,TJA1050收发器 | 更换为TJA1145(支持CAN FD 5Mbps),PCB增加共模电感 |
| 驱动层 | CAN_InitTypeDef结构体 | 替换为FDCAN_InitTypeDef,新增NominalPrescaler/DataPrescaler两套分频参数 |
| 消息结构 | CAN_MSG_T.dlc最大8 | 扩展为uint8_t dlc(支持0-15,对应64字节数据) |
| 滤波器 | 14个16位过滤器组 | H7支持72个32位过滤器,可配置为精确ID匹配或范围匹配 |
关键提醒:CAN FD的“标称比特率”(Nominal Bit Rate)和“数据比特率”(Data Bit Rate)是独立的。例如,标称段500kbps(兼容传统节点),数据段2Mbps(提升吞吐)。本工程的滤波器配置逻辑(掩码计算)完全适用,只需在初始化时分开设置两套时序参数。
6.3 我的血泪习惯:每次改CAN配置必做的三件事
从那以后我每次调整CAN波特率、滤波器或中断优先级,都强制走一遍这三步验证流程,省去90%的深夜抓包时间:
- 第一步:示波器看波形—— 接CANH-CANL到示波器,触发边沿,测一个完整比特时间,确认是否等于理论值(如500kbps → 2μs/bit)。若偏差>±1%,立刻检查
CAN_Prescaler和APB1时钟源; - 第二步:回环测试—— 将CAN1的TXD引脚(非CANH)直接连到CAN2的RXD引脚(非CANL),用
CAN1发、CAN2收,绕过物理总线干扰,验证纯软件逻辑; - 第三步:压力灌包—— 用PC上位机(如CANalyzer)以1000帧/秒持续发送,观察30分钟内是否丢帧。若丢帧,优先检查
CAN_RFLM=DISABLE(FIFO锁存)是否误开,而非怀疑硬件。
希望帮到你。
本文还有配套的精品资源,点击获取