esrf

Beamline Instrument Software Support
SPEC Macro documentation: [ Macro Index | BCU Home ]

#%TITLE% K2701.mac
#%NAME%
#   Macros to work with a Keithley 2701 via a tcp/ip socket interface.
#%CATEGORY% Multimeter, temperature
#%DESCRIPTION%
#   The keithley 2701 is a multichannel multimeter with a socket interface.
#   Different kind of thermocouples measuring, voltage, current, resistance
#   and frequency are possible.
#%BR% %BR%
#   These macros have been tested only with thermocouples of type K and 
#   4-wire pt100.  Even if the code could, in principle, support other
#   types of measuring, precise configuration may be necessary.
#%BR% %BR%
#   This device is not recommended for fast measuring. At the ESRF an alternative 
#   solution is to use wago modules. Typical overhead measuring times for 
#   a Keithley 2701 are around 200 ms for one channel, around 400 ms 
#   for a three channel configuration compared with just a few ms for wago modules. 
#   Nevertheless this device presents at least two advantages. One is the 
#   flexibility and easyness of its setup. The second one is that some features
#   are available that are not available with wago: for example, the higher
#   accuracy obtained with a 4wire pt100, instead of only 3-wire supported by
#   the corresponding wago module.
#%BR% %BR%
#   The keithley tcp/ip socket connection is a single client connection.  This
#   would, in principle, stops several application configuring measuring channels
#   on the device at the same time as only one can have an open socket to the
#   device.   It is possible to have a multi-client setup by using a Socketserver
#   proxy device server (check your bliss contact to set it up).  Nevertheless,
#   as clients may have different measuring configurations, a conflict could quickly
#   arise.  Multiple clients MUST use exactly the same channel configuration.  
#
#%EXAMPLE%
#%PRE%
#   k2701setup k2701-1 id06/k2701/1
#   k2701setup k2701-2 keithid32a:1394
#
#   k2701add kt1 k2701-1 101 TEMP_K
#   k2701add kt2 k2701-1 102 TEMP_K
#   k2701add kt3 k2701-1 101 TEMP_PT100
#   k2701add kvolt k2701-2 101 VOLT
#
#   k2701init
#%PRE%
#%END%

global K2701CTRL  K2701CTRLPAR 
global K2701CHANS K2701PAR 
global K2701READ 

global K2701_NO_READ
K2701_NO_READ = -999

#%UU% cntlname device
#%MDESC%
#   Defines a keithley 2701 controller in the system. 
#   Parameter  ident is an arbitrary name to identify the
#   controller later. 
#%BR% %BR%
#   Parameter device can take two forms:
#%DL%
#       %DT%socket %DD%host:port (ex:  keythid06a:1394 )
#       %DT%server %DD%tango devname (ex: id06/k2701/1)
#%XDL%
#%BR% %BR%
#   The socket naming is simple and does not require any extra software
#   on the other hand k2701 devices only support one simultaneous
#   client to talk to it.
#%BR% %BR%
#   If you want to have it configured at the same time in more than
#   one spec application or GUI, the second option must be used.  For
#   this it is necessary to run a tango server that needs to be 
#   configured and started beforehand.
#
def k2701setup '{

    if ($# != 2) {
       print "Usage: $0 ident device"
       exit
    }
    
    K2701CTRL["$1"] = "$1"
    K2701CTRLPAR["$1"]["dev"] = "$2"

    if ( index("$2",":")  > 0 ) {
      K2701CTRLPAR["$1"]["socket"] = "socket"
    } else {
      K2701CTRLPAR["$1"]["socket"] = "tango"
    }

    setup_tail("k2701_ctrl","$1")
}'

#%UU% <mne> <ctrl-ident> <ch> <readtype>
#%MDESC%
#  Add a counter on one Keithley 2701 controller previously
#  defined with k2701setup
#%BR% %BR%
#  Parameters are:
#%DL%
#    %DT%mne:        %DD%counter mnemonic where counts will be read
#    %DT%ctrl-ident: %DD%ident given in previous k2701setup
#    %DT%ch:         %DD%channel in controller for this counter
#    %DT%readtype:   %DD%one of the following: 
#    %DT% %DD%
#        TEMP:PT100, 
#        TEMP:K, 
#        CURR:AC, 
#        CURR:DC, 
#        VOLT:AC, 
#        VOLT:DC, 
#        RES, 
#        FREQ, 
#%XDL%
def k2701add '{

    if ($#!=4) {
        print "$0 <mne> <ctrl-ident> <ch> <readtype>"
	exit
    }

    cntnum = cnt_num("$1")
    K2701CHANS["$1"] = cntnum

    K2701PAR["$1"]["ctrl"]     = "$2"
    K2701PAR["$1"]["channel"]  = int($3)
    K2701PAR["$1"]["readtype"] = "$4"
    K2701PAR["$1"]["enable"]   = 1

    setup_tail("k2701_","$1")
}'

#%IU%
#%MDESC% 
#  Performs clean up on unsetup (take away k2701add from setup)
def k2701_unsetup '{
    local mne

    mne = "$1"

    cdef("","",mne,"delete")
    for (ky in K2701PAR[mne]) {
        delete K2701PAR[mne][ky]
    }
    delete K2701CHANS[mne]
}'

#%IU%
#%MDESC% 
#  Performs clean up on ctrl unsetup (take away k2701setup from setup)
def k2701_ctrlunsetup '{
    local ctrl

    ctrl = "$1"

    split(K2701CTRLPAR[ctrl]["chans"], chans, ",")

    cdef("","",ctrl,"delete")

    for (chan in chans) {
        mne = chans[chan]
        K2701PAR[mne]["enable"] = 0
    }

    for (ky in K2701CTRLPAR[ctrl]) {
        delete K2701CTRLPAR[ctrl][ky]
    }
    delete K2701CTRL[mne]
}'

#%UU%
#%MDESC% 
#  Initializes controllers. Includes a reset after configuration
def k2701init '{

    local mnes mne mneno ctrl 
    local nb_use_chan nb_total_chan

    # fill up lists of channels per controller
    k2701channels

    for (ctrl in K2701CTRL) {

        nb_total_chan = split( K2701CTRLPAR[ctrl]["chans"], mnes, ",")

        if (nb_total_chan == 0 ) {
            print "No channel defined for controller " ctrl
            continue
        }
        
        nb_use_chan = 0

        for (mneno in mnes) {
            mne = mnes[mneno]
            if ( K2701PAR[mne]["enable"] ) {
                nb_use_chan += 1
            }
        }

        if (nb_use_chan != 0) {
           k2701_resetctrl( ctrl )
        }
  
        for (mneno in mnes) {
           local cdefstr
           mne = mnes[mneno]
           k2701_confchan( mne )
           cdefstr = sprintf("k2701_chget %s\n",mne)
           cdef("user_getcounts",cdefstr,mne,0x2)
        }

        if (nb_use_chan != 0) {
           k2701_confctrl( ctrl )
           cdef("user_getcounts",sprintf("k2701_ctrlget %s\n",ctrl),ctrl,0x10)
        }

    }

}'

#%IU%
#%MDESC%
def k2701channels '{
    local ctrls ctrl mne

    for (ctrl in K2701CTRL) {
        K2701CTRLPAR[ctrl]["chans"] = ""
    }

    for (mne in K2701CHANS) {
         ctrl = K2701PAR[mne]["ctrl"]

         if ( whatis( sprintf("K2701CTRLPAR[\'%s\'][\'chans\'", ctrl)) == 0 ) {
              sprintf("Wrong K2701 controller (%s) for counter %s. Disabled\n", ctrl, mne)
              K2701PAR[mne]["enable"] = 0
              continue
         }

         if (K2701CTRLPAR[ctrl]["chans"] == "") {
             K2701CTRLPAR[ctrl]["chans"] = mne
         } else {
             K2701CTRLPAR[ctrl]["chans"] = K2701CTRLPAR[ctrl]["chans"] "," mne
         }
    }
}'

#%IU%
def k2701_resetctrl( ctrl ) '{

    local nbchan chlist

    _k2701_reset(ctrl) 
}'


#%IU%
def k2701_confctrl( ctrl ) '{

    local nbchan chlist allchan chno

    nbchan = split( K2701CTRLPAR[ctrl]["chans"], allchan, ",")
    _k2701_trig(ctrl, nbchan)
    _k2701_form(ctrl)
    chlist = ""
    for (chno=0;chno<nbchan;chno++) {
       chan = K2701PAR[ allchan[chno] ]["channel"]
       if (chlist == "") {
          chlist = chan
       } else {
          chlist = chlist  "," chan
       }
    }
    _k2701_route(ctrl, chlist) 
}'

#%IU%
def k2701_confchan( mne ) '{
    local readtype channel ctrl

    ctrl     = K2701PAR[mne]["ctrl"]
    readtype = K2701PAR[mne]["readtype"]
    channel  = K2701PAR[mne]["channel"]

    if (readtype == "TEMP_PT100") {
        _k2701_config_pt100(ctrl, channel) 
    } else if (readtype == "TEMP_K") {
        _k2701_config_tc(ctrl, channel, "K") 
    } else if (readtype == "VOLTAC") {
        _k2701_config_voltac(ctrl, channel) 
    } else if (readtype == "VOLTDC") {
        _k2701_config_voltdc(ctrl, channel) 
    } else if (readtype == "CURRAC") {
        _k2701_config_currac(ctrl, channel)
    } else if (readtype == "CURRDC") {
        _k2701_config_currdc(ctrl, channel)
    } else if (readtype == "RES") {
        _k2701_config_res(ctrl, channel) 
    } else if (readtype == "FREQ") {
        _k2701_config_freq(ctrl, channel)
    } else {
        print "Unkown readtype for k2701 channel. Disabled"
        K2701PAR[mne]["enable"] = 0
    }
}'

#%UU%
#%MDESC%
#  Not implemented yet
def k2701show '{
    #  Show current config
}'


#%IU%
#%MDESC% resets a controller
def _k2701_reset(ctrl) '{
  k2701send( ctrl, "*RST; *CLS; :ABORT; INIT:CONT OFF;")
}'

#%IU%
#%MDESC% gets a small improvement in speed
def _k2701_speed(ctrl) '{
  k2701send( ctrl, ":DISP:ENAB OFF;:SYST:AZER:STAT OFF")
}'


#%IU%
#%MDESC% 
def _k2701_config_voltdc(ctrl,chlist) '{ 
  k2701send( ctrl, sprintf("FUNC \'VOLT:DC\', (@%s)", chlist))
}'

#%IU%
#%MDESC% 
def _k2701_config_voltac(ctrl,chlist) '{ 
  k2701send( ctrl, sprintf("FUNC \'VOLT:AC\', (@%s)", chlist))
}'

#%IU%
#%MDESC% 
def _k2701_config_currdc(ctrl,chlist) '{ 
  k2701send( ctrl, sprintf("FUNC \'CURR:DC\', (@%s)", chlist))
}'

#%IU%
#%MDESC% 
def _k2701_config_currac(ctrl,chlist) '{ 
  k2701send( ctrl, sprintf("FUNC \'CURR:AC\', (@%s)", chlist))
}'

#%IU%
#%MDESC% 
def _k2701_config_res(ctrl,chlist) '{ 
  k2701send( ctrl, sprintf("FUNC \'RES\', (@%s)", chlist))
}'

#%IU%
#%MDESC% 
def _k2701_config_freq(ctrl,chlist) '{ 
  k2701send( ctrl, sprintf("FUNC \'FREQ\', (@%s)", chlist))
}'

#%IU%
#%MDESC% 
def _k2701_config_tc(ctrl,chlist,tc_type) '{ 

  kcmds["0"] = sprintf("FUNC \'TEMP\', (@%s)", chlist)
  kcmds["1"] = sprintf("UNIT:TEMP C,(@%s)", chlist)
  kcmds["2"] = sprintf("SENS:TEMP:TRAN TC,(@%s)", chlist)
  kcmds["3"] = sprintf("SENS:TEMP:TC:TYPE %s,(@%s)", tc_type, chlist)

  # kcmds["4"] = sprintf("SENS:TEMP:NPLC 1,(@%s)", chlist)

  k2701send( ctrl, kcmds,4 )

}'

#%IU%
#%MDESC% 
def _k2701_config_pt100(ctrl,chlist) '{ 

  local kcmds

  kcmds["0"] = sprintf("FUNC \'TEMP\', (@%s)", chlist)
  kcmds["1"] = sprintf("UNIT:TEMP C,(@%s)", chlist)
  kcmds["2"] = sprintf("SENS:TEMP:TRAN FRTD,(@%s)", chlist)
  kcmds["3"] = sprintf("SENS:TEMP:FRTD:TYPE PT100, (@%s)", chlist)
  kcmds["4"] = sprintf("SENS:TEMP:NPLC 1,(@%s)", chlist)

  k2701send( ctrl, kcmds,5 )
  
}'

#%IU%
#%MDESC% 
def _k2701_trig(ctrl, nbchan) '{

  local kcmds

  kcmds["0"] = "TRIG:COUN 1"
  kcmds["1"] = sprintf("SAMP:COUN %d", nbchan)
  kcmds["2"] = "TRIG:SOUR IMM"
  kcmds["3"] = "TRIG:DEL 0.100"

  k2701send( ctrl, kcmds , 4)
}'

#%IU%
#%MDESC% 
def _k2701_form(ctrl) '{
  k2701send( ctrl,"FORM:ELEM CHAN,READ,UNIT")
}'

#%IU%
#%MDESC% 
def _k2701_routopen(ctrl,chlist) '{
  k2701send( ctrl,"ROUT:OPEN:ALL")
}'

#%IU%
#%MDESC% 
def _k2701_route(ctrl,chlist) '{
  
  local kcmds
   
  kcmds["0"] = sprintf("ROUT:SCAN (@%s)", chlist )
  kcmds["1"] = "ROUT:SCAN:TSO IMM"
  kcmds["2"] = "ROUT:SCAN:LSEL INT"

  k2701send( ctrl, kcmds, 3 )

}'

#%IU%
#%MDESC% 
def k2701_ctrlget '{

  local allans

  ctrl = "$1"

  unglobal K2701READ
  global K2701READ

  # -- slower version -- 
  # allans = k2701read( ctrl, "READ?")

  allans = k2701read( ctrl, "INIT;FETCH?")

  # If we sent k2701 at init time we should get readings in the form value, chan, value, chan...
  nb_fields = split( allans, fields, ",")

  if (nb_fields == 0) { 
           tty_cntl("md")
           printf("No reading from k2701 controller\n") 
           printf("  - whole line from controller is: %s\n", allans)
           tty_cntl("me")
  }

  for ( nf = 0; nf < nb_fields; nf+=2) {
       nn = sscanf( fields[nf+1], "%d", chnum )
       # clean spurious minus sign at beginning of string
       cleanval = k2701_cleanminus( fields[nf] )
       nv = sscanf( cleanval, "%f", chval )  
       if (nn == 0 || nv == 0) {
           printf("unknown reading from k2701 (value=%s) (chan=%s)\n", fields[nf], fields[nf+1])
           printf("Whole line from controller is: %s\n", allans)
       } else {
           K2701READ[ctrl][chnum] = chval
       }
  }
   
}'

#%IU%
def k2701_chget '{

   local mne ctrl chan ctval

   # reading is done with k2701_ctrlget.. assign to counters
   mne = cnt_mne($1)

   ctrl = K2701PAR[mne]["ctrl"] 
   chan = K2701PAR[mne]["channel"]

   if ( K2701READ[ctrl][chan] == 0) {
       K2701READ[ctrl][chan] = K2701_NO_READ
   } 

   ctval = K2701READ[ctrl][chan]
   
   S[$1] = ctval
}'
   


#%IU%
#%MDESC%
#   Sends one or several commands.  No answer expected
def k2701send( ctrl, kcmds, nbcmds ) '{
  local socktype sockdev

  socktype = K2701CTRLPAR[ctrl]["socket"]
  sockdev  = K2701CTRLPAR[ctrl]["dev"]

  if (nbcmds == 0 || nbcmds == "") {
      _k2701_write( kcmds, sockdev, socktype )
  } else {
     for(ii=0;ii<nbcmds;ii++) { 
        _k2701_write( kcmds[ii], sockdev, socktype )
     }
  }
}'


#%IU%
#%MDESC%
#   Sends one command.  Returns answer
def k2701read(ctrl, cmd) '{
  local socktype sockdev

  socktype = K2701CTRLPAR[ctrl]["socket"]
  sockdev  = K2701CTRLPAR[ctrl]["dev"]

  return _k2701_read(cmd, sockdev, socktype)
}'


#%IU%
#%MDESC%
#   Sends one command through socket.  No answer expected
def _k2701_write( cmd, sockdev, socktype ) '{

    cmd = sprintf("%s\r\n",cmd)
    if ( socktype == "socket") {
         sock_put( sockdev, cmd )
    } else if ( socktype == "tango") {
         esrf_io(sprintf("tango:%s", sockdev), "Write", cmd)
    }
}'


#%IU%
#%MDESC%
#   Sends one command through selected socket.  Returns answer
def _k2701_read( cmd , sockdev, socktype) '{

    if (socktype == "") socktype = "socket"

    cmd = sprintf("%s\r\n",cmd)

    if ( socktype == "socket") {
         sock_put( sockdev, cmd )
         return sock_get( sockdev )
    } else if ( socktype == "tango") {
         return esrf_io(sprintf("tango:%s", sockdev), "Writeread", cmd)
    }
}'

#%IU%
#%MDESC%  
#  Clean spurious minus sign at the beginning of response string
def k2701_cleanminus( instr ) '{
  local outstr

   outstr = instr

   while (1) {
      minidx = index( outstr, "-")

      if (minidx == 1) {
          # is there a second one?
          if ( index( substr(outstr,2) , "-") == 1) {  # yes
             outstr = substr(outstr,2)
          } else {  # no. thats it
             return outstr
          }
      } else {  # no minus sign
          return outstr
      }
   }

}'

#%MACROS%
#%IMACROS%
#%TOC%
#%AUTHOR% V.Rey BLISS/ESRF (2008)