CAN FD 休眠机制:从硬件状态到软件握手

说到 CAN FD 的休眠,很多工程师第一反应就是「把时钟关了不就行了?」。嗯,没那么简单。我刚开始做车载项目时也这么想,结果发现休眠不是断电,而是一套完整的握手流程。今天我们就把它拆开来看。

CAN FD 控制器的休眠状态

先搞清楚一件事:CAN FD 控制器的休眠,不是 CPU 的停机模式。它有自己的状态机。STM32 的 CAN FD 外设(比如 bxCAN 或 FDCAN)在休眠时,时钟还在,但收发器被关掉了。

具体来说,控制器会进入这几个状态之一:

状态 时钟状态 收发器状态 总线监听 唤醒能力
工作态 运行 使能 持续 不需要
主动休眠 运行 禁用 关闭 软件触发
被动休眠 运行 禁用 低功耗监听 总线活动唤醒
深度休眠 停止 禁用 关闭 外部中断

这里有个坑:深度休眠虽然最省电,但 CAN 总线上的报文是叫不醒它的。我有个客户就踩过这个雷——他们以为总线唤醒是万能的,结果报文发了一堆,设备纹丝不动。后来发现是时钟停了,CAN 模块根本没在听。

主动休眠 vs 被动休眠

这两个概念容易搞混。我用自己的话给你捋一遍。

主动休眠

说白了就是「我主动要求睡」。软件写一个寄存器位,告诉控制器:我要睡了,你准备一下。这时候控制器会:

  • 停止发送报文
  • 关闭收发器
  • 等待当前帧传输完成
  • 进入休眠状态

主动休眠的唤醒方式通常是软件触发——比如一个 GPIO 中断,或者定时器到期。我在做 OBD 诊断设备时就用这种方式:设备空闲 5 分钟后主动休眠,等诊断仪插上来再唤醒。

被动休眠

被动休眠是「总线让我睡」。控制器检测到总线空闲超过一定时间(比如 CAN 规范里的 Bus-Idle 条件),自动进入低功耗模式。但它留了一手——还在监听总线。

被动休眠的唤醒源是总线活动。只要总线上出现一个显性位,控制器就能自己醒来。这个特性在车载网络中特别有用:节点不需要额外连线,靠 CAN 总线就能实现同步唤醒。

关键区别:主动休眠靠软件唤醒,被动休眠靠总线唤醒。前者功耗更低,后者响应更快。

休眠请求与确认流程

休眠不是一拍脑袋就睡的。CAN FD 控制器有一套标准的请求-确认机制。我画了个流程图,你看一眼就明白了。

CAN FD 休眠请求与确认流程 应用层发起休眠请求 检查 TX 缓冲器是否为空 等待当前帧发送完成 设置 CSR 位请求休眠 等待 CSA 位确认 进入休眠状态 ⚠ 常见问题 TX 缓冲器非空时请求休眠 会导致 CSA 位永远不置位 控制器卡死在等待状态 💡 超时处理 建议加 10ms 超时 超时后强制复位控制器

流程里有两个关键寄存器位:

  • CSR(Clock Stop Request):软件写 1 表示请求休眠
  • CSA(Clock Stop Acknowledge):硬件置 1 表示确认休眠

我曾经在一个项目中,发现 CSA 位一直不置位。查了半天,原来是 TX 缓冲器里还有一帧没发出去。控制器很老实——它不会丢帧去睡觉。所以休眠前一定要确保发送队列清空。

我的习惯:在请求休眠前,先读一下 TX 缓冲器状态寄存器。如果非空,就等一个发送完成中断,再发起休眠请求。这样最稳妥。

休眠唤醒时序图

时序图能帮你理解信号之间的先后关系。下面这张图展示了从休眠到唤醒的完整过程。

CAN FD 休眠唤醒时序图 CAN 总线 CSR 位 CSA 位 唤醒事件 正常通信 总线空闲 CSR=0 CSR=1(请求休眠) CSA=0 CSA=1(确认休眠) 唤醒脉冲 工作阶段 休眠请求阶段 休眠阶段 唤醒阶段

从时序图里你能看到几个关键点:

  1. 总线空闲是休眠的前提条件。如果总线上还在跑报文,控制器不会响应 CSR 请求。
  2. CSR 置位到 CSA 确认之间有一个延迟。这个延迟取决于当前帧是否发送完毕。
  3. 唤醒事件可以是总线上的显性脉冲,也可以是外部中断。唤醒后控制器会自动清除 CSR 和 CSA 位。

我曾经踩过的坑:在休眠后立即发送唤醒报文,结果控制器还没完全退出休眠状态,报文被吞了。后来我在唤醒后加了 1ms 的延时,等控制器状态稳定再发数据。这个问题在 STM32H7 系列上尤其明显,因为它的时钟恢复需要时间。

代码实现要点

最后给一段实际代码片段。这是我在 STM32G4 上用的休眠请求函数:

// 请求 CAN FD 休眠
HAL_StatusTypeDef CANFD_RequestSleep(FDCAN_HandleTypeDef *hfdcan)
{
    uint32_t tickstart = HAL_GetTick();
    
    // 1. 检查 TX 缓冲器是否为空
    if (hfdcan->Instance->TXBCR != 0) {
        return HAL_BUSY;  // 还有帧在发送
    }
    
    // 2. 设置 CSR 位请求休眠
    hfdcan->Instance->CCCR |= FDCAN_CCCR_CSR;
    
    // 3. 等待 CSA 位确认,超时 10ms
    while ((hfdcan->Instance->CCCR & FDCAN_CCCR_CSA) == 0) {
        if ((HAL_GetTick() - tickstart) > 10) {
            // 超时处理:复位控制器
            hfdcan->Instance->CCCR &= ~FDCAN_CCCR_CSR;
            return HAL_TIMEOUT;
        }
    }
    
    return HAL_OK;
}

这段代码我用了很多项目。注意那个超时处理——没有它,一旦 CSA 位卡住,你的系统就死锁了。我习惯设 10ms,够用又不拖沓。

总结一下:CAN FD 休眠不是简单的关时钟,而是一个状态机驱动的握手过程。主动休眠靠软件唤醒,被动休眠靠总线唤醒。记住检查 TX 缓冲器、等待 CSA 确认、加超时保护——这三点做到位,休眠基本不会出问题。