499 lines
16 KiB
Tcl
Executable File
499 lines
16 KiB
Tcl
Executable File
# sim_lib.tcl: haltcl procs for sim configurations
|
|
|
|
#----------------------------------------------------------------------
|
|
# Notes (Joints-Axes):
|
|
# 1) if ::KINS(KINEMATICS) exists:
|
|
# loadrt the kins using any included parameters
|
|
# example: for inifile item:
|
|
# [KINS]KINEMATICS = trivkins coordinates=XZ kinstype=BOTH
|
|
# use:
|
|
# loadrt trivkins coordinates=xz kinstype=BOTH
|
|
# else:
|
|
# loadrt trivkins
|
|
#
|
|
# 2) NB: If $::KINS(KINEMATICS) specifies coordinates=, the
|
|
# coordinates must agree with ::TRAJ(COORDINATES)
|
|
#
|
|
# 3) if known kins (trivkins) and xyz, make hypotenuse velocity
|
|
# pins for xy,xyz
|
|
#----------------------------------------------------------------------
|
|
|
|
proc indices_for_trivkins {axes} {
|
|
# ref: src/emc/kinematics/trivkins.c
|
|
if {"$axes" == ""} {set axes {x y z a b c u v w}}
|
|
set i 0
|
|
foreach a [string tolower $axes] {
|
|
# assign to consecutive joints:
|
|
set ::SIM_LIB(jointidx,$a) $i
|
|
incr i
|
|
}
|
|
} ;# indices_for_trivkins
|
|
|
|
proc get_traj_coordinates {} {
|
|
# initraj.cc: coordinates may be with or without spaces X Z or XZ
|
|
# convert either form to list like {x z}
|
|
set coordinates [lindex $::TRAJ(COORDINATES) 0]
|
|
set coordinates [string map {" " "" "\t" ""} $coordinates]
|
|
set coordinates [split $coordinates ""]
|
|
return [string tolower $coordinates]
|
|
} ;# get_traj_coordinates
|
|
|
|
proc check_ini_items {} {
|
|
foreach {section item} {KINS KINEMATICS
|
|
KINS JOINTS
|
|
TRAJ COORDINATES
|
|
} {
|
|
if ![info exists ::${section}($item)] {
|
|
return -code error "Missing inifile item: \[$section\]$item"
|
|
}
|
|
}
|
|
|
|
if { [info exists ::DISPLAY(LATHE)]
|
|
&& [lsearch $::KINS(KINEMATICS) trivkins] >= 0
|
|
} {
|
|
# reject historical lathe config using default trivkins coordinates (all)
|
|
if {[string first "=" $::KINS(KINEMATICS)] < 0} {
|
|
set msg "trivkins lathe config must specify coordinates= "
|
|
set msg "$msg\n(typically use \[KINS]KINEMATICS trivkins coordinates=XZ)"
|
|
return -code error "$msg"
|
|
}
|
|
}
|
|
|
|
set n_extrajoints 0
|
|
if [info exists ::EMCMOT(EMCMOT)] {
|
|
set mot [split [lindex $::EMCMOT(EMCMOT) 0]]
|
|
if {[string first motmod $mot] >= 0} {
|
|
foreach item $mot {
|
|
if {[string first num_extrajoints= $item] < 0} continue
|
|
set n_extrajoints [lindex [split $item =] end]
|
|
} ;# foreach
|
|
}
|
|
}
|
|
|
|
set kins [split [lindex $::KINS(KINEMATICS) 0]]
|
|
if {[string first trivkins $kins] >= 0} {
|
|
foreach item $kins {
|
|
if {[string first coordinates= $item] < 0} continue
|
|
set tcoords [lindex [split $item =] end]
|
|
set len_tcoords [string len $tcoords]
|
|
set expected_joints [expr $len_tcoords + $n_extrajoints]
|
|
if {$expected_joints != $::KINS(JOINTS)} {
|
|
set m "\ncheck_ini_items: WARNING:\n"
|
|
set m "$m trivkins coordinates=$tcoords specifies $len_tcoords joints\n"
|
|
set m "$m trivkins extrajoints=$n_extrajoints\n"
|
|
set m "$m trivkins totaljoints=$expected_joints\n"
|
|
set m "$m !!! but \[KINS\]JOINTS=$::KINS(JOINTS)\n"
|
|
puts stderr $m
|
|
}
|
|
} ;# foreach
|
|
}
|
|
return
|
|
} ;# check_ini_items
|
|
|
|
proc setup_kins {axes} {
|
|
if ![info exists ::KINS(KINEMATICS)] {
|
|
puts stderr "setup_kins: NO \[KINS\]KINEMATICS, trying default trivkins"
|
|
loadrt trivkins
|
|
return
|
|
}
|
|
set kins_kinematics [lindex $::KINS(KINEMATICS) end]
|
|
set cmd "loadrt $kins_kinematics" ;# may include parms
|
|
set kins_module [lindex $kins_kinematics 0]
|
|
|
|
puts stderr "setup_kins: cmd=$cmd"
|
|
if [catch {eval $cmd} msg] {
|
|
puts stderr "\nmsg=$msg\n"
|
|
}
|
|
|
|
# set up axis indices for known kins
|
|
switch $kins_module {
|
|
trivkins {indices_for_trivkins $axes}
|
|
default {
|
|
puts stderr "setup_kins: unknown \[KINS\]KINEMATICS=<$::KINS(KINEMATICS)>"
|
|
}
|
|
}
|
|
} ;# setup_kins
|
|
|
|
proc core_sim {axes
|
|
number_of_joints
|
|
servo_period
|
|
{base_period 0}
|
|
{emcmot motmod}
|
|
} {
|
|
# adapted as haltcl proc from core_sim.hal
|
|
# note: with default emcmot==motmot,
|
|
# thread will not be added for (default) base_pariod == 0
|
|
|
|
setup_kins $axes
|
|
|
|
if {"$emcmot" == "motmod"} {
|
|
if [catch {loadrt $emcmot \
|
|
base_period_nsec=$base_period \
|
|
servo_period_nsec=$servo_period \
|
|
num_joints=$number_of_joints} msg
|
|
] {
|
|
# typ: too many joints attempted
|
|
puts stderr "\n"
|
|
puts stderr "core_sim: loadrt $emcmot FAIL"
|
|
puts stderr " msg=$msg\n"
|
|
exit 1
|
|
}
|
|
} else {
|
|
# known special case with additional parameter:
|
|
# unlock_joint_mask=0xNN
|
|
# num_extrajoints=n
|
|
set module [split [lindex $emcmot 0]]
|
|
set modname [lindex $module 0]
|
|
set modparm [lreplace $module 0 0]
|
|
if [catch {eval loadrt $modname $modparm \
|
|
base_period_nsec=$base_period \
|
|
servo_period_nsec=$servo_period \
|
|
num_joints=$number_of_joints} msg
|
|
] {
|
|
puts stderr "\n"
|
|
puts stderr "core_sim:unhandled emcmot<$emcmot>"
|
|
puts stderr " modname=$modname"
|
|
puts stderr " modparm=$modparm"
|
|
puts stderr " msg=$msg\n"
|
|
exit 1
|
|
}
|
|
}
|
|
|
|
addf motion-command-handler servo-thread
|
|
addf motion-controller servo-thread
|
|
|
|
set pid_names ""
|
|
set mux_names ""
|
|
for {set jno 0} {$jno < $number_of_joints} {incr jno} {
|
|
set pid_names "${pid_names},J${jno}_pid"
|
|
set mux_names "${mux_names},J${jno}_mux"
|
|
}
|
|
set pid_names [string trimleft $pid_names ,]
|
|
set mux_names [string trimleft $mux_names ,]
|
|
loadrt pid names=$pid_names
|
|
loadrt mux2 names=$mux_names
|
|
|
|
# pid components
|
|
# The pid comp is used as a pass-thru device (FF0=1,all other gains=0)
|
|
# to emulate the connectivity of a servo system
|
|
# (e.g., no short-circuit connection of motor-pos-cmd to motor-pos-fb)
|
|
foreach cname [split $pid_names ,] {
|
|
addf ${cname}.do-pid-calcs servo-thread
|
|
# FF0 == 1 for pass-thru, all others 0
|
|
do_setp ${cname}.FF0 1.0
|
|
foreach pin {Pgain Dgain Igain FF1 FF2} { do_setp ${cname}.$pin 0 }
|
|
}
|
|
|
|
# mux components
|
|
# The mux comp is used as a sample-hold to simulate a machine
|
|
# with encoders that measure output position when power
|
|
# is not applied to the motors or controllers
|
|
foreach cname [split $mux_names ,] {
|
|
addf $cname servo-thread
|
|
}
|
|
|
|
# signal connections:
|
|
net estop:loop <= iocontrol.0.user-enable-out
|
|
net estop:loop => iocontrol.0.emc-enable-in
|
|
|
|
net tool:prep-loop <= iocontrol.0.tool-prepare
|
|
net tool:prep-loop => iocontrol.0.tool-prepared
|
|
|
|
net tool:change-loop <= iocontrol.0.tool-change
|
|
net tool:change-loop => iocontrol.0.tool-changed
|
|
|
|
net sample:enable <= motion.motion-enabled
|
|
|
|
for {set jno 0} {$jno < $number_of_joints} {incr jno} {
|
|
net sample:enable => J${jno}_mux.sel
|
|
|
|
net J${jno}:enable <= joint.$jno.amp-enable-out
|
|
net J${jno}:enable => J${jno}_pid.enable
|
|
|
|
net J${jno}:pos-cmd <= joint.$jno.motor-pos-cmd
|
|
net J${jno}:pos-cmd => J${jno}_pid.command
|
|
|
|
net J${jno}:on-pos <= J${jno}_pid.output
|
|
net J${jno}:on-pos => J${jno}_mux.in1 ;# pass thru when motion-enabled
|
|
|
|
net J${jno}:pos-fb <= J${jno}_mux.out
|
|
net J${jno}:pos-fb => J${jno}_mux.in0 ;# hold position when !motion-enabled
|
|
net J${jno}:pos-fb => joint.$jno.motor-pos-fb
|
|
}
|
|
} ;# core_sim
|
|
|
|
proc make_ddts {number_of_joints} {
|
|
# make vel,accel ddts and signals for all joints
|
|
# if xyz, make hypotenuse xy,xyz vels
|
|
|
|
set ddt_names ""
|
|
set ddt_ct 0
|
|
for {set jno 0} {$jno < $number_of_joints} {incr jno} {
|
|
incr ddt_ct 2
|
|
set ddt_names "${ddt_names},J${jno}_vel,J${jno}_accel"
|
|
}
|
|
set ddt_names [string trimleft $ddt_names ,]
|
|
loadrt ddt names=$ddt_names
|
|
foreach cname [split $ddt_names ,] {
|
|
addf $cname servo-thread
|
|
}
|
|
|
|
# joint vel,accel signal connections:
|
|
set ddt_ct 0
|
|
for {set jno 0} {$jno < $number_of_joints} {incr jno} {
|
|
incr ddt_ct 2
|
|
net J${jno}:pos-fb => J${jno}_vel.in ;# net presumed to exist
|
|
net J${jno}:vel <= J${jno}_vel.out
|
|
net J${jno}:vel => J${jno}_accel.in
|
|
net J${jno}:acc <= J${jno}_accel.out
|
|
}
|
|
|
|
set has_xyz 1
|
|
foreach letter {x y z} {
|
|
if ![info exists ::SIM_LIB(jointidx,$letter)] {
|
|
set has_xyz 0
|
|
break
|
|
}
|
|
}
|
|
if $has_xyz {
|
|
loadrt hypot names=hyp_xy,hyp_xyz ;# vector velocities
|
|
addf hyp_xy servo-thread
|
|
addf hyp_xyz servo-thread
|
|
net J$::SIM_LIB(jointidx,x):vel <= J$::SIM_LIB(jointidx,x)_vel.out
|
|
net J$::SIM_LIB(jointidx,x):vel => hyp_xy.in0
|
|
net J$::SIM_LIB(jointidx,x):vel => hyp_xyz.in0
|
|
|
|
net J$::SIM_LIB(jointidx,y):vel <= J$::SIM_LIB(jointidx,y)_vel.out
|
|
net J$::SIM_LIB(jointidx,y):vel => hyp_xy.in1
|
|
net J$::SIM_LIB(jointidx,y):vel => hyp_xyz.in1
|
|
|
|
net J$::SIM_LIB(jointidx,z):vel <= J$::SIM_LIB(jointidx,z)_vel.out
|
|
net J$::SIM_LIB(jointidx,z):vel => hyp_xyz.in2
|
|
|
|
net xy:vel => hyp_xy.out
|
|
net xyz:vel <= hyp_xyz.out
|
|
}
|
|
} ;# make_ddts
|
|
|
|
proc use_hal_manualtoolchange {} {
|
|
# adapted as haltcl proc from axis_manualtoolchange.hal
|
|
loadusr -W hal_manualtoolchange
|
|
|
|
# disconnect if previously connected:
|
|
unlinkp iocontrol.0.tool-change
|
|
unlinkp iocontrol.0.tool-changed
|
|
# remove signal with no connections:
|
|
delsig tool:change-loop
|
|
|
|
net tool:change <= iocontrol.0.tool-change
|
|
net tool:change => hal_manualtoolchange.change
|
|
|
|
net tool:changed <= hal_manualtoolchange.changed
|
|
net tool:changed => iocontrol.0.tool-changed
|
|
|
|
net tool:prep-number <= hal_manualtoolchange.number
|
|
net tool:prep-number => iocontrol.0.tool-prep-number
|
|
} ;# use_hal_manualtoolchange
|
|
|
|
proc simulated_home {number_of_joints} {
|
|
# uses sim_home_switch component
|
|
set switch_names ""
|
|
for {set jno 0} {$jno < $number_of_joints} {incr jno} {
|
|
set switch_names "${switch_names},J${jno}_switch"
|
|
}
|
|
set switch_names [string trimleft $switch_names ,]
|
|
loadrt sim_home_switch names=$switch_names
|
|
foreach cname [split $switch_names ,] {
|
|
addf $cname servo-thread
|
|
}
|
|
|
|
for {set jno 0} {$jno < $number_of_joints} {incr jno} {
|
|
# add pin to pre-existing signal:
|
|
net J${jno}:pos-fb => J${jno}_switch.cur-pos
|
|
|
|
net J${jno}:homesw <= J${jno}_switch.home-sw
|
|
net J${jno}:homesw => joint.$jno.home-sw-in
|
|
|
|
# set sim_home_switch .hysteresis,.home-pos pins
|
|
# according to traj units and joint type
|
|
if ] {
|
|
# use component defaults
|
|
} else {
|
|
if {"[set ::JOINT_[set jno](TYPE)]" == "ANGULAR"} {
|
|
# use component defaults
|
|
} else {
|
|
if ![info exists ::TRAJ(LINEAR_UNITS)] {
|
|
# use component defaults
|
|
} else {
|
|
switch $::TRAJ(LINEAR_UNITS) {
|
|
in - inch - imperial {
|
|
do_setp J${jno}_switch.hysteresis 0.05
|
|
do_setp J${jno}_switch.home-pos 0.10
|
|
}
|
|
default { # use component default }
|
|
}
|
|
}
|
|
if { [info exists ::JOINT_[set jno](HOME_SEARCH_VEL)]
|
|
&& [set ::JOINT_[set jno](HOME_SEARCH_VEL)] < 0} {
|
|
do_setp J${jno}_switch.home-pos -[getp J${jno}_switch.home-pos]
|
|
}
|
|
}
|
|
} ;# type
|
|
if [info exists ::JOINT_[set jno](HOME_USE_INDEX)] {
|
|
if [set ::JOINT_[set jno](HOME_USE_INDEX)] {
|
|
# Note: use default for joint.${jno}.index-delay-ms
|
|
net J${jno}:index-enable <= joint.${jno}.index-enable
|
|
net J${jno}:index-enable => J${jno}_switch.index-enable
|
|
}
|
|
}
|
|
} ;# for
|
|
} ;# simulated_home
|
|
|
|
proc sim_spindle {} {
|
|
# adapted as haltcl proc from sim_spindle_encoder.hal
|
|
# simulated spindle encoder (for spindle-synced moves)
|
|
loadrt sim_spindle names=sim_spindle
|
|
do_setp sim_spindle.scale 0.01666667
|
|
|
|
loadrt limit2 names=limit_speed
|
|
loadrt lowpass names=spindle_mass
|
|
loadrt near names=near_speed
|
|
loadrt scale names=rpm_rps
|
|
|
|
setp rpm_rps.gain .0167
|
|
|
|
# this limit doesn't make any sense to me:
|
|
do_setp limit_speed.maxv 5000.0 ;# rpm/second
|
|
|
|
# encoder reset control
|
|
# hook up motion controller's sync output
|
|
net spindle-index-enable <=> spindle.0.index-enable
|
|
net spindle-index-enable <=> sim_spindle.index-enable
|
|
|
|
# report our revolution count to the motion controller
|
|
net spindle-pos <= sim_spindle.position-fb
|
|
net spindle-pos => spindle.0.revs
|
|
|
|
# simulate spindle mass
|
|
do_setp spindle_mass.gain .07
|
|
|
|
# spindle speed control
|
|
net spindle-speed-cmd <= spindle.0.speed-out
|
|
net spindle-speed-cmd => limit_speed.in
|
|
net spindle-speed-cmd => near_speed.in1
|
|
|
|
net spindle-speed-limited <= limit_speed.out
|
|
net spindle-speed-limited => sim_spindle.velocity-cmd
|
|
net spindle-speed-limited => spindle_mass.in
|
|
|
|
# for spindle velocity estimate
|
|
net spindle-rpm-filtered <= spindle_mass.out
|
|
net spindle-rpm-filtered rpm_rps.in
|
|
net spindle-rps-filtered rpm_rps.out spindle.0.speed-in
|
|
net spindle-rpm-filtered => near_speed.in2
|
|
|
|
# at-speed detection
|
|
do_setp near_speed.scale 1.1
|
|
do_setp near_speed.difference 10
|
|
|
|
net spindle-at-speed <= near_speed.out
|
|
net spindle-at-speed => spindle.0.at-speed
|
|
|
|
net spindle-orient <= spindle.0.orient
|
|
net spindle-orient <= spindle.0.is-oriented
|
|
|
|
addf limit_speed servo-thread
|
|
addf spindle_mass servo-thread
|
|
addf rpm_rps servo-thread
|
|
addf near_speed servo-thread
|
|
addf sim_spindle servo-thread
|
|
} ;# sim_spindle
|
|
|
|
proc save_hal_cmds {savefilename {options ""} } {
|
|
set tmpfile /tmp/save_hal_cmds_tmp
|
|
set date [clock format [clock seconds]]
|
|
set script [info script]
|
|
set save_arg all ;# suffices if only basic_sim in use
|
|
if {[llength $::HAL(HALFILE)] > 1} {
|
|
set save_arg allu ;# do *all* unconnected pins including
|
|
;# pins from other HALFILEs
|
|
}
|
|
set fd [open $savefilename w] ;# overwrite any existing file
|
|
puts $fd "# $date
|
|
#
|
|
# This file: $savefilename
|
|
# Created by: $script
|
|
# With options: $::argv
|
|
# From inifile: $::env(INI_FILE_NAME)
|
|
# Halfiles: $::HAL(HALFILE)
|
|
#
|
|
# This file contains the hal commands produced by [file tail $script]
|
|
# (and any hal commands executed prior to its execution).
|
|
# ------------------------------------------------------------------
|
|
# To use $savefilename in the original inifile (or a copy of it),
|
|
# edit to change:
|
|
# \[HAL\]
|
|
# HALFILE = LIB:basic_sim.tcl parameters
|
|
# to:
|
|
# \[HAL\]
|
|
# HALFILE = $savefilename
|
|
#
|
|
# Notes:
|
|
# 1) Inifile Variables substitutions specified in the inifile
|
|
# and interpreted by halcmd are automatically substituted
|
|
# in the created halfile ($savefilename).
|
|
# 2) Input pins connected to a signal with no writer are
|
|
# not included in the setp listings herein so must be added
|
|
# manually
|
|
#
|
|
"
|
|
if {[lsearch $options use_hal_manualtoolchange] >= 0} {
|
|
puts $fd "# user space components"
|
|
puts $fd "loadusr -W hal_manualtoolchange"
|
|
puts $fd ""
|
|
}
|
|
|
|
hal save $save_arg $tmpfile
|
|
|
|
set ftmp [open $tmpfile r]
|
|
set gave_msg 0
|
|
set setp_fmt "%-3s %-30s %s"
|
|
while {![eof $ftmp]} {
|
|
gets $ftmp line
|
|
if {([string first "unconnected pin values" $line] >0) && !$gave_msg} {
|
|
set gave_msg 1
|
|
puts $fd "# Note: ALL unconnected pins follow"
|
|
puts $fd "# (includes pins using default with no explicit setp command)"
|
|
} else {
|
|
scan $line "%s %s %s" cmd arg1 remainder
|
|
switch $cmd {
|
|
setp {puts $fd [format $setp_fmt $cmd $arg1 $remainder]}
|
|
loadrt {
|
|
if { [string first tpmod [list $line]] >= 0
|
|
|| [string first homemod [list $line]] >= 0
|
|
} {
|
|
puts $fd "#preloaded module: $line"
|
|
} else {
|
|
puts $fd $line
|
|
}
|
|
}
|
|
default {puts $fd $line}
|
|
}
|
|
}
|
|
} ;# while
|
|
close $ftmp
|
|
file delete $tmpfile
|
|
if {("$save_arg" != "allu") && [info exists ::SIM_LIB(setp_list)]} {
|
|
puts $fd "# setp commands for unconnected input pins"
|
|
foreach {pname value} $::SIM_LIB(setp_list) {
|
|
puts $fd [format $setp_fmt setp $pname $value]
|
|
}
|
|
}
|
|
close $fd
|
|
} ;# save_hal_cmds
|
|
|
|
proc do_setp {pname value} {
|
|
setp $pname $value
|
|
lappend ::SIM_LIB(setp_list) $pname $value
|
|
} ;# do_setp
|