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的执行流程:
- 发起方拉低CC线(至少1ms)
- 双方检测到CC线拉低,立即停止供电
- 双方复位到初始状态
- 等待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固件开发就成功了一半。