chore: finalize remaining project artifacts

This commit is contained in:
wangdequan
2026-07-05 22:13:40 -04:00
parent 4224f835dc
commit 6b937a038d
30 changed files with 14093 additions and 46 deletions

View File

@@ -0,0 +1,882 @@
# 5axis-xyzbc-trt-sim 执行过程分析
本文分析当前 LinuxCNC 源码树中 `5axis-xyzbc-trt-sim` 对应的仿真配置。实际入口配置文件为:
```text
configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini
```
该配置是一个 `axis` GUI + `basic_sim.tcl` 仿真 HAL + `xyzbc-trt-kins` 可切换运动学 + PyVCP/halui 控制面板 + Vismach 三维模型的五轴转台仿真。
## 1. 启动入口
如果从项目中已有快捷方式启动,入口文件是:
```text
linuxcnc-rtcp-5axis-shortcuts/table-rotary-tilting/xyzbc-trt.desktop
```
其中的执行命令为:
```bash
/home/mes123456/linuxcnc-master/scripts/rip-environment linuxcnc /home/mes123456/linuxcnc-master/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini
```
`rip-environment` 负责设置 RIP 开发环境,然后调用 `linuxcnc` 脚本并传入 `xyzbc-trt.ini`
`scripts/linuxcnc` 会读取 INI 中的关键段:
- `[TASK] TASK = milltask`
- `[HAL] HALUI = halui`
- `[HAL] HALFILE = LIB:basic_sim.tcl`
- `[DISPLAY] DISPLAY = axis`
- `[KINS] KINEMATICS = xyzbc-trt-kins sparm=identityfirst`
- `[TRAJ] COORDINATES = XYZBC`
总体启动顺序如下:
1. 启动 `linuxcncsvr`,创建和持有 NML 通道。
2. 启动 realtime/HAL。
3. 加载 `tpmod``homemod`
4. 启动 `[TASK] TASK = milltask`
5. 启动 `[HAL] HALUI = halui`
6. 执行 `[HAL] HALFILE = LIB:basic_sim.tcl`
7. 执行 INI 中所有 `[HAL] HALCMD = ...`
8. 执行 `halcmd start`,启动实时线程。
9. 启动 `[DISPLAY] DISPLAY = axis`
## 2. INI 核心配置
目标配置文件:
```text
configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini
```
关键内容:
```ini
[EMC]
MACHINE = sim-xyzbc-trt-kins (switchkins)
[DISPLAY]
GEOMETRY = XYZB
OPEN_FILE = ./demos/xyzbc_switchkins.ngc
PYVCP = ./xyzbc-trt.xml
JOG_AXES = XYZC
DISPLAY = axis
[RS274NGC]
SUBROUTINE_PATH = ./remap_subs
HAL_PIN_VARS = 1
REMAP = M428 modalgroup=10 ngc=428remap
REMAP = M429 modalgroup=10 ngc=429remap
REMAP = M430 modalgroup=10 ngc=430remap
PARAMETER_FILE = xyzbc.var
[KINS]
KINEMATICS = xyzbc-trt-kins sparm=identityfirst
JOINTS = 5
[TRAJ]
COORDINATES = XYZBC
LINEAR_UNITS = mm
ANGULAR_UNITS = deg
```
`COORDINATES = XYZBC` 表示该配置使用 5 个坐标字母,对应 5 个 joint
- `joint.0` -> X
- `joint.1` -> Y
- `joint.2` -> Z
- `joint.3` -> B
- `joint.4` -> C
`sparm=identityfirst` 是本配置的重要细节。它改变了 `xyzbc-trt-kins` 的默认类型顺序,使启动时的 `switchkins-type 0` 是 identity kinematics。
## 3. basic_sim.tcl 建立仿真 HAL
INI 中:
```ini
[HAL]
HALFILE = LIB:basic_sim.tcl
```
对应文件:
```text
lib/hallib/basic_sim.tcl
lib/hallib/sim_lib.tcl
```
`basic_sim.tcl` 读取 INI 中的坐标、joint 数、servo period然后调用 `core_sim`
`core_sim` 位于 `lib/hallib/sim_lib.tcl`,主要完成:
1. 调用 `setup_kins` 加载 `[KINS] KINEMATICS` 指定的运动学模块。
2. 加载 `motmod`
3.`motion-command-handler``motion-controller` 加入 `servo-thread`
4. 为每个 joint 创建 `pid`
5. 为每个 joint 创建 `mux2`
6.`joint.N.motor-pos-cmd` 经由 `pid``mux2` 接回 `joint.N.motor-pos-fb`,形成理想伺服仿真闭环。
7. 加载仿真回零、仿真主轴、手动换刀等用户态/实时组件。
运行后可生成等效 HAL 文件:
```text
configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt_cmds.hal
```
其中关键 HAL 命令为:
```hal
loadrt xyzbc-trt-kins sparm=identityfirst
loadrt motmod base_period_nsec=0 servo_period_nsec=1000000 num_joints=5
loadrt pid names=J0_pid,J1_pid,J2_pid,J3_pid,J4_pid
loadrt mux2 names=J0_mux,J1_mux,J2_mux,J3_mux,J4_mux
addf motion-command-handler servo-thread
addf motion-controller servo-thread
```
每个 joint 的典型连接形态为:
```hal
net J0:pos-cmd joint.0.motor-pos-cmd => J0_pid.command
net J0:on-pos J0_pid.output => J0_mux.in1
net J0:pos-fb J0_mux.out => joint.0.motor-pos-fb
```
这说明该仿真没有真实驱动器,反馈位置由仿真 HAL 直接产生。
## 4. switchkins 初始化
运动学源码入口:
```text
src/emc/kinematics/xyzbc-trt-kins.c
src/emc/kinematics/switchkins.c
src/emc/kinematics/trtfuncs.c
```
`xyzbc-trt-kins.c``switchkinsSetup()` 根据 `sparm` 配置三套运动学函数。
因为 INI 使用:
```ini
KINEMATICS = xyzbc-trt-kins sparm=identityfirst
```
所以实际类型顺序为:
- `switchkins-type 0`identity kinematics
- `switchkins-type 1`xyzbc TRT kinematics
- `switchkins-type 2`userk kinematics
如果没有 `identityfirst`,默认 type 0 才是 `xyzbc-trt-kins`
`switchkins.c` 提供统一包装函数:
- `kinematicsForward()`
- `kinematicsInverse()`
- `kinematicsSwitch()`
- `kinematicsSwitchable()`
并创建状态 HAL pin
- `kinstype.is-0`
- `kinstype.is-1`
- `kinstype.is-2`
启动时 `switchkins_type = 0`,随后调用 `kinematicsSwitch(0)`,因此本配置启动后处于 identity kinematics。
## 5. xyzbc TRT 几何参数
`trtKinematicsSetup()` 位于:
```text
src/emc/kinematics/trtfuncs.c
```
它为 TRT 运动学创建 HAL 输入:
- `xyzbc-trt-kins.x-rot-point`
- `xyzbc-trt-kins.y-rot-point`
- `xyzbc-trt-kins.z-rot-point`
- `xyzbc-trt-kins.x-offset`
- `xyzbc-trt-kins.y-offset`
- `xyzbc-trt-kins.z-offset`
- `xyzbc-trt-kins.tool-offset`
- `xyzbc-trt-kins.conventional-directions`
INI 中对这些参数的设置和连接为:
```hal
net :tool-offset motion.tooloffset.z
net :tool-offset xyzbc-trt-kins.tool-offset xyzbc-trt-gui.tool-offset
net :x-offset xyzbc-trt-kins.x-offset xyzbc-trt-gui.x-offset
net :z-offset xyzbc-trt-kins.z-offset xyzbc-trt-gui.z-offset
sets :x-offset -20
sets :z-offset -15
setp xyzbc-trt-kins.x-rot-point 0
setp xyzbc-trt-kins.y-rot-point 0
setp xyzbc-trt-kins.z-rot-point 0
setp xyzbc-trt-kins.conventional-directions 0
```
因此实际计算中:
```text
dx = x-offset = -20
dz = z-offset + tool-offset
```
`tool-offset` 来自 `motion.tooloffset.z`,也就是 LinuxCNC 当前刀长补偿的 Z 分量。
## 6. xyzbc 正/逆运动学
`xyzbcKinematicsForward()` 位于:
```text
src/emc/kinematics/trtfuncs.c
```
它从 joint 坐标计算当前笛卡尔位姿:
```text
joints[X/Y/Z/B/C] -> EmcPose XYZBC
```
`xyzbcKinematicsInverse()` 从程序位姿计算 joint 目标:
```text
EmcPose XYZBC -> joints[X/Y/Z/B/C]
```
关键输入包括:
- X/Y/Z/B/C 指令位姿
- B 轴角度
- C 轴角度
- `x-offset`
- `z-offset`
- `tool-offset`
- 旋转中心 `x/y/z-rot-point`
- `conventional-directions`
在 coordinated motion 中motion 控制循环调用当前运动学的 inverse将轨迹规划器输出的笛卡尔目标位置转换为 joint 目标。
路径为:
```text
trajectory planner
-> emcmotStatus->carte_pos_cmd
-> kinematicsInverse()
-> xyzbcKinematicsInverse()
-> joint.N.coarse_pos
-> joint.N.motor-pos-cmd
-> 仿真 PID/mux
-> joint.N.motor-pos-fb
```
反馈/状态更新时则通过 forward
```text
joint feedback/cmd
-> kinematicsForward()
-> xyzbcKinematicsForward()
-> 当前 XYZBC 位姿
```
## 7. Vismach 可视化模型
INI 中启动 Vismach
```hal
loadusr -W xyzbc-trt-gui
```
对应文件:
```text
bin/xyzbc-trt-gui
src/hal/user_comps/vismach/xyzbc-trt-gui.py
```
`xyzbc-trt-gui.py` 创建 HAL 用户组件:
```python
c = hal.component("xyzbc-trt-gui")
```
并创建输入 pin
- `table-x`
- `saddle-y`
- `spindle-z`
- `tilt-b`
- `rotate-c`
- `z-offset`
- `x-offset`
- `tool-offset`
INI 中连接为:
```hal
net :table-x joint.0.pos-fb xyzbc-trt-gui.table-x
net :saddle-y joint.1.pos-fb xyzbc-trt-gui.saddle-y
net :spindle-z joint.2.pos-fb xyzbc-trt-gui.spindle-z
net :tilt-b joint.3.pos-fb xyzbc-trt-gui.tilt-b
net :rotate-c joint.4.pos-fb xyzbc-trt-gui.rotate-c
```
因此 Vismach 显示的是 joint 反馈,不是直接显示 G-code 坐标。
偏置和刀长补偿也传给 Vismach
```hal
net :tool-offset xyzbc-trt-kins.tool-offset xyzbc-trt-gui.tool-offset
net :x-offset xyzbc-trt-kins.x-offset xyzbc-trt-gui.x-offset
net :z-offset xyzbc-trt-kins.z-offset xyzbc-trt-gui.z-offset
```
这样三维模型的几何位置和运动学计算使用同一组偏置。
## 8. PyVCP 面板与按钮
AXIS 根据:
```ini
[DISPLAY]
PYVCP = ./xyzbc-trt.xml
```
加载:
```text
configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.xml
```
该 XML 创建一个 `SWITCHKINS` 面板,包含:
- 多状态标签:`0:IDENTITY``1: XYZBC``2: USERK`
- 按钮 `IDENTITY`
- 按钮 `TCP:XYZBC`
- 按钮 `userk`
- 按钮 `vismach-clear`
AXIS 创建 PyVCP 组件后,再执行:
```ini
[HAL]
POSTGUI_HALFILE = switchkins_postgui.hal
```
对应文件:
```text
configs/sim/axis/vismach/5axis/table-rotary-tilting/switchkins_postgui.hal
```
其中连接:
```hal
net :kinstype.is-0 <= kinstype.is-0 => pyvcp.multilabel.0.legend0
net :kinstype.is-1 <= kinstype.is-1 => pyvcp.multilabel.0.legend1
net :kinstype.is-2 <= kinstype.is-2 => pyvcp.multilabel.0.legend2
net :type0-button <= pyvcp.type0-button => halui.mdi-command-00
net :type1-button <= pyvcp.type1-button => halui.mdi-command-01
net :type2-button <= pyvcp.type2-button => halui.mdi-command-02
```
INI 中 `[HALUI]` 配置为:
```ini
MDI_COMMAND = M429
MDI_COMMAND = M428
MDI_COMMAND = M430
```
所以按钮和运动学类型的实际关系是:
- `IDENTITY` -> `halui.mdi-command-00` -> `M429` -> type 0
- `TCP:XYZBC` -> `halui.mdi-command-01` -> `M428` -> type 1
- `userk` -> `halui.mdi-command-02` -> `M430` -> type 2
## 9. M428/M429/M430 切换链路
remap 子程序位于:
```text
configs/sim/axis/vismach/5axis/table-rotary-tilting/remap_subs/428remap.ngc
configs/sim/axis/vismach/5axis/table-rotary-tilting/remap_subs/429remap.ngc
configs/sim/axis/vismach/5axis/table-rotary-tilting/remap_subs/430remap.ngc
```
由于本配置使用 `sparm=identityfirst`,实际映射为:
- `M429``#<kinstype> = 0`identity
- `M428``#<kinstype> = 1`xyzbc TRT
- `M430``#<kinstype> = 2`userk
`M428` 为例,`428remap.ngc` 中核心语句为:
```ngc
#<kinstype> = 1
#<SWITCHKINS_PIN> = 3
M68 E#<SWITCHKINS_PIN> Q#<kinstype>
M66 E0 L0
```
实际等价于:
```ngc
M68 E3 Q1
M66 E0 L0
```
`M68 E3 Q1` 设置 `motion.analog-out-03 = 1`
INI 已连接:
```hal
net :kinstype-select <= motion.analog-out-03 => motion.switchkins-type
```
因此完整切换路径为:
```text
PyVCP button
-> halui.mdi-command-01
-> M428
-> 428remap.ngc
-> M68 E3 Q1
-> motion.analog-out-03 = 1
-> motion.switchkins-type = 1
-> motion servo thread 检测到变化
-> handle_kinematicsSwitch()
-> kinematicsSwitch(1)
-> 激活 xyzbc TRT kinematics
```
`M66 E0 L0` 用于强制解释器和 motion 同步,确保后续 G-code 在切换后的运动学类型下执行。
## 10. motion 伺服周期中的 switchkins 处理
motion 控制循环位于:
```text
src/emc/motion/control.c
```
每个 servo cycle 中会执行:
```c
read_homing_in_pins(ALL_JOINTS);
handle_kinematicsSwitch();
process_inputs();
do_forward_kins();
...
get_pos_cmds(period);
...
output_to_hal();
```
`handle_kinematicsSwitch()` 的逻辑:
1. 如果当前运动学不可切换,直接返回。
2. 读取 `motion.switchkins-type`
3. 如果值没变,直接返回。
4. 如果值变化,调用 `kinematicsSwitch(new_type)`
5. 用当前 joint 位置做一次 forward kinematics。
6. 更新 `emcmotStatus->carte_pos_cmd`
7. 更新 trajectory planner 当前位姿。
这一步很重要切换运动学时LinuxCNC 会用当前 joint 位置重新计算新的笛卡尔位置,避免轨迹规划器还停留在旧运动学解释下的位置。
## 11. M68 到 motion analog output 的路径
`M68` 在解释器中被转换为设置 analog output 的 canonical command。
源码路径:
```text
src/emc/rs274ngc/interp_convert.cc
src/emc/task/emccanon.cc
src/emc/task/taskintf.cc
src/emc/motion/command.c
```
核心过程:
1. `interp_convert.cc` 识别 `M68 E... Q...`
2. 调用 `SET_AUX_OUTPUT_VALUE(index, value)`
3. `emccanon.cc` 生成 `EMC_MOTION_SET_AOUT`
4. task 层调用 `emcMotionSetAout()`
5. motion 收到 `EMCMOT_SET_AOUT`
6. `emcmotAioWrite(index, value)` 写入 `motion.analog-out-XX`
本配置中 index 为 3所以写入
```text
motion.analog-out-03
```
再通过 HAL net 传给:
```text
motion.switchkins-type
```
## 12. 演示 G-code 执行过程
AXIS 启动后自动打开:
```text
configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/xyzbc_switchkins.ngc
```
该文件内容很短:
```ngc
o<xyzbc_switchkins_sub> call [10] [5] [10][1000][3][0][20][45][20]
m2
```
它调用:
```text
configs/sim/axis/vismach/5axis/table-rotary-tilting/remap_subs/xyzbc_switchkins_sub.ngc
```
参数含义:
- `zmax = 10`
- `zmin = 5`
- `r = 10`
- `frate = 1000`
- `n = 3`
- `a = 0`
- `b = 20`
- `c = 45`
- `dist = 20`
`xyzbc_switchkins_sub.ngc` 在四个象限重复类似流程:
```ngc
M429
G53 G0 X0 Y0 Z#<zmax> B0 C0
G10 L20 P0 X0 Y0 Z#<zmax> B0 C0
G0 X... Y... Z#<zmax>
o<helix_bc> call [...]
```
也就是:
1.`M429` 切到 identity。
2.`G53` 在机床坐标中移动到安全位置。
3.`G10 L20 P0` 重设当前工件坐标系。
4. 移动到当前象限的加工中心。
5. 调用 `helix_bc`
`helix_bc.ngc` 的核心流程为:
```ngc
M429
G0 X[#<_x> - #<r>]
G10 L20 P0 X0 Y0 Z#<zmax> B0 C0
M428
G0 B#<b> C#<c>
F#<frate> G2 I#<r> Z#<zmin> P#<n>
M429
G0 X0 Y0 Z#<zmax> B0 C0
G0 X[#<_x> + #<r>]
M428
```
含义:
1. 先切回 identity方便做直观的定位和坐标系设置。
2. 调整 X 到圆弧起点。
3. 重设 G54。
4. `M428` 切换到 xyzbc TRT 运动学。
5. 移动 B/C 到指定角度。
6. 执行带 Z 下降的 `G2 ... P#<n>` 螺旋插补。
7. 切回 identity回到安全位置。
8. 最后再次切到 xyzbc保持显示/演示状态。
## 13. 运行时数据流总览
启动阶段:
```text
desktop/rip-environment
-> scripts/linuxcnc
-> linuxcncsvr
-> realtime/HAL
-> milltask
-> halui
-> basic_sim.tcl
-> loadrt xyzbc-trt-kins sparm=identityfirst
-> loadrt motmod num_joints=5
-> HALCMD loadusr -W xyzbc-trt-gui
-> axis GUI
-> PyVCP
-> switchkins_postgui.hal
```
切换运动学:
```text
PyVCP button 或 G-code M428/M429/M430
-> remap ngc
-> M68 E3 Q<type>
-> motion.analog-out-03
-> motion.switchkins-type
-> handle_kinematicsSwitch()
-> kinematicsSwitch(type)
```
加工运动:
```text
G-code XYZBC
-> interpreter
-> task
-> trajectory planner
-> emcmotStatus->carte_pos_cmd
-> kinematicsInverse()
-> xyzbcKinematicsInverse() 或 identityKinematicsInverse()
-> joint.N.motor-pos-cmd
-> pid/mux 仿真闭环
-> joint.N.motor-pos-fb
-> Vismach 显示
```
反馈显示:
```text
joint.N.pos-fb
-> xyzbc-trt-gui table/saddle/spindle/tilt/rotate pins
-> Vismach 三维模型
kinstype.is-N
-> pyvcp.multilabel
-> SWITCHKINS 面板显示当前类型
```
## 14. 关键结论
该仿真不是简单的五个独立轴显示程序,而是一个完整的 switchkins 示例:
1. 启动默认是 identity kinematics因为 `sparm=identityfirst`
2. `M429` 选择 identity`switchkins-type 0`
3. `M428` 选择 xyzbc TRT`switchkins-type 1`
4. `M430` 选择 userk`switchkins-type 2`
5. 运动学切换不是按钮直接写运动学模块,而是经过 `halui -> MDI -> remap -> M68 -> motion.analog-out-03 -> motion.switchkins-type`
6. `xyzbcKinematicsInverse()` 是 RTCP/TCP 行为的核心,它根据 XYZBC 指令、B/C 角度、转台偏置和刀长补偿计算 joint 目标。
7. Vismach 使用 joint feedback 和同一组几何偏置显示机床模型,因此可视化结果跟运动学参数保持一致。
8. 演示程序通过 identity 与 xyzbc TRT 之间反复切换,展示了在普通机床坐标定位和五轴 TCP 加工之间切换的完整过程。
## 15. 主要相关文件清单
配置入口:
```text
configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini
```
HAL/GUI
```text
configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.xml
configs/sim/axis/vismach/5axis/table-rotary-tilting/switchkins_postgui.hal
configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt_cmds.hal
src/hal/user_comps/vismach/xyzbc-trt-gui.py
bin/xyzbc-trt-gui
```
G-code/remap
```text
configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/xyzbc_switchkins.ngc
configs/sim/axis/vismach/5axis/table-rotary-tilting/remap_subs/xyzbc_switchkins_sub.ngc
configs/sim/axis/vismach/5axis/table-rotary-tilting/remap_subs/helix_bc.ngc
configs/sim/axis/vismach/5axis/table-rotary-tilting/remap_subs/428remap.ngc
configs/sim/axis/vismach/5axis/table-rotary-tilting/remap_subs/429remap.ngc
configs/sim/axis/vismach/5axis/table-rotary-tilting/remap_subs/430remap.ngc
```
运动学源码:
```text
src/emc/kinematics/xyzbc-trt-kins.c
src/emc/kinematics/trtfuncs.c
src/emc/kinematics/switchkins.c
src/emc/kinematics/userkfuncs.c
include/kinematics.h
```
motion/task/interpreter 相关:
```text
src/emc/motion/motion.c
src/emc/motion/control.c
src/emc/motion/command.c
src/emc/rs274ngc/interp_convert.cc
src/emc/task/emccanon.cc
src/emc/task/taskintf.cc
```
启动脚本和 HAL 库:
```text
scripts/linuxcnc
scripts/rip-environment
lib/hallib/basic_sim.tcl
lib/hallib/sim_lib.tcl
```
## 16. 与 LinuxCNC 数据系统核心原理的对应关系
`xyzbc-trt` 仿真可以看作 LinuxCNC 数据系统的一次完整穿透:从 INI 装配,到 NML 命令,到 task/interpreter到 motion 实时共享内存,到 HAL pin/signal再到 Vismach 显示。
### 16.1 INI 是装配入口
`xyzbc-trt.ini` 决定系统启动时要加载的主要对象:
```ini
[DISPLAY] DISPLAY = axis
[DISPLAY] PYVCP = ./xyzbc-trt.xml
[DISPLAY] OPEN_FILE = ./demos/xyzbc_switchkins.ngc
[KINS] KINEMATICS = xyzbc-trt-kins sparm=identityfirst
[KINS] JOINTS = 5
[TRAJ] COORDINATES = XYZBC
[HAL] HALFILE = LIB:basic_sim.tcl
[HAL] POSTGUI_HALFILE = switchkins_postgui.hal
```
INI 本身不做实时控制,它描述“系统应该如何装配”。真正的运行时数据交换由 NML、motion shared memory 和 HAL 完成。
### 16.2 NML 负责 GUI/halui/task 命令与状态
当用户点击 PyVCP 按钮时,按钮并不直接写 `motion.switchkins-type`。实际路径是:
```text
PyVCP button
-> halui.mdi-command-N
-> halui 通过 NML 发送 MDI 命令
-> milltask 接收命令
-> interpreter 执行 M428/M429/M430 remap
```
这体现了 LinuxCNC 的基本原则GUI 和 halui 表达操作意图task 负责调度和合法性motion 负责实时执行。
### 16.3 Interpreter/Canonical 层把 M428 转成 motion 能执行的动作
`M428` 并不是 motion 原生命令,而是 remap 子程序:
```ngc
M68 E3 Q1
M66 E0 L0
```
解释器把 `M68 E3 Q1` 转换为 canonical motion output command最终由 task 发送给 motion
```text
M68 E3 Q1
-> SET_AUX_OUTPUT_VALUE(3, 1)
-> EMC_MOTION_SET_AOUT
-> emcMotionSetAout()
-> usrmotWriteEmcmotCommand()
-> realtime motion command shared memory
```
这说明 G-code 的作用不是直接改 HAL而是通过解释器和 task 进入 motion。
### 16.4 Motion shared memory 是 task 和实时 motion 的边界
task 写入 `emcmot_command_t`,实时 motion 读取并执行。motion 每个 servo cycle 更新 `emcmot_status_t`task 再把它汇总到 `EMC_STAT`
`xyzbc-trt` 中,`M68 E3 Q1` 最终被 motion 执行为:
```text
motion.analog-out-03 = 1
```
然后 HAL 连接把这个值传给:
```text
motion.switchkins-type = 1
```
### 16.5 HAL 是实时信号网络
本配置最关键的 HAL net 是:
```hal
net :kinstype-select <= motion.analog-out-03 => motion.switchkins-type
```
它的本质是让两个 HAL pin 共享同一个 signal 值:
```text
writer: motion.analog-out-03
signal: :kinstype-select
reader: motion.switchkins-type
```
servo-thread 中 `handle_kinematicsSwitch()` 读取 `motion.switchkins-type`,发现从 0 变成 1 后调用:
```text
kinematicsSwitch(1)
```
于是当前运动学从 identity 切换到 xyzbc TRT。
### 16.6 Vismach 是 HAL 数据消费者
Vismach 模型不参与 NML也不参与 motion command 调度。它作为 HAL 用户组件读取 pin
```hal
joint.0.pos-fb -> xyzbc-trt-gui.table-x
joint.1.pos-fb -> xyzbc-trt-gui.saddle-y
joint.2.pos-fb -> xyzbc-trt-gui.spindle-z
joint.3.pos-fb -> xyzbc-trt-gui.tilt-b
joint.4.pos-fb -> xyzbc-trt-gui.rotate-c
```
因此 Vismach 显示的是 HAL 中的 joint feedback 快照,而不是直接读取 G-code 或 motion 内部轨迹队列。
### 16.7 该配置的数据链总图
```text
INI 装配
-> basic_sim.tcl 加载 motion/k 等 HAL 模块
-> AXIS 创建 PyVCP
-> PyVCP 按钮触发 halui MDI
-> NML command 到 milltask
-> interpreter 执行 M428/M429/M430 remap
-> M68 变成 EMC_MOTION_SET_AOUT
-> task 写 emcmot_command_t
-> realtime motion 写 motion.analog-out-03
-> HAL net 传给 motion.switchkins-type
-> servo-thread 切换 kinematics
-> inverse kinematics 生成 joint 目标
-> 仿真 PID/mux 生成 joint feedback
-> Vismach 读取 HAL feedback 显示机床
-> motion status 汇总到 EMC_STAT
-> AXIS/halui 显示状态
```
详细通用原理见:
```text
项目分析/LinuxCNC数据系统核心原理.md
```