esrf

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

#%TITLE% dc.mac
#%NAME% 
#   Data collector macros
#
#%CATEGORY% Tools
#
#%DESCRIPTION%
# This macro set is intended to transfer information between SPEC and the
# data collector (DC). Data can be a single scalar or a vector. It is sent
# to and read from DC devices, identified by names. For reading or creating
# a DC device a command name must be specified.%BR% %BR%
# A first interface is provided through the macro %B%dcsetup%B%, which 
# hooks into %B%prompt_mac%B% a command that puts on the specified DC device
# a fixed number of parameters.%BR% %BR%
#
# The other group of macros link motors and counters to DC devices. Macro sets
# %B%cnt2dc%B% and %B%mot2dc%B% store counter counts and motor positions 
# on the DC, respectively. On the other hand, macro set %B%dc2mot%B% move 
# motors to positions read from the DC, while reading from the DC to pseudo
# counters is made through the %B%multim%B% macros. The %B%xx2yy%B% macro sets
# share the same user interface: a %B%setup%B% macro and an %B%add%B% macro.
# All the macros allow the data on the DC be stored as scalars or vectors.
# In the case of vectors, different counters or motors can be linked to the
# same device by specifying different array positions.%BR% %BR%
#
# In the %B%setup%B% macros the DC device name is specified, as well as its
# size (1 for scalars) and command. In the case of %B%cnt2dcsetup%B% and 
# %B%mot2dcsetup%B%, which are supposed to create the DC device, the 
# variable type can also be specified; D_FLOAT_TYPE and D_VAR_FLOTARARR 
# are used by default for scalar and vector devices, respectively. If counters
# or motors are specified at the end of the %B%setup%B% macros, they will
# be passed to the corresponding %B%add%B% macros, where they are associated
# to a position in the DC vector (0 for scalars).%BR% %BR%
#
# Reading from DC to counters is implemented thorugh %B%multimsetup%B%
# %B%type=dc.%B% Supported parameters are:%BR%
#
# %UL%
#   %LI% %B%device:%B%  DC device name  (mandatory)
#   %LI% %B%cmd:%B%     device read command (mandatory)
#   %LI% %B%arrsize:%B% DC data size    (optional, default: 0=scalar)
#   %LI% %B%channel:%B% index in vector (optional, default: scalar -> 0,
#                                        vector -> config "channel" par.)
# %XUL%
#
# If %B%channel%B% parameter is not specified, it will be set to 0 for scalar 
# devices, or it will be taken from the "channel" counter parameter in the 
# config file for vector devices. All the pseudo counters defined in the same
# %B%multimsetup%B% line will share the same device, so %B%channel%B% could
# not be speficified explicitely but stored in the config file. The %B%cmd%B%
# and %B%arrsize%B% parameters for a device can be omitted if another
# %B%multimsetup%B% %B%type=dc%B% with the same device specified those
# parameters before.
#
#%EXAMPLE%
#
# %DL%
#   %DT% dcsetup ID00/Counters/1 blcounters S[det] S[mon] A[energy] MYSECRETNR
#   %DD% Hooks into %B%promt_mac%B% a command that writes in 
#        %B%ID00/Counters/1%B% the counts of %B%det%B% and %B%mon,%B% the 
#        position of motor %B%energy%B% and the value of the global variable 
#        %B%MYSECRETNR%B%
#
#   %DT% cnt2dcsetup ID00/Counters/2 DevReadAll 5 D_VAR_FLOTARARR det 0 mon 1
#   %DD% Initializes DC device %B%ID00/Counters/2%B% with read command 
#        %B%DevReadAll%B% as vector of size 5 and links counters %B%det%B% 
#        and %B%mon%B% to its first 2 elements. Counter counts will be 
#        written on the DC after each count %B%(user_getcounts).%B%
#
#   %DT% cnt2dcadd ID00/Counters/2 cnt1 2 cnt2 3 cnt3 4
#   %DD% Links counters %B%cnt1,%B% %B%cnt2%B% and %B%cnt3%B% to the last 
#        3 elements of the same device
#
#   %DT% mot2dcsetup ID00/Energy/1 DevReadAll 3
#   %DD% Initializes DC device %B%ID00/Energy/1%B% with read command 
#        %B%DevReadAll%B% as vector of size 3.
#
#   %DT% mot2dcadd ID00/Energy/1 mono 0 energy 1 u42u 2
#   %DD% Links motors %B%mono,%B% %B%energy%B% and %B%u42u%B% to the same
#        device. Motor positions will be written to the DC after each movement
#        %B%(user_getpangles%B% and %B%user_checkall).%B%
#
#   %DT% dc2motsetup ID00/MainRot/1 DevRead 1 mrot 0
#   %DD% Links the scalar DC device %B%ID00/MainRot/1%B% with motor 
#        %B%mrot.%B% The device will be read each time %B%user_checkall%B%
#        is executed and the corresponding motor will be moved to the read
#        position. A very simple loop can put a session in a "server mode":
#        
#   %DT% while (1) { mv dummy 0; sleep(0.1) }
#   %DD% will read all the time the DC and move the %B%mrot%B% motor if
#        necessary, provided that %B%dummy%B% is a non-hooked,
#        NONE-type motor.
#
#   %DT% multimsetup calorim type=dc device=ID00/Calorim/1 cmd=DevRead
#        %B%polltime=0.1%B%
#   %DD% Sets up the counter %B%calorim%B% to read the scalar DC device
#        %B%ID00/Calorim/1%B% with the command %B%DevRead%B% and a polling
#        time of 0.1 seconds. %B%polltime%B% is a parameter recognized by
#        %B%multim%B% macros. The final count will be the average of all
#        the readings made. See %B%multim.mac%B% documentation for details.
# %XDL%
#
%LOG%
# history - 05/10/94 - ag  - created
#           11/10/94 - jk  - improved it radically
#           12/10/94 - ag  - made it more generic
#           24/04/02 - ah  - added counter and motor stuff
#
# $Revision: 4.3 $
#
# $Log: dc.mac,v $
# Revision 4.3  2008/02/27 16:42:30  rey
# documentation changes
#
# Revision 4.2  2007/01/21 11:34:42  ahoms
# Set arrsize=0 for scalars, allowing arrays with arrsize=1
#
# Revision 4.1  2004/10/28 12:48:05  ahoms
# Added dc_create; DC readers will create and initialise devices if not exist.
# Device array size can be modified in SETUP.
# Changed to if-else style.
#
# Revision 4.0  2002/05/23 11:14:47  ahoms
# Added support for linking motors and counters to DC devices
#
#%END%

#%UU% dev-name key VAL1 .... VAL8
#%MDESC%  Creates a pseudo device %B%dev-name%B% in data collector and hooks 
# into %B%prompt_mac%B% to write the specified %B%VALx%B% as an array.
#
def dcsetup '
  esrf_dc("$1","create","DevReadAll","D_VAR_FLOATARR")
#
# define the macro to be executed during the prompt
# 
  cdef("prompt_mac","dc_update $* ;",sprintf("%s","$2"),0)
'

#
#%IU% dev-name key VAL1 .... VAL8
#%MDESC% update data collector with values
#
def dc_update '{
  local arr

  arr[0] = $3;
  arr[1] = $4;
  arr[2] = $5;
  arr[3] = $6;
  arr[4] = $7;
  arr[5] = $8;
  arr[6] = $9;
  arr[7] = $10;

  esrf_dc("$1","put", arr)
}'


#
#%IU% (arr-name, add-mac-name, create-dev, setup-str)
#%MDESC% Analizes the %B%setup-str%B% (with format "dev-name cmd arr-size
# [var-type] [mot-cnt-name arrpos] ...") passed to the calling setup macro. 
# Initializes the DC device %B%dev-name%B% (only if %B%create-dev%B% is
# non-zero) and the variables in the array %B%arr-name,%B% and calls the 
# macro %B%add-mac-name%B% for linking motors/counters if they are specified. 
# %B%var-type%B% is not analyzed if not %B%create-dev.%B% Asks the user if the
# values are not specified in the command line of the calling macro.
#
def dc_low_setup(arrname, addmac, create, setupstr) '{
  local devname cmdname arrsize vartype
  local npar parr[] parstr par parstr firstmne
  
  npar = split(setupstr, parr)
  devname = (npar > 0) ? parr[0] : getval("Enter the DC device name", "")
  cmdname = (npar > 1) ? parr[1] : getval("Enter the command name", \
                                          "DevReadSigValues")
  arrsize = (npar > 2) ? parr[2] : getval("Enter the number of channels", 0)
  if (arrsize < 0) {
    print "Invalid number of channels:", arrsize
    exit
  }

  if (create) {
    vartype = (npar > 3) ? parr[3] : ""
    if (dc_create(devname, cmdname, vartype, arrsize) < 0)
      exit
  }

  @arrname[devname]["arrsize"] = arrsize
  @arrname[devname]["cmd"] = cmdname

  firstmne = create ? 4 : 3
  if (npar > firstmne) {
    parstr = ""
    for (par = firstmne; par < npar; par += 2)
      parstr = sprintf("%s %s %s", parstr, parr[par], parr[par + 1])

    eval(sprintf("%s %s %s", addmac, devname, parstr))
  }
}'

#
#%IU% (dev-name, cmd-name, var-type, arr-size)
#%MDESC% Creates a DC device
#
def dc_create(devname, cmdname, vartype, arrsize) '{
  local ret

  if (vartype == "")
    vartype = (arrsize > 0) ? "D_VAR_FLOATARR" : "D_FLOAT_TYPE"

  ret = esrf_dc(devname, "create", cmdname, vartype)
  if (ret < 0)
    print "Error creating DC device", devname
 
  return ret
}'


#
#%IU% (arr-name, setup-mac, is-cnt, chan-mac, post-mac, add-str)
#%MDESC% Analizes the %B%add-str%B% (with format "dev-name mot-cnt-name 
# arrpos [cnt-name arr-pos] ...") passed to the calling "add" macro. 
# Links the specified motors/counters %B%mot-cnt-name%B% to the the DC 
# device %B%dev-name%B% (defined in %B%arr-name%B% by the macro 
# %B%setup-mac%B%) at position %B%arr-pos.%B% %B%is-cnt%B% is 1 for 
# counters and 0 for motors. The real hook is made by calling 
# %B%"chan-mac(dev-name, mot-cnt-name, arr-pos)"%B% for every channel 
# and %B%"post-mac(dev-name)"%B% for the device. Asks the user if the
# values are not specified in the command line of the calling macro.
#
def dc_low_add(arrname, setupmac, iscnt, chanmac, postmac, addstr) '{
  local type devname name num arrsize arrpos key mstr 
  local param[] nrpar ipar maxlooppar msg ret cmd

  type = iscnt ? "counter" : "motor"

  if (whatis(arrname) == 0) {
    print "No DC device configured. Run", setupmac, "first"
    exit
  }

  nrpar = split(addstr, param)
  devname = (nrpar > 0) ? param[0] : getval("Enter the DC device name", "")
  cmd = @arrname[devname]["cmd"]
  if (!cmd) {
    print "Device", devname, "not configured. Run", setupmac, "first"
    exit
  }
  arrsize = @arrname[devname]["arrsize"]

  maxlooppar = (nrpar > 3) ? nrpar : 3
  ipar = 1
  while (ipar < maxlooppar) {
    msg = sprintf("Enter the %s name", type)
    name = (nrpar > ipar) ? param[ipar] : getval(msg, "")
    num = iscnt ? cnt_num(name) : motor_num(name)
    if (num == -1) {
      printf("Invalid %s: %s\n", type, name)
      exit
    }
    ipar++

    msg = "Enter the DC channel index"
    if (arrsize > 0) {
      arrpos = (nrpar > ipar) ? param[ipar] : \
                                ((arrsize > 1) ? getval(msg, 0) : 0)
      if (arrpos >= arrsize) {
        msg = "%s: Invalid channel index: %d, only %d configured for " \
              "DC device %s\n"
        printf(msg, name, arrpos, arrsize, devname)
        exit
      }
    } else
      arrpos = 0
    ipar++

    ret = eval(sprintf("%s(\"%s\", \"%s\", \"%d\")", \
                       chanmac, devname, name, arrpos))
    if (ret < 0)
      exit
  }

  eval(sprintf("%s(\"%s\")", postmac, devname))
}'


#
#%IU% (arr-name, dev-name)
#%MDESC% Fills the DC device %B%dev-name%B% with the values previously 
# stored in the array %B%arr-name.%B%
#
def dc_put(arrname, devname) '{
  local ipos asize arr[]
 
  asize = @arrname[devname]["arrsize"]
  if (asize > 0) {
    for (ipos = 0; ipos < asize; ipos++)
      arr[ipos] = @arrname[devname][ipos]

    esrf_dc(devname, "put", arr)
  } else
    esrf_dc(devname, "put", @arrname[devname][0])
}'


#
#%IU% (arr-name, dev-name)
#%MDESC% Reads the DC device %B%dev-name%B% into the array %B%arr-name.%B%
#
def dc_read(arrname, devname) '{
  local cmd ipos asize arr[] ret chanread try isarr

  cmd = @arrname[devname]["cmd"]
  asize = @arrname[devname]["arrsize"]
  isarr = (asize > 0)
  for (try = 0; try < 2; try++) {
    if (isarr) {
      ret = chanread = esrf_dc(devname, cmd, arr)
    } else {
      ret = arr[0] = esrf_dc(devname, cmd)
      chanread = 1
    }
    if (ret != -1)
      break

    if (try == 0) {
      print "DC device", devname, "might not exist. Trying to create it."
      ret = dc_create(devname, cmd, "", asize)
      if (isarr) {
        for (ipos = 0; ipos < asize; ipos++)
          arr[ipos] = 0
        esrf_dc(devname, "put", arr)
      } else
        esrf_dc(devname, "put", 0)
    } else
      print "Failed to create DC device", devname
  }

  if (ret >= 0) {
    if (isarr && (chanread < asize))
      printf("Warning! Read %d elements from DC device %s, expecting %d\n", \
             chanread, devname, asize)
    for (ipos = 0; ipos < (isarr ? asize : 1); ipos++)
      @arrname[devname][ipos] = (ipos >= chanread) ? 0 : arr[ipos]
    @arrname[devname]["chanread"] = chanread
  } else {
    print "Failed to create device", devname
    @arrname[devname]["chanread"] = 0
  }

  return ret
}'

#
#%UU% dev-name dev-cmd arr-size [var-type] [cnt-name arr-pos] ...
#%MDESC% Defines a new DC device %B%dev-name%B% with size %B%arr-size,%B%
# command %B%dev-cmd%B% and type %B%var-type,%B% and links the specified 
# counters to it by calling %B%cnt2dcadd.%B%
#
def cnt2dcsetup '{
  global C2DC[]

  dc_low_setup("C2DC", "cnt2dcadd", 1, "$*")
}'

#
#%UU% dev-name cnt-name arr-pos [cnt-name arr-pos] ...
#%MDESC% Links the specified counters %B%cnt-name%B% to the DC device
# %B%dev-name%B% at position %B%arr-pos.%B%
#
def cnt2dcadd '{
  dc_low_add("C2DC", "cnt2dcsetup", 1, "cnt2dc_inst", "cnt2dc_post", "$*")
}'

#
#%IU% (dev-name, cnt-name, arr-pos)
#%MDESC% Called by dc_low_add for each counter. Hooks into 
# %B%cnt2dc_getcounts%B% the command for filling the C2DC array position 
# %B%arr-pos%B% with the %B%cnt-name%B% counts.
#
def cnt2dc_inst(devname, cntname, arrpos) '{
  local mstr

  mstr = sprintf("C2DC[\"%s\"][%d] = S[%s]\n", devname, arrpos, cntname)
  cdef("cnt2dc_getcounts", mstr, cntname, 0x02)
}'

#
#%IU% (dev-name)
#%MDESC% Called by dc_low_add for each DC device. Hooks into 
# %B%user_getcounts%B% the command for filling all the DC devices, 
# %B%cnt2dc_getcounts,%B% and hooks into it for filling the specified 
# device %B%dev-name.%B%
#
def cnt2dc_post(devname) '{
  local mstr

  cdef("user_getcounts", "cnt2dc_getcounts\n", "cnt2dc", 0x20)

  mstr = sprintf("dc_put(\"C2DC\", \"%s\")\n", devname)  
  cdef("cnt2dc_getcounts", mstr, devname, 0x20)
}'


#
#%UU% dev-name dev-cmd arr-size [var-type] [mot-name arr-pos] ...
#%MDESC% Defines a new DC device %B%dev-name%B% with size %B%arr-size,%B%
# command %B%dev-cmd%B% and type %B%var-type,%B% and links the specified 
# motors to it by calling %B%mot2dcadd.%B%
#
def mot2dcsetup '{
  global M2DC[]

  dc_low_setup("M2DC", "mot2dcadd", 1, "$*")
}'

#
#%UU% dev-name mot-name arr-pos [mot-name arr-pos] ...
#%MDESC% Links the specified motors %B%mot-name%B% to the DC device
# %B%dev-name%B% at position %B%arr-pos.%B%
#
def mot2dcadd '{
  dc_low_add("M2DC", "mot2dcsetup", 0, "mot2dc_inst", "mot2dc_post", "$*")
}'

#
#%IU% (dev-name, mot-name, arr-pos)
#%MDESC% Called by dc_low_add for each motor. Hooks into 
# %B%mot2dc_getangles%B% the command for filling the M2DC array position 
# %B%arr-pos%B% with the %B%mot-name%B% position.
#
def mot2dc_inst(devname, motname, arrpos) '{
  local mstr

  mstr = sprintf("M2DC[\"%s\"][%d] = A[%s]\n", devname, arrpos, motname)
  cdef("mot2dc_getangles", mstr, motname, 0x01)
}'

#
#%IU% (dev-name)
#%MDESC% Called by dc_low_add for each DC device. Hooks into 
# %B%user_getpangles%B% and %B%user_finished1%B% the command for filling all 
# the DC devices, %B%mot2dc_getcounts,%B% and hooks into it for filling the 
# specified device %B%dev-name.%B%
#
def mot2dc_post(devname) '{
  local mstr

  cdef("user_getpangles", "mot2dc_getangles\n", "mot2dc", 0x20)
  cdef("user_finished1", "mot2dc_getangles\n", "mot2dc", 0x20)

  mstr = sprintf("dc_put(\"M2DC\", \"%s\")\n", devname)  
  cdef("mot2dc_getangles", mstr, devname, 0x20)
}'



#
#%UU% dev-name dev-cmd arr-size [mot-name arr-pos] ...
#%MDESC% Defines a new DC device %B%dev-name%B% with size %B%arr-size%B% and
# command %B%dev-cmd,%B% and links the specified motors to it by calling 
# %B%dc2motadd.%B%
#
def dc2motsetup '{
  global DC2M[]

  dc_low_setup("DC2M", "dc2motadd", 0, "$*")
}'

#
#%UU% dev-name mot-name arr-pos [mot-name arr-pos] ...
#%MDESC% Links the specified motors %B%mot-name%B% to the DC device
# %B%dev-name%B% at position %B%arr-pos.%B%
#
def dc2motadd '{
  dc_low_add("DC2M", "dc2motsetup", 0, "dc2mot_inst", "dc2mot_post", "$*")
}'

#
#%IU% (dev-name, mot-name, arr-pos)
#%MDESC% Called by dc_low_add for each motor. Hooks into 
# %B%dc2mot_checkall%B% the command for setting the %B%mot-name%B% position
# from the DC2M array index %B%arr-pos.%B%
#
def dc2mot_inst(devname, motname, arrpos) '{
  local mstr

  mstr = sprintf("A[%s] = DC2M[\"%s\"][%d]\n", motname, devname, arrpos)
  cdef("dc2mot_checkall", mstr, motname, 0x01)
}'

#
#%IU% (dev-name)
#%MDESC% Called by dc_low_add for each DC device. Hooks into 
# %B%user_checkall%B% the command for filling the motor angles array A[] from
# the DC devices, %B%dc2mot_checkall,%B% and hooks into it for reading the 
# specified device %B%dev-name.%B%
#
def dc2mot_post(devname) '{
  local mstr

  cdef("user_checkall", "dc2mot_checkall\n", "dc2mot", 0x10)

  mstr = sprintf("dc_read(\"DC2M\", \"%s\")\n", devname)  
  cdef("dc2mot_checkall", mstr, devname, 0x10)
}'



#
#%IU% 
#%MDESC% Called when %B%multimsetup%B% %B%<cnt-mne>%B% %B%type=dc%B% is 
# invoked. Checks for the parameters %B%dev,%B% %B%cmd,%B% %B%channel%B% and 
# %B%arrsize.%B%
# If %B%channel%B% is not specified it is taken from the config file;
# if %B%arrsize%B% is not specified it is set to 0. Parameters %B%cmd%B%
# and %B%arrsize%B% can be omitted if another %B%DC%B% %B%multim%B% counter 
# was defined before with the same %B%dev.%B%
#
def multim_init_dc '
  global MULTIM_DC[]

  if ("device" in MULTIM[cntr]) {
    local devname channel msg arrsize cntarrsize
      
    devname = MULTIM[cntr]["device"]

    arrsize = MULTIM_DC[devname]["arrsize"]
    cntarrsize = MULTIM[cntr]["arrsize"]
    if ((arrsize < 0) || (SETUP_N != MULTIM_DC[devname]["setup_n"])) {
      arrsize = (cntarrsize > 0) ? cntarrsize : 0
      MULTIM_DC[devname]["arrsize"] = arrsize
      MULTIM_DC[devname]["setup_n"] = SETUP_N
    } else if ((cntarrsize > 0) && (cntarrsize != arrsize)) {
      msg = "Nr. of channels %d for DC %s on counter %s different from " \
            "previous specification (=%d).\n  Set \"arrsize\" parameter " \
            "properly on multimsetup type=dc\n"
      printf(msg, cntarrsize, devname, cnt_mne(cntr), arrsize)
      return 1
    }
    
    if ("channel" in MULTIM[cntr])
      channel = MULTIM[cntr]["channel"]
    else {
      channel = (arrsize > 0) ? counter_par(cntr, "channel") : 0
      MULTIM[cntr]["channel"] = channel
    }

    if (MULTIM_DC[devname]["cmd"] == "") {
      if (MULTIM[cntr]["cmd"] == "") {
        msg = "Command not defined for DC %s.\n  Specify the \"cmd\" " \
              "parameter in multimsetup type=dc for counter %s\n"
        printf(msg, devname, cnt_mne(cntr))
        return 1
      } else
        MULTIM_DC[devname]["cmd"] = MULTIM[cntr]["cmd"]
    } else if ((MULTIM[cntr]["cmd"] != "") && \
             (MULTIM[cntr]["cmd"] != MULTIM_DC[devname]["cmd"])) {
      msg = "Command %s for DC %s on counter %s different from " \
            "previous specification (%s).\n  Set \"cmd\" parameter " \
            "properly on multimsetup type=dc\n"
      printf(msg, MULTIM[cntr]["cmd"], devname, cnt_mne(cntr), \
             MULTIM_DC[devname]["cmd"])
      return 1
    }

    if (channel < 0) {
      printf("Invalid DC channel %d in counter %s\n", channel, \
             cnt_mne(cntr))
      return 1
    } else if ((arrsize > 0) && (channel >= arrsize)) {
      msg = "DC Channel %d greater than nr. of configured channels " \
            "for device \"%s\" (=%d)\n"
      printf(msg, channel, devname, arrsize)
      return 1
    }

    if (dc_read("MULTIM_DC", devname) < 0)
      return 1
  } else {
    printf("No \"dev\" parameter in multimsetup for DC counter %s\n", \
           cnt_mne(cntr))
    return 1 
  }
'


#
#%IU% 
#%MDESC% Called from %B%multim_readinstrument%B% for counters with 
# %B%type=dc.%B% Local variables %B%cntr,%B% %B%ok%B% and %B%value%B% are
# defined; the last two must be properly set.
#. 
def multim_read_dc '

  if ("device" in MULTIM[cntr]) {
    local devname channel
    devname = MULTIM[cntr]["device"]
    if (MULTIM["loop_n"] > MULTIM_DC[devname]["last_n"]) {
      dc_read("MULTIM_DC", devname)
      MULTIM_DC[devname]["last_n"] = MULTIM["loop_n"]
    }

    channel = MULTIM[cntr]["channel"]
    if (MULTIM_DC[devname]["chanread"] > channel) {
      value = MULTIM_DC[devname][channel]
      ok = 1
    }
  }
'

#%MACROS%
#
#%IMACROS%
#
#%INTERNALS%
#
# All macros (except %B%dcsetup%B%) share the same DC read/write mechanism.
# They are based in an associative array that store for each DC device the
# read command, the array size and all the numbers to write to or read from
# it. Thus only two functions are needed to access the DC: %B%dc_put%B% and
# %B%dc_read.%B% In the same way, the %B%setup%B% and %B%add%B% macros share
# the same interfaces, which are implemented in %B%dc_low_setup%B% and 
# %B%dc_low_add.%B% The first macro initializes the particular internal 
# array, and will call an %B%add%B% macro if motors/counters are specified.
# The second macro will call an %B%inst%B% macro for each channel specified
# and a %B%post%B% macro for the device. Those "user" macros are supposed to
# hook in the correct macros the reading or writing operations.
# 
# The %B%multim_read_dc%B% uses the new internal variable %B%loop_n%B%
# supported by the %B%multim%B% macros for reading the DC only once per
# %B%multim%B% loop.
#
#%DEPENDENCIES% 
# Reading from the DC to counters is made using %B%multim%B% macros.
#
#%AUTHOR% AG, JK - 10/94, AH - 02/02 %BR%
#  $Revision: 4.3 $ / $Date: 2008/02/27 16:42:30 $
#%TOC%