外观
FNIRSI DPS-150:逆向串口协议,Python 直控桌面电源
巴掌大的 FNIRSI DPS-150 数控电源,官方只给 Windows 上位机。 社区已把它的 USB 串口协议完整逆向,一行 pyserial 就能读遥测、设电压电流、开关输出。 本文全程实测(DPS-150P / 固件 V1.6),代码已固化成
tool/dps150.py。
成果速览
一条命令设定并开启输出,软启动约 1.5 s 爬升到位:
| 能力 | 实测 |
|---|---|
| 读型号/固件/硬件 | DPS-150P · V1.6 · V1.0 |
| 读遥测 | 输入 20.08V、温度 30.5°C、输出 V/A/W 全状态 139 字节 |
| 写设定 | 5.000V / 0.500A,回读一致 |
| 开关输出 | 开 → 5.000V(软启动 ~1.5s);关 → 泄放 ~1.2s 归零 |
设备识别
手册里叫 DPS-150,实机是 DPS-150P,走 Micro-USB 数据口,枚举为 USB CDC 虚拟串口(内部主控是 Artery AT32):
bash
system_profiler SPUSBDataType | grep -A6 "AT32 Virtual Com Port" # 识别设备
ls /dev/cu.usbmodem* # 串口设备节点协议全景
帧结构极简——头 1 字节 + 命令 + 寄存器 + 长度 + 数据 + 校验和:
| 0xF1 | 命令 | 寄存器 | 长度 | 数据 (0..N) | 校验和 |
|---|---|---|---|---|---|
| Header | Cmd | Reg | Len | Data… | Sum |
校验和 =
(寄存器 + 长度 + Σ数据) & 0xFF(头与命令不参与);设备回包头0xF0;数值一律 IEEE754 float32 小端。
命令字节:
| 命令 | 用途 |
|---|---|
0xA1 | 读寄存器 |
0xB1 | 写寄存器 |
0xB0 | 设波特率 |
0xC1 | 会话连接/断开 |
0xC0 | ⚠️ 进 DFU 刷机模式——禁发(CDC 消失,需拔插恢复) |
关键寄存器:
| 寄存器 | 类型 | 含义 |
|---|---|---|
0xC1 | float32 | 电压设定 |
0xC2 | float32 | 电流设定 |
0xDB | uint8 | 输出开关(0/1) |
0xC0 | float32 | 输入电压 |
0xC3 | 3×float32 | 实测输出 V/A/W |
0xDC | uint8 | 保护状态(0=OK,1=OVP…6=REP) |
0xDD | uint8 | 0=CC / 1=CV |
0xFF | 139B | 全状态 dump(含 6 组预设、保护阈值、Ah/Wh) |
控制闭环实测
常用操作:
bash
python3 tool/dps150.py status # 读全状态
python3 tool/dps150.py set --voltage 12.0 --current 2.0 # 设 12V/2A(不开启)
python3 tool/dps150.py set --on # 开输出
python3 tool/dps150.py set --off # 关输出踩坑档案
| 现象 | 根因 | 解决 |
|---|---|---|
| 读寄存器字段错乱、只回 12 字节 | 设备主动推送遥测帧(0xC3 等)混进响应流 | 读回按寄存器号过滤,丢弃非目标帧 |
| 单独读 0xE2/0xE3 返回 0 | 能力寄存器只在推送帧里有值 | 从全状态 dump 的 0x6F/0x73 偏移取 max V/I |
| 设 30V 设不上去 | 降压架构:输出上限钳到输入电压以下(20V 输入 → 19.88V max) | 想高输出先提高输入电压 |
| 开输出立刻读电压只有零点几 V | 软启动爬升中 | 等待 ≥1.5s 再读 |
| 面板按键失效 | 连上位机后自动锁面板 | 断开串口会话即解锁 |
一个危险字节
0xC0 不是重启,是进 DFU 刷机模式:USB CDC 端口直接消失,必须物理拔插恢复。设备没有软重启命令。
复现与出处
- 控制工具:skill
fnirsi-dps150的tool/dps150.py(仅依赖 pyserial) - 协议出处:社区逆向 nasheed-x/dps150_api(PROTOCOL.md,交叉验证 JS/C/Python/Rust 四份实现 + 反编译官方 .NET 上位机)
- 完整寄存器表、139 字节布局、预设组公式见 skill
fnirsi-dps150参考文档