Controlling the Korad KA3005P using Python

15 Jul 2022 - tsp
Last update 15 Jul 2022
Reading time 13 mins

TL;DR: The protocol is simple, no start and termination of commands, plain ASCII over a serial port or the USB to serial bridge. The code presented is available on GitHub

Introduction

So after having used them at work for a few months I just had to get my own KA3005P bench-top lab power supply. Up until then I used some dumb (not controllable via serial port / USB / Ethernet) cheap bench-top power supplies that covered the same range as well as some custom home built supplies - and also still have the plan to build some switchmode supplies based on the TL494 by myself, especially for the high voltage regime. But since those supplies are rather affordable and their control is done simple via RS232 or an integrated USB to serial converter this had been the easier way to go for simple non specialized stuff.

The KA3005P power supply

The next step was to look into how those devices are controlled. There is some documentation available:

Since the protocol is really pretty simple I started to implement my own library in ANSI C and in Python (I tend to use C99 for serious stuff and Python for playing around). To avoid designing APIs over and over again I also started a small collection of Python base classes for various laboratory devices such as power supplies, function generators, etc. - this will grow for sure - from which I then derive the implementations themselves so every new device will use exactly the same API as similar ones which allows one to simply swap devices.

The main reasons for a custom own implementation are the same as usual:

The KA3005P is a standard linear power supply that support setting a target voltage as well as a current limit. It would also allow one to store different settings at four memory banks (I don’t make use of that - it’s mainly useful when you’re controlling the supply manually) and in addition to normal operation that one knows from other lab supplies it also support overcurrent and overvoltage “protection” modes in which triggering the voltage or current limit (constant voltage or constant current mode) disabled the output again which might be useful under some circumstances especially when testing circuits.

The KA3005P

The interfaces

The KA3005P support computer or microcontroller control via two different interfaces though only one can be active at the same time - there is a main serial port that runs on RS232 levels as well as an integrated USB to serial adapter that runs as a full speed (12 Mbps) USB device with the identification “Nuvoton USB Virtual COM”. It’s vendor ID is 0x0416, the product ID 0x5011 and announces itself as a communication device (CDC ACM). Under FreeBSD the stock kernel uses the umodem driver to supply an tty device one can use to communicate with the power supply when using the USB port.

On both interfaces the settings are:

In addition I usually define a short timeout of a second at most.

The power supply does not use any kind of robust serial communication such as a start pattern, a checksum or an end pattern. It seems to do simple pattern matching on the commands sent and usually one is required to know the length of the expected response to robustly decode it. Despite this the protocol seems to orient itself somewhat at SCPI (which also differs from device to device) there are no line endings, it’s really simple to implement using write and read from the serial port using pyserial or standard Unix file access methods.

The protocol

The following functions that I know of are supported by the KA3005P:

Command Response length (bytes) Description
*IDN? 30? Returns the type, software version and serial number of the power supply (ex.: KORAD KA3005P V5.8 SN:YYYYYYYY )
OCP0, OCP1 0 Disable or enable the over current protection. When this is enabled and the current exceeds the set value the power supply simply disables the output
OVP0, OVP1 0 Disable or enable the over voltage protection. In case the voltage exceeds the set value the power supply disabled the output
OUT0, OUT1 0 Disable or enable the output
VSET1:XX.XX 0 Sets the voltage to the specified value (XX.XX). The value always has 2 digits before and two after the decimal dot in ASCII notation
ISET1:X.XXX 0 Sets the current to the specified value (X.XXX). The value consists of ASCII values and a decimal dot at the second position
STATUS? 1 Query the single status byte. This is interpreted binary (see below)
VSET1? 5 Query the current voltage setting. The response are 2 ASCII digits, one decimal dot and 2 ASCII digits after the decimal dot
ISET1? 5 Query the current current setting. The response has the same format of 4 ASCII digits with a decimal dot at the second position
VOUT1? 5 Request a measurement of the actual voltage on the output. This has the same format XX.XX as setting and getting the set value
IOUT1? 5 Request a measurement of the actual current on the output. This has the same format X.XXX as setting and getting the set value

The status flags known to me are:

Bit Meaning Comments
7 Overvoltage protection enabled (1) or disabled (0) Seems to be somewhat unreliable
6 Output enabled (1) or disabled (0)  
5    
4 Overcurrent protection enabled (1) or disabled (0) Seems to be somewhat unreliable
3    
2    
1    
0 Constant current mode (1) or constant voltage mode (0)  

The Python library

TL;DR: The code is spread over two repositories:

First I decided on the abstraction for power-supplies that I’d like to have and implemented those in my pylabdevs library as PowerSupply base class. The public functions that will later be used by applications are:

Method Description
__init__ See below, this configured the behavior of the power supply and it’s capabilities
setChannelEnable(enable, channel=1) Switches the output on or off
setVoltage(voltage, channel=1) Sets the desired target voltage
setCurrent(current, channel=1) Sets the desired target current
getVoltage(channel=1) Returns a tuple with the measured (or None) and the set voltage
getCurrent(channel=1) Returns a tuple with the measured (or None) and the set current
off() Disables all outputs
getLimitMode(channel=1) Returns either PowerSupplyLimit.VOLTAGE or PowerSupplyLimit.CURRENT when output is enabled for constant voltage and constant current mode or PowerSupplyLimit.NONE in case the output is disabled

The init function is rather complex and allows one to specify the capabilities of the power supply:

All methods verify the supplied parameters already in the base class and are not overridden by the actual implementations. Note that voltage and current setters also verify if the maximum power range is exceeded or not with the desired setting and will raise an ValueError in this case.

The actual communication will be done by a set of private routines that are overridden by the actual classes and which do raise an NotImplementedError() in case they are called on the base class. Those are:

Implementation method Description
_setChannelEnable(enable, channel) Enables or disabled the output of the channel
_setVoltage(voltage, channel) Try to set (and verify the setting of) the target voltage of the given channel
_setCurrent(current, channel) Try to set (and verify setting of) the current limit of the given channel
_getVoltage(channel) Measure the actual voltage on the given channel
_getCurrent(channel) Measure the actual current on the given channel
_off() Disable all outputs at once
_getLimitMode(channel) Return in which limit mode (voltage, current or none) the supply currently runs
_isConnected() Returns True when the device is currently connected

The setting methods usually should set a value and then read back the current setting from the device if supported to make sure it has been written correctly. It’s also assumed the implementations support using with by supplying an __enter__ and __exit__ routine. When exiting the context the power supply should turn off all outputs - the same goes for terminating the process which is usually realized using atexit even though the base class also implements calling _off() in an atexit routine itself.

In my own implementation in pyka3005p I’ve implemented the KA3005PSerial class that inherits from PowerSupply. There are two methods that handle the serial communication internally:

In addition an internal __close method is implemented and registered as an atexit handler to tear down the serial port in case of an abnormal termination of in case a normal context exit is triggered.

This now allows pretty simple usage of the power supply using context routines or an imperative style:

from pyka3005p.ka3005pserial import KA3005PSerial

with KA3005PSerial("/dev/ttyU0") as psu:
	psu.setVoltage(5.00)
	psu.setCurrent(0.510)
	psu.setChannelEnable(True)

	measV, setV = psu.getVoltage()
	measA, setA = psu.getCurrent()

	print("Measured voltage {} V (set {} V)".format(measV, setV))
	print("Measured current {} A (set {} A)".format(measA, setA))

	sleep(10)

	psu.setChannelEnable(False)

Alternatively one can use the imperative style without context routines which is particularly useful for more complex scripts or Jupyter notebooks:

from pyka3005p.ka3005pserial import KA3005PSerial

psu = KA3005PSerial("/dev/ttyU0")

# One has to call connect ...
psu.connect()

psu.setVoltage(5.00)
psu.setCurrent(0.510)
psu.setChannelEnable(True)

measV, setV = psu.getVoltage()
measA, setA = psu.getCurrent()

print("Measured voltage {} V (set {} V)".format(measV, setV))
print("Measured current {} A (set {} A)".format(measA, setA))

sleep(10)

psu.setChannelEnable(False)

# And in the end disconnect
psu.disconnect()

Example usages

There are of course many applications especially in physics experiments such as setting magnetic fields, controlling heating elements, measuring pressure using the power dissipated in a Pirani gauge, controlling laser power and frequency, etc.

Recording voltage-current curves

A simple application of this library is just recording constant current curves when characterizing electronic parts or systems. In this case one can simply scan the voltage until the output current reaches a desired limit and record the measured current at each point. In addition this can of course be repeated more than once to reduce measurement error on noisy systems but since the supply will only return with a resolution of 10 mA this won’t be necessary in many cases. A simple example script that does exactly that and plots the result with matplotlib is supplied in the examples directory of the pyka3005p repository.

A simple example measurement of a 1N4001 diode in forward direction up to 2A of current (exceeding the specification of 1A) is shown in the graph below:

Example capture of a 1N4001 diode

A simple example on how to capture such data is available as GitHub GIST

This article is tagged:


Data protection policy

Dipl.-Ing. Thomas Spielauer, Wien (webcomplains389t48957@tspi.at)

This webpage is also available via TOR at http://rh6v563nt2dnxd5h2vhhqkudmyvjaevgiv77c62xflas52d5omtkxuid.onion/

Valid HTML 4.01 Strict Powered by FreeBSD IPv6 support