app_power.c 学习笔记:从端口状态机到 DCDC 调压链路

📅 2026/6/30 3:42:03
app_power.c 学习笔记:从端口状态机到 DCDC 调压链路
app_power.c 学习笔记从端口状态机到 DCDC 调压链路本文是对 SDK 中app_power.c电源控制逻辑的一次学习整理重点围绕pwr_process()、drv_protocol_power_get()、drv_pwr_adjust()三个函数梳理从协议功率获取到 DCDC 调压执行的完整链路。一、学习目标今天主要复习并梳理app_power.c中电源控制主线目标是搞清楚三个问题系统什么时候开电、关电、进入 5V、安全握手、快充调压协议层协商出来的电压、电流是怎么传到 power 层的power 层拿到目标电压电流后如何加工并下发给 DCDC最终形成的完整链路如下协议层 / 快充协议 ↓ drv_protocol_power_get() ↓ pwr_process() ↓ drv_pwr_adjust() ↓ dev_ctrl() ↓ DCDC driver ↓ 硬件执行二、整体电源控制主线这套 SDK 的电源控制不是某一个函数单独完成的而是多个模块分层配合main.c ↓ 创建任务 / 初始化模块 ↓ app_port.c / app_prot.c ↓ 检测 Type-C / PD / 端口状态 ↓ app_power.c ↓ 根据端口状态控制电源 ↓ app_protect.c ↓ 异常保护 ↓ dev_ctrl() ↓ DCDC / MOS / ADC / GPIO 等底层驱动可以用一句话概括Port 负责判断状态Power 负责执行电源策略Protect 负责异常保护Driver 负责真正操作硬件。三、pwr_process()app_power 的电源状态机核心pwr_process()是今天学习的重点之一。它的核心作用是根据当前端口状态_status决定当前端口应该关电、开 5V、等待握手、进入快充调压还是保护关断。函数开头会读取几个关键状态port_type_t_typeapp_port_get_type(hpwr-id);port_state_t_statusapp_port_get_status(hpwr-id);port_power_role_t_pprapp_port_get_ppr(hpwr-id);port_protocol_t_protocolapp_port_get_protocol(hpwr-id);这几个变量决定了后续动作变量作用_status当前端口状态例如空闲、拔出、握手、快充、保护挂起_ppr当前电源角色例如 Source、Sink、Bypass_protocol当前协议类型例如 PD、QC、FCP、SCP、UFCS、VOOCpwr_process()的核心结构是switch(_status){casePORT_STATE_IDLE:break;casePORT_STATE_UNATTACHED:break;casePORT_STATE_VBUS_SAFE:break;casePORT_STATE_HANDSHAKE:break;casePORT_STATE_FAST_CHARGE:break;casePORT_STATE_ATTACHED_OCP_HANG:break;}也就是说它本质上是一个电源状态机执行函数。四、pwr_process()中几个重要状态1.PORT_STATE_IDLE空闲状态空闲状态下主要是清理当前端口的电源记录h_power[hpwr-id].curr_pwr.vol0;h_power[hpwr-id].curr_pwr.curr0;hpwr-vsafe5v_waitfalse;可以理解为端口当前没有电源动作 ↓ 清空电压、电流记录 ↓ 等待下一次插入或状态变化2.PORT_STATE_UNATTACHED拔出状态当端口拔出时代码会执行drv_pwr_vsafe0v(hpwr);app_port_set_status(hpwr-id,PORT_STATE_IDLE);含义是设备拔出 ↓ 关闭 VBUS ↓ 卸放到 0V ↓ 状态回到 IDLE这里的重点是拔出后不能让 VBUS 残留高电压所以要执行关断和卸放。3.PORT_STATE_VBUS_SAFE安全 5V 阶段这个阶段不是快充而是快充前的安全准备阶段。Source 放电时Source 表示当前端口对外供电。流程大致是进入 VBUS_SAFE ↓ DCDC 先建立安全 5V ↓ 检测 VBUS 是否达到安全范围 ↓ 打开 Source MOS ↓ 进入 HANDSHAKE核心动作包括drv_pwr_vsafe5v_src(hpwr);app_power_src_sw_on(hpwr-id);app_port_set_status(hpwr-id,PORT_STATE_HANDSHAKE);这里不是一上来就快充而是先保证 5V 安全输出。Sink 充电时Sink 表示当前设备被外部充电器供电。流程大致是进入 VBUS_SAFE ↓ 准备接收外部 5V ↓ 确认 VBUS 没有异常高压 ↓ 打开 Sink MOS ↓ 进入 HANDSHAKE核心动作包括drv_pwr_vsafe5v_snk(hpwr);app_power_snk_sw_on(hpwr-id);app_port_set_status(hpwr-id,PORT_STATE_HANDSHAKE);总结一句话VBUS_SAFE 阶段的核心作用是先建立安全 5V为后续协议握手和快充调压做准备。4.PORT_STATE_HANDSHAKE协议握手阶段进入HANDSHAKE时端口已经有了安全 5V但协议不一定已经完成快充协商。因此这里通常先使用默认 5V 和默认电流_power.volPOWER_VSAFE_5V;不同角色下默认电流不同if(_pprPPR_SRC_SINGLE){_power.currPOWER_CURR_3A3;}elseif(_pprPPR_SNK_SINGLE){_power.currPOWER_CURR_1A5;}可以理解为HANDSHAKE 阶段 ↓ 快充还没真正开始 ↓ 先用默认 5V 维持供电或充电这里也可能调用drv_pwr_adjust()但目的不是进入快充而是维持默认 5V 电源状态。5.PORT_STATE_FAST_CHARGE快充调压阶段这个阶段才是真正进入快充调压。流程如下进入 FAST_CHARGE ↓ 根据 _protocol 判断当前协议类型 ↓ drv_protocol_power_get() 获取协议目标电压电流 ↓ 如果获取成功 ↓ drv_pwr_adjust() 调整 DCDC 电压电流核心代码逻辑是_retdrv_protocol_power_get(hpwr,(port_protocol_t)(1i),_ppr,_power);if(_retERR_OK){drv_pwr_adjust(hpwr,_ppr,_power);}所以快充阶段的关键链路是协议层协商结果 ↓ drv_protocol_power_get() ↓ _power.vol / _power.curr ↓ drv_pwr_adjust() ↓ DCDC 调压调流6. 保护挂起状态例如casePORT_STATE_ATTACHED_OTP_HANG:casePORT_STATE_ATTACHED_UVP_HANG:casePORT_STATE_ATTACHED_OCP_HANG:{app_power_sw_off(hpwr-id);break;}这些状态表示端口处于保护挂起状态含义OTP_HANG过温保护挂起UVP_HANG欠压 / 过放保护挂起OCP_HANG过流 / 短路保护挂起这说明app_protect.c和app_power.c之间不是孤立的。典型流程是app_protect 检测到异常 ↓ 设置端口状态为 xxx_HANG ↓ pwr_process() 看到 HANG 状态 ↓ 关闭电源开关五、drv_protocol_power_get()从协议层获取目标功率drv_protocol_power_get()的作用可以简单理解为问协议层一句话当前应该使用几伏几安它不负责协议协商也不直接调 DCDC只负责把协议层已经得到的结果取出来填到power-vol power-curr函数接口如下staticerrno_tdrv_protocol_power_get(app_power_t*hpwr,port_protocol_t_prot,port_power_role_t_ppr,app_bus_power_t*power)参数含义参数含义hpwr当前端口的 power 句柄_prot当前协议类型例如 PD、QC、FCP、SCP、UFCS_ppr当前电源角色Source 或 Sinkpower输出参数用来保存协议目标电压、电流1. PD 协议分支PD 分支的核心流程是读取 PD 当前电源切换状态 ↓ 如果正在切换电压 / 电流返回 ERR_ERR ↓ 如果状态可用读取 PD 协商功率 ↓ 填入 power-vol / power-curr ↓ 根据 Source Fixed PDO 情况做电流补偿 ↓ 返回 ERR_OK核心代码是_ps_statusps_pd_power_transition_status_get(id2pdport(hpwr-id));if(_ps_statusPS_SRC_PRE_TRANSITION||_ps_statusPS_SNK_TRANSITION){returnERR_ERR;}ps_pd_power_transition_get(id2pdport(hpwr-id),(power_pd_t*)power);其中ps_pd_power_transition_get(...,(power_pd_t*)power);可以理解为从 PD 协议层拿出已经协商好的电压、电流 ↓ 写入 power 结构体例如 PD 协商结果是 9V / 3A则power-vol 9000 power-curr 3000如果当前是 Source 且 PD 是 Fixed PDO还会执行power-curr400;这属于电流 offset 补偿用来应对硬件限流误差避免过早触发限流。2. 非 PD 协议分支QC、FCP、SCP、UFCS、VOOC 等协议大多通过prot_mp_ctrl(id2mpport(hpwr-id),PROT_IO_CTRL_CMD_MP_DISCHRG_POWER_TRANSITION,(void*)power);来获取协议目标电压电流。可以理解为向多协议 MP 模块询问 当前协议协商出来的是几伏几安不同协议还会根据自身特点做限流或补偿例如协议处理逻辑QC/QC30Sink 最大 3ASource 电流加 offset 后封顶AFC/FCP12V 档限制电流避免功率过高SCP高压档限制电流低压档允许更大电流接近恒功率控制UFCS高压档电压补偿电流小幅补偿VOOC电流最大限制到 5.6A六、drv_pwr_adjust()把目标功率加工成 DCDC 设置值drv_pwr_adjust()的作用是把协议或策略给出的目标电压、电流结合 Source/Sink 角色进行加工然后通过dev_ctrl()下发给 DCDC。它和drv_protocol_power_get()的区别是函数作用drv_protocol_power_get()从协议层拿目标电压电流drv_pwr_adjust()把目标电压电流加工成 DCDC 实际设置值函数中有两个重要变量app_bus_power_t*_p_pwr_info(app_bus_power_t*)arg;app_bus_power_t_pwr_info;含义如下变量含义_p_pwr_info原始目标值来自协议或策略_pwr_info加工后的实际 DCDC 设置值七、Source 放电电压往上补Source 表示当前端口对外供电。在 Source 放电时主要考虑两个问题电流限流补偿IR Drop 线损补偿1. 电流限流补偿代码中会根据平台和电流档位对目标电流进行补偿例如_pwr_info.curr_p_pwr_info-curr;_pwr_info.curr500;这类补偿的目的不是随意超协议输出而是为了修正硬件限流误差避免实际电流还没达到协议目标就提前限流。2. IR Drop 电压补偿Source 对外供电时线材、MOS、PCB 走线都会产生压降。例如协议目标是 9V但由于线损负载端可能只收到 8.8V。因此代码会根据电流计算补偿电压_vol_irdrop0app_power_get_ibus(PORT_USB_PORT_1)/10;_vol_irdrop1app_power_get_ibus(PORT_USB_PORT_2)/10;注释中说明1A 补 100mV所以1A → 补 100mV 2A → 补 200mV 3A → 补 300mV最终_pwr_info.vol_p_pwr_info-vol_vol_irdrop;即协议目标电压 线损补偿 DCDC 实际设置电压例如协议目标9V 线损补偿0.2V DCDC 设置9.2V八、Sink 充电电压往下让电流慢慢升Sink 表示当前设备被外部充电器供电。Sink 充电时和 Source 放电完全不同。1. 充电自适应电压Sink 充电时DCDC 不会直接设置成外部输入电压而是设置得低一点if(_p_pwr_info-volPOWER_VOL_15V){_pwr_info.vol_p_pwr_info-vol-POWER_VOL_1V5;}elseif(_p_pwr_info-volPOWER_VOL_5V5){_pwr_info.vol_p_pwr_info-vol-POWER_VOL_1V;}elseif(_p_pwr_info-volPOWER_VOL_3V){_pwr_info.vol_p_pwr_info-vol-POWER_VOL_0V5;}else{_pwr_info.volPOWER_VOL_4V4;}可以理解为外部输入电压要比内部 DCDC 设置电压高一点这样 DCDC 才有调节空间。例如外部协议电压DCDC 实际设置留出调节余量20V18.5V1.5V12V11V1V9V8V1V5V4.5V0.5V核心理解Source 放电电压往上补保证负载端电压够 Sink 充电电压往下让保证 DCDC 有调节空间2. 充电电流缓增Sink 充电时电流也不会一下子拉满而是通过operate_curr慢慢爬升_operate_currhpwr-operate_curr;if((_operate_curr_pwr_info.curr)(ticker_out(hpwr-pwr_timer,TIME_PWR_CURRENT_STEP)true)){hpwr-pwr_timerticker_read();_operate_currCFG_PWR_CURRENT_STEP;}例如目标电流是 3000mA实际过程可能是500mA ↓ 600mA ↓ 700mA ↓ ... ↓ 3000mA这样做的目的避免输入电压被瞬间拉低 避免 DCDC 冲击 避免 MOS 电流冲击 避免误触发 OCP 避免充电器误判异常总结一句话Sink 充电不是暴力拉电流而是先留电压余量再让电流慢慢爬升。九、dev_ctrl()从 app 层到 driver 层的命令通道当drv_pwr_adjust()算出最终要设置的电压、电流后会调用dev_ctrl(hpwr-fd_dev_dcdc,DEV_IO_CTRL_CMD_DCDC_SET_VBUS,(void*)_pwr_info.vol);dev_ctrl(hpwr-fd_dev_dcdc,DEV_IO_CTRL_CMD_DCDC_SET_IBUS,(void*)_operate_curr);这里的含义是找到当前端口绑定的 DCDC 设备 ↓ 发送 SET_VBUS / SET_IBUS 命令 ↓ 由 DCDC driver 真正执行所以dev_ctrl()不是最终控制硬件的函数而是 app 层通向 driver 层的统一命令入口。十、今天打通的完整链路今天最终理解的完整链路如下PD / QC / FCP / SCP / UFCS / VOOC 协议层 ↓ 协议层得到目标电压电流 ↓ drv_protocol_power_get() ↓ 写入 power-vol / power-curr ↓ pwr_process() ↓ 判断当前端口状态是否允许调电 ↓ drv_pwr_adjust() ↓ 根据 Source / Sink 做补偿、限流、缓启动 ↓ dev_ctrl() ↓ 下发 SET_VBUS / SET_IBUS ↓ DCDC driver ↓ 真正控制硬件也可以压缩成一句话协议层决定目标功率pwr_process 判断状态drv_protocol_power_get 获取目标drv_pwr_adjust 加工目标dev_ctrl 下发命令DCDC 驱动执行硬件动作。十一、今日学习收获今天主要掌握了以下内容pwr_process()是app_power.c的电源状态机核心。VBUS_SAFE阶段是快充前的安全 5V 准备阶段。HANDSHAKE阶段还不是真正快充主要是默认 5V 供电或充电。FAST_CHARGE阶段才会从协议层获取目标功率并进入真正调压调流。drv_protocol_power_get()负责从协议层读取目标电压电流。drv_pwr_adjust()负责把目标电压电流加工成 DCDC 实际设置值。Source 放电时电压往上补主要是补线损和限流误差。Sink 充电时电压往下让主要是给 DCDC 留调节空间。Sink 充电电流需要缓慢爬升避免冲击和误触发保护。dev_ctrl()是 app 层到 driver 层的统一命令入口。十二、下一步学习方向下一步可以继续看drv_protocol_reset_dtc()drv_ac_adapter_aout_dtc()pwr_telemetry()重点关注协议复位如何检测 适配器掉压 / 掉档如何判断 power 层如何结合采样值做动态控制 protect 保护状态如何影响 power 状态机当前阶段已经基本打通了端口状态 → 协议功率 → power 决策 → DCDC 调压 → 底层执行后面需要继续补强的是异常检测 → 协议复位 → 掉档处理 → protect 和 power 的联动