esrf

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

#%TITLE% NECST.MAC 
#
#%NAME%
# Macros to control ESRF NECST device over ethernet. 
# The NECST can be used as "Counter" or "Counter/Timer".
# Tested with embedded firmware 00.02
#
#%DESCRIPTION%
#%DL%
#%DT%Configuring counters%DD%
#
# Macro counters can be configured to read board counters 
# (counters 1->12 have physical inputs, 13->16 are internals only).
#
# Configure first in SCALERS a new "Macro Counter" controller with 
# DEVICE field set to string "necst" and ADDR field set to the 
# hostname of NECST device. The "NUM" field must be set to 16%BR%
#
# Then configure counters using the previous controller, the "channel" 
# is used to select the counter to read (from 1 to 16)%BR%
#
#%DT%Configuring the timer%DD%
#
# Configure first in SCALERS a new "Macro Counter/Timer" controller with 
# DEVICE field set to string "necst" and ADDR field set to the 
# hostname of NECST device. The "NUM" field must be set to 16%BR%
# 
# Configure the "timebase" counter ("sec") to use the previous controller, 
# the "Channel" must be set to 0, the "Scale Factor" must can be 
# 2e4 1e5 1e6 1e7 1e8 2e8 (the internal NECST clock will be set 
# according to this value)
# 
# Then configure counters using the previous controller, the "Channel" 
# is used to select the counter to read (from 1 to 16)%BR%
#
#%DT%Electrical signals%DD%
#
# When used as "counter/timer, the gate signal of the timer is available
# on output "DO1" of the front end.
#
# When used as "pure counters", the counters are gated by the signal
# incoming in input "DI1"  of the front end.
#
# Currently, these signals are not configurable but hardcoded in
# the macros, sorry. 
#
#%XDL%
#
#%END%
#


# Mandatory dependencies
need deepdevice


#%UU% hostname
#%MDESC%
# Launch an interactive command line interface.
# This allows for intance to check/setup the instrument configuration
# using direct command like ?HELP
#
def necst '{
  local cmd
  local pyc

  if($# != 1) {
    print "Usage: $0 hostname"
    exit
  }

  print "NOT IMPLEMENT YET, sorry"
  exit

  pyc = "pepuconsole.py"

  p sprintf("%s/bin/%s", BLISSADM, pyc)
  if(!file_info(sprintf("%s/bin/%s", BLISSADM, pyc), "-x")) {
    printf("Missing \"%s\" script, hint use blissinstaller\n", pyc)
    exit
  }

  cmd = sprintf("%s %s", pyc, "$1")
  unix(cmd)
}'


#%IU%()
#%MDESC%
# Called by spec after reading the config file
#
def necst_config(num, type, p1, p2, p3) '{
  global NECST[]
  global NECST_CLK[]

  #
  # p1==controller index p2==number of motors supported
  #
  if(type == "ctrl") {
    local dev
    local ans
    local par
    local idx

    # get hostname from config
    dev = necst_ADDR

    # minimum check on controller
    ans = necst_comm(dev, "?APPNAME")
    if(!index(ans, "NECST")) {
      _necst_err
      printf("not a NECST instrument: \"%s\"\n", dev)
      return ".error."
    }

    ans = necst_comm(dev, "?VERSION")
    printf("Using \"%s\" for NECST instrument (version %s)\n", dev, ans)

    # cleanup any previous configuration
    idx = sprintf("%s#%d", dev, p1)
    for(par in NECST[idx]) {
      delete NECST[idx][par]
    }
    for(par in NECST_CLK) {
      delete NECST_CLK[par]
    }

    # TODO: add optional controller parameters using necst_CONPAR
    NECST[idx]["timer_channel"] = 16
    NECST[idx]["timer_clock"]   = 0
    NECST[idx]["timer_gateout"] = "DO1"
    NECST[idx]["timer_gatein"]  = "DI1"
    NECST[idx]["timer_used"]    = 0

    # board capabilities for firmware version 00.01
    NECST_CLK[2e4] =  "20KHZ"
    NECST_CLK[1e5] = "100KHZ"
    NECST_CLK[1e6] =   "1MHZ"
    NECST_CLK[1e7] =  "10MHZ"
    NECST_CLK[1e8] = "100MHZ"
    NECST_CLK[2e8] = "200MHZ"

    # normal end
    return
  }


  #
  # p1==unit p2==0 p3==channel
  #
  if(type == "cnt") {
    local dev
    local cha
    local cmd
    local ans
    local cntcfg 
    local timcha 
    local timgdo
    local src
    local idx


    cntcfg = "#"
    cntcfg = cntcfg "CNTCFG CNT%d "
    cntcfg = cntcfg "50OHM ON FILT OFF "
    cntcfg = cntcfg "SRC %s "
    cntcfg = cntcfg "TRIG OUT1 FALL "
    cntcfg = cntcfg "GATE %s "
    cntcfg = cntcfg "START SOFT STOP SOFT CLEAR "
    cntcfg = cntcfg "OMODE GATE"

    # retrieve hardware location
    dev = counter_par(num, "address")
    cha = counter_par(num, "channel")
    idx = sprintf("%s#%d", dev, counter_par(num, "unit"))

    # retrieve counter to be used as timer
    timcha = NECST[idx]["timer_channel"]
    timgdo = NECST[idx]["timer_gateout"]
 

    # the timer counter is identified by channel 0
    if(cha == 0) {

      # handle the timer counter
      ch   = timcha
      src  = "CLK1"
      gate = "OFF"
 
      # automatically adjust clock
      clk  = NECST_CLK[counter_par(num, "scale")]
      if(clk == 0) {
        _necst_err
        printf("invalid scale factor for channel 0 on NECST : \"%s\"\n", dev)
        return ".error."
      }
      NECST[idx]["timer_clock"] = clk
      NECST[idx]["timer_used"]  = 1

    } else {

      # handle normal counter
      ch   = cha
      src  = "INPUT"

      # if the controller is not a counter/timer,
      # configure an external gating throung a digital input
      # NOTE: as the timer counter has to have channel 0, we can
      # consider that it has already be configured and therefore that
      # we can trust the global var
      if(NECST[idx]["timer_used"]) {

        # handle counter/timer controller
        gate = sprintf("OUT%d", timcha)
      } else {

        # handle pure counter controller
        gate = NECST[idx]["timer_gatein"]

        # some cleanup, for cosmetics only 
        delete NECST[idx]["timer_channel"]
        delete NECST[idx]["timer_clock"]
        delete NECST[idx]["timer_gateout"]
      }
    }

    # configure the counter
    cmd = sprintf(cntcfg, ch, src, gate)
    ans = necst_comm(dev, cmd)
    if(index(ans, "ERROR")) {
      _necst_err
      printf("unable to configure channel %d on NECST : \"%s\"\n", cha, dev)
      return ".error."
    }

    # enable the counter
    cmd = sprintf("#SOFTSIG ENB CNT%d", ch)
    ans = necst_comm(dev, cmd)
    if(index(ans, "ERROR")) {
      _necst_err
      printf("unable to enable channel %d on NECST : \"%s\"\n", cha, dev)
      return ".error."
    }

    # update list of counters used
    NECST[idx]["counters"] = sprintf("CNT%d %s", ch, NECST[idx]["counters"])

    # from here, timer specific configuration
    if(cha != 0) {
      return
    }

    # timer internal clock
    cmd = sprintf("#CLKCFG CLK1 %s", NECST[idx]["timer_clock"])
    ans = necst_comm(dev, cmd)
    if(index(ans, "ERROR")) {
      _necst_err
      printf("unable to set internal clock on NECST : \"%s\"\n", dev)
      return ".error."
    }

    # timer gate routed to digital ouput
    cmd = sprintf("#DOCFG %s SRC OUT%d", timgdo, timcha)
    ans = necst_comm(dev, cmd)
    if(index(ans, "ERROR")) {
      _necst_err
      printf("unable to set channel %d to DO on NECST : \"%s\"\n", cha, dev)
      return ".error."
    }
    
    return
  }


}'


#%IU%()
#%MDESC%
# Called by spec on motor or counter operations
# 
def necst_cmd(num, key, p1, p2, p3) '{
  global NECST[]
  local  mne
  local  dev
  local  cha
  local  cmd ans val
  local  idx


  #
  # Handle actions at controller level
  #
  if (key == "prestart_all") {
    # p1==countring tim1 p2==counting mode p3==unit
    dev = necst_ADDR
    idx = sprintf("%s#%d", dev, p3)

    # TODO: remove this once ?CNTSTATUS is available
    NECST[idx]["timer_preset"] = 0

    cmd = sprintf("SOFTSIG CLR %s", NECST[idx]["counters"])
    ans = necst_comm(dev, cmd)
    if(index(ans, "ERROR")) {
      _necst_err
      printf("unable to clear counters on NECST : \"%s\"\n", dev)
      return ".error."
    }

    return
  }



  #
  # Handle actions at counter level
  #
  if(num == "..") { 
    return
  }
  dev = counter_par(num, "address")
  cha = counter_par(num, "channel")
  idx = sprintf("%s#%d", dev, counter_par(num, "unit"))



  # called for any counter,
  # the timer is the last one to be called
  if (key == "start_one") {

    # the timer counter is identified by channel 0
    if(cha == 0) {
      local cntcfg 

      # for the timer only, p1 is preset time in sec and p2 is counting mode
      cha = NECST[idx]["timer_channel"]
      val = p1 * counter_par(num, "scale")
      # TODO: remove this once ?CNTSTATUS is available
      NECST[idx]["timer_preset"] = val

      # configure the counter preset time
      cntcfg = "#"
      cntcfg = cntcfg "CNTCFG CNT%d "
      cntcfg = cntcfg "PRESET %d "
      cmd = sprintf(cntcfg, cha, val)
      ans = necst_comm(dev, cmd)
      if(index(ans, "ERROR")) {
        _necst_err
        printf("unable to configure channel %d on NECST : \"%s\"\n", cha, dev)
        return ".error."
      }
    }

    cmd = sprintf("#SOFTSIG START CNT%d", cha)
    ans = necst_comm(dev, cmd)
    if(index(ans, "ERROR")) {
      _necst_err
      printf("unable to start channel %d on NECST : \"%s\"\n", cha, dev)
      return ".error."
    }
    return
  }



  # returns non null if timer still running
  if (key == "get_status") {
    # called only for the timer, 
    cha = NECST[idx]["timer_channel"]
    cmd = sprintf("?CNTSTAT CNT%d", cha)
    ans = necst_comm(dev, cmd)
    if(index(ans, "ERROR")) {
      _necst_err
      printf("unable to read channel status %d on NECST : \"%s\"\n", cha, dev)
      return ".error."
    }

    # WARNING: the value is returned in hexadecimal
    if(sscanf(ans, "%x", val) != 1) {
      _necst_err
      print "unable to parse channel value"
      return ".error."
    }

    # bit0:  counter/timer running (1) or not (0)
    # bit31: timer (1) or counter (0)
    return (val&0x01)

    # TODO: missing command ?CNTSTATUS in firmware
    val = necst_cmd(0, "counts")
    return !(val >=  NECST[idx]["timer_preset"])
  }



  # called for any counter,
  # returns the current counter value
  if (key == "counts") {
    local lat

    # the timer counter is identified by channel 0
    if(cha == 0) {
      cha = NECST[idx]["timer_channel"]
      lat = ""
    } else {
      lat = "LATCH"
    }

    cmd = sprintf("?CNTVAL CNT%d %s", cha, lat)
    ans = necst_comm(dev, cmd)
    if(index(ans, "ERROR")) {
      _necst_err
      printf("unable to read channel %d on NECST : \"%s\"\n", cha, dev)
      return ".error."
    }

    # WARNING: the value is returned in hexadecimal
    if(sscanf(ans, "%x", val) != 1) {
      _necst_err
      print "unable to parse channel value"
      return ".error."
    }

    # timer must return the value in seconds
    if(cha == 0) {
      val = val / counter_par(num, "scale")
    }

    return val
  }

}'



#%IU%()
#%MDESC%
# Called by spec on motor_par() or counter_par()
# 
def necst_par(num, key, todo, p1) '{
  local  dev
  local  cha
  local  cmd ans val

  # return new counter_par() argins handled
  if (key == "?" && todo == "get") {
    return("config")
  }

  dev = counter_par(num, "address")
  cha = counter_par(num, "channel")

  # counter configuration
  if (key == "config") {
    if(todo == "set") {
      _necst_err
      printf("not a implemented yet\n")
      return ".error."
    }

    #TODO: bug to be fixed in firmware
    cmd = sprintf("#?CNTCFG CNT%d", cha)
    ans = necst_comm(dev, cmd)
    if(index(ans, "ERROR")) {
      _necst_err
      printf("unable to get config of channel %d on NECST : \"%s\"\n", cha, dev)
      return ".error."
    }
    return(ans)
  }

}'


#%IU%(hostname, command)
#%MDESC%
# Send a command to the given device and returns the answer if any
#
def necst_comm(hn, cmd) '{
  local dev ans

  # initialize communication if not already done
  if(deepdev_check(hn) == -1) {
    local dev
    dev = sprintf("%s%s", hn, index(hn,":")?"":":5000")
    deepdev_add(hn, dev)
  }

  # send the command to the device
  ans = deepdev_comm(hn, cmd)

  # handle and translate errors
  if(ans == DEEPDEV_ERRANSW) {
    return sprintf("ERROR: %s", DEEPDEV_ERRMSG)
  } 

  if(ans == DEEPDEV_ERR) {
    return sprintf("ERROR: fail to talk to \"%s\"", hn)
  } 

  # normal end
  return ans
}'



#%IU%
#%MDESC%
# Pure cosmetic macro
#
def _necst_err '{
 tty_cntl("md")
 printf("NECST ERROR: ")
 tty_cntl("me")
}'

#%IU%
#%MDESC%
# Test macro
# Expect a 100MHz incoming signal on cnt1 counter.
#
def _necst_test '{
  local tbeb tend texe
  local tct
  local i

  local cnt

  cnt = cnt1
  for(i=0;;i++) {
    tct = (i%2)?.1:.6
    tbeg = time()
    ct tct
    tend = time() 
    texe = (tend-tbeg)*1000
  
    printf("%s:%04d:Execution time: %10.2fms\n",date(), i, texe)

    if(0) {
      tct *= 1000
      if(fabs(tct - texe) > 70) {
        exit
      }
    }
   
    if(1) {
      if(fabs(S[cnt] - (tct*1e8)) > 1000) {
        exit
      }
    }

  }

}'


#%MACROS%
#%IMACROS%
#%AUTHOR% MP BLISS (Original 2/2017).
# %BR%$Revision: 1.1 $ / $Date: 2017/03/20 10:19:59 $
#%TOC%