Tenma72-2540 Linux serial control

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.

References

Advertisements

1 Comment

Filed under code, tools

One response to “Tenma72-2540 Linux serial control

  1. Pingback: Tenma DC 72-2540 graphical interface | Castells

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s