Skip to content

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)校验和
HeaderCmdRegLenData…Sum

校验和 = (寄存器 + 长度 + Σ数据) & 0xFF头与命令不参与);设备回包头 0xF0;数值一律 IEEE754 float32 小端。

命令字节:

命令用途
0xA1读寄存器
0xB1写寄存器
0xB0设波特率
0xC1会话连接/断开
0xC0⚠️ 进 DFU 刷机模式——禁发(CDC 消失,需拔插恢复)

关键寄存器:

寄存器类型含义
0xC1float32电压设定
0xC2float32电流设定
0xDBuint8输出开关(0/1)
0xC0float32输入电压
0xC33×float32实测输出 V/A/W
0xDCuint8保护状态(0=OK,1=OVP…6=REP)
0xDDuint80=CC / 1=CV
0xFF139B全状态 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-dps150tool/dps150.py(仅依赖 pyserial)
  • 协议出处:社区逆向 nasheed-x/dps150_api(PROTOCOL.md,交叉验证 JS/C/Python/Rust 四份实现 + 反编译官方 .NET 上位机)
  • 完整寄存器表、139 字节布局、预设组公式见 skill fnirsi-dps150 参考文档