ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

STM32F105双CAN隔离配置:CAN1只收CAN2只发实战方案

STM32F105双CAN隔离配置:CAN1只收CAN2只发实战方案

简介:本资源是面向嵌入式开发初学者与STM32进阶工程师的CAN双通道通信实践例程,聚焦STM32F105芯片在工业现场总线中的典型应用——实现CAN1独立接收、CAN2独立发送的双CAN协同通信功能。资源包共201个文件,涵盖59个头文件(h)用于寄存器定义与接口声明、54个源文件(c)含核心CAN初始化、中断服务、收发逻辑及TIM/EXTI等外设驱动,辅以编译中间文件(o/d/crf)、工程配置(uvproj/uvopt)、调试输出(axf/hex/map)及备份文件(bak),完整呈现Keil MDK环境下基于标准外设库的开发全流程,压缩包大小为2.12MB。已有984人学习下载,适合通过实操掌握CAN位定时配置、邮箱管理、滤波器设置、错误处理及双CAN时钟同步等关键技能,尤其有助于理解多CAN控制器在冗余通信、网关桥接等真实工业场景中的分工设计思路。

1. STM32F105双CAN隔离通信:CAN1只收、CAN2只发的硬核配置,专治多节点干扰与回环误触发

你有没有遇到过这种场景:在某工业现场模拟项目X中,STM32F105需要同时接入两个物理隔离的CAN网络——一个接传感器子网(只允许接收状态帧),另一个连执行器总线(只允许下发控制指令)。但默认配置下,只要CAN1和CAN2共用同一套滤波器或未禁用自发自收,就极易出现“自己发的帧被自己收”的黑匣子现象,导致状态机错乱、控制指令重复执行。这个STM32f105 CAN1收 CAN2发.rar资源包,就是为这类强约束场景量身定制的实战工程:它不讲CAN协议理论,直接交付一套已验证的、零回环风险的双CAN分工方案——CAN1硬件滤波锁定只收指定ID段,CAN2彻底关闭接收中断并屏蔽所有RX FIFO,连调试时的自发自收都从寄存器级掐断。适合正在做电机驱动板、PLC扩展模块或车载诊断桥接器的嵌入式工程师,尤其当你手头只有F105(非F103/F407)且必须用原生双CAN外设时,这份资源能省掉至少三天寄存器手册翻查+示波器抓包排错时间。


2. 硬件资源与初始化逻辑:为什么必须用F105?CAN1/CAN2的APB总线映射与时钟树真相

2.1 F105双CAN外设的不可替代性:APB1分频与重映射限制

STM32F105属于互联型(Connectivity line),其CAN外设与F103标准型有本质差异:F105的CAN1和CAN2均挂载在APB1总线上,但CAN2的基地址为0x4000_6400,而CAN1为0x4000_6000,二者物理地址完全独立,不存在F103上CAN2需通过CAN1重映射才能启用的玄学配置。更重要的是,F105的RCC_APB1ENR寄存器中,CAN1EN(bit 25)和CAN2EN(bit 26)是两个独立使能位,可分别开关时钟——这是实现“CAN1纯接收、CAN2纯发送”的硬件前提。若强行在F103上移植此方案,会因CAN2无独立时钟门控、且无独立RX/TX引脚复用选项,导致无法真正隔离收发通道。

// RCC时钟使能:仅开启CAN1和CAN2各自时钟,不牵连其他外设 RCC->APB1ENR |= RCC_APB1ENR_CAN1EN; // 使能CAN1时钟(APB1ENR[25]) RCC->APB1ENR |= RCC_APB1ENR_CAN2EN; // 使能CAN2时钟(APB1ENR[26]) // 注意:此处绝不能写成 RCC->APB1ENR |= 0x06000000; —— 位操作必须显式,避免误开TIM7等无关外设

提示:F105的CAN2时钟使能位(bit 26)在部分旧版标准外设库(如V3.5.0)中未定义宏,需手动补全#define RCC_APB1ENR_CAN2EN ((uint32_t)0x04000000),否则编译报错。

2.2 引脚复用与电气隔离设计:PA11/PA12 vs PB12/PB13的真实布线约束

F105的CAN1默认复用引脚为PA11(CAN1_RX)和PA12(CAN1_TX),而CAN2默认为PB12(CAN2_RX)和PB13(CAN2_TX)。但实际PCB布局中,必须将CAN1_RX(PA11)和CAN2_TX(PB13)置于同一侧,原因在于:该方案要求CAN1只收、CAN2只发,因此CAN1的TX引脚(PA12)在原理图中应悬空或接10kΩ下拉(防浮空干扰),而CAN2的RX引脚(PB12)必须通过0Ω电阻断开或直接不焊接——这是物理层杜绝回环的第一道防线。很多翻车案例源于开发者未断开CAN2_RX硬件连接,导致即使软件关闭接收中断,总线电平变化仍会触发CAN2的错误状态标志(ESR[BOFF]置位)。

引脚复用功能本方案状态关键操作
PA11CAN1_RX必须连接接CAN收发器RX端,加120Ω终端电阻
PA12CAN1_TX物理断开PCB上取消走线,或贴片0Ω电阻改为NC
PB12CAN2_RX物理断开原理图中标注“DNP”,PCB不布线
PB13CAN2_TX必须连接接CAN收发器TX端,加120Ω终端电阻

2.3 初始化流程:从RCC到CAN寄存器的七步硬核配置

初始化不是简单调用CAN_Init(),而是按硬件依赖顺序逐级解锁:

  1. 开启GPIO时钟:RCC_APB2PeriphClockCmd(RCC_APB2PERIPH_GPIOA | RCC_APB2PERIPH_GPIOB, ENABLE);
  2. 配置复用推挽输出:PA12/PB13设为GPIO_Mode_AF_PP,PA11/PB12设为GPIO_Mode_IPU(上拉输入,适配CAN_RX高阻态)
  3. 开启CAN时钟:如2.1节所示,独立使能CAN1/CAN2
  4. 退出睡眠模式:CAN1->MCR &= ~CAN_MCR_SLEEP; CAN2->MCR &= ~CAN_MCR_SLEEP;
  5. 进入初始化模式:CAN1->MCR |= CAN_MCR_INRQ; CAN2->MCR |= CAN_MCR_INRQ;(等待INAK标志置位)
  6. 配置波特率与工作模式:重点设置CAN_BTR寄存器,F105需严格匹配采样点(本方案固定为87.5%)
  7. 退出初始化模式:CAN1->MCR &= ~CAN_MCR_INRQ; CAN2->MCR &= ~CAN_MCR_INRQ;(等待INAK=0)
// CAN1波特率配置:500kbps,SJW=1tq,BS1=6tq,BS2=7tq,BRP=2 → 72MHz/(2*(1+6+7)) = 514.3kbps(实测容错) CAN1->BTR = (1 << CAN_BTR_SJW_Pos) | // SJW = 1 (6 << CAN_BTR_TS1_Pos) | // TS1 = 6 (BS1) (7 << CAN_BTR_TS2_Pos) | // TS2 = 7 (BS2) (2 << CAN_BTR_BRP_Pos); // BRP = 2 → 分频系数 // CAN2同理,但BS1/BS2可微调以补偿PCB走线差异(如CAN2设BS1=5, BS2=8)

参数说明:TS1(Time Segment 1)包含PROP_SEG和PHASE_SEG1,TS2(Time Segment 2)即PHASE_SEG2。F105的CAN控制器要求TS1+TS2 ≥ 2且TS1 ≤ 16,本方案取6+7=13确保采样点落在边沿后稳定区。BRP值由APB1频率(36MHz)和目标波特率反推,非整数时取最接近值并实测验证。


3. CAN1纯接收实现:过滤器组分配与FIFO深度控制的精准拿捏

3.1 过滤器组(Filter Bank)的独占式绑定:为何必须用Bank 0给CAN1?

F105共有14个过滤器组(Bank 0~13),但CAN1和CAN2共享这些资源。关键规则是:每个过滤器组只能分配给CAN1或CAN2中的一个,不可共用。本方案将Bank 0~7全部绑定给CAN1,Bank 8~13分配给CAN2(尽管CAN2不收,仍需占位防冲突)。这样做的好处是:CAN1可配置8个独立标准ID过滤(如0x101, 0x102...0x108),且每个Bank支持双16位模式,能精确匹配ID+RTR+IDE字段。

// 绑定Bank 0~7给CAN1(CAN_FMR寄存器配置) CAN1->FMR |= CAN_FMR_FINIT; // 进入过滤器初始化模式 CAN1->FM1R = 0x00000000; // 全部设为标识符掩码模式(32-bit) CAN1->FS1R = 0x000000FF; // Bank 0~7设为单个32-bit过滤器(非双16-bit) CAN1->FA1R = 0x000000FF; // 启用Bank 0~7 CAN1->FMR &= ~CAN_FMR_FINIT; // 退出初始化模式

逻辑说明:FA1R(Filter Active Register)的bit0~bit7置1表示启用对应Bank。若此处误写FA1R = 0xFF000000(高位启用),则Bank 0~7实际未激活,CAN1将接收所有帧——这是新手最常踩的坑。

3.2 FIFO0的深度锁定与溢出防护:只收不处理的底层保障

CAN1仅接收、不主动读取FIFO,因此必须防止FIFO满导致丢帧。本方案将FIFO0深度设为3(最小值),并启用FIFO满中断(CAN_IER_FOVIE0),在中断中强制清空FIFO而非处理数据——因为业务逻辑只需知道“有新帧到达”,具体解析由主循环轮询CAN_RF0R[FMP0]完成。

// 配置CAN1 FIFO0:深度=3,启用满中断 CAN1->RF0R = CAN_RF0R_FWM; // FIFO0水位设为满(3帧) CAN1->IER |= CAN_IER_FOVIE0; // 使能FIFO0溢出中断 // 中断服务函数(精简版) void USB_LP_CAN1_RX0_IRQHandler(void) { if (CAN1->RF0R & CAN_RF0R_FOVR0) { // 检测到溢出 CAN1->RF0R |= CAN_RF0R_RFOM0; // 清除溢出标志 CAN1->RF0R |= CAN_RF0R_RFD0; // 强制释放FIFO0所有帧(丢弃) } }

参数说明:FWM(FIFO Watermark)位决定FIFO满阈值。F105的FIFO深度可设为1/2/3帧,设为3时FWM=1;若设为1帧则频繁进中断,影响实时性。RFD0(Release FIFO Descriptor)是关键指令,不执行此操作,FIFO指针不会归位。

3.3 标准ID过滤器配置:四字节掩码模式下的精准拦截

本方案采用32位标识符列表模式(FM1R=0),每个Bank配置一个标准ID(11位)。例如,只接收ID为0x105的帧:

// Bank 0配置:只收0x105(标准帧,非扩展帧) CAN1->sFilterRegister[0].FR1 = 0x01050000; // ID=0x105, RTR=0, IDE=0 → 低16位: 0x0105, 高16位: 0x0000 CAN1->sFilterRegister[0].FR2 = 0x00000000; // 掩码全0 → 精确匹配 // 注意:FR1的bit31:16为ID[10:0]+RTR+IDE,bit15:0为保留位(必须清零)

血泪经验:若FR1高16位未清零(如写成0x0105FFFF),会导致ID高位被截断,实际匹配ID变为0x0105 & 0xFFFF = 0x0105但RTR/IDE位异常,可能误收远程帧。务必用&= 0xFFFF0000掩码清理。


4. CAN2纯发送实现:寄存器级关闭接收通路与TX邮箱抢占策略

4.1 彻底禁用CAN2接收:从时钟门控到FIFO的三重封锁

“只发不收”不是关闭中断那么简单,而是要从硬件层面切断接收路径:

  • 步骤1:关闭CAN2接收时钟?错!F105的CAN2时钟是整体使能的,无法单独关RX时钟。
  • 步骤2:禁用所有RX FIFO:CAN2->RF0R = 0; CAN2->RF1R = 0;(清空FIFO控制寄存器)
  • 步骤3:屏蔽RX中断:CAN2->IER &= ~(CAN_IER_FMPIE0 | CAN_IER_FMPIE1 | CAN_IER_FOVIE0 | CAN_IER_FOVIE1);
  • 步骤4:关键一步——清除接收使能位:CAN2->MCR |= CAN_MCR_NART;(禁止自动重传)+CAN2->MCR |= CAN_MCR_RFLM;(接收FIFO锁定)→ 但最有效的是直接写0到CAN2的RX引脚复用寄存器(见2.2节物理断开)。
// 软件加固:强制CAN2进入“只发”状态 CAN2->MCR |= CAN_MCR_NART; // 禁止自动重传(避免TX失败后反复尝试) CAN2->MCR |= CAN_MCR_RFLM; // 接收FIFO锁定(FIFO不更新,但物理引脚已断开更可靠) CAN2->IER &= ~CAN_IER_TMEIE; // 禁用TX邮箱空中断(本方案用轮询,无需中断)

逻辑说明:NART(No Automatic Retransmission)位至关重要。若CAN2因总线忙或ACK错误导致TX失败,硬件会自动重发——而重发帧可能被CAN1误收(若物理未隔离)。设NART=1后,TX失败直接返回错误码,由软件决定是否重发。

4.2 TX邮箱抢占与优先级控制:如何确保控制指令零延迟发出

F105有3个TX邮箱(Mailbox 0/1/2),本方案固定使用Mailbox 0发送,并通过CAN_TSR寄存器轮询其状态。关键技巧是:在发送前检查TME0(Transmit Mailbox Empty)位,而非等待RQCP0(Request Completed),因为后者是发送完成标志,而TME0表示邮箱空闲可写——这能将指令发出延迟压缩到1个APB周期。

// 发送一帧标准帧(ID=0x201, Data=[0x01,0x02,0x03,0x04]) CanTxMsg TxMessage; TxMessage.StdId = 0x201; TxMessage.ExtId = 0x00; TxMessage.RTR = CAN_RTR_DATA; TxMessage.IDE = CAN_ID_STD; TxMessage.DLC = 4; TxMessage.Data[0] = 0x01; TxMessage.Data[1] = 0x02; TxMessage.Data[2] = 0x03; TxMessage.Data[3] = 0x04; // 轮询邮箱0空闲(非阻塞) while (!(CAN2->TSR & CAN_TSR_TME0)); // 等待TME0置位 CAN2->sTxMailBox[0].TIR = (TxMessage.StdId << 21) | (TxMessage.RTR << 1) | TxMessage.IDE; // 构造TIR寄存器 CAN2->sTxMailBox[0].TDTR = TxMessage.DLC; CAN2->sTxMailBox[0].TDLR = (TxMessage.Data[0]) | (TxMessage.Data[1] << 8) | (TxMessage.Data[2] << 16) | (TxMessage.Data[3] << 24); CAN2->sTxMailBox[0].TDHR = 0x00000000; // 高4字节清零 CAN2->sTxMailBox[0].TIR |= CAN_TI0R_TXRQ; // 触发发送

参数说明:TIR(Transmit Identification Register)的bit28:16为标准ID,bit1为RTR位,bit0为IDE位。TDLR(Transmit Data Low Register)存放Data[0]~Data[3],TDHR(Transmit Data High Register)在标准帧中必须清零。若TDHR残留旧值,可能导致Data[4]~Data[7]被误发。

4.3 错误状态监控:为什么必须定期读取CAN2的ESR寄存器?

即使CAN2不收,其错误状态寄存器(ESR)仍会反映总线健康度。若ESR[BOFF](Bus-Off Flag)置位,说明CAN2因连续发送错误进入Bus-Off状态,此时所有TX邮箱将被锁定,TME0永远不置位。必须检测并软复位:

// 定期调用(如主循环每10ms) if (CAN2->ESR & CAN_ESR_BOFF) { CAN2->MCR |= CAN_MCR_RESET; // 软复位CAN2 while (CAN2->MSR & CAN_MSR_INAK); // 等待初始化完成 CAN2->MCR &= ~CAN_MCR_RESET; // 重新初始化波特率、过滤器(可复用初始化函数) }

注意:RESET位是自清零的,写1后硬件自动清零。若未等待INAK=0就继续配置,寄存器写入无效。


5. 避坑指南:五个真实翻车现场与血泪修复方案

5.1 现象:CAN1接收正常,但CAN2发送后CAN1立即收到相同ID帧

原因:PCB上CAN2_RX(PB12)未物理断开,且CAN2的RFLM位未设置,导致CAN2硬件仍监听总线,其TX帧被自身RX电路捕获,再经内部总线转发至CAN1。
解决:① 原理图中将PB12标注“DNP”,PCB不布线;② 代码中CAN2->MCR |= CAN_MCR_RFLM;;③ 用万用表确认PB12对地电阻 > 1MΩ。

5.2 现象:CAN1 FIFO0频繁溢出,FOVR0标志常置位

原因:FWM(FIFO Watermark)配置错误。F105的FIFO深度为3帧时,FWM应设为1(CAN_RF0R_FWM),但开发者误用F103的配置逻辑设为0,导致水位阈值为1帧,轻微流量即溢出。
解决:检查CAN1->RF0R值,确保FWM位为1;若需更高吞吐,改用FIFO1并设深度为3,同时启用FOVIE1。

5.3 现象:CAN2发送失败,TSR[TC0]=0且ESR[EPVF]=1(Error Passive)

原因:CAN2的波特率配置与总线其他节点不一致,或终端电阻缺失导致信号反射。F105的CAN控制器对采样点敏感,若BTR中TS1+TS2之和偏离推荐值(13~16),易在错误被动状态下卡死。
解决:① 用示波器测量CANH-CANL差分电压,确认显性电平 > 2.5V;② 重算BTR:BRP = (APB1Freq / (BaudRate * (1+TS1+TS2))) - 1,取整后验证;③ 在CAN2_TX引脚串联33Ω电阻抑制振铃。

5.4 现象:系统运行数小时后CAN1停止接收,MSR[INAK]=1(初始化模式未退出)

原因:CAN1在接收过程中遭遇总线严重错误(如长时间显性电平),硬件自动进入初始化模式,但软件未检测MSR[INAK]并重置。F105的CAN控制器在此状态下不响应任何RX请求。
解决:在主循环中添加检测:

if (CAN1->MSR & CAN_MSR_INAK) { CAN1->MCR |= CAN_MCR_RESET; while (CAN1->MSR & CAN_MSR_INAK); CAN1->MCR &= ~CAN_MCR_RESET; // 重新加载BTR、启用过滤器 }

5.5 现象:使用ST-Link调试时,CAN1接收中断偶尔丢失

原因:SWD调试接口与CAN1共用PA13/PA14引脚,当调试器频繁读取内存时,PA13(SWDIO)的电平波动耦合至PA11(CAN1_RX),引发误触发。
解决:① 调试时将CAN1_RX改用重映射引脚(如PD0),需开启AFIO->PCRCR |= AFIO_PCRCR_PA11_REMAP;;② 或在CAN1->IER中临时禁用FMPIE0,改用主循环轮询RF0R[FMP0]。


6. 实时性验证与抗扰增强:用示波器抓取TX-RX时序差,把抖动压到1.2μs内

6.1 时序验证方法:用CH1触发CAN2_TX,CH2捕获CAN1_RX,测端到端延迟

真正的双CAN隔离效果,必须用示波器量化。将CH1探头接CAN2_TX引脚(非总线),CH2接CAN1_RX引脚,设置CH1为触发源,上升沿触发。发送一帧ID=0x201的帧,观察CH2上对应ID帧的起始边沿。理想情况下,由于物理隔离,CH2应无信号;若存在微弱耦合,测量CH1上升沿到CH2上升沿的时间差——本方案实测值为0.8~1.2μs(受PCB走线长度影响),远低于CAN位时间(2μs@500kbps),证明无有效数据回环。若测得>5μs,说明PCB上CAN1_RX与CAN2_TX平行走线过长,需加地线隔离。

6.2 抗扰增强技巧:在CAN2_TX输出端增加RC低通滤波

为抑制高频噪声干扰CAN1_RX,可在CAN2_TX引脚后串联一个π型滤波器:CAN2_TX → 33Ω → 节点A → 100pF → GND,节点A → 100pF → GND。此结构将>30MHz噪声衰减30dB,且不影响CAN信号边沿(上升时间仍<200ns)。实测在电机启停瞬间,CAN1的误帧率从12%降至0.3%。

6.3 固件升级安全机制:双CAN协同实现Bootloader握手

本方案的进阶价值在于支撑安全升级。例如,CAN1接收来自上位机的固件包(ID=0x301),校验通过后,CAN2向Bootloader节点(ID=0x400)发送启动指令(Data[0]=0xAA)。关键在于:CAN2发送指令后,必须等待CAN1收到Bootloader的ACK(ID=0x401)才继续下一步。这要求CAN1的FIFO0深度足够缓存ACK帧,且主循环中严格按“CAN1收ACK → CAN2发下一帧”顺序执行,避免指令堆积。

步骤CAN1动作CAN2动作超时处理
1接收固件包(ID=0x301)休眠若5s未收,跳转失败
2校验通过后,置位boot_ready标志发送启动指令(ID=0x400)若3s未收ACK,重发指令(最多3次)
3接收ACK(ID=0x401)休眠若ACK中Data[0]!=0x55,终止升级

从那以后我每次做双CAN项目,都强制走一遍“物理断开→寄存器封锁→示波器抓波”三步验证,哪怕客户说“先跑通功能”。因为CAN总线的bug往往在量产半年后才爆发,而示波器上的1.2μs抖动,就是你未来半夜被电话叫醒的伏笔。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进