第三章:硬件平台搭建——选对芯片,少走弯路

好,咱们进入实操环节。前面两章讲了CANopen协议栈和运动控制的基本概念,现在该落地了。硬件平台怎么搭?说白了就是三样东西:微控制器(MCU)、CAN控制器、CAN收发器。这三者怎么选、怎么连,我踩过的坑可不少,今天一次性说清楚。

核心思路:CAN控制器负责协议处理,收发器负责电平转换,MCU负责应用逻辑。三者缺一不可,但选型顺序有讲究。

3.1 微控制器选型——别光看主频

很多人选MCU第一眼看主频,其实这是个误区。我做过一个项目,选了200MHz的芯片,结果CAN通信死活不稳定。后来发现是CAN控制器外设的缓冲区太小,导致数据帧溢出。

我个人习惯按这个顺序来选:

  • 内置CAN控制器优先——省掉外部CAN控制器芯片,成本低、布线简单
  • 至少2个独立的CAN通道——万一要扩展节点,不用重新设计
  • RAM要够大——运动控制算法+协议栈,至少64KB起步
  • 定时器资源丰富——同步周期、心跳报文都需要硬件定时器

举个例子,我常用的STM32F407系列,内置两个CAN控制器,主频168MHz,RAM 192KB。嗯,这个配置跑CANopen运动控制绰绰有余。你想想看,如果选个低端芯片,光协议栈就占掉一半RAM,后面加个PID算法都费劲。

我的经验:如果预算允许,优先选带CAN-FD的MCU。虽然现在用标准CAN 2.0,但未来升级CAN-FD时不用换硬件。我手头三个项目都是这么干的,省了两次改板费用。

3.2 CAN控制器——内置还是外置?

这个问题其实没啥好纠结的。现在主流MCU基本都内置CAN控制器,比如STM32、GD32、NXP的LPC系列。但如果你用的是低端MCU或者FPGA方案,那就得外挂。

外置CAN控制器我推荐两个:

型号 特点 适用场景
MCP2515 SPI接口,便宜,资料多 低端MCU、Arduino扩展
SJA1000 并行接口,稳定,工业级 工控主板、PLC扩展

我曾经在一个项目里用了MCP2515,结果发现SPI速率上不去,导致CAN总线利用率低。后来换成内置控制器,问题直接解决。所以我的建议是:能用内置就别用外置,除非你确实需要独立控制。

3.3 CAN收发器——别小看这颗芯片

收发器负责把CAN控制器的逻辑电平转换成差分信号。很多人觉得随便选一个就行,其实不然。我见过一个案例,选了便宜的TJA1050,结果在强电磁干扰环境下频繁丢帧。

选收发器要注意三点:

  • 速率匹配——标准CAN最高1Mbps,CAN-FD最高8Mbps,收发器要支持
  • 耐压等级——工业环境建议选±36V耐压的,比如SN65HVD230
  • 待机功耗——如果做电池供电设备,选带待机模式的

我个人常用的组合是:STM32F407(内置CAN控制器)+ TJA1050(收发器)。这个组合我用了五年,没出过问题。当然,如果你预算充足,可以上隔离型收发器,比如ISO1050,能彻底解决共模干扰问题。

避坑指南:我曾经在收发器的终端电阻上吃过亏。CAN总线两端必须各接一个120Ω电阻,少一个都不行。有一次调试时忘了接,结果通信时好时坏,查了两天才发现。嗯,这个坑你们别踩。

3.4 最小系统搭建——连线图

下面这张图是我画的最小系统框架,包含了MCU、CAN控制器(内置)、收发器、终端电阻和电源。你照着连就行。

微控制器 STM32F407 内置CAN控制器 CAN_TX CAN_RX CAN收发器 TJA1050 CAN_H CAN_L 120Ω 120Ω CAN_H CAN_L 3.3V/5V电源 GND CANopen最小系统硬件框架 注:MCU内置CAN控制器,通过CAN_TX/CAN_RX连接收发器 收发器将差分信号输出到CAN_H/CAN_L总线 总线两端各接一个120Ω终端电阻

3.5 实际连线注意事项

图纸画好了,实际焊接时还有几个细节要注意:

  1. 电源去耦——每个芯片的VCC引脚旁边放一个0.1μF电容,我习惯再加一个10μF电解电容
  2. 收发器使能引脚——TJA1050的RS引脚接地(高速模式),S引脚接高电平(正常模式)
  3. 终端电阻位置——必须放在总线物理两端,不是PCB两端。如果节点少,直接在收发器旁边焊上
  4. 共模扼流圈——工业环境建议在CAN_H和CAN_L上串一个共模扼流圈,能有效抑制干扰

我的小技巧:调试时先别焊终端电阻,用示波器看CAN_H和CAN_L的差分信号。如果波形干净,再焊上电阻。这样能快速定位是芯片问题还是总线问题。

3.6 代码示例——初始化CAN外设

硬件连好了,得让MCU跑起来。下面是一段STM32的CAN初始化代码,我加了详细注释:

// CAN初始化函数
void CAN_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    CAN_InitTypeDef CAN_InitStructure;
    CAN_FilterInitTypeDef CAN_FilterInitStructure;
    
    // 1. 使能时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
    
    // 2. 配置CAN引脚(PB8=RX, PB9=TX)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    
    // 3. 复用功能映射
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource8, GPIO_AF_CAN1);
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource9, GPIO_AF_CAN1);
    
    // 4. CAN参数配置
    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锁定
    CAN_InitStructure.CAN_TXFP = DISABLE;      // 发送优先级由ID决定
    CAN_InitStructure.CAN_Mode = CAN_Mode_Normal; // 正常模式
    CAN_InitStructure.CAN_SJW = CAN_SJW_1tq;   // 同步跳转宽度
    CAN_InitStructure.CAN_BS1 = CAN_BS1_4tq;   // 时间段1
    CAN_InitStructure.CAN_BS2 = CAN_BS2_3tq;   // 时间段2
    CAN_InitStructure.CAN_Prescaler = 6;        // 分频系数
    CAN_Init(CAN1, &CAN_InitStructure);
    
    // 5. 配置滤波器(接收所有ID)
    CAN_FilterInitStructure.CAN_FilterNumber = 0;
    CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask;
    CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit;
    CAN_FilterInitStructure.CAN_FilterIdHigh = 0x0000;
    CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000;
    CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x0000;
    CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000;
    CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_FIFO0;
    CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;
    CAN_FilterInit(&CAN_FilterInitStructure);
    
    // 6. 使能CAN中断
    CAN_ITConfig(CAN1, CAN_IT_FMP0, ENABLE);
    NVIC_EnableIRQ(CAN1_RX0_IRQn);
}

这段代码配置了1Mbps的波特率(42MHz APB1时钟下)。你想想看,如果分频系数算错了,通信就会失败。我建议用示波器实测CAN_H上的位时间,确保在1μs左右。

3.7 硬件调试——三板斧

硬件焊好了,代码烧进去了,但通信不通怎么办?别慌,我总结了三板斧:

  1. 测电源——先量MCU和收发器的VCC,确保电压正常。我遇到过5V供电的收发器接了3.3V,结果完全没反应
  2. 看波形——用示波器看CAN_TX引脚有没有方波输出。如果有,说明MCU在发数据;如果没有,检查初始化代码
  3. 查终端电阻——断开总线,用万用表量CAN_H和CAN_L之间的电阻。正常应该是60Ω(两个120Ω并联)

曾经踩过的坑:有一次我焊完板子,通信死活不通。查了两天,最后发现是CAN收发器的S引脚悬空了。TJA1050的S引脚是静音模式控制,悬空会导致芯片进入静音状态,只能收不能发。嗯,从此以后我焊完板子第一件事就是检查所有使能引脚的电平。

好了,硬件平台搭建就讲到这里。你按照上面的选型建议和连线图,搭一个最小系统应该没问题。记住,硬件是基础,基础打牢了,后面的协议栈和运动控制才能跑得稳。