#%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%
|