
简介面向充电器设计、电源适配器及智能设备调试人员重点解决通用充电器兼容 QC2.0、QC3.0、MTK PE/PE 等主流快充协议的识别与选型问题。内容从快充历史与 USB-IF 电池充电规范讲起逐步拆解手机端 DP/DM 加载电压如何控制充电器输出电压电流并对比 QC2.0、QC3.0、MTK PE/PE 等协议的输出档位与适用场景随后结合 Genesys Logic GL889M-10 等识别芯片说明通用充电器兼容多品牌设备的设计方法。资源为单个 doc 文档大小 176KB便于阅读与二次编辑可作硬件方案讨论时的共享笔记。文档涵盖快充协议发展脉络、基本原理、协议类型对比、通用充电器设计方法和识别芯片说明等完整知识模块并带有常见协议的端口类型、充电电流、输出电压与功率对照表能为硬件工程师选型、方案评估及入门读者理解快充原理提供直接可用的参考资料。目前已有 1696 人浏览学习属于短小精悍、信息密度较高的技术文档。1. 快充协议识别是通用充电器的第一步通用充电器要同时充高通和MTK平台的手机就不能只做一根“5V都行”的线。QC2.0/QC3.0走D/D-电压协商MTK PE/PE走VBUS脉冲而USB BC1.2充电识别决定了设备怎么看这个充电器。识别顺序错乱轻则充电慢重则烧手机充电IC。这篇文章把这几套机制的通信原理、参数配置和STM32上的状态机实现按顺序拆一遍让做充电器、移动电源和协议分析工具的人能直接照着搭。新手按章节复现就能跑通有经验的工程师可以重点看第4章的冲突处理和最后的示波器调试思路。2. QC2.0与QC3.0的D/D-电压协商实现2.1 QC协议的本质用D/D-电压传递调压指令QCQuick Charge是高压快充充电器通过USB数据线D和D-上的模拟直流电压来告诉设备自己支持的能力设备再通过改变D/D-上的分压电阻来“回话”。因此实现QC不需要USB PD那种完整协议栈只需要两个ADC通道、两个DAC或者一个分压电阻网络MCU主频低一点也无所谓。这也是很多充电器用廉价PWM单片机就能做QC的原因。QC2.0规定的是固定输出电压5V、9V、12V、20V其中20V通常只在Class B里出现手机端很少主动请求。QC3.0则把电压从3.6V到20V以200mV为步进连续可调。下表是设计和调试时常用的参数表我把它们写进固件数组需要时就查表。协议电压范围步进最大电流典型应用QC2.0 Class A5V / 9V固定3A手机快充QC2.0 Class B5V / 9V / 12V / 20V固定3A平板、笔记本QC3.0 Class A3.6V - 12V200mV3A中端手机QC3.0 Class B3.6V - 20V200mV3A旗舰手机、便携屏这里要特别区分“固定档位”和“步进”两个概念。QC2.0是设备直接请求一个离散电压充电器必须精确输出9V或12V不能有中间态。QC3.0是在当前电压基础上加或减200mV所以充电器内部要维护一个“当前输出电压”的变量每次收到一个脉冲就加/减一个步进。如果直接按QC2.0的方式跳到目标电压设备会在握手阶段放弃快充并退回5V。2.2 用ADC采集D/D-用DAC输出识别电压硬件上充电器端的D和D-各接一个分压电阻到MCU的ADC引脚同时通过一个模拟开关连接到DAC输出。这样既能读到设备给出的模拟电压也能主动输出识别电压来宣告充电器支持快充。常见的MCU选型是STM32G0自带12bit ADC和12bit DAC双DAC正好对应D/D-代码量很紧凑。初始化DAC的代码可以这样写// 初始化DAC引脚PA4输出DPA5输出D- void dac_init(void) { RCC-AHB2ENR | RCC_AHB2ENR_DAC12EN; DAC12-MCR | DAC12_MCR_MODE1(0) | DAC12_MCR_MODE2(0); DAC12-DHR12R1 0; DAC12-DHR12R2 0; DAC12-CR | DAC_CR_EN1 | DAC_CR_EN2; } // 设置D电压输入毫伏基准3.3V void set_dplus_mv(uint16_t v_mv) { uint32_t dac (uint32_t)v_mv * 4095UL / 3300UL; if (dac 4095) dac 4095; DAC12-DHR12R1 dac; }set_dplus_mv是我们随身用的函数输入毫伏直接写DAC寄存器。STM32G0的DAC12是电压输出型不需要外部运放就能驱动几十kΩ的负载但D/D-线上有设备端的电阻网络所以还是要加一个几百Ω的隔离电阻。DAC的更新频率最好在10kHz以上否则QC3.0的高频脉冲会失真。注意DAC的基准电压要稳我用3.3V LDO的输出不能用VBUS经过LDO后的电压否则负载变化会影响DAC满幅值。2.3 握手状态机和关键时序QC2.0/QC3.0的握手分为两个阶段先是BC1.2识别这个放到第4章讲然后才是调压阶段。调压阶段充电器会先输出一个“支持快充”的基准电压等待设备端把D或D-拉低然后根据D/D-的电压组合选择目标电压。下面是一个可移植的QC固定档位跳转代码typedef enum { QC_INIT, QC_HANDSHAKE, QC_ADJUST, QC_ERROR } qc_state_t; static uint16_t target_mv; void set_vbus_target(uint16_t mv) { target_mv mv; // 通过数字电位器写升压芯片反馈电阻 } void qc_poll(qc_state_t *state, uint16_t dplus_mv, uint16_t dminus_mv) { switch (*state) { case QC_INIT: set_dplus_mv(600); // 示例值0.6V握手电平 set_dminus_mv(0); if (dplus_mv 1000) { *state QC_HANDSHAKE; } break; case QC_HANDSHAKE: if (dplus_mv 2000 dminus_mv 200) { set_vbus_target(9000); // 9V档 *state QC_ADJUST; } else if (dplus_mv 1000 dminus_mv 1000) { set_vbus_target(12000); // 12V档 *state QC_ADJUST; } else { *state QC_ERROR; } break; case QC_ADJUST: if (vbus_sampled() target_mv - 300) { *state QC_INIT; } break; default: *state QC_INIT; break; } }代码里的set_vbus_target是升压芯片的目标电压写入函数不同方案是不同接口但逻辑一致先把目标电压存到一个全局变量再通过数字电位器或FB引脚分压电阻去设置输出电压。vbus_sampled返回当前的VBUS ADC采样值用它来判断输出电压是否稳定。QC3.0的连续调压需要在这个状态机里额外增加一个脉冲解析分支在QC_ADJUST状态下D上持续出现低电平脉冲每个低脉冲代表一个步进增量。常见做法是用单片机的定时器捕获输入检测下降沿然后在两次下降沿之间测量低电平宽度宽度在15~35ms内就认为是一个有效步进。收到一个步进就把current_vbus_mv加200mV然后重新调用set_vbus_target。我在调试中发现步进脉冲的电压阈值不要定得太死D电平低于0.3V就算低脉冲高于0.8V就算高电平因为设备端的OCP和线材压降会让实际电平有0.1V以上的偏移。3. MTK PE与PE协议通过VBUS脉冲改变电压3.1 PE协议与QC的差异不看D/D-看VBUS纹波MTK的Pump Express协议和QC走两条完全不同的物理通道。PE协议不占用D/D-而是通过VBUS上的电流负载变化来传递信息。设备在握手时快速拉低VBUS上的电流充电器端检测到VBUS电压出现“跌落—恢复”的脉冲序列每个脉冲代表一次电压调整请求。PE在此基础上增加了更高的电压档位。这带来的好处是数据线完全释放D/D-可以留给充电识别用但也让PE解调对电源纹波特别敏感如果电源本身的动态响应太快会把正常负载波动误判成请求脉冲。PE协议常见档位是5V、7V、9V、12VPE在PE基础上扩展到15V和20V。与QC的直流电平不同PE的每个脉冲宽度和数量是有要求的。以我调试过的设备来看一个有效的PE脉冲宽度大约是5~10ms设备连续发送3个脉冲数量表示请求某一档电压。具体档位对应的脉冲数量因设备版本而异通用做法是把所有组合做成一张映射表放到固件里通过产品测试校准。3.2 PE握手状态机的实现充电器端对VBUS做高频采样检测下跳沿然后进入脉宽判定。下面这段代码展示了一个最基本的PE状态机typedef enum { PE_WAIT_IDLE, PE_WAIT_PULSE, PE_UPDATE_VBUS } pe_state_t; void pe_poll(pe_state_t *st, uint16_t vbus_mv) { static uint16_t pulse_count 0; static uint32_t last_fall 0; switch (*st) { case PE_WAIT_IDLE: if (vbus_mv 4700) { last_fall now_ms(); *st PE_WAIT_PULSE; } break; case PE_WAIT_PULSE: if (vbus_mv 4900 (now_ms() - last_fall) 8) { pulse_count; if (pulse_count 3) { set_vbus_target(7000); // 3个脉冲示例请求7V pulse_count 0; *st PE_UPDATE_VBUS; } } else if ((now_ms() - last_fall) 20) { pulse_count 0; *st PE_WAIT_IDLE; } break; case PE_UPDATE_VBUS: if (vbus_mv 6800) { *st PE_WAIT_IDLE; } break; } }代码里4700mV和4900mV两个阈值是示例实际项目要根据VBUS纹波大小调整。我一般会先用示波器抓一下空载和负载跳变时的VBUS振幅把阈值设定在振幅的2倍以上避免误触发。now_ms()是毫秒时间戳可以用任意定时器实现。注意PE状态机里每个脉冲之后要重新等待“恢复”到高电平再数下一个脉冲上面的简化版直接每次下降沿就加1在真实场景里会漏数所以你需要在PE_WAIT_PULSE里再嵌套一个“等待上升沿”的小状态。3.3 与QC协议共存的冲突处理通用充电器通常同时支持QC和PE识别冲突就成了最大的坑。QC的握手信号在D/D-上PE的握手信号在VBUS上从物理层看互不干扰。但设备上电初期会有BC1.2识别动作这个动作会把D/D-短接导致D/D-同时被拉低充电器如果此时正在PE脉冲检测就会把设备端“短接D/D-”这个瞬态当成VBUS脉冲。于是我在固件里强制规定了一个时序见下表时间窗口动作说明0-100msD/D-短接VBUS5V完成BC1.2 DCP识别100-400ms释放短接ADC扫描D/D-等待QC握手400-700ms若D/D-无变化启用PE脉冲检测等待MTK请求700ms维持5V输出设备不支持快充这个时序的本质是让BC1.2识别、QC识别、PE识别在时间上错开。QC设备通常在上电100ms内就开始在D/D-上打信号所以100ms前不做VBUS脉冲检测MTK设备慢热400ms后才开始拉VBUS脉冲所以400ms前不做PE检测。在100-400ms的窗口里如果D/D-没有任何变化再跳到PE检测就能避免“用QC的D/D-短接动作去触发PE计数器”的误判。另外还需要注意PE协议允许充电器在升压过程中不改变D/D-状态所以当你检测到PE请求并提升VBUS后千万不要顺手把D/D-设置成QC的握手电平否则MTK设备会乱套。正确做法是升压期间把D/D-设置成高阻只保持VBUS闭环。4. 充电识别BC1.2与协议选择策略4.1 BC1.2三种模式SDP、CDP、DCP充电识别是USB BC1.2规范定义的内容它描述了设备如何判断自己插到了什么样的端口。SDP标准下行端口是PC上的普通USB口D和D-分别有15kΩ下拉电阻设备只能拿到500mACDP充电下行端口是既能传数据又能提供大电流的端口D和D-短接设备可以拉取1.5A以上DCP专用充电端口是充电器最常见的形态D和D-直接连在一起设备会认为这是“墙充”电流上限由充电能力决定。通用充电器必须实现DCP否则很多手机插上只会按500mA慢慢充。BC1.2的检测过程由设备发起设备先给D施加一个小电流看D的电压是否被拉低如果D和D-都处于低电平就判定为DCP。这个检测过程在充电器上电后的几十毫秒内完成。所以充电器侧在0~100ms内短接D/D-正是为了让设备顺利识别成DCP。4.2 识别顺序为什么先做BC1.2再做快充设备端识别完BC1.2之后才会去尝试QC或PE握手。如果你在BC1.2阶段把D/D-长按在一起那么后续QC的D/D-调压信号就会被物理短路设备自然无法请求9V。因此充电器必须在BC1.2识别完成之后把D/D-从短接状态切换成独立状态再开始扫描QC/PE信号。下面这段代码是BC1.2识别完成后切换到快充检测的典型逻辑uint8_t detect_fastcharge(uint16_t dplus_mv, uint16_t dminus_mv) { // 断开D/D-短接MOS让引脚浮空 mos_toggle(0); delay_ms(20); // 快速扫描判断设备端意图 if (dplus_mv 300 dminus_mv 100) { return CHG_BC12_DCP_ONLY; // 设备只做DCP } if (dplus_mv 500 dminus_mv 50) { return CHG_QC_INIT; // 设备拉高D进入QC握手 } if (dplus_mv 50 dminus_mv 500) { return CHG_PE_DETECT; // 设备拉高D-进入PE检测 } return CHG_UNKNOWN; }这段代码里的mos_toggle(0)是断开D/D-短接MOS管。注意断开后要延时20ms等线路电容上的电荷放干净再做ADC扫描。如果扫描到D低、D-低说明设备没有发起任何快充协议那就保持5V输出。如果D电压大于0.5V而D-接近0就是一个典型的QC握手信号反之D-大于0.5V而D接近0则可能是PE设备在拉D-。这个判断的阈值不能太小我一般取0.3V为低、0.5V为高留足噪声余量。实际中有些设备会一开始把D和D-都拉到高电平表示“我是支持多种协议的设备你来挑”。这种情况比较少我返回CHG_UNKNOWN后继续轮询不做任何升压动作避免误升压。4.3 苹果充电识别与快充共存的参数表除了BC1.2苹果设备有自己的一套充电识别方式通过D/D-分压电阻来识别充电器类型。通用充电器如果要兼容苹果设备需要在BC1.2阶段短接D/D-然后在设备完成识别后切换成苹果电阻网络。下表是常见的苹果识别组合也是很多充电器出厂时默认烧录的参数。识别模式D分压电阻D-分压电阻输出能力Apple 1A75kΩ到地75kΩ到地5V / 1AApple 2.1A75kΩ到地43.2kΩ到地5V / 2.1AApple 2.4A75kΩ到地43.2kΩ到地5V / 2.4ABC1.2 DCP短接短接5V / 最大电流这里的电阻值是从苹果设备的识别协议里反推出来的具体实现时用D/D-上的MOS管开关切换不同阻值的下拉电阻阵列。需要注意的是苹果设备识别完成之后不会去发起QC/PE所以没必要为苹果设备升压保持5V就行。如果你想要“给iPhone快充”实际需要的是USB PD协议而不在QC/PE的讨论范围内这点不要混在一起。5. 用STM32搭一个快充识别的最小系统与调试技巧5.1 一个最小硬件框图和接线要验证上面的状态机不需要完整充电器我通常用STM32G0B1加一颗升压芯片搭最小系统。D和D-各通过一个100kΩ电阻接到ADC同时通过模拟开关接到DAC输出。VBUS通过电阻分压后接到另一个ADC通道。升压芯片的反馈电阻用数字电位器MCU通过SPI修改阻值来设定目标电压。接线如下MCU引脚功能PA0VBUS采样PA4DAC1 - DPA5DAC2 - D-PB0升压使能PB1数字电位器片选主频跑48MHzADC采样率350kspsDAC更新率10kHz可以应付QC和PE的脉冲波形。5.2 调试小技巧用示波器同时看D/D-/VBUS快充协议调试最烦的是“时序不对但电压看着都对”。我的做法是把示波器三根通道分别接D、D-和VBUS触发模式设为Normal触发电平设成VBUS下降沿。插拔手机时QC3.0的步进脉冲会以一串宽度约5ms的方波出现在D上PE脉冲则表现为VBUS上的一次深度跌落两种波形在屏幕上一眼就能区分。把串口日志的时间戳和示波器画面对齐就能确定是充电器端状态机跳错还是设备端根本没发请求。另一个技巧是往固件里加一个protocol_dump()函数每次状态切换时把D/D-电压、目标电压、当前电压都通过串口打印出来。这样没有示波器也能用串口工具分析。我一般会把protocol_dump()放在中断外调用避免影响实时性。调试结束后这个函数可以直接保留产线测试时通过读取串口日志来判定产品是否支持QC和PE比用电子负载还快。本文还有配套的精品资源点击获取