File: sim_lib.tcl

package info (click to toggle)
linuxcnc 1%3A2.9.4-2
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 282,780 kB
  • sloc: python: 201,110; ansic: 106,370; cpp: 99,219; tcl: 16,054; xml: 10,617; sh: 10,258; makefile: 1,251; javascript: 138; sql: 72; asm: 15
file content (498 lines) | stat: -rw-r--r-- 16,266 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
# 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 ![info exists ::JOINT_[set jno](TYPE)] {
      # 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