第三章 物理层与硬件接口:从电平信号到数据帧的桥梁

各位同学,欢迎来到第三章。这一章我们聊点实在的——物理层。

很多人觉得物理层就是看波形、量电压,没什么技术含量。我年轻时也这么想,直到有一次在实车测试中,CAN总线莫名其妙丢帧,查了三天软件,最后发现是终端电阻虚焊。嗯,从那以后我再也不敢小看物理层了。

说白了,物理层就是通信的「地基」。地基不稳,上层协议栈写得再漂亮也是白搭。今天我们就从CAN总线、车载以太网、UART调试接口这三个维度,把TBOX的物理层讲透。

3.1 CAN总线物理特性:差分信号与终端电阻

CAN总线在汽车圈的地位,就像水电煤在生活中的地位——无处不在,但你很少注意到它。

差分信号原理

CAN总线用两条线:CAN_H和CAN_L。它不靠对地电压传数据,而是靠两条线的电压差。

  • 显性位(逻辑0):CAN_H比CAN_L高2V左右
  • 隐性位(逻辑1):两条线电压几乎相等(约2.5V)

为什么用差分?抗干扰。我在做EMC测试时遇到过,单端信号在发动机点火瞬间直接崩掉,而CAN总线纹丝不动。这就是差分的优势——共模噪声被抵消了。

关键参数速查表

参数典型值说明
总线电压(隐性)2.5VCAN_H和CAN_L相等
总线电压(显性)CAN_H=3.5V, CAN_L=1.5V差分电压约2V
终端电阻120Ω两端各一个,匹配总线阻抗
最大节点数30-110取决于收发器驱动能力
总线长度40m@1Mbps速率越低,距离越长

终端电阻的坑

我曾经在一个项目中,CAN总线偶尔出现CRC错误。查了半天,发现是终端电阻用了100Ω而不是120Ω。阻抗不匹配导致信号反射,在长距离传输时尤其明显。

避坑指南:终端电阻必须放在总线物理两端,而不是ECU端。很多人把电阻焊在PCB上,结果多个节点并联,等效电阻远低于120Ω。正确的做法是:总线两端各一个120Ω,中间节点不要加。

3.2 车载以太网(100BASE-T1):单对线搞定高速通信

传统以太网用4对线,车载以太网只用1对。为什么?省线、省空间、减重量。你想想看,一辆车线束总长动不动就几公里,能省一点是一点。

100BASE-T1 vs 标准以太网

特性100BASE-TX100BASE-T1
线对数量4对(8线)1对(2线)
最大距离100m15m(车内够用)
信号编码MLT-3PAM3
共模电压0V1.5V(需共模扼流圈)
应用场景办公室网络车载骨干网

100BASE-T1用了PAM3编码,也就是每个符号可以表示3种电平状态(-1, 0, +1)。相比传统以太网的NRZ编码,它在同样带宽下能传更多数据。

我记得第一次调试100BASE-T1时,示波器一看波形,怎么跟我想的不一样?后来才明白,PAM3的波形看起来像三层的「阶梯」,而不是简单的方波。这一点跟CAN的差分信号完全不同。

个人经验:100BASE-T1的PCB走线要特别注意。差分对要等长,阻抗控制在100Ω±10%。我见过一个案例,走线长度差了5mm,结果链路协商失败。5mm啊,在高速信号面前就是天堑。

硬件设计要点

  • 必须加共模扼流圈(CMC),抑制共模辐射
  • PHY芯片和连接器之间走线尽量短
  • MDI接口要加ESD保护器件
  • 参考时钟精度要求±50ppm以内

3.3 UART与RS232调试接口:最后的救命稻草

CAN和以太网再牛,系统挂了的时候你怎么办?这时候UART调试接口就是你的「生命线」。

UART vs RS232:不是一回事

很多人把UART和RS232混为一谈。其实UART是协议层(起始位、数据位、停止位),RS232是电气层(±12V电平)。

TBOX上常用的调试接口是UART转USB,电平是3.3V CMOS。但有些老设备还用RS232,这时候就需要电平转换芯片,比如MAX3232。

调试接口设计原则

// 典型的UART初始化代码(STM32平台)
void Debug_UART_Init(void)
{
    // 配置GPIO:TX=PA9, RX=PA10
    GPIO_InitTypeDef gpio = {0};
    gpio.Pin = GPIO_PIN_9 | GPIO_PIN_10;
    gpio.Mode = GPIO_MODE_AF_PP;
    gpio.Pull = GPIO_PULLUP;
    gpio.Speed = GPIO_SPEED_FREQ_HIGH;
    gpio.Alternate = GPIO_AF7_USART1;
    HAL_GPIO_Init(GPIOA, &gpio);

    // 配置UART:115200, 8N1
    USART_InitTypeDef usart = {0};
    usart.BaudRate = 115200;
    usart.WordLength = USART_WORDLENGTH_8B;
    usart.StopBits = USART_STOPBITS_1;
    usart.Parity = USART_PARITY_NONE;
    usart.Mode = USART_MODE_TX_RX;
    usart.HwFlowCtl = USART_HWCONTROL_NONE;
    HAL_USART_Init(&husart1, &usart);
}

我曾经踩过的坑:调试接口的TX/RX不要接反!听起来像废话对吧?但我在项目里至少见过三次这种低级错误。还有,调试接口的GND必须接,不接的话信号飘忽不定,你根本没法用。

波特率选择建议

  • 115200:通用,够用,推荐
  • 921600:高速调试,但线长超过1米可能丢数据
  • 9600:兼容老设备,但传输慢得让人抓狂

3.4 硬件抽象层设计:让上层代码不依赖具体硬件

终于到了这一章的重头戏——HAL(硬件抽象层)。

为什么要做HAL?很简单:你今天用NXP的MCU,明天可能换成瑞萨的。如果代码里到处都是寄存器操作,换芯片等于重写。HAL就是给上层提供一个「统一接口」,底层怎么实现,上层不关心。

HAL设计原则

  1. 接口统一:所有外设的操作函数命名风格一致
  2. 参数抽象:不要暴露寄存器地址,用枚举和结构体
  3. 错误处理:返回标准错误码,而不是直接死循环
  4. 可移植性:硬件相关代码集中在一个目录,方便替换

CAN HAL接口示例

// can_hal.h - 硬件无关的CAN接口
typedef enum {
    CAN_OK = 0,
    CAN_ERROR_TIMEOUT,
    CAN_ERROR_BUS_OFF,
    CAN_ERROR_TX_FULL,
    CAN_ERROR_RX_EMPTY
} CAN_Status_t;

typedef struct {
    uint32_t id;           // 标准帧或扩展帧ID
    uint8_t  data[8];      // 数据
    uint8_t  dlc;          // 数据长度
    uint8_t  is_extended;  // 0=标准帧, 1=扩展帧
    uint8_t  is_remote;    // 0=数据帧, 1=远程帧
} CAN_Frame_t;

// 初始化CAN控制器
CAN_Status_t CAN_Init(uint32_t baudrate);

// 发送一帧数据
CAN_Status_t CAN_Send(CAN_Frame_t *frame, uint32_t timeout_ms);

// 接收一帧数据(阻塞)
CAN_Status_t CAN_Receive(CAN_Frame_t *frame, uint32_t timeout_ms);

// 获取总线状态
CAN_Status_t CAN_GetStatus(uint8_t *error_count);

你看,这个接口里没有任何跟具体MCU相关的东西。换芯片时,只需要重新实现这几个函数就行。

我的习惯:我会在HAL层加一个「自检函数」。上电时调用它,检查CAN收发器是否正常、终端电阻是否在位。这个习惯帮我省了不少排查时间。你想想看,如果系统跑起来才发现硬件有问题,那得多被动?

UART HAL接口设计

// uart_hal.h
typedef void (*UART_RxCallback_t)(uint8_t data);

void UART_Init(uint32_t baudrate);
void UART_SendByte(uint8_t data);
void UART_SendBuffer(uint8_t *buf, uint16_t len);
void UART_RegisterRxCallback(UART_RxCallback_t callback);

这里用了回调函数的方式处理接收。为什么?因为调试接口的数据往往是异步到达的,轮询方式太浪费CPU。注册一个回调,数据来了自动处理,干净利落。

硬件抽象层设计要点总结:

  • 接口要「薄」:不要封装过度,否则性能损失大
  • 错误码要「全」:每个可能的失败场景都要有对应码
  • 配置要「灵活」:波特率、帧格式等参数可动态设置
  • 测试要「早」:HAL写完后立刻用回环测试验证

好了,这一章的内容就到这里。物理层是通信的基石,CAN总线的差分特性、车载以太网的PAM3编码、UART调试接口的可靠性,每一个都值得你花时间吃透。下一章我们进入数据链路层,聊聊CAN帧的过滤和优先级管理——那又是另一番天地了。

记住:硬件接口设计得好,上层协议栈写起来就像搭积木。设计得不好,那就是在沙子上盖楼。