esrf

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

#%TITLE% ELMO.MAC 
#
#%NAME%
# Macros to control ELMO motor controller through macro motor. 
# Tested only with model "Whistle 2.02.07.00" through RS232 and
# driving a single rotation stage.
#
#%CATEGORY% Positioning
#
#%DESCRIPTION%
#%DL%
#%DT%Configuring macro motors %DD%
# %DL%
#  %DT% 1)
#       You must have an entry in "MOTORS" table for each controller
#       %DL%
#         %DT% -
#         The "DEVICE" field must be set to string "elmo"
#         %DT% -
#         The "ADDR" must be set to the serial line already declared
#         in the current session config (ex: "6"). The serial line
#         settings are %B%19200%B% bauds with mode %B%Raw%B%
#         
#         %DT% -
#         The "NUM" should be set to
#         %DT% -
#         The "TYPE" field must be set to "Macro Motors"
#       %XDL%
#  %DT% 2) 
#       For each axis you must define a motor with:
#       %DL%
#         %DT% -
#         The "Controller" field set to "MAC_MOT"
#         %DT% -
#         The "Unit" field, numbered from 0,
#         must be set to the MOTORS entry.
#         %DT% -
#         The "Module" field is not used
#         %DT% -
#         The "Chan" field is not used
#         %DT% -
#         The "Linear/Rotary" field set to 1 for rotary motors, 0 otherwise
#       %XDL%
# %XDL%
#%XDL%
#
#%DL%
#%DT%Controller mode (user_mode motor_par) %DD%
# %DL%
#  %DT% Position control mode
#  %DD% On spec %B%reconfig%B%, the control position is automatically set. 
#  The ELMO corresponding user_mode is 4 for linear motors (aux. encoder feedback loop) or 
#  5 for rotary motors (main encoder feedback loop) depending on "Linear/Rotary" field in spec config.
#  %DT% Speed control mode
#  %DD% Correspond to ELMO user_mode = 2. In this mode, higher velocity can be reached but 
#  no control position commands can be executed (ie. no standard mv/mvr/...).
# %XDL%
#%XDL%
#
#%DL%
#%DT% Motor parameters implemented %DD%
# %DL%
#  %DT% closed_loop
#  %DD% Switch on(1) / off(0) the closed loop
#  %DT% power
#  %DD% Switch motor on(1) / off(0). On power ON, the command position is updated. 
#  If the %B%user_mode%B% is in control position (4 or 5), on power ON, the command position is updated. 
#  If ran outside config (ie. on a motor_par), read_motors(0x6) is called to update spec internals.
#  %DT% enc_position
#  %DD% Return actual encoder position
#  %DT% user_mode
#  %DD% Change the ELMO control mode (4/5: position control, 2: speed control). When switching to position control
#  mode, the command position is updated. The read_motors(0x6) is executed to update spec internals.
#  %DT% jog_speed
#  %DD% Start a jog speed motion in (deg or mm)/sec. Stop it if speed = 0.
# %XDL%
#%XDL%
#
#%DL%
#%DT% Homing procedure implemented %DD%
# %DL%
#  %DT% chg_dial(mot, "home+")
#  %DD% Jog motor in user positive direction until home switch. Sets the controller position (dial) to the
#  position eventually defined in spec config as "home position" (0 by default)
#  %DT% chg_dial(mot, "home-")
#  %DD% Same procedure moving in negative direction
# %XDL%
#%XDL%
#%END%
#

constant ELMO_SLBAUD 19200
constant ELMO_ERR    "ELMO ERROR: "
constant ELMO_CTLERR "ELMO CONTROLLER ERROR: "

#%UU%
#%MDESC%
# Toggle on/off debug messages
#
def elmodebug '{
 global ELMO_DEBUG

 if (ELMO_DEBUG) {
  rdef elmo__debug \'#\$#\'
  print "ELMO debug mode is OFF"
  ELMO_DEBUG=0
 } else {
  rdef elmo__debug \'print "ELMO: "\'
  print "ELMO debug mode is ON"
  ELMO_DEBUG=1
 }
}'

def elmodebug_init '{
 if(!(whatis("elmo__debug")&0x2)) {
  global ELMO_DEBUG
  rdef elmo__debug \'#\$#\'
  ELMO_DEBUG= 0
 }
}'
elmodebug_init


#%IU%
#%MDESC%
# MACRO MOTOR: 
# Called by spec after reading the config file
#
def elmo_config(num,type,p1,p2,p3) '{
 local sl ans mne rot um
 global ELMO_PAR[]
 local i


 # Get the communication link
 sl=elmo_ADDR

 # p1==controller index p2==number of motors supported
 if(type=="ctrl") {


  # Check serial line validity
  if(ser_par(sl,"device_id")==-1) {
    print ELMO_ERR "wrong serial line"
    return ".error." 
  }
  
  # Check serial line settings
  # NOTE MP 002Aug2011: seems to not work with TACO serial line
  #if(ser_par(sl,"baud") != ELMO_SLBAUD) {
  #  print ELMO_ERR "wrong serial line baud rate, must be: "ELMO_SLBAUD
  #  return ".error." 
  #}
  
  # Check that the controller is alive
  ans = elmo_query(sl,"VR")
  if((ans=="") || (ans==-1)) {
    print ELMO_ERR "missing controller"
    return ".error." 
  }

  print "ELMO: new controller: "ans

 }


 # p1==unit p2==module p3==channel
 if(type=="mot") {
  mne= motor_mne(num)
  ELMO_PAR[mne]["config"]= 1
  rot= motor_par(num, "rotary")
  # user mode = 4 : linear ; 5 : rotation
  um= rot ? 5 : 4

  # Check user-mode
  mode= elmo_par(num, "user_mode", "get")
  if (mode != um) {
    mode= elmo_par(num, "user_mode", "set", um)
    if (mode != um) {
      ELMO_PAR[mne]["config"]= 0
      return ".error."
    }
  } else {
    # Check closed loop on
    power= elmo_par(num, "power", "get")
    if (power==0) {
      power= elmo_par(num, "power", "set", 1)
      if (power!=1) {
        ELMO_PAR[mne]["config"]= 0
        return ".error."
      }
    }
  }

  # reset jog speed
  ELMO_PAR[mne]["jog"]= 0
  ELMO_PAR[mne]["config"]= 0

  print "ELMO: closed loop switched on for motor: \""mne"\""
 }

}'



#%IU%
#%MDESC%
# MACRO MOTOR: 
# Called by spec after reading the config file, after calling _config()
# and only if parameters are set in the config file for a motor.
#
def elmo_par(num,key,todo,p1) '{
 local mne
 local sl

 # get the motor mnemonic string
 mne=motor_mne(num)

 # Get the communication link
 sl=motor_par(num,"address")

 # return new motor_par() argins handled
 if (key == "?" && todo == "get") {
  return("power,closed_loop,user_mode,enc_position,jog_speed,jog_range")
 }

 #
 #
 #
 if (key == "closed_loop") {
  local sta ans
 
  
  # Change the closed loop state
  if(todo == "set") {
    elmo_query(sl,sprintf("MO=%d",(p1==1?1:0)))
  }

  # Return the current state of closed loop 
  # bit4 of Status Register (page 3.135)
  ans = elmo_query(sl,"SR")
  if(sscanf(ans,"%d",sta) != 1) {
   print ELMO_ERR "unable to get status"
   return(-1)
  }
  return((sta&(1<<4))?1:0)
 }

 if (key == "power") {
  local sta ans

  if (todo == "set") {
    elmo_query(sl, sprintf("MO=%d", (p1==1?1:0)))
  }

  ans= elmo_query(sl, "MO")
  if (sscanf(ans, "%d", sta) != 1) {
   print ELMO_ERR "unable to get power status"
   return (-1)
  }

  if (ELMO_PAR["srot"]["mode"] != 2) {
    if ((todo == "set")&&(sta==1)) {
       elmo_query(sl, "PA=DV[3]")
       if (!ELMO_PAR[mne]["config"]) {
         read_motors(0x6)
       }
    }
  }
     
  return (sta==1)
 }

 if (key == "user_mode") {
  local ans mode
  local newmode ncmd cmds[] vals[]

  if (todo == "set") {
    mode= int(p1)
    ncmd= 0
    cmds[ncmd]= "MO=0"
    cmds[++ncmd]= sprintf("UM=%d", mode)
    if (mode==2) {
      cmds[++ncmd]= "PM=1"
    }
    cmds[++ncmd]= "MO=1"
    if (elmo_multiquery(sl, ++ncmd, cmds, vals)==-1) {
      print ELMO_ERR "failed to set user_mode to " mode
      return (-1)
    }

    if ((mode==4) || (mode==5)) {
      ans= elmo_query(sl, "PA=DV[3]")
      if (ans==-1) return (-1)
      if (!ELMO_PAR[mne]["config"]) {
        read_motors(0x6)
      }
    }
  } 
  ans= elmo_query(sl, "UM")
  if (sscanf(ans, "%d", mode) != 1) {
    print ELMO_ERR "unable to get user_mode"
    return (-1)
  }
  ELMO_PAR[mne]["mode"]= mode
  return mode
 }

 if (key == "enc_position") {
  if (todo == "set") {
    print ELMO_ERR "enc_position is read-only parameter"
  }
  rot= motor_par(num, "rotary")
  cmd= rot ? "PX" : "PY"
  ans= elmo_query(sl, cmd)
  if (sscanf(ans, "%d", sta) != 1) {
    print ELMO_ERR "unable to get enc_position"
    return (-1)
  }
  return sta
 }

 if (key == "jog_speed") {
  if (todo == "set") {
   if (p1 != 0) {
    if (ELMO_PAR[mne]["jog"] != 0) {
     print ELMO_ERR "jog motion already started"
    } else {
     val= motor_par(num, "sign") * p1 * motor_par(num, "step_size")
     if (elmo_query(sl, sprintf("JV=%d", val))!=-1) {
      ELMO_PAR[mne]["jog"]= p1
      elmo__debug "start jog speed at " p1 " deg/sec [" val " steps/sec]"
      elmo_query(sl, "BG")
     }
    }
   } else {
    # --- stop jog and wait deceleration
    elmo_query(sl, "JV=0")
    elmo_query(sl, "BG")
    val= (ELMO_PAR[mne]["jog"]*motor_par(num, "step_size")) / \
         motor_par(num, "velocity") * motor_par(num, "acceleration")
    ELMO_PAR[mne]["jog"]= 0
    elmo__debug "waiting deceleration " val " ms"
    sleep(val/1000.)

    val= elmo_query(sl, "MF")
    if (val>0) {
     print ELMO_ERR "motor failure on last jog command"
    } 
   }
  } else {
   return ELMO_PAR[mne]["jog"]
  }
 }
 if (key == "jog_range") {
  if (todo == "get") {
   local val1 val2
   val1= elmo_query(sl, "VL[2]") / motor_par(num, "step_size")
   val2= elmo_query(sl, "VH[2]") / motor_par(num, "step_size")
   return sprintf("%g:%g [deg/sec]", val1, val2)
  } else {
   print ELMO_ERR "jog_range is read-only"
  }
 }
}'




#%IU%
#%MDESC%
# MACRO MOTOR: 
# Called by spec on motor operation.
# 
def elmo_cmd(num,key,p1,p2) '{
 local mne
 local sl
 local ans

 if(num == "..") { return }

 # get the motor mnemonic string
 mne=motor_mne(num)

 # Get the communication link
 sl=motor_par(num,"address")

 #
 # return the current motor position in mm or deg
 #
 if (key == "position") {
  local sta pos pos_cmd pos_str

  pos_cmd= motor_par(num, "rotary") ? "PX" : "PY"

  if (ELMO_PAR[mne]["mode"] == 2) {
    # --- in velocity mode, always return encoder position
    pos= elmo_query(sl, pos_cmd)
  } else {
    # --- in position mode, returns:
    # encoder position if moving
    # command position if motion finished
    ans = elmo_query(sl,"MS")
    if(sscanf(ans,"%d",sta) != 1) {
      print ELMO_ERR "unable to get status"
      return(0)
    }
    if (sta == 2) {
      pos = elmo_query(sl, pos_cmd)
    } else {
      # workaround for controller bug: when a limitswitch is hitten
      # some times the PA position is wrong, only the encoder one
      # can be trusted
      #
      # workaround for controller bug: on a ST command the PA remains
      # to the target value and therefore is no more synchronized 
      # with the PX encoder value. Force PA to the PX value.
      #
      if(ELMO_PAR[mne]["abort_one"]) {

        # give some time for the encoder stabilization
        sleep(0.1)

        # get encoder position
        pos = elmo_query(sl, pos_cmd)
        elmo_query(sl,sprintf("PA=%d",pos))

        # can not use SR bit28 which vanished
        ELMO_PAR[mne]["abort_one"] = 0

      } else {
        # return target value (to avoid rounding effects for next motions??)
        pos = elmo_query(sl, "PA")
      }
    }
  }

  pos_str = sprintf("%.15g", pos / motor_par(num,"step_size"))
  return(pos_str)
 }


 #
 # start a motion (p1==abs pos, p2==rel pos, with pos in mm or deg)
 #
 if (key == "start_one") {
  local pos sta

  if (ELMO_PAR[mne]["mode"] == 2) {
    print ELMO_ERR "no motion allowed in jog mode"
    return ".error."
  }

  # check first that the controller is ready to move
  # bit0 of Status Register (page 3.135)
  ans = elmo_query(sl,"SR")
  if(sscanf(ans,"%d",sta) != 1) {
   print ELMO_ERR "unable to get status"
   return(-1)
  }
  if(sta&1) {
   print ELMO_ERR "problem into the drive"
   return ".error."
  }
  if(!(sta&(1<<4))) {
   print ELMO_ERR "closed loop open"
   return ".error."
  }

  # launch the absolute motion
  pos=elmo_round(p1*motor_par(num,"step_size"))
  elmo_query(sl,sprintf("PA=%d",pos))
  elmo_query(sl,"BG")

  return
 }

 #
 # return the current motor status
 #
 if (key == "get_status") {
  local ret
  local sta
  local _ms _ls _lh 

  if (ELMO_PAR[mne]["mode"] == 2) {
    return 0
  }

  ret = 0
  ans = elmo_query(sl,"SR")
  if(sscanf(ans,"%d",sta) != 1) {
   print ELMO_ERR "unable to get status. Will try again ..."
   sleep(MOTORSPOLLTIME)
   ans = elmo_query(sl, "SR")
   if (sscanf(ans, "%d", sta) != 1) {
     print ELMO_ERR "failed to get status"
     return(0)
   }
  }

  # motor is moving
  if (ELMO_PAR[mne]["homing"]) {
    _ms= (sta >> 11) & 0x1
    if (_ms == 0) {
      ELMO_PAR[mne]["homing"]= 0
      ans= elmo_query(sl, "PA=DV[3]")
    }
  } else {
    _ms= (sta >> 14) & 0x3
  }
  if (_ms != 0) {
   ret = 0x02
  }

  if(ELMO_DEBUG) {
    printf("SR: 0x%08x\n",elmo_query(sl, "SR"))
    printf("IP: 0x%08x\n",elmo_query(sl, "IP"))
  }

  # limit switch active
  _ls= (sta >> 28) & 0x1
  if (_ls) {
   _lh= elmo_query(sl, "IP")
   _lh= (_lh >> 10) & 0x3
   if (_lh&0x1) {
    ret = 0x08
   } else if (_lh&0x2) {
    ret = 0x04
   }
   # the controller has already stopped the motion
   elmo_cmd(num, "abort_one")
  }

  elmo__debug "get_status " ret
  return(ret)
 }



 #
 # stop a single motor
 #
 if (key == "abort_one") {
  local nst sta pos rot cmd

  elmo_query(sl,"ST")
  ELMO_PAR[mne]["abort_one"]=1

  # workaround for controller bug: a ST command while within
  # an homing sequence lets the MS bit sets but still saying
  # that the closed loop is on (MO==1). Unique solution found,
  # cycle the closed loop to cleanup MS bits
  if (ELMO_PAR[mne]["homing"]) {
    elmo_par(num, "power", "set", 0)
    sleep(0.1)
    elmo_par(num, "power", "set", 1)
    ELMO_PAR[mne]["homing"] = 0
  }
  sta= 1
  nst= 0
  while (sta && (nst<10)) {
    sleep(MOTORSPOLLTIME)
    ans= elmo_query(sl, "MS")
    if (sscanf(ans, "%d", sta) != 1) {
      print ELMO_ERR "unable to get status on abort"
      return 0
    }
    nst+=1
  }

  return
 }



 #
 # set position (p1=mm)
 #
 if (key == "set_position") {
  local pos rot cmd

  rot= motor_par(num, "rotary")
  cmd= rot ? "PX" : "PY"

  # The encoder can only be set if the motor is off
  elmo_par(num,"closed_loop","set",0)
  pos=elmo_round(p1*motor_par(num,"step_size"))
  elmo_query(sl,sprintf("%s=%d",cmd,pos))
  elmo_par(num,"closed_loop","set",1)
  elmo_query(sl,sprintf("PA=%d",pos))

  return
 }

 #
 # set acceleration (p1=ms p2=steps/sec^2)
 #
 if (key == "acceleration") {
  elmo_query(sl,sprintf("AC=%d",p2))
  elmo_query(sl,sprintf("DC=%d",p2))
  return
 }

 #
 # set base_rate (p1=rate in Hz)
 #
 if (key == "base_rate") {
  return
 }

 #
 # set slew_rate (p1=rate in Hz)
 #
 if (key == "slew_rate") {
  elmo_query(sl,sprintf("SP=%d",p1))
  return
 }

 #
 # home search
 #
 if (key == "search") {

  if (substr(p1,1,4) == "home") {
    local pos dir ncmd cmds[] vals[]

    if (p1=="home-") {
       dir= -1
    } else {
       dir= 1
    }
    pos= motor_par(num, "home_position")
    pos= elmo_round(pos*motor_par(num, "sign")*motor_par(num, "step_size"))

    ncmd= 0
    cmds[ncmd++]= "HM[3]=3"
    cmds[ncmd++]= sprintf("HM[2]=%d", pos)
    cmds[ncmd++]= "HM[4]=0"
    cmds[ncmd++]= "HM[5]=0"
    cmds[ncmd++]= "HM[1]=1"

    if (elmo_multiquery(sl, ncmd, cmds, vals)==-1) {
      print ELMO_ERR "failed to setup HOME search"
      return ".error."
    }
    ncmd= 0
    cmds[ncmd++]= sprintf("JV=%d", dir*motor_par(num, "sign")*motor_par(num, "velocity"))
    cmds[ncmd++]= "BG"
    if (elmo_multiquery(sl, ncmd, cmds, vals)==-1) {
      print ELMO_ERR "failed to start HOME search"
      return ".error."
    }
    ELMO_PAR[mne]["homing"]= 1
  } else {
    print ELMO_ERR "Only home search implemented"
    return ".error."
  }
 }
}'


#%IU%(serialline, command)
#%MDESC%
# Send the command to the ELMO controller and returns its string
# answer if any or empty string if none. 
# If case of error, returns -1.
#
def elmo_query(sl,cmd) '{
  local cmd_send ans ret ntry

  elmo__debug "sending " cmd

  cmd_send= cmd "\r"

  ntry= 0
  ret= -1
 
  while ((ret == -1) && (ntry < 3)) { 
      ntry+=1

      # Yes, a little bit paranoid
      ser_par(sl, "flush")

      # Chaud devant
      ser_put(sl, cmd_send)

      # The controller always echo the command received
      ans = ser_get(sl,";")
      ret= elmo_parseanswer(cmd, ans)

      if (ret == -1) {
          if (ntry < 3) {
              elmo__debug "query failed, retry #" ntry
          } else {
              elmo__debug "query still failed, give up."
          }
      }
  }
 
  elmo__debug "returning " ret
  return(ret)
}'

#%IU%(command, answer)
#%MDESC%
# Parse controller serial response. Check command echo, error character,
# and return answer or (-1) on error
#
def elmo_parseanswer(cmd, ans) '{
  local cmd_term cmd_echo ret

  if (ans == "") {
    print ELMO_ERR "missing command echo on", cmd
    return (-1)
  }

  cmd_term= index(ans, "\r")
  cmd_echo= substr(ans, 1, cmd_term-1)
  if (cmd_echo != cmd) {
    print ELMO_ERR "wrong command echo on", cmd
    return (-1)
  }

  ret= substr(ans, cmd_term+1, length(ans)-cmd_term-1)
  if (substr(ret, length(ret), 1) == "?") {
    print ELMO_ERR "when sending", cmd
    elmo_printerr(substr(ret, length(ret)-1, 1))
    return (-1)
  }
  if (!length(ret)) {
    ret= 0
  }

  return (ret)
}'

#%IU%
#%MDESC%
# Send multiple commands to controller (ncmd commands defined in ass array cmds[]).
# Parse answer of each commands and return answer or (-1) on errors in ass array vals[]
# Global return is 0 if sucess, -1 if one of the commands fails.
#
def elmo_multiquery(sl, ncmd, cmds, vals) '{
  local cmd_send icmd ans ret

  cmd_send= ""
  for (icmd=0; icmd<ncmd; icmd++) {
    cmd_send= cmd_send sprintf("%s\r", cmds[icmd])
    elmo__debug "sending " cmds[icmd]
  }
  
  ser_par(sl, "flush")
  ser_put(sl, cmd_send)

  ret= 0

  for (icmd=0; icmd<ncmd; icmd++) {
    ans= ser_get(sl, ";")
    vals[icmd]= elmo_parseanswer(cmds[icmd], ans)
    elmo__debug cmds[icmd] " returns " vals[icmd]
    if (vals[icmd]==-1) {
      ret= -1
    }
  }

  return (ret)
}'




#%IU%(err)
#%MDESC%
# Print a string message corresponding to the binary error code given.
# See "Command Reference Manual" page 3-32 for detailed list.
#
def elmo_printerr(err_ch) '{
 local err

 err=asc(err_ch)

 if(err==2) {
  print ELMO_CTLERR "bad command"
 } else if(err==12) {
  print ELMO_CTLERR "command not available in this unit mode"
 } else if(err==18) {
  print ELMO_CTLERR "empty assign"
 } else if(err==21) {
  print ELMO_CTLERR "operand out of range"
 } else if(err==25) {
  print ELMO_CTLERR "command not valid while moving"
 } else if(err==28) {
  print ELMO_CTLERR "out of limit range"
 } else if(err==57) {
  print ELMO_CTLERR "motor must be off"
 } else if(err==58) {
  print ELMO_CTLERR "motor must be on"
 } else if(err==66) {
  print ELMO_CTLERR "drive not ready"
 } else if(err==84) {
  print ELMO_CTLERR "the system is not in point to point mode"
 } else if(err==148) {
  print ELMO_CTLERR "nothing in the expression"
 } else if(err==168) {
  print ELMO_CTLERR "speed to large to start the motor"
 } else {
  print ELMO_CTLERR "unknown error (check MAN-SIMCR.pdf page 3-32): "err
 }

}'


#%UU% <motor> <user_position>
#%MDESC%
# Search home switch in positive direction, 
# sets the position to 0 and then move motor to user_position
#
def elmohome '{
  if ($#!=2) {
    print "Usage: $0 <motor> <user_position>"
    exit
  }
  if (motor_par($1, "device_id")!="elmo") {
    print $1 " is not controlled by elmo !!"
    exit
  }

  print "Starting homing procedure"
  chg_dial($1, "home+")
  move_poll

  print "Moving to user position"
  umv $1 $2
}'


#%IU%(x)
#%MDESC%
# Return a well rounded integer as opposed to int() which always
# round down
#
def elmo_round(x) '{ if (x>=0) return int(x+0.5); else return int(x-0.5) }'



#%MACROS%
#%IMACROS%
#%AUTHOR% MP BLISS (Original 8/2011).
# %BR%$Revision: 1.13 $ / $Date: 2017/06/13 15:03:55 $
#%TOC%