MicroPython 在 ESP32 上实现高精度电压测量系统(基于 CH224K 的 PD 诱骗方案)
硬件平台: ESP32
编程语言: MicroPython
外设元件: 10kΩ 电阻、1kΩ 电阻、100nF 电容
PD 诱骗芯片: CH224K
供电接口: USB Type-C (16P)
引言
在现代电子设备中,支持 USB Power Delivery(PD)协议的电源系统越来越普及。通过 PD 协议,设备可以在 5V、9V、12V、15V 和 20V 等多个电压档位之间动态切换,极大提升了供电灵活性。
本文介绍如何使用 ESP32 搭载 CH224K PD 诱骗芯片,结合简单的分压电路与软件滤波算法,在 MicroPython 环境下构建一个高精度、可校准的电压测量系统。该系统不仅能实时监测当前电压,还支持基于 PD 供电环境的自动/手动校准,适用于电源模块测试、快充适配器验证等场景。
一.硬件设计
1.分压电路原理图

2.分压电路原理
ESP32 内置 ADC 的最大输入电压约为 3.3V(启用衰减后可达 ~2V),无法直接测量 PD 输出的高电压(最高 20V)。因此采用 电阻分压电路:
- R1 = 10kΩ(接高压端)
- R2 = 1kΩ(接地)
- ADC 采样点位于 R1 与 R2 之间
分压比计算如下:
Vout = Vin × (R2 / (R1 + R2)) = Vin × (1k / 11k) ≈ Vin / 11
即:输入电压被衰减约 11 倍后送入 ADC。
✅ 理论分压比:11:1
3.滤波设计
为抑制高频噪声和开关电源纹波,在分压节点与地之间并联一个 100nF 陶瓷电容,构成 RC 低通滤波器,提升 ADC 采样的稳定性。
4.CH224K接线原理图

5.CH224K数据手册原理图

6.PD 电压控制:CH224K 芯片
本系统使用 CH224K 作为 PD 诱骗芯片,通过控制其 CFG1、CFG2、CFG3 引脚电平,可主动请求电源端输出指定电压档位(5V、9V、12V、15V、20V)。
ESP32 的 GPIO25、GPIO32、GPIO27 分别连接 CH224K 的 CFG 引脚,实现电压档位切换:
self.voltage = {
20: (0, 1, 0),
15: (0, 1, 1),
12: (0, 0, 1),
9: (0, 0, 0),
5: (1, 0, 0)
}
⚙️ 工作流程: ESP32 设置 GPIO → CH224K 发送 PD 请求 → 电源调整输出电压
二.软件架构概述
整个系统基于 MicroPython 实现,主要类如下:
| 类 | 功能 |
|---|---|
|
|
JSON 配置文件读写,持久化存储校准参数 |
|
|
滑动窗口 + 去除极值的均值滤波 |
|
|
非阻塞毫秒级定时器 |
|
|
控制 CH224K 设置 PD 输出电压 |
|
|
主电压检测类,集成采样、滤波、校准、匹配逻辑 |
|
|
提供自动/手动校准功能 |
三.核心代码解析
1. 配置文件管理:File 类
系统使用 set.json(可以更改) 存储最大电压和校准系数:
file = File('set.json') # 可以更改文件路径
if not file:
data = {
'Vmaximum': 20,
'Vcalibration': {
'20': 11.10553,
'15': 11.3803,
'12': 11.63826,
'9': 12.09389,
'5': 13.40413
},
}
file.update(data)
file.write()
⚠️ 实际分压比因电阻误差、ADC 偏移等需通过校准获得。
2. ADC 滤波:ADCFilter 类
采用 滑动窗口 + 去除最大最小值 的滤波策略,有效抑制异常脉冲干扰:
def get(self):
if len(self._data) <= 2:
return sum(self._data) / len(self._data)
sorted_data = sorted(self._data)
return sum(sorted_data[1:-1]) / (len(sorted_data) - 2)
3. 电压检测主类:VoltageDetection
核心功能包括:
- 初始化 ADC(
ADC.ATTN_6DB,量程 ~2V) - 实时采样与滤波
- 动态匹配当前电压档位
- 加载对应校准系数
def get_ADC(self):
return self._filter.update(self._ADC.read()) * (2.0 / 4095)
def get(self):
return round(self.get_ADC() * self._calibration, 1)
4. 电压校准机制(基于 PD 供电)
由于硬件差异,理想分压比(11)不准确。系统提供两种校准方式:
自动校准(不推荐)
VC=VoltageCalibration()
VC.automatic()
原理:
系统依次切换到 5V、9V、12V、15V、20V 档位,读取当前电压并直接输入当前电压的值,(如:获取5V电压,直接输入5)自动记录校准值。
手动校准(推荐)
VC = VoltageCalibration()
VC.manual()
流程:
- 系统通过 CH224K 切换到目标电压档位(如 12V)
- 用户使用万用表测量实际输出电压
- 输入实测值(如 12.05V)
- 系统自动计算并保存真实分压比
✅ 校准公式:
calibration = 实际电压 / ADC 测量电压
注意:关于校准如果不需要太高精度,电阻R1,R2可以使用高精度±0.1的碳膜电阻,就不需要在进行首次运行校准。
5. 高效主循环设计
系统主循环运行效率极高,单次 PS.update() 执行时间约为 1ms,确保实时性:
Time = TimeMs(1000, 1000)
while True:
PS.update() # ~1ms
if Time.update(): # 每秒触发一次
print(PS.get()) # 输出电压
time.sleep_ms(1) # 轻量延时,避免 CPU 占用过高
✅ 非阻塞设计,不影响其他任务运行。
四.使用说明
1:首次运行与校准
VC = VoltageCalibration()
VC.manual() # 推荐方式
依次设置 5V、9V、12V、15V、20V 档位,输入万用表实测值完成校准。
🔋 供电要求: 最好使用支持 PD 20V 输出的 Type-C 充电器。
2:正常运行
PS = VoltageDetection()
while True:
PS.update()
if timer.update():
print("Voltage:", PS.get(), "V")
time.sleep_ms(1)
3.电压调整
通过VoltageDetection的Restart方法来调整 15为要设置的电压支持(5,9,12,15,20)
PS = VoltageDetection()
PS.Restart(15) # 设置电压重启
注意:设置新的电压使用PS.Restart方法,不要使用PS.PM.set(PS.PM.set会出现电压缓步增加或减少的输出)方法。
五.系统总结
1.优点
- ✅ 支持 5V~20V 多档位 PD 电压测量
- ✅ 基于 CH224K 实现主动电压切换
- ✅ 分压电路简单,成本低
- ✅ 软件滤波提升稳定性
- ✅ 支持手动校准,精度高
- ✅ 非阻塞设计,实时性强(~1ms/次)
- ✅ 配置持久化,断电不丢失
2.局限性
尽管本系统已具备较高实用性,但仍存在以下局限性,未来可进一步优化:
1. ADC 精度限制
- ESP32 的 ADC 存在非线性、温漂等问题,尤其在低电压段(如 5V)误差较明显。(可以使用外置高精度的ADC)
2. 电阻温漂影响
- 普通贴片电阻在温度变化时阻值漂移,影响分压比稳定性。(可以使用低温漂的金属膜电阻,或高精度电阻±0.1%)
六.结语
本文实现了一套基于 ESP32、CH224K 和 MicroPython 的 PD 电压测量系统,具备高精度、易校准、响应快等优点,特别适用于 PD 电源开发与测试场景。
通过合理的软硬件协同设计,即使是资源有限的 MicroPython 平台,也能完成复杂的电源监测任务。未来可通过引入外部 ADC、温补算法等方式进一步提升系统性能。
七.源码
from machine import ADC, Pin, Timer
from File import file
from ADCFilter import ADCFilter
import time,json
class File(dict):
'''文件字典'''
def __init__(self, path, *args, **kwargs):
super().__init__(*args, **kwargs)
self._path = path
self._update()
def _update(self):
'''更新文件数据'''
self.clear()
try:
with open(self._path, 'r', encoding='utf-8') as f:
self.update(json.load(f))
except:
self.write()
def write(self):
'''数据写入'''
with open(self._path, 'w', encoding='utf-8') as f:
json.dump(self, f)
file = File('set.json')
if not file:
data = {
'Vmaximum': 20,
'Vcalibration': { # 电压校准
'20': 11.10553,
'15': 11.3803,
'12': 11.63826,
'9': 12.09389,
'5': 13.40413
},
}
file.update(data)
file.write()
class ADCFilter:
"""ADC 滤波器"""
def __init__(self, length=10):
'''
初始化
:param length: 滤波器长度
'''
self._length = length
self._data = []
def update(self, data):
"""
更新数据
:param data: 输入数据
:return: 滤波后的数据
"""
self._data.append(data)
if len(self._data) > self._length:
self._data.pop(0)
return self.get()
def get(self):
"""
获取数据
:return:滤波后的数据|去除最大最小值
"""
if len(self._data) <= self._length:
return sum(self._data) / len(self._data)
sorted_data = sorted(self._data) # 排序
return sum(sorted_data[1:-1]) / (len(sorted_data) - 2) # 去除高低各1个极值
def clear(self):
'''清空原有数据 用于预防突变'''
self._data = []
class TimeMs:
'''时间控制器'''
def __init__(self, val: int, start=0):
'''
:param val: 时间间隔 毫秒
:param start: 开始时间的起始位置-start 毫秒
'''
self._interval = val # 时间间隔
self._start = time.ticks_ms() - start # 开始时间
def change(self, val: int):
'''改变时间间隔'''
self._interval = val
def reset(self):
'''重置计时'''
self._start = time.ticks_ms() # 开始时间
def update(self):
'''更新时间'''
t1 = time.ticks_ms()
if t1 - self._start >= self._interval:
self._start = t1
return True
return False
class VoltageCalibration:
'''电压校准
前提条件: 请确保设备由PD快充充电器供电 最好支持20V'''
def _get_maximum_voltage(self):
'''获取最大电压列表'''
print('正在获取最大电压')
# 如果配置电压大于20将不重启电压
if not file.get('Vmaximum') >= 20:
PS.Restart(20) # 设置最高电压 并 重启
timeMs = TimeMs(3000) # 等待3秒
while True:
if timeMs.update():
max_v = PS.get() # 获取当前的电压
return [x for x in [5, 9, 12, 15, 20] if x <= max_v + 1]
PS.update() # 更新电压
time.sleep_ms(5)
def automatic(self):
'''自动校准'''
max_v = self._get_maximum_voltage() # 获取最大电压以下可以操作的列表
for v in max_v:
print('正在校准' + str(v) + '伏')
timeMs = TimeMs(3000) # 等待3秒
PS.Restart(v) # 设置电压重启
while True:
if timeMs.update():
PS.calibrate(v, v)
break
PS.update() # 更新电压
time.sleep_ms(5)
file.write() # 保存数据
PS.Restart() # 设置默认电压 并 重启
print('校准完成')
def manual(self):
'''手动校准'''
max_v = self._get_maximum_voltage() # 获取最大电压以下可以操作的列表
for v in max_v:
print('校准电压' + str(v) + '伏')
timeMs = TimeMs(3000) # 等待3秒
PS.Restart(v) # 设置电压重启
while True:
if timeMs.update():
PS.calibrate(v, float(input('请输入当前电压值:')))
break
time.sleep_ms(1)
PS.update() # 更新电压
file.write() # 保存数据
PS.Restart() # 设置默认电压 并 重启
print('校准完成')
def getmin(key, val):
'''获取最接近的key'''
return min(key, key=lambda x: abs(x - val))
class PowerManagement:
"""电源管理"""
def __init__(self):
# 创建电源引脚
self.CFG1 = Pin(25, Pin.OUT)
self.CFG2 = Pin(32, Pin.OUT)
self.CFG3 = Pin(27, Pin.OUT)
self.voltage = {
20: (0, 1, 0),
15: (0, 1, 1),
12: (0, 0, 1),
9: (0, 0, 0),
5: (1, 0, 0)} # 创建CH224K相对应的匹配项
def set(self, value):
"""
设置电压 20,15,12,9,5
"""
if value not in self.voltage:
return False
cfg1, cfg2, cfg3 = self.voltage[value]
self.CFG1.value(cfg1)
self.CFG2.value(cfg2)
self.CFG3.value(cfg3)
return True
class VoltageDetection:
'''电压检测'''
# 创建电源管理 并设置当前电压
PM = PowerManagement()
PM.set(file['Vmaximum'])
def __init__(self, pin=33):
# 创建均值滤波器
self._filter = ADCFilter(50)
# 创建ADC
self._ADC = ADC(Pin(pin))
self._ADC.atten(ADC.ATTN_6DB)
self._configure = 2.0 # ATTEN_6DB对应0-2V量程
self._calibration = 11.0 # 默认分压比,通过校准调整
# 获取60个样本进行填充
self.dataFill()
self._Time = TimeMs(600, 600) # 用于控制每次匹配电压的时间
self._state = None
self.adjust() # 调整电压策略
def dataFill(self):
'''数据填充'''
self._filter.clear()
for _ in range(60):
self.get()
def get_ADC(self):
'''
:return: 获取的 ADC 采样电压
'''
return self._filter.update(self._ADC.read()) * (self._configure / 4095)
def get(self):
'''
转换电压值
因为self.get返回的是经过10k电阻和1k电阻之间的电压值,所以要乘以 self._calibration
:return: 电压值
'''
return round(self.get_ADC() * self._calibration, 1)
def calibrate(self, key, voltage):
"""
已知电压校准
:param key: 更改的电压值
:param voltage: 当前电压
"""
self.dataFill()
readings = [self.get_ADC() for _ in range(60)] # 获取新60个样本
file['Vcalibration'][str(key)] = self._calibration = voltage / (sum(readings) / len(readings))
def adjust(self):
'''电压调整'''
self._calibration = file.get('Vcalibration')[str(self.matching())]
def matching(self):
'''
:return: 匹配相近电压值
'''
return getmin([5, 9, 12, 15, 20], self.get()) # 获取当前电压并匹配,最接近的key的值
def Restart(self, val: int | bool = False):
'''
准备重启电源,设置电压=》更新电压并在1秒后重置对象状态
:param val: 是否设置电压
'''
# 设置电压
self.PM.set(val)
def timer_callback(timer):
timer.deinit() # 先释放资源
self.__init__() # 重新初始化
# 创建并启动定时器
timer = Timer(1)
timer.init(mode=Timer.ONE_SHOT, period=1000, callback=timer_callback)
def update(self):
'''更新'''
if self._Time.update():
voltage = self.matching()
if self._state != voltage: # 如果当前电压和以前不同则调整电压策略
self._state = voltage
self.adjust()
return True
PS = VoltageDetection()
PS.Restart()
'''
#自动校准
VC=VoltageCalibration()
VC.automatic()
#手动校准
VC=VoltageCalibration()
VC.manual()
建议用’手动校准‘
'''
if __name__ == '__main__':
Time = TimeMs(1000, 1000)
while True:
PS.update()
if Time.update():
print(PS.get())
time.sleep_ms(1)
更多推荐
所有评论(0)