STM32 CAN FD休眠唤醒与低功耗设计实战 - 第4节

CAN FD 唤醒机制:本地唤醒与远程唤醒

说到 CAN FD 的休眠唤醒,我估计很多朋友第一反应就是——「不就是发个报文唤醒吗?」
嗯,没那么简单。实际项目中,唤醒机制分两种:本地唤醒和远程唤醒。搞混了,板子要么睡不踏实,要么醒不过来。

我个人习惯把唤醒机制分成两个维度来看:
谁触发的?—— 本地还是远程。
怎么触发的?—— 硬件引脚还是总线活动。

1. 本地唤醒:自己把自己叫醒

本地唤醒,说白了就是 MCU 自己检测到某个条件,主动退出休眠模式。
常见触发源:

  • GPIO 中断:比如按键、传感器输出跳变
  • 定时器唤醒:RTC 闹钟、LPTIM 超时
  • 比较器/ADC 阈值:电压跌到警戒线,自动唤醒处理

我在做车载 T-Box 项目时遇到过一个问题:
本地唤醒用的是 PA0 引脚,结果休眠时引脚浮空,噪声频繁触发唤醒。
后来加了内部上拉,并在唤醒中断里做 10ms 消抖,才算稳定。

我的建议:本地唤醒源一定要做去抖处理。硬件上串 RC,软件里加延时采样。别省这一步,否则半夜板子自己反复唤醒,功耗根本压不下去。

2. 远程唤醒:总线上的「敲门声」

远程唤醒,就是 CAN 总线上其他节点发报文,把休眠中的节点叫醒。
STM32 的 CAN FD 控制器支持两种远程唤醒方式:

唤醒方式 触发条件 典型场景
任意帧唤醒 总线上出现任何显性位 调试阶段、兼容老设备
特定帧唤醒 匹配唤醒滤波器规则的帧 量产产品、低功耗要求高

任意帧唤醒最简单,但功耗也最高——总线上一丁点噪声都能把节点叫醒。
我做过一个项目,客户反馈电池掉电快,查了半天发现是 CAN 总线上的反射噪声频繁唤醒节点。
后来改成特定帧唤醒,功耗直接降了 80%。

唤醒帧格式要求

远程唤醒不是随便发个报文就行。CAN FD 协议对唤醒帧有明确要求:

  • 帧格式:必须是标准帧或扩展帧,数据帧或远程帧均可
  • ID 范围:由唤醒滤波器决定,通常支持 11 位或 29 位 ID 匹配
  • 数据长度:唤醒帧通常只检查 ID,不检查数据场(但有些实现会检查前几个字节)
  • 位速率:唤醒阶段必须使用标称位速率(仲裁段速率),不能用数据段高速率
关键点:唤醒帧的 ID 必须与滤波器配置一致。ID 不匹配,节点不会醒来。我见过有人把滤波器配反了,整车网络都唤不醒那个节点,最后用 CAN 分析仪抓了半天才发现是 ID 掩码写错了。

唤醒滤波器配置

唤醒滤波器,就是给 CAN 控制器装一个「门卫」。
只有符合规则的帧才能通过,把 MCU 从休眠中叫醒。

STM32 的唤醒滤波器结构

以 STM32H7 系列为例,CAN FD 控制器内部有独立的唤醒滤波器:

  • 滤波器数量:通常 2~4 个,具体看型号
  • 滤波模式:ID 掩码模式、ID 列表模式
  • 滤波宽度:支持 11 位标准 ID 或 29 位扩展 ID

配置流程我一般这样走:

  1. 系统上电后,配置唤醒滤波器寄存器
  2. 设置滤波模式(掩码或列表)
  3. 写入期望的 ID 和掩码值
  4. 使能唤醒中断
  5. 进入休眠模式
注意:唤醒滤波器必须在进入休眠之前配置好。休眠后 CAN 控制器只保留唤醒逻辑工作,内核不再执行代码。我曾经在休眠后才配置滤波器,结果节点永远醒不过来——因为配置根本没写进去。

代码示例:配置唤醒滤波器

/* 配置 CAN FD 唤醒滤波器 - 基于 STM32H743 */
void CANFD_WakeUpFilter_Config(void)
{
    /* 1. 使能 CAN FD 时钟 */
    __HAL_RCC_FDCAN1_CLK_ENABLE();
    
    /* 2. 配置滤波器:只唤醒 ID = 0x123 的标准帧 */
    hfdcan1.Instance = FDCAN1;
    hfdcan1.Init.FrameFormat = FDCAN_FRAME_CLASSIC;
    hfdcan1.Init.Mode = FDCAN_MODE_NORMAL;
    HAL_FDCAN_Init(&hfdcan1);
    
    /* 3. 设置唤醒滤波器 */
    HAL_FDCAN_ConfigWakeUpFilter(&hfdcan1, 
                                 0x123,           /* 期望的 ID */
                                 FDCAN_FILTER_MASK_ID,  /* 掩码模式 */
                                 FDCAN_FILTER_11BIT);   /* 11 位 ID */
    
    /* 4. 使能唤醒中断 */
    HAL_FDCAN_ActivateNotification(&hfdcan1, 
                                   FDCAN_IT_WAKEUP, 0);
    
    /* 5. 使能自动唤醒 */
    HAL_FDCAN_EnableAutoWakeUp(&hfdcan1);
}

唤醒中断处理

节点被唤醒后,第一件事就是进中断。
唤醒中断处理函数里,我一般做这几件事:

  • 清除唤醒标志:防止重复进中断
  • 恢复时钟和 PLL:从休眠时的低速时钟切回高速时钟
  • 重新初始化外设:CAN 控制器、DMA、定时器等
  • 读取唤醒帧 ID:确认是谁叫醒了自己(可选)
  • 设置唤醒原因标志:供主循环判断处理
我的经验:唤醒中断里不要做耗时操作。我习惯只设一个标志位,具体业务逻辑放到主循环里处理。中断里做太多事,容易影响其他唤醒源的响应。

代码示例:唤醒中断处理

/* 唤醒中断回调 */
void HAL_FDCAN_WakeUpCallback(FDCAN_HandleTypeDef *hfdcan)
{
    /* 1. 清除唤醒标志 */
    __HAL_FDCAN_CLEAR_FLAG(hfdcan, FDCAN_FLAG_WAKEUP);
    
    /* 2. 记录唤醒源 */
    wakeup_source = WAKEUP_SOURCE_CAN;
    
    /* 3. 恢复系统时钟 */
    SystemClock_Config_HSE();
    
    /* 4. 重新初始化 CAN FD */
    CANFD_Init();
    
    /* 5. 设置唤醒标志,主循环处理 */
    g_wakeup_flag = 1;
}

知识结构总览

下面这张图把 CAN FD 唤醒机制的完整链路画出来了:

CAN FD 唤醒机制结构图 CAN FD 唤醒机制结构图 本地唤醒 远程唤醒 GPIO 中断 定时器 / RTC 闹钟 比较器 / ADC 阈值 任意帧唤醒 特定帧唤醒 (需滤波器匹配) 唤醒滤波器配置 ID 掩码模式 | ID 列表模式 | 11位 / 29位 ID 唤醒中断处理 清除标志 → 恢复时钟 → 重初始化外设 → 主循环处理

避坑指南

做唤醒设计这几年,我踩过的坑不少。挑几个典型的说说:

  • 滤波器配置时机:必须在休眠前配好,休眠后写不进去
  • 唤醒消抖:总线噪声可能误唤醒,硬件加 RC 或软件加延时
  • 中断优先级:唤醒中断优先级别设太高,避免打断关键时序
  • 时钟切换:唤醒后先恢复高速时钟,再操作外设,否则 CAN 控制器可能工作异常
我曾经犯过的错:有个项目,唤醒后直接发 CAN 报文,结果总线一直报错。查了半天,发现是 PLL 还没稳定,CAN 控制器用的还是低速时钟,位时序全乱了。后来在唤醒中断里加了 5ms 延时等 PLL 锁定,问题解决。

好了,CAN FD 的唤醒机制就聊到这儿。
记住一句话:唤醒设计,三分靠硬件,七分靠软件配置。
滤波器配对了,中断处理写利索了,你的节点才能睡得安稳、醒得及时。