Last year I bought a Ruideng RD6006 variable power supply. It arrived poorly calibrated but fortunately the manufacturer provides Windows software and instructions for re-calibration. Out of necessity I installed the software in a virtual machine (in Virtualbox) and bridged the serial port on the host to let it talk to the hardware.
The calibration procedure involves finding the correct y intercept and coefficient (slope) for a linear function that maps the raw internal ADC sampling of two sets of voltage and current, to real values. Getting it perfect (or as close to perfect as possible) is a little fiddly.
After some tinkering I though it would be much better to ditch the Windows software and make my own tools. Other folks had already documented the modbus control registers for the RD6006 and there is an easy to use Python module for it.
Reverse engineering calibration adjustment
The first tool I wanted was something to read and set the eight calibration registers (0x55 through 0x62) so that I could ditch the VM. This appeared to work at first, but the new calibration values wouldn’t stick - they reverted to the old values if the power supply was turned off. I figured there must be some commit command, so I worked out a setup for sniffing the serial port. The first step is to have the VM open a UNIX domain socket instead of the character device file, i.e., instead of
/dev/ttyUSB0, to open something like
/tmp/vboxserial0. Next, I had
socat bridge between the UNIX socket and a listening TCP socket:
socat unix-connect:/tmp/vboxserial0 tcp-listen:8001
Finally, I had another
socat process connect the real serial port to the TCP port and also echo the binary data as hex to stdout:
socat -xd /dev/ttyUSB1,b115200,raw,echo=0 tcp:localhost:8001
With some careful timing and patience, I narrowed down on a command issued when the PC software saves (previously written) calibration registers to NVRAM:
> 2020/04/01 15:44:07.547249 length=8 from=520 to=527 01 06 00 36 15 01 a6 94
Happily this did the trick:
def write_calibration_registers(rd, regs): set_panel_locked(rd, True) for address, value in zip(range(55, 63), regs): print("Writing %d = %d" % (address, value)) rd._write_register(address, value) # magic register/value to commit cal registers to nvram # 01 06 00 36 15 01 a6 94 # 0x54 = 36 # 0x1501 = 5377 rd._write_register(54, 5377) set_panel_locked(rd, False) print("Done.")
The second tool I wanted was something to sweep across the output voltage range of the power supply while logging the actual voltage measured via my multi-meter:
$ ./sweep.py Usage ./sweep.py [PSU_SERIAL_PORT] [DMM_SERIAL_PORT] [OUTPUT_FILE] $ ./sweep.py /dev/ttyUSB0 /dev/ttyUSB1 before RD6006 or other detected 0.000000 0.010000 0.000100 0.009900 1.000000 1.000000 1.156400 -0.156400 2.000000 2.000000 2.317000 -0.317000 <snip> 60.000000 56.050000 64.990000 -8.940000 61.000000 56.050000 65.000000 -8.950000 62.000000 56.050000 64.990000 -8.940000
The idea is to check the calibration by plotting the result
gnuplot> plot \ 'before' using 1:2 with linespoints title "PSU display", \ 'before' using 1:3 with linespoints title "DMM V"
Now with the means to properly measure the effects to calibration adjustments, I used the first tool to iteratively align the two curves. For demonstration, this run shows switching from the “before” to “after” values:
$ ./calibrate.py /dev/ttyUSB0 RD6006 or other detected Current calibration registers: OUTPUT V ZERO 18 OUTPUT V SCALE 26770 BACK V ZERO 19 BACK V SCALE 14985 OUTPUT I ZERO 256 OUTPUT I SCALE 25278 BACK I ZERO 78 BACK I SCALE 14965 Enter new calibration registers: OUTPUT V ZERO : 20 OUTPUT V SCALE : 23205 BACK V ZERO : 20 BACK V SCALE : 17290 OUTPUT I ZERO : OUTPUT I SCALE : BACK I ZERO : BACK I SCALE : Writing new calibration registers: Writing 55 = 20 Writing 56 = 23205 Writing 57 = 20 Writing 58 = 17290 Writing 59 = 256 Writing 60 = 25278 Writing 61 = 78 Writing 62 = 14965 Done. Reading back new calibration registers: OUTPUT V ZERO 20 OUTPUT V SCALE 23205 BACK V ZERO 20 BACK V SCALE 17290 OUTPUT I ZERO 256 OUTPUT I SCALE 25278 BACK I ZERO 78 BACK I SCALE 14965 New registers were written successfully.
Running and plotting the sweep again, the voltage is reasonably accurate:
I’ve published my scripts (here) for others to use, and hopefully improve. Here is whats missing:
- Current calibration (I need a suitable load to do this).
- Support for output measurement with something other than the UNI-T 61e (perhaps using libsigrok to make it generic?).
- Some kind of error-minimising loop to perform auto-calibration.