esrf

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

#%TITLE%
#  PICOAMMETER.MAC
#%NAME%
# Support for the Keithley 485/486/487 picoammeter as a pseudo counter
#%DESCRIPTION%
# The Keithley 486 picoamtermeter is a very sensitive detector for weak 
# electrical currents with a detection limit of 10 fA. 
# It directly converts current into a digital reading that is display on the
# front panel and can be read via a GPIB interface on the back panel.
# %BR%
# A common application is to read out X-ray detectors such as silicon PIN diodes
# or ionization chambers. A silicon PIN diode is connected directly to the
# current input of the picoammter without any voltage source. This way the
# photocurrent is proportional to the photon flux over typically 9 order of
# magnitude or more.
# %BR%
# Alternatively the picoammeter can used a current amplifier. At its analog
# on the back panel it generated a voltage between -2 and +2 that is
# proportional to the current with a gain factor of 10^3 to 10^9 V/A in steps
# of a factor 10. This gain factor can be set via the front panel or the GPIB
# interface.
# The picoammeter is used this way when the time structure of the current signal
# is of interest, e.g. for recording on an oscilloscope, lock-in detection or
# gating. Also the analog output can be used as input for a voltage-to-
# frequency converter with digital counter, if long-term averaging of a noisy
# signal is desired.

#%UU% mnemonic GPIB=addr
#%MDESC%
#Setup the channel of the Keithley Picoampermeter as counter

# for "ceil" and "binary"
need spec_utils

def picoammeter_setup '
{
  local n t mne addr
  global PICOAMMETER[]

  if ($# != 2) {print "Usage: $0 mnemonic GPIB=addr"; exit}
  mne = "$1"
  if (whatis(mne,1) != "A built-in immutable counter mnemonic.") {
    print "$0: Did you define \""mne"\" as a counter in \"config\"?\n"; exit
  }
  n = sscanf("$2","GPIB=%s",addr)
  if (n == 0) {print "Usage: $0 mnemonic GPIB=addr"; exit}

  PICOAMMETER[mne]["address"]  = addr

  # define "hook macros"
  cdef ("","",mne,"delete")
  cdef ("user_prepcount",t="picoammeter_precount "mne"\n",mne,0x02)
  cdef ("user_pollcounts",t="picoammeter_pollcounts "mne"\n",mne,0x02)
  cdef ("user_getcounts",t="picoammeter_getcounts "mne"\n",mne,0x02)
  # flag 0x02 = only include if key is a counter mnemonic and the counter is
  # not disabled.
  cdef ("config_mac",t="picoammeter_check "mne"\n",mne)
}'

#%UU% counter GPIB=addr [voltage_counter]
#%MDESC%
# Assigns a software counter to a picoammeter.
# You have to define a scaler in "config" first with the name "current_counter"
# and "Device" set to NONE.
# %BR% %BR%
# Syntax 1: picoammeter_setup counter GPIB=addr
# %BR% %BR%
# Directly read the current from te biult-in analog-to-digital converter
# via GPIB.
# The value of the counter is the measured current in amperes.
# If you want to measure the current in a different unit, e.g. nanoampere, set
# the "Scale factor" in "config" to 1e9.
# %BR%
# The picoammeter will be read multiple times during counting for averaging.
# This macro tries to speed up the readout rate by making use of the
# internal data store of the picoameter and transferring GPIB data in blocks.
# This way typically 25 readings are averaged per second of counting time.
# %BR% %BR%
# Syntax 2: picoammeter_setup counter GPIB=addr voltage_counter
# %BR% %BR%
# This is an alternative way of reading a Picoammeter, using it as current
# amplifier and reading the analog output voltage. %BR%
# This way a long-term average can be obtained which higher precision, if a
# voltage-to-frequency is used in conjection with a digital counter. %BR%
# This macro assumed that the "voltage_counter" is scaled so its units are
# volts. Then the "counter" will be in units of ampere if its
# scale factor is set to 1.
# If the counter "voltage_counter" is an integrating counter such as VCT6, then
# is should be scaled to Vs and "counter" will be in units of As. %BR%
# This macro does nothing but reading the counter "voltage_counter" and the
# current gain settings of the picoammeter via GPIB, calculate the current and
# assign it to the "counter".

def picoammeter_setup '
{
  local addr
  global PICOAMMETER[]

  if ($# < 2 || $# > 3) {
    print "Usage: $0 counter GPIB=addr voltage_counter"; exit
  }
  if (cnt_num("$1") == -1) {
    print "$0: Did you define \"$1\" as a counter in \"config\"?\n"; exit
  }
  if (sscanf("$2","GPIB=%s",addr) == 0) {
    print "Usage: $0 counter GPIB=addr voltage_counter"; exit
  }
  if ($# == 3 && cnt_num("$3") == -1) {
    print "$0: Did you define \"$3\" as a counter in \"config\"?\n"; exit
  }

  PICOAMMETER["$1"]["address"]  = addr

  if ($# == 2)
  {
    # define "hook macros"
    cdef ("","","$1","delete")
    cdef ("user_prepcount","picoammeter_precount $1\n","$1",0x02)
    cdef ("user_pollcounts","picoammeter_pollcounts $1\n","$1",0x02)
    cdef ("user_getcounts","picoammeter_getcounts $1\n","$1",0x02)
    # flag 0x02 = only include if key is a counter mnemonic and the counter is
    # not disabled.
  }
  if ($# == 3)
  {
    PICOAMMETER["$1"]["voltage_counter"] = "$3"

    # define counter "hook macros"
    cdef ("","","$1","delete")
    cdef ("user_getcounts","picoammeter_calculate_current $1\n","$1",0x22)
    # Flag 0x02 = only include if key is a counter mnemonic and the counter is
    # not disabled. Flag 0x20 = place in the back part of the macro.
  }
  cdef ("config_mac","picoammeter_check $1\n","$1")
}'

#%UU%
#%MDESC% undo "picoammter_setup"

def picoammeter_unsetup '
{
  local i n s[] mne
  global PICOAMMETER[]

  for (i in PICOAMMETER)
  {
    split(i,s,"\034"); mne = s[0]
    cdef ("","",mne,"delete")
    n = cnt_num(mne)
    if (n != -1) S[n] = 0
  }
  unglobal PICOAMMETER
}'
  
  
#%UU%
#%MDESC% print status info

def picoammeter '
{
  local i c n s[] mne addr status
  global PICOAMMETER[]

  n=0
  for (i in PICOAMMETER)
  {
    split(i,s,"\034")
    if (s[1] == "address")
    {
      mne = s[0]
      addr = PICOAMMETER[i]
      c = cnt_num(mne)
      if (c == -1) status = "(not configured)"
      else if (counter_par(c,"disable") == 1) status = "(disabled)"
      else status=""
      printf ("GPIB=%-4s, counter \"%s\" %s\n",addr,mne,status)
      n++
    }
  }
  if (n==0) print "No picoammeter devices configured."
}'

#%UU% mnemonic|address [range]
#%MDESC% change gain setting. Allowed values for range are
# "auto", "2nA", "20nA", "200nA", "2uA", "20uA", "200uA", "2mA" and "fix".
# "fix" disables autoranging, keeping the currently chosen gain.

def picoammeter_range '
{
  local usage addr command ranges[] range i status
  global PICOAMMETER[]

  usage = "$0 mnemonic|address [range]"

  if ($# < 1) {print usage; exit}

  addr = PICOAMMETER["$1"]["address"]
  if (addr == 0) addr = "$1"

  ranges["auto"]  = "R0"
  ranges["2nA"]   = "R1"
  ranges["20nA"]  = "R2"
  ranges["200nA"] = "R3"
  ranges["2uA"]   = "R4"
  ranges["20uA"]  = "R5"
  ranges["200uA"] = "R6"
  ranges["2mA"]   = "R7"
  ranges["fix"]   = "R10"

  if ($# > 1)  # set range
  {
    range = "$2"
    if (!(range in ranges)) {
      printf ("Allowed ranges are: ")
      for (i in ranges) printf ("%s ",i); printf ("\n"); exit
    }
    command = ranges[range]"X"
    picoammeter_gpib_put (addr,command)
  }
  if ($# == 1) # get range
  {
    picoammeter_gpib_put(addr,"U0X") # query machine status word
    status = picoammeter_gpib_get(addr)
    i = index (status,"R")
    autorange = substr(status,i+1,1)
    range = "R"substr(status,i+2,1)
    for (i in ranges) if (range == ranges[i]) range = i
    if (autorange) print "auto (currently "range")"
    else print range" (fixed)"
  }
}'

#%UU% mnemonic|address [mode]
#%MDESC% change zero check setting. Allowed values are
# "auto", "on", "off".

def picozerochk '
{
  local usage addr command modes[] mode i status
  global PICOAMMETER[]

  usage = "$0 mnemonic|address [mode]"

  if ($# < 1) {print usage; exit}

  addr = PICOAMMETER["$1"]["address"]
  if (addr == 0) addr = "$1"

  modes["auto"]  = "2"
  modes["on"]   = "1"
  modes["off"]  = "0"

  if ($# > 1)  # set mode
  {
    mode = "$2"
    if (!(mode in modes)) {
      printf ("Allowed modes are: ")
      for (i in modes) printf ("%s ",i); printf ("\n"); exit
    }
    command = modes[mode]"X"
    picoammeter_gpib_put (addr,command)
  }
  if ($# == 1) # get mode
  {
    picoammeter_gpib_put(addr,"U0X") # query machine status word
    status = picoammeter_gpib_get(addr)
    i = index (status,"C")
    automode = substr(status,i+1,1)
    mode = "C"substr(status,i+2,1)
    for (i in modes) if (mode == modes[i]) mode = i
    if (automode) print "auto (currently "mode")"
    else print mode" (NA)"
  }
}'

#%UU%
#%MDESC% disable readout of all picoammeter devices until next "reconfig"
# or "picoammeter_on"

def picoammeter_off '
{
  local i s[] mne n addr usage
  global PICOAMMETER[]

  if ($# < 1) {

    for (i in PICOAMMETER) {
      split(i,s,"\034"); mne = s[0]
      n = cnt_num(mne)
      if (n != -1) {counter_par(n,"disable",1); S[n]=0}

    }
  } else {
    mne = $1
    addr = PICOAMMETER["$1"]["address"]
    n = cnt_num(mne)
    if ((n != -1)&&(addr!=0)) {
      counter_par(n,"disable",1); 
      S[n]=0

    } else {
      usage = "usage:$0 mnemonic"
      print usage
      print "$1 not a known mnemonic"
    }
  }
}'
  
#%UU%
#%MDESC% use this macro after "picoammeter_off" to reenable the picoammeter
# readout

def picoammeter_on '
{
  local i s[] mne n addr usage
  global PICOAMMETER[]

  if ($# < 1) {
    for (i in PICOAMMETER) {
      split(i,s,"\034"); mne = s[0]
      n = cnt_num(mne)
      if (n != -1) counter_par(n,"disable",0)
    }
  } else {
    mne = $1
    addr = PICOAMMETER["$1"]["address"]
    n = cnt_num(mne)
    if ((n != -1)&&(addr!=0)) {
      counter_par(n,"disable",0); 
    } else {
      usage = "usage:$0 mnemonic"
      print usage
      print "$1 not a known mnemonic"
    }
  }
}'

#%IU% mnemonic
#%MDESC% "hook macro" for "config_mac"
# Checks avaliability of device and disables the counter hook macros if device
# does not repond (not powered up, GPIB-ENET unavailable, SPEC configuration
# changed, cabling problem, etc.)

def picoammeter_check '
{
  local address n nb

  n = cnt_num("$1")
  addr = PICOAMMETER["$1"]["address"]

  if (n != -1 && address != "")
  {
    nb = picoammeter_gpib_put(address,"\r")
    if (nb == -1)
    {
      print "Picoammeter \"$1\" (GPIB="address") is unusable."
      S[n] = 0
      counter_par(n,"disable",1)
    }
  }
}'

#%IU% mnemonic
#%MDESC% "hook macro" for "user_prepcounts". %BR%
# Setup picoammeter to use the internat data store for maximum
# acqisition rate. In priciple up to 512 value can be recoreded in one go, one
# each 20 ms. However in practive the number values is limited by SPEC`s GPIB
# buffer size of 512 bytes to 30.

def picoammeter_precount '{
  local cnt addr c dt i n s max

  cnt = cnt_num("$1")
  addr = PICOAMMETER["$1"]["address"]

  if (cnt != -1 && addr != "")
  {
    S[cnt] = 0
    PICOAMMETER["$1"]["sum"]  = 0
    PICOAMMETER["$1"]["n"] = 0

    dt=0.02
    n = ceil(COUNT_TIME/dt)
    max = int (512/length("NDCI-1.23456E-03,"))
    if (n >= max) n = max
    # The useful buffer size is limited to 30 readings by SPEC`s GPIB buffer
    # size of 512 bytes.

    if (n > 1)
    {
      PICOAMMETER["$1"]["buffer_size"] = n
      c = "T4"     # Trigger: 2 = multiple on GET, 4 = multiple on X
      c = c"Q"dt   # Interval in seconds
      c = c"N"n    # set data store to buffer size
      c = c"B2"    # reading source = all readings from data store
      c = c"X"     # execute
    }
    if (n == 1)
    {
      PICOAMMETER["$1"]["buffer_size"] = 0
      c = "B0"     # reading source = current A/D converter reading
      c = c"X"     # execute
    }

    n = picoammeter_gpib_put (addr,c)

    # It the device is down or unreachable for some reason, is is best to
    # disable the corresponding counter because of the long GPIB timeout.
    if (n == -1) { S[cnt] = 0; counter_par (cnt,"disable",1) }
  }
}'

#%IU% mnemonic
#%MDESC% "hook macro" for "user_pollcounts"

def picoammeter_pollcounts '
{
  # when addressed as talker, the Picoammeter send an ASCII string of the form
  # "NDCI-1.23456E-03". If internal buffering is used up to 512 of those numbers
  # can becon catenated as comma-separated string.
  # The maximum length of GPIB data SPEC will receive is limited to 511.
  # If the answer length exactly 511, the last number is most likely truncated
  # and has to be discarded.

  local address I answer v[] n i
  global PICOAMMETER[]

  address = PICOAMMETER["$1"]["address"]

  if (PICOAMMETER["$1"]["buffer_size"] >= 30 && address != "")
  {
    answer = picoammeter_gpib_get(address)

    picoammeter_gpib_put(address,"N30B2X")
    # rearm the data store, make sure that data store is sent on next read

    n = split (answer,v,",")
    if (length(answer) == 511) n--
    for (i=0; i<n; i++)
    {
      I=substr(v[i],5)
      PICOAMMETER["$1"]["sum"] += I
      PICOAMMETER["$1"]["n"]++
    }
  }
}'

#%IU% mnemonic
#%MDESC% "hook macro" for "user_getcounts"

def picoammeter_getcounts '
{
  # when addressed as talker, the Picoammeter send an ASCII string of the form
  # "NDCI-1.23456E-03". If internal buffering is used up to 512 of those numbers
  # can becon catenated as comma-separated string.
  # The maximum length of GPIB data SPEC will receive is limited to 511.
  # If the answer length exactly 511, the last number is most likely truncated
  # and has to be discarded.

  local address I answer v[] c n i average
  global PICOAMMETER[]

  c = cnt_num("$1")
  address = PICOAMMETER["$1"]["address"]

  if (c != -1 && address != "")
  {
    answer = picoammeter_gpib_get(address)

    n = split (answer,v,",")
    if (length(answer) == 511) n--
    for (i=0; i<n; i++)
    {
      I=substr(v[i],5)
      PICOAMMETER["$1"]["sum"] += I
      PICOAMMETER["$1"]["n"]++
    }
    if (PICOAMMETER["$1"]["n"] > 0)
    {
      average = PICOAMMETER["$1"]["sum"] / PICOAMMETER["$1"]["n"]
      S[c] = average*counter_par(c,"scale")
    }
    picoammeter_gpib_put(address,"B0X")
    # current A/D reading for next readout (default setting)
  }
}'

#%IU% mnemonic
#%MDESC% "hook macro" for "user_getcounts" %BR%
# This macro reads the current gain settings of the corresponding picoammeter
# via GPIB, aplies it as scaling factor to the analog voltage output,
# calculates the current and assign it to the counter "mnemonic".

def picoammeter_calculate_current '{
  local n address vc status i range scale_factor
  global PICOAMMETER[]

  n = cnt_num("$1")
  address = PICOAMMETER["$1"]["address"]
  vc = cnt_num(PICOAMMETER["$1"]["voltage_counter"])

  if (n != -1 && address != "" && vc != -1)
  {
    picoammeter_gpib_put(address,"U0X") # query machine status word
    status = picoammeter_gpib_get(address)
    i = index (status,"R")
    range = substr(status,i+2,1)
    if (range == 1) scale_factor = 1E-9 # 0..2 V = 0..2 nA
    if (range == 2) scale_factor = 1E-8 # 0..2 V = 0..20 nA
    if (range == 3) scale_factor = 1E-7 # 0..2 V = 0..200 nA
    if (range == 4) scale_factor = 1E-6 # 0..2 V = 0..2 uA
    if (range == 5) scale_factor = 1E-5 # 0..2 V = 0..20 uA
    if (range == 6) scale_factor = 1E-4 # 0..2 V = 0..200 uA
    if (range == 7) scale_factor = 1E-3 # 0..2 V = 0..2 mA
    S[n] = S[vc]*scale_factor*counter_par(n,"scale")
  }
}'

#%IU% (address,string)
#%MDESC% This function sends data to a GPIB device, like "gpib_put()".
# However, is catches the error condition "Not CIC or lost CIC during command" 
# (CIC = controller in charge),
# which occurres frequently with the GPIB-ENET controller. 
# This is a transient error occuring when another SPEC application is
# using the same GPIB controller and can be mended by sending the command again.
# %BR%
# Then return value is the number of bytes send.

def picoammeter_gpib_put (address,s) '{
  local HDW_ERR n
    
  HDW_ERR = -1
  n = gpib_put(address,s)
   
  if (HDW_ERR == 4)
  {
    HDW_ERR = -1
    n = gpib_put(address,s)
  }
  return n
}'


#%IU% (address)
#%MDESC% This function receives data from a GPIB device, like "gpib_get()".
# However, is catches the error condition "Not CIC or lost CIC during command" 
# (CIC = controller in charge),
# which occurres frequently with the GPIB-ENET controller. 
# This is a transient error occuring when another SPEC application is
# using the same GPIB controller and can be mended by retrying.
# %BR%
# Then return value is the number of bytes send.

def picoammeter_gpib_get (address) '{
  local HDW_ERR s
    
  HDW_ERR = -1
  s = gpib_get(address)
   
  if (HDW_ERR == 4)
  {
    HDW_ERR = -1
    s = gpib_get(address)
  }
  return s
}'


#%MACROS% %IMACROS%
#%AUTHOR% Schotte, 21 Feb 2000 - 17 June 2000
#%TOC%