4. 状态机设计:SRC与SNK状态机迁移、PS_RDY与Wait行为、Hard Reset流程

好,咱们今天聊点硬核的——状态机。说实话,PD协议里最核心的东西就是状态机。你代码写得再花哨,状态机搞错了,设备就是死活不通电。我当年第一次调PD的时候,就栽在状态迁移上,折腾了两天才发现是PS_RDY的时序没卡对。

4.1 SRC与SNK状态机:两个角色的博弈

PD协议里,Source和Sink各自维护一套状态机。说白了,就是两边各唱各的戏,但得对上节奏。

Source端状态机,我习惯把它分成三个阶段:

  • 未连接态:等待CC检测,啥也不干
  • 协商态:发现设备插入,开始讨价还价
  • 供电态:协议谈妥,稳定输出

Sink端状态机,其实更简单:

  • 等待态:等Source发能力
  • 请求态:我要多少电压电流
  • 运行态:拿到电了,正常工作

你想想看,这两个状态机就像一对舞伴。Source先伸手,Sink接住,然后一起转圈。谁先迈错一步,整个舞就乱了。

核心要点:SRC和SNK的状态迁移必须严格遵循PD协议定义的时序。任何提前或延迟,都可能导致协议层错误。

4.2 PS_RDY与Wait行为:最容易踩的坑

PS_RDY这个玩意儿,我刚开始做的时候觉得它就是个普通消息。后来才发现,它其实是整个供电切换的「发令枪」。

PS_RDY的作用

  • Source发完能力后,必须等Sink回复Request
  • Source收到Request后,开始调整电压
  • 电压稳定后,Source发PS_RDY告诉Sink:「好了,你可以用了」
  • Sink收到PS_RDY,才能开始从新电压取电

这里有个关键点——Wait行为。我记得有一次调试,Sink端总是收不到PS_RDY,查了半天发现是Source端电压调整时间太长,超过了协议规定的15ms。嗯,这就是典型的Wait超时问题。

避坑指南:我曾经在项目中遇到Sink端在收到PS_RDY之前就开始拉大电流,结果Source端电压瞬间跌落,触发了过流保护。正确的做法是:Sink必须等到PS_RDY后再切换负载。

代码实现上,PS_RDY的处理逻辑大概是这样的:

// Source端:电压稳定后发送PS_RDY
void src_send_ps_rdy(void) {
    // 等待电压稳定
    while(!is_voltage_stable()) {
        delay_ms(1);
        if(timeout > 15) {  // 15ms超时
            send_hard_reset();
            return;
        }
    }
    // 发送PS_RDY
    pd_send_message(PDO_PS_RDY);
    // 进入供电态
    state_machine_set_state(SRC_STATE_ACTIVE);
}

// Sink端:收到PS_RDY后切换负载
void snk_on_ps_rdy(void) {
    // 确认是PS_RDY消息
    if(pd_msg.type == PD_MSG_PS_RDY) {
        // 安全切换负载
        power_switch_to_new_voltage();
        state_machine_set_state(SNK_STATE_ACTIVE);
    }
}

4.3 Hard Reset流程:最后的救命稻草

Hard Reset,说白了就是「重启大法」。协议里规定,当通信出现不可恢复的错误时,双方都要执行Hard Reset。

触发Hard Reset的场景

  • 协议层超时(比如Source等Request等了太久)
  • 消息CRC校验失败多次
  • 收到无效的消息类型
  • 电压/电流异常

Hard Reset的执行流程

  1. 发起方拉低CC线(至少1ms)
  2. 双方检测到CC线拉低,立即停止供电
  3. 双方复位到初始状态
  4. 等待CC线恢复,重新开始协商

我个人习惯在代码里把Hard Reset设计成一个独立的状态,而不是嵌套在其他状态里。这样逻辑更清晰:

// Hard Reset状态处理
void state_hard_reset(void) {
    // 1. 拉低CC线
    gpio_set_level(CC_PIN, 0);
    delay_ms(2);  // 至少1ms
    
    // 2. 关闭电源输出
    power_disable_output();
    
    // 3. 复位所有协议状态
    pd_protocol_reset();
    
    // 4. 释放CC线
    gpio_set_level(CC_PIN, 1);
    
    // 5. 进入等待态,重新开始
    state_machine_set_state(SRC_STATE_WAIT);
}

经验之谈:Hard Reset虽然粗暴,但很有效。我建议在固件里加一个Hard Reset计数器,如果短时间内连续触发多次(比如3次),就进入错误锁定状态,避免反复重启损坏硬件。

4.4 状态机实现的几个关键点

最后,我总结几个实际开发中容易忽略的地方:

关键点 说明 常见错误
状态迁移条件 每个迁移必须有明确的触发条件 遗漏了超时条件,导致状态卡死
PS_RDY时序 电压稳定后才能发,不能提前 电压还没稳就发PS_RDY,Sink端炸了
Hard Reset恢复 复位后要清空所有缓存和状态 没清缓存,导致复位后收到旧消息
状态超时处理 每个状态都要有超时保护 没有超时,设备死机后无法恢复

嗯,状态机这块儿,说白了就是「该干啥的时候干啥,不该干啥的时候别瞎干」。你只要把每个状态的进入条件、退出条件、超时处理都理清楚,代码写起来就顺了。

我记得有一次帮客户调试,他们的设备总是在插拔时死机。最后发现是状态机里少了一个「CC线断开」的检测分支。加上之后,问题就解决了。所以啊,状态机设计时,一定要把所有可能的路径都覆盖到,尤其是异常路径。

总结:SRC与SNK的状态机是PD协议的骨架,PS_RDY是供电切换的开关,Hard Reset是最后的保底手段。把这三点吃透了,PD固件开发就成功了一半。