I daily work with a DC power supply to run different boards and prototypes. One day, plugging in the power supply I realized there was a USB and a COM port!
It is not correct from a tinkerer’s to avoid this option. So this day ended up with a small Python program (also golang, but that’s a different story) to use this feature.
As a quick link. Here is the github with this project code ๐. Since it is plain python, just clone and run!
Disclaimer: You’ll need the pyserial library
TenmaSerial
An easy way to see what this small program can do, is to just run the –help option. In this post we’ll see the different commands of the TENMA 72-2540, and the small implementation needed to run them.
usage: tenmaControl.py [-h] [-v VOLTAGE] [-c CURRENT] [-s SAVE] [-r RECALL] [--on] [--off] [--verbose] [--debug] device Control a Tenma 72-2540 power supply connected to a serial port Positional arguments: device Optional arguments: -h, --help Show this help -v VOLTAGE, --voltage VOLTAGE mV to set -c CURRENT, --current CURRENT mA to set -s SAVE, --save SAVE Save current configuration to unit Memory -r RECALL, --recall RECALL Load configuration from unit Memory --on Set output to ON --off Set output to OFF --verbose Chatty program --debug print serial commands
Known limitations
Disclaimer. Not everything works ๐ฆ
--save
does not work as one would expect. It always saves to Memory 1, instead of the memory passed as parameter.
--currentValues
is not implemented as a flag in the command line, but exists in the library. It should read the actual current readings on the DC POWER SUPPLY, but it actually reads nothing.
Tenma command list
The Temna72-2540 manual is cryptic in how to contact their device:
* Run this query command via the terminal application, such as MTTTY (multi threaded TTY)
Aaaaand yes, they forget to list the command to run. Oh well, time to search the web like crazy until we stumble upon with the PDF listing all the commands.
Command | Description |
---|---|
ISET{Channel}:{Amperes} | Set Current |
ISET? | Get the current output setting |
VSET{Channel}:{Volts} | Set Voltage |
VSET? | Get the Voltage setting |
IOUT? | Get the actual output current |
VOUT? | Get the actual output voltage |
BEEP[1|0] | Enable/disable the beeping. |
OUT[1|0] | Turns ON/OFF the output. |
STATUS? | Return the TENMA status. |
*IDN? | Return the identifier string for the power supply unit. |
RCL{index} | Load a setting from memory. |
SAV{index} | Save current setting to memory. |
OCP[1|0] | Set overcurrent protection. |
OVP[1|0] | Set overvoltage protection. |
Nothing too fancy really. Very simple.
Structure
The program is separed in two:
- A tenmaDcLib.py file with a Tenma72_2540 class.
- A tenmaControl.py that works as a command line tool.
The tenmaControl will interface with tenmaDcLib. Not that I’m going to program it, but if a different Tenma needs a different implementation we can work from there.
Establishing the connection
The connection settings are provided in the manual, and are apparently correct since the Dc Power Supply beeps when you try to write something.
self.ser = serial.Serial(port=serialPort, baudrate=9600, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE)
Sending a single command
As with all the serial interfaces, we need a basic send/receive data flow. For this case, I create two functions __sendCommand
and __readOutput
. Pretty self explanatory.
See that in this functions I assume that the serial connection is already established (the self.ser
variable).
def __readOutput(self): out="" while self.ser.inWaiting() > 0: out += self.ser.read(1) if self.DEBUG: print "< ", out return out def __sendCommand(self, command): if self.DEBUG: print "> ", command self.ser.write(command) time.sleep(0.5) #give it time to process
The only thing to note here, is that Tenma does not accept the carriage return in the commands, and that it expects all the bytes within a time frame. That is why when trying to interface it from picocom or minicom we get nothing out from it.
Then. Once this two functions are working correctly, we just create a specific function for each action we want to perform. For example, to read the power supply version:
def getVersion(self): self.__sendCommand("*IDN?") return self.__readOutput()
Or to set the voltage:
def setVoltage(self, channel, mV): if channel > self.NCHANNELS: raise TenmaException("Trying to set CH{channel} with only {nch} channels".format( channel=channel, nch=self.NCHANNELS )) if mV > self.MAX_MV: raise TenmaException("Trying to set CH{channel} to {mv}mV, the maximum is {max}mV".format( channel=channel, mv=mV, max=self.MAX_MV )) command = "VSET{channel}:{volt:.2f}" V = float(mV) / 1000.0 command = command.format(channel=1, volt=V) self.__sendCommand(command) readvolt = self.readVoltage(channel) if readvolt * 1000 != mV: raise TenmaException("Set {set}mV, but read {read}mV".format( set=mV, read=readvolt * 1000, ))
Whoop. This is much longer than expected! Let’s walk through it.
First the function signature.
def setVoltage(self, channel, mV):
Since the power supplies may have different channels, we pass an integer with the channel number. This is limited by the class attribute NCHANNELS
. For this unit in particular, there’s only one channel (that is, channel 1). Anything bigger is considered an error. (you could supply channel 0 and it would happily chug along, doing nothing).
The second parameter are the millivolts. The serial command expects Volts, but I find it easier to handle only integers (thus the milli), and transform before sending the command.
This value is also limited by a class value MAX_MV
. I believe this could be optional since the unit will never go higher than the expected Voltage, but it is nice to know so we can signal from the command line that an invalid value has been issued.
After that (signature and parameter checking) we can continue with the command:
V = float(mV) / 1000.0 command = "VSET{channel}:{volt:.2f}" command = command.format(channel=1, volt=V)
First, transform the mV to volts (dividing by 1000.0). Remember that the .0 is important to signal Python that we want a float.
The command is just a string, formatted in a special way. And for that we have the .format function.
Once the command is sent, we’ll double check the value (because it’s just polite to do so). The __readVoltage
function can be re-used for this purpose.
More functions
The rest of the commands are implemented following the same approach.
Command Line application
With all the work done in the library, the command line application just handles a list of flags that directly map to a library call. argparse to the rescue:
import argparse from tenmaDcLib import * parser = argparse.ArgumentParser( description='Control a Tenma 72-2540 power supply connected to a serial port') parser.add_argument('device', default="/dev/ttyUSB0") parser.add_argument('-v','--voltage', help='mV to set', required=False, type=int) parser.add_argument('-c','--current', help='mA to set', required=False, type=int) parser.add_argument('-s','--save', help='Save current configuration to Memory', required=False, type=int) parser.add_argument('-r','--recall', help='Load configuration from Memory', required=False, type=int) parser.add_argument('--off', help='Set output to OFF', action="store_true", default=False) parser.add_argument('--on', help='Set output to ON', action="store_true", default=False) parser.add_argument('--verbose', help='Chatty program', action="store_true", default=False) parser.add_argument('--debug', help='print serial commands', action="store_true", default=False) args = vars(parser.parse_args()) # and here call the library functions
Some details are important but not imperative. For example, there is no --reset
option. This is performed by providing the two flags --off --on
. The order in the command line flags is not important, but it is in how we run those functions in the code. Since --on
followed by --off
, does not make sense, the program has to handle OFF before it handles ON.
The same applies with --voltage 1000 --off
. If we first set the voltage to higher value, and then turn the unit off, we risk burning something.
For this simple reason, OFF should be the first action to be performed in any case. If it is not provided.. Well, the user should proceed with care anyways.
Conclusion
Nothing extremely complicated, just finding the real-deal protocol, and then it is a piece of cake. It’s been very useful for me, and I hope it will be useful for others too.
Pingback: Tenma DC 72-2540 graphical interface | Castells
Pingback: The dropped posts, and 2017 closing | Castells
Hey – Just wanted to say thanks for creating this. I’ve found it really useful for scripting Tenma PSUs :-). Been using it for a few months now.
Thanks ๐ my pleasure.
Just found this excellent script, thanks. I have a model 72-2720 and all commands work except maximum current is 3000 mA – can I just add a new class to the library using the new values?
Sure! feel free to open a PR in github with this change
You have examples on how it’s done in the same source https://github.com/kxtells/tenma-serial/blob/master/tenma/tenmaDcLib.py#L422
Ping me if there’s something not working ๐