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,338 @@
# 5axis-xyzbc-trt-sim 执行流程图
本文档用 Mermaid 描述 `configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini` 的主要执行流程。
## 1. 总体启动流程
```mermaid
flowchart TD
A["xyzbc-trt.desktop"] --> B["scripts/rip-environment"]
B --> C["scripts/linuxcnc xyzbc-trt.ini"]
C --> D["读取 INI 配置"]
D --> D1["[TASK] TASK = milltask"]
D --> D2["[HAL] HALUI = halui"]
D --> D3["[HAL] HALFILE = LIB:basic_sim.tcl"]
D --> D4["[DISPLAY] DISPLAY = axis"]
D --> D5["[KINS] KINEMATICS = xyzbc-trt-kins sparm=identityfirst"]
C --> E["启动 linuxcncsvr"]
E --> F["启动 realtime / HAL"]
F --> G["loadrt tpmod / homemod"]
G --> H["启动 milltask"]
H --> I["启动 halui"]
I --> J["执行 basic_sim.tcl"]
J --> K["执行 INI 中的 HALCMD"]
K --> L["halcmd start 启动实时线程"]
L --> M["启动 AXIS GUI"]
M --> N["加载 PyVCP: xyzbc-trt.xml"]
N --> O["执行 POSTGUI_HALFILE: switchkins_postgui.hal"]
```
## 2. basic_sim.tcl 与仿真 HAL 建立流程
```mermaid
flowchart TD
A["[HAL] HALFILE = LIB:basic_sim.tcl"] --> B["basic_sim.tcl"]
B --> C["读取 TRAJ.COORDINATES = XYZBC"]
B --> D["读取 KINS.JOINTS = 5"]
B --> E["读取 EMCMOT.SERVO_PERIOD = 1000000"]
C --> F["core_sim axes=xyzbc joints=5"]
D --> F
E --> F
F --> G["setup_kins"]
G --> H["loadrt xyzbc-trt-kins sparm=identityfirst"]
F --> I["loadrt motmod num_joints=5 servo_period_nsec=1000000"]
I --> J["addf motion-command-handler servo-thread"]
I --> K["addf motion-controller servo-thread"]
F --> L["loadrt pid names=J0_pid..J4_pid"]
F --> M["loadrt mux2 names=J0_mux..J4_mux"]
F --> N["loadrt sim_home_switch"]
F --> O["loadusr hal_manualtoolchange"]
F --> P["loadrt sim_spindle / limit2 / lowpass / near / scale"]
L --> Q["joint.N.motor-pos-cmd -> JN_pid.command"]
Q --> R["JN_pid.output -> JN_mux.in1"]
R --> S["JN_mux.out -> joint.N.motor-pos-fb"]
S --> T["形成理想伺服仿真闭环"]
```
## 3. switchkins 初始化流程
```mermaid
flowchart TD
A["loadrt xyzbc-trt-kins sparm=identityfirst"] --> B["rtapi_app_main in switchkins.c"]
B --> C["调用 xyzbc-trt-kins.c:switchkinsSetup"]
C --> D{"sparm 包含 identityfirst?"}
D -- 是 --> E["type 0 = identity"]
D -- 是 --> F["type 1 = xyzbc TRT"]
D -- 是 --> G["type 2 = userk"]
D -- 否 --> H["type 0 = xyzbc TRT"]
D -- 否 --> I["type 1 = identity"]
D -- 否 --> J["type 2 = userk"]
E --> K["创建 HAL pin: kinstype.is-0/1/2"]
F --> K
G --> K
H --> K
I --> K
J --> K
K --> L["创建 TRT 几何 HAL pin"]
L --> L1["x/y/z-rot-point"]
L --> L2["x/y/z-offset"]
L --> L3["tool-offset"]
L --> L4["conventional-directions"]
L --> M["switchkins_type = 0"]
M --> N["kinematicsSwitch(0)"]
N --> O["启动默认状态: identity kinematics"]
```
## 4. PyVCP 按钮到运动学切换流程
```mermaid
flowchart TD
A["AXIS 加载 xyzbc-trt.xml"] --> B["创建 PyVCP SWITCHKINS 面板"]
B --> C1["按钮: IDENTITY"]
B --> C2["按钮: TCP:XYZBC"]
B --> C3["按钮: userk"]
A --> D["执行 switchkins_postgui.hal"]
C1 --> E1["pyvcp.type0-button"]
C2 --> E2["pyvcp.type1-button"]
C3 --> E3["pyvcp.type2-button"]
E1 --> F1["halui.mdi-command-00"]
E2 --> F2["halui.mdi-command-01"]
E3 --> F3["halui.mdi-command-02"]
F1 --> G1["M429"]
F2 --> G2["M428"]
F3 --> G3["M430"]
G1 --> H1["429remap.ngc: kinstype = 0"]
G2 --> H2["428remap.ngc: kinstype = 1"]
G3 --> H3["430remap.ngc: kinstype = 2"]
H1 --> I1["M68 E3 Q0"]
H2 --> I2["M68 E3 Q1"]
H3 --> I3["M68 E3 Q2"]
I1 --> J["motion.analog-out-03"]
I2 --> J
I3 --> J
J --> K["HAL net :kinstype-select"]
K --> L["motion.switchkins-type"]
L --> M["servo thread: handle_kinematicsSwitch()"]
M --> N["kinematicsSwitch(type)"]
N --> O1["type 0: identity"]
N --> O2["type 1: xyzbc TRT"]
N --> O3["type 2: userk"]
N --> P["kinstype.is-N 更新"]
P --> Q["PyVCP multilabel 显示当前类型"]
```
## 5. M68 到 motion.switchkins-type 的内部路径
```mermaid
flowchart TD
A["G-code: M68 E3 Q<type>"] --> B["RS274NGC interpreter"]
B --> C["interp_convert.cc"]
C --> D["SET_AUX_OUTPUT_VALUE(3, type)"]
D --> E["emccanon.cc 创建 EMC_MOTION_SET_AOUT"]
E --> F["taskintf.cc: emcMotionSetAout"]
F --> G["motion command: EMCMOT_SET_AOUT"]
G --> H["command.c: emcmotAioWrite(3, type)"]
H --> I["motion.analog-out-03 = type"]
I --> J["HAL net :kinstype-select"]
J --> K["motion.switchkins-type = type"]
```
## 6. 运动执行数据流
```mermaid
flowchart TD
A["G-code XYZBC"] --> B["RS274NGC interpreter"]
B --> C["milltask"]
C --> D["motion trajectory planner"]
D --> E["emcmotStatus->carte_pos_cmd"]
E --> F{"当前 switchkins type"}
F -- "type 0" --> G["identityKinematicsInverse"]
F -- "type 1" --> H["xyzbcKinematicsInverse"]
F -- "type 2" --> I["userkKinematicsInverse"]
H --> H1["读取 x-offset = -20"]
H --> H2["读取 z-offset = -15"]
H --> H3["读取 tool-offset = motion.tooloffset.z"]
H --> H4["读取 B/C 角度和旋转中心"]
G --> J["joint target positions"]
H1 --> J
H2 --> J
H3 --> J
H4 --> J
I --> J
J --> K["joint.N.coarse_pos"]
K --> L["joint.N.motor-pos-cmd"]
L --> M["仿真 PID / mux2"]
M --> N["joint.N.motor-pos-fb"]
N --> O["motion 状态反馈"]
N --> P["Vismach xyzbc-trt-gui"]
```
## 7. Vismach 显示数据流
```mermaid
flowchart TD
A["joint.0.pos-fb"] --> B["xyzbc-trt-gui.table-x"]
C["joint.1.pos-fb"] --> D["xyzbc-trt-gui.saddle-y"]
E["joint.2.pos-fb"] --> F["xyzbc-trt-gui.spindle-z"]
G["joint.3.pos-fb"] --> H["xyzbc-trt-gui.tilt-b"]
I["joint.4.pos-fb"] --> J["xyzbc-trt-gui.rotate-c"]
K["xyzbc-trt-kins.x-offset"] --> L["xyzbc-trt-gui.x-offset"]
M["xyzbc-trt-kins.z-offset"] --> N["xyzbc-trt-gui.z-offset"]
O["motion.tooloffset.z"] --> P["xyzbc-trt-kins.tool-offset"]
P --> Q["xyzbc-trt-gui.tool-offset"]
B --> R["Vismach 机床模型"]
D --> R
F --> R
H --> R
J --> R
L --> R
N --> R
Q --> R
```
## 8. 演示 G-code 执行流程
```mermaid
flowchart TD
A["AXIS OPEN_FILE: demos/xyzbc_switchkins.ngc"] --> B["调用 xyzbc_switchkins_sub"]
B --> C["参数: zmax=10 zmin=5 r=10 frate=1000 n=3 b=20 c=45 dist=20"]
C --> D["象限 I"]
C --> E["象限 II"]
C --> F["象限 III"]
C --> G["象限 IV"]
D --> H["M429: identity"]
E --> H
F --> H
G --> H
H --> I["G53 G0 X0 Y0 Zzmax B0 C0"]
I --> J["G10 L20 P0 重设 G54"]
J --> K["G0 移动到当前象限中心"]
K --> L["调用 helix_bc"]
L --> M["M429: identity"]
M --> N["调整 X 到圆弧起点"]
N --> O["G10 L20 P0 重设坐标"]
O --> P["M428: xyzbc TRT"]
P --> Q["G0 B#<b> C#<c>"]
Q --> R["G2 I#<r> Z#<zmin> P#<n> 螺旋插补"]
R --> S["M429: identity"]
S --> T["回安全位置"]
T --> U["M428: xyzbc TRT"]
U --> V{"四个象限完成?"}
V -- 否 --> H
V -- 是 --> W["最终 M429 回 identity 并复位"]
```
## 9. 核心关系简图
```mermaid
flowchart LR
A["INI: xyzbc-trt.ini"] --> B["HAL: basic_sim.tcl"]
A --> C["KINS: xyzbc-trt-kins"]
A --> D["GUI: axis"]
A --> E["Vismach: xyzbc-trt-gui"]
A --> F["PyVCP: xyzbc-trt.xml"]
A --> G["Remap: M428/M429/M430"]
B --> H["motmod + servo-thread + sim feedback"]
C --> I["switchkins type 0/1/2"]
D --> F
F --> G
G --> J["motion.switchkins-type"]
J --> I
I --> K["inverse / forward kinematics"]
K --> H
H --> E
```
## 10. LinuxCNC 数据系统核心原理图
高清 PNG
```text
项目分析/LinuxCNC数据系统核心原理高清流程图.png
```
可编辑 SVG
```text
项目分析/LinuxCNC数据系统核心原理高清流程图.svg
```
对应原理文档:
```text
项目分析/LinuxCNC数据系统核心原理.md
```
```mermaid
flowchart TD
A["INI 配置数据"] --> B["scripts/linuxcnc 启动装配"]
B --> C["linuxcncsvr / NML channels"]
B --> D["HAL: loadrt/loadusr/HALFILE/HALCMD"]
B --> E["milltask"]
B --> F["GUI: AXIS / halui"]
F --> G["NML emcCommand"]
G --> E
E --> H["Interpreter / Canonical Commands"]
H --> I["taskintf.cc"]
I --> J["Motion Shared Memory: emcmot_command_t"]
J --> K["Realtime motion servo cycle"]
K --> L["HAL pins/signals"]
L --> M["驱动 / 仿真组件 / Vismach / halui"]
M --> L
K --> N["emcmot_status_t"]
N --> E
E --> O["EMC_STAT: task + motion + io"]
O --> P["NML emcStatus"]
P --> F
Q["emcError"] --> F
E --> Q
K --> Q
```
核心区分:
```text
NML: 系统命令、系统状态、错误信息。
HAL: 实时机器信号、pin/signal/parameter、servo-thread 函数顺序。
Motion shared memory: task 和 realtime motion 的命令/状态边界。
INI: 启动装配数据,不是实时数据通道。
```

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
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

@@ -0,0 +1,284 @@
<svg xmlns="http://www.w3.org/2000/svg" width="4800" height="7200" viewBox="0 0 4800 7200">
<defs>
<marker id="arrow" markerWidth="18" markerHeight="18" refX="14" refY="6" orient="auto" markerUnits="strokeWidth">
<path d="M2,2 L14,6 L2,10 Z" fill="#1f3b57"/>
</marker>
<style>
svg { background: #ffffff; }
text { font-family: Noto Sans CJK SC, DejaVu Sans, sans-serif; fill: #17212b; }
.title { font-size: 76px; font-weight: 800; }
.subtitle { font-size: 34px; fill: #52606d; }
.section-title { font-size: 44px; font-weight: 800; fill: #24415f; }
.box-title { font-size: 34px; font-weight: 800; }
.box-text { font-size: 30px; font-weight: 650; }
.box-small { font-size: 27px; fill: #35495e; }
.note-title { font-size: 34px; font-weight: 800; fill: #7a4b00; }
.note-text { font-size: 27px; fill: #5c440b; }
.arrow-label { font-size: 26px; font-weight: 700; fill: #24415f; }
.legend { font-size: 28px; fill: #34495e; }
</style>
</defs>
<rect x="0" y="0" width="4800" height="7200" fill="#ffffff"/>
<text x="2400" y="120" class="title" text-anchor="middle">5axis-xyzbc-trt-sim 执行流程图</text>
<text x="2400" y="178" class="subtitle" text-anchor="middle">LinuxCNC: xyzbc-trt.ini / switchkins / Vismach / PyVCP / M428-M429-M430</text>
<rect x="120" y="260" width="4560" height="780" rx="36" fill="#eef6ff" stroke="#b8c7dc" stroke-width="4"/>
<text x="154" y="330" class="section-title">1. 总体启动链路</text>
<rect id="s1" x="210" y="390" width="500" height="150" rx="18" fill="#ffffff" stroke="#416788" stroke-width="4"/>
<text x="460.0" y="434" class="box-title" text-anchor="middle">xyzbc-trt.desktop</text>
<text x="238" y="484" class="box-small">快捷方式入口</text>
<rect id="s2" x="820" y="390" width="520" height="150" rx="18" fill="#ffffff" stroke="#416788" stroke-width="4"/>
<text x="1080.0" y="434" class="box-title" text-anchor="middle">rip-environment</text>
<text x="848" y="484" class="box-small">设置 RIP 环境</text>
<rect id="s3" x="1450" y="390" width="560" height="150" rx="18" fill="#ffffff" stroke="#416788" stroke-width="4"/>
<text x="1730.0" y="434" class="box-title" text-anchor="middle">scripts/linuxcnc</text>
<text x="1478" y="484" class="box-small">读取 xyzbc-trt.ini</text>
<rect id="s4" x="2120" y="390" width="520" height="150" rx="18" fill="#ffffff" stroke="#416788" stroke-width="4"/>
<text x="2380.0" y="434" class="box-title" text-anchor="middle">linuxcncsvr</text>
<text x="2148" y="484" class="box-small">NML 通道</text>
<rect id="s5" x="2750" y="390" width="560" height="150" rx="18" fill="#ffffff" stroke="#416788" stroke-width="4"/>
<text x="3030.0" y="434" class="box-title" text-anchor="middle">realtime / HAL</text>
<text x="2778" y="484" class="box-small">加载 RTAPI/HAL</text>
<rect id="s6" x="3420" y="390" width="500" height="150" rx="18" fill="#ffffff" stroke="#416788" stroke-width="4"/>
<text x="3670.0" y="434" class="box-title" text-anchor="middle">milltask / halui</text>
<text x="3448" y="484" class="box-small">任务与 MDI</text>
<rect id="s7" x="4030" y="390" width="520" height="150" rx="18" fill="#ffffff" stroke="#416788" stroke-width="4"/>
<text x="4290.0" y="434" class="box-title" text-anchor="middle">AXIS GUI</text>
<text x="4058" y="484" class="box-small">前台显示</text>
<path d="M 710 465.0 L 820 465.0" fill="none" stroke="#1f3b57" stroke-width="6" marker-end="url(#arrow)"/>
<path d="M 1340 465.0 L 1450 465.0" fill="none" stroke="#1f3b57" stroke-width="6" marker-end="url(#arrow)"/>
<path d="M 2010 465.0 L 2120 465.0" fill="none" stroke="#1f3b57" stroke-width="6" marker-end="url(#arrow)"/>
<path d="M 2640 465.0 L 2750 465.0" fill="none" stroke="#1f3b57" stroke-width="6" marker-end="url(#arrow)"/>
<path d="M 3310 465.0 L 3420 465.0" fill="none" stroke="#1f3b57" stroke-width="6" marker-end="url(#arrow)"/>
<path d="M 3920 465.0 L 4030 465.0" fill="none" stroke="#1f3b57" stroke-width="6" marker-end="url(#arrow)"/>
<rect id="ini" x="450" y="690" width="1050" height="230" rx="18" fill="#ffffff" stroke="#416788" stroke-width="4"/>
<text x="975.0" y="734" class="box-title" text-anchor="middle">INI 核心项</text>
<text x="478" y="784" class="box-small">[KINS] xyzbc-trt-kins sparm=identityfirst; [TRAJ]</text>
<text x="478" y="818" class="box-small">XYZBC; [DISPLAY] axis + PyVCP; [HAL] basic_sim.tcl</text>
<rect id="halcmd" x="1760" y="690" width="1050" height="230" rx="18" fill="#ffffff" stroke="#416788" stroke-width="4"/>
<text x="2285.0" y="734" class="box-title" text-anchor="middle">HAL 加载</text>
<text x="1788" y="784" class="box-small">basic_sim.tcl 建立仿真闭环; HALCMD 启动 Vismach</text>
<text x="1788" y="818" class="box-small">并连接 pins</text>
<rect id="postgui" x="3070" y="690" width="1050" height="230" rx="18" fill="#ffffff" stroke="#416788" stroke-width="4"/>
<text x="3595.0" y="734" class="box-title" text-anchor="middle">GUI 后置 HAL</text>
<text x="3098" y="784" class="box-small">AXIS 创建 PyVCP 后执行 switchkins_postgui.hal</text>
<path d="M 1500 805 L 1760 805" fill="none" stroke="#1f3b57" stroke-width="6" marker-end="url(#arrow)"/>
<path d="M 2810 805 L 3070 805" fill="none" stroke="#1f3b57" stroke-width="6" marker-end="url(#arrow)"/>
<rect x="120" y="1140" width="2200" height="1490" rx="36" fill="#f2fbf2" stroke="#b8c7dc" stroke-width="4"/>
<text x="154" y="1210" class="section-title">2. basic_sim.tcl / 仿真 HAL</text>
<rect id="b1" x="230" y="1280" width="520" height="150" rx="18" fill="#ffffff" stroke="#3f7d4a" stroke-width="4"/>
<text x="490.0" y="1324" class="box-title" text-anchor="middle">basic_sim.tcl</text>
<text x="258" y="1374" class="box-small">读取</text>
<text x="258" y="1408" class="box-small">coordinates/joints/servo</text>
<text x="258" y="1442" class="box-small">period</text>
<rect id="b2" x="910" y="1280" width="560" height="150" rx="18" fill="#ffffff" stroke="#3f7d4a" stroke-width="4"/>
<text x="1190.0" y="1324" class="box-title" text-anchor="middle">setup_kins</text>
<text x="938" y="1374" class="box-small">loadrt xyzbc-trt-kins</text>
<text x="938" y="1408" class="box-small">sparm=identityfirst</text>
<rect id="b3" x="1590" y="1280" width="560" height="150" rx="18" fill="#ffffff" stroke="#3f7d4a" stroke-width="4"/>
<text x="1870.0" y="1324" class="box-title" text-anchor="middle">motmod</text>
<text x="1618" y="1374" class="box-small">num_joints=5 servo=1ms</text>
<path d="M 750 1355 L 910 1355" fill="none" stroke="#1f3b57" stroke-width="6" marker-end="url(#arrow)"/>
<path d="M 1470 1355 L 1590 1355" fill="none" stroke="#1f3b57" stroke-width="6" marker-end="url(#arrow)"/>
<rect id="bc0" x="250" y="1600" width="720" height="150" rx="18" fill="#ffffff" stroke="#3f7d4a" stroke-width="4"/>
<text x="610.0" y="1644" class="box-title" text-anchor="middle">pid J0..J4</text>
<text x="278" y="1694" class="box-small">basic_sim.tcl 创建/连接</text>
<rect id="bc1" x="1120" y="1600" width="720" height="150" rx="18" fill="#ffffff" stroke="#3f7d4a" stroke-width="4"/>
<text x="1480.0" y="1644" class="box-title" text-anchor="middle">mux2 J0..J4</text>
<text x="1148" y="1694" class="box-small">basic_sim.tcl 创建/连接</text>
<rect id="bc2" x="250" y="1830" width="720" height="150" rx="18" fill="#ffffff" stroke="#3f7d4a" stroke-width="4"/>
<text x="610.0" y="1874" class="box-title" text-anchor="middle">sim_home_switch</text>
<text x="278" y="1924" class="box-small">basic_sim.tcl 创建/连接</text>
<rect id="bc3" x="1120" y="1830" width="720" height="150" rx="18" fill="#ffffff" stroke="#3f7d4a" stroke-width="4"/>
<text x="1480.0" y="1874" class="box-title" text-anchor="middle">sim_spindle</text>
<text x="1148" y="1924" class="box-small">basic_sim.tcl 创建/连接</text>
<rect id="bc4" x="250" y="2060" width="720" height="150" rx="18" fill="#ffffff" stroke="#3f7d4a" stroke-width="4"/>
<text x="610.0" y="2104" class="box-title" text-anchor="middle">hal_manualtoolchange</text>
<text x="278" y="2154" class="box-small">basic_sim.tcl 创建/连接</text>
<path d="M 1870 1430 L 1870 1530 L 610 1530 L 610 1600" fill="none" stroke="#1f3b57" stroke-width="6" marker-end="url(#arrow)"/>
<path d="M 1870 1430 L 1870 1530 L 1480 1530 L 1480 1600" fill="none" stroke="#1f3b57" stroke-width="6" marker-end="url(#arrow)"/>
<rect id="loop" x="250" y="2300" width="1720" height="190" rx="18" fill="#ffffff" stroke="#3f7d4a" stroke-width="4"/>
<text x="1110.0" y="2344" class="box-title" text-anchor="middle">理想伺服仿真闭环</text>
<text x="278" y="2394" class="box-small">joint.N.motor-pos-cmd -&gt; JN_pid.command -&gt; JN_mux.in1 -&gt; joint.N.motor-pos-fb</text>
<path d="M 1110 2210 L 1110 2300" fill="none" stroke="#1f3b57" stroke-width="6" marker-end="url(#arrow)"/>
<rect x="2480" y="1140" width="2200" height="1490" rx="36" fill="#f9f3ff" stroke="#b8c7dc" stroke-width="4"/>
<text x="2514" y="1210" class="section-title">3. switchkins 初始化</text>
<rect id="k1" x="2590" y="1280" width="620" height="150" rx="18" fill="#ffffff" stroke="#7246a3" stroke-width="4"/>
<text x="2900.0" y="1324" class="box-title" text-anchor="middle">xyzbc-trt-kins</text>
<text x="2618" y="1374" class="box-small">switchkinsSetup()</text>
<rect id="k2" x="3350" y="1280" width="620" height="150" rx="18" fill="#ffffff" stroke="#7246a3" stroke-width="4"/>
<text x="3660.0" y="1324" class="box-title" text-anchor="middle">sparm=identityfirst</text>
<text x="3378" y="1374" class="box-small">改变 type 顺序</text>
<path d="M 3210 1355 L 3350 1355" fill="none" stroke="#1f3b57" stroke-width="6" marker-end="url(#arrow)"/>
<rect id="t0" x="2630" y="1620" width="520" height="150" rx="18" fill="#ffffff" stroke="#7246a3" stroke-width="4"/>
<text x="2890.0" y="1664" class="box-title" text-anchor="middle">type 0</text>
<text x="2658" y="1714" class="box-small">identity kinematics</text>
<rect id="t1" x="3280" y="1620" width="520" height="150" rx="18" fill="#ffffff" stroke="#7246a3" stroke-width="4"/>
<text x="3540.0" y="1664" class="box-title" text-anchor="middle">type 1</text>
<text x="3308" y="1714" class="box-small">xyzbc TRT kinematics</text>
<rect id="t2" x="3930" y="1620" width="520" height="150" rx="18" fill="#ffffff" stroke="#7246a3" stroke-width="4"/>
<text x="4190.0" y="1664" class="box-title" text-anchor="middle">type 2</text>
<text x="3958" y="1714" class="box-small">userk kinematics</text>
<path d="M 3660 1430 L 3660 1530 L 2890 1530 L 2890 1620" fill="none" stroke="#1f3b57" stroke-width="6" marker-end="url(#arrow)"/>
<path d="M 3660 1430 L 3660 1530 L 3540 1530 L 3540 1620" fill="none" stroke="#1f3b57" stroke-width="6" marker-end="url(#arrow)"/>
<path d="M 3660 1430 L 3660 1530 L 4190 1530 L 4190 1620" fill="none" stroke="#1f3b57" stroke-width="6" marker-end="url(#arrow)"/>
<rect id="pins" x="2700" y="1940" width="1560" height="180" rx="18" fill="#ffffff" stroke="#7246a3" stroke-width="4"/>
<text x="3480.0" y="1984" class="box-title" text-anchor="middle">HAL pins</text>
<text x="2728" y="2034" class="box-small">kinstype.is-0/1/2; x/y/z-rot-point; x/y/z-offset; tool-offset;</text>
<text x="2728" y="2068" class="box-small">conventional-directions</text>
<path d="M 3540 1770 L 3480 1940" fill="none" stroke="#1f3b57" stroke-width="6" marker-end="url(#arrow)"/>
<rect id="default" x="2920" y="2280" width="1120" height="160" rx="18" fill="#ffffff" stroke="#7246a3" stroke-width="4"/>
<text x="3480.0" y="2324" class="box-title" text-anchor="middle">启动默认状态</text>
<text x="2948" y="2374" class="box-small">switchkins_type = 0 -&gt; identity</text>
<path d="M 3480 2120 L 3480 2280" fill="none" stroke="#1f3b57" stroke-width="6" marker-end="url(#arrow)"/>
<rect x="120" y="2730" width="4560" height="1400" rx="36" fill="#fff8ee" stroke="#b8c7dc" stroke-width="4"/>
<text x="154" y="2800" class="section-title">4. PyVCP / M-code / motion.switchkins-type 切换链路</text>
<rect id="p1" x="240" y="2880" width="680" height="160" rx="18" fill="#ffffff" stroke="#ba7a20" stroke-width="4"/>
<text x="580.0" y="2924" class="box-title" text-anchor="middle">PyVCP SWITCHKINS 面板</text>
<text x="268" y="2974" class="box-small">IDENTITY / TCP:XYZBC / userk</text>
<rect id="p2" x="1100" y="2880" width="620" height="160" rx="18" fill="#ffffff" stroke="#ba7a20" stroke-width="4"/>
<text x="1410.0" y="2924" class="box-title" text-anchor="middle">switchkins_postgui.hal</text>
<text x="1128" y="2974" class="box-small">按钮接入 halui.mdi-command</text>
<rect id="p3" x="1900" y="2880" width="580" height="160" rx="18" fill="#ffffff" stroke="#ba7a20" stroke-width="4"/>
<text x="2190.0" y="2924" class="box-title" text-anchor="middle">halui MDI</text>
<text x="1928" y="2974" class="box-small">执行 M429 / M428 / M430</text>
<rect id="p4" x="2660" y="2880" width="640" height="160" rx="18" fill="#ffffff" stroke="#ba7a20" stroke-width="4"/>
<text x="2980.0" y="2924" class="box-title" text-anchor="middle">remap 子程序</text>
<text x="2688" y="2974" class="box-small">429/428/430remap.ngc</text>
<rect id="p5" x="3480" y="2880" width="520" height="160" rx="18" fill="#ffffff" stroke="#ba7a20" stroke-width="4"/>
<text x="3740.0" y="2924" class="box-title" text-anchor="middle">M68 E3 Qn</text>
<text x="3508" y="2974" class="box-small">设置 analog out</text>
<rect id="p6" x="4160" y="2880" width="420" height="160" rx="18" fill="#ffffff" stroke="#ba7a20" stroke-width="4"/>
<text x="4370.0" y="2924" class="box-title" text-anchor="middle">M66 E0 L0</text>
<text x="4188" y="2974" class="box-small">同步解释器与 motion</text>
<path d="M 920 2960 L 1100 2960" fill="none" stroke="#1f3b57" stroke-width="6" marker-end="url(#arrow)"/>
<path d="M 1720 2960 L 1900 2960" fill="none" stroke="#1f3b57" stroke-width="6" marker-end="url(#arrow)"/>
<path d="M 2480 2960 L 2660 2960" fill="none" stroke="#1f3b57" stroke-width="6" marker-end="url(#arrow)"/>
<path d="M 3300 2960 L 3480 2960" fill="none" stroke="#1f3b57" stroke-width="6" marker-end="url(#arrow)"/>
<path d="M 4000 2960 L 4160 2960" fill="none" stroke="#1f3b57" stroke-width="6" marker-end="url(#arrow)"/>
<rect id="m429" x="520" y="3260" width="660" height="150" rx="18" fill="#ffffff" stroke="#ba7a20" stroke-width="4"/>
<text x="850.0" y="3304" class="box-title" text-anchor="middle">M429</text>
<text x="548" y="3354" class="box-small">type 0: identity</text>
<rect id="m428" x="1480" y="3260" width="660" height="150" rx="18" fill="#ffffff" stroke="#ba7a20" stroke-width="4"/>
<text x="1810.0" y="3304" class="box-title" text-anchor="middle">M428</text>
<text x="1508" y="3354" class="box-small">type 1: xyzbc TRT</text>
<rect id="m430" x="2440" y="3260" width="660" height="150" rx="18" fill="#ffffff" stroke="#ba7a20" stroke-width="4"/>
<text x="2770.0" y="3304" class="box-title" text-anchor="middle">M430</text>
<text x="2468" y="3354" class="box-small">type 2: userk</text>
<rect id="aout" x="3400" y="3260" width="720" height="150" rx="18" fill="#ffffff" stroke="#ba7a20" stroke-width="4"/>
<text x="3760.0" y="3304" class="box-title" text-anchor="middle">motion.analog-out-03</text>
<text x="3428" y="3354" class="box-small">HAL net :kinstype-select</text>
<path d="M 850 3040 L 850 3260" fill="none" stroke="#1f3b57" stroke-width="6" marker-end="url(#arrow)"/>
<rect x="700.0" y="3102.0" width="300" height="46" rx="12" fill="#ffffff" opacity="0.92"/>
<text x="850.0" y="3134.0" class="arrow-label" text-anchor="middle">M429</text>
<path d="M 2200 3040 L 1810 3260" fill="none" stroke="#1f3b57" stroke-width="6" marker-end="url(#arrow)"/>
<rect x="1855.0" y="3102.0" width="300" height="46" rx="12" fill="#ffffff" opacity="0.92"/>
<text x="2005.0" y="3134.0" class="arrow-label" text-anchor="middle">M428</text>
<path d="M 2200 3040 L 2770 3260" fill="none" stroke="#1f3b57" stroke-width="6" marker-end="url(#arrow)"/>
<rect x="2335.0" y="3102.0" width="300" height="46" rx="12" fill="#ffffff" opacity="0.92"/>
<text x="2485.0" y="3134.0" class="arrow-label" text-anchor="middle">M430</text>
<path d="M 1180 3335 L 3400 3335" fill="none" stroke="#1f3b57" stroke-width="6" marker-end="url(#arrow)"/>
<rect x="2140.0" y="3287.0" width="300" height="46" rx="12" fill="#ffffff" opacity="0.92"/>
<text x="2290.0" y="3319.0" class="arrow-label" text-anchor="middle">Q0</text>
<path d="M 2140 3335 L 3400 3335" fill="none" stroke="#1f3b57" stroke-width="6" marker-end="url(#arrow)"/>
<rect x="2620.0" y="3287.0" width="300" height="46" rx="12" fill="#ffffff" opacity="0.92"/>
<text x="2770.0" y="3319.0" class="arrow-label" text-anchor="middle">Q1</text>
<path d="M 3100 3335 L 3400 3335" fill="none" stroke="#1f3b57" stroke-width="6" marker-end="url(#arrow)"/>
<rect x="3100.0" y="3287.0" width="300" height="46" rx="12" fill="#ffffff" opacity="0.92"/>
<text x="3250.0" y="3319.0" class="arrow-label" text-anchor="middle">Q2</text>
<rect id="swpin" x="1580" y="3700" width="760" height="170" rx="18" fill="#ffffff" stroke="#ba7a20" stroke-width="4"/>
<text x="1960.0" y="3744" class="box-title" text-anchor="middle">motion.switchkins-type</text>
<text x="1608" y="3794" class="box-small">float HAL input, 被截断为整数 type</text>
<rect id="handle" x="2580" y="3700" width="820" height="170" rx="18" fill="#ffffff" stroke="#ba7a20" stroke-width="4"/>
<text x="2990.0" y="3744" class="box-title" text-anchor="middle">handle_kinematicsSwitch()</text>
<text x="2608" y="3794" class="box-small">servo-thread 每周期检测并调用</text>
<text x="2608" y="3828" class="box-small">kinematicsSwitch(type)</text>
<path d="M 3760 3410 L 1960 3700" fill="none" stroke="#1f3b57" stroke-width="6" marker-end="url(#arrow)"/>
<path d="M 2340 3785 L 2580 3785" fill="none" stroke="#1f3b57" stroke-width="6" marker-end="url(#arrow)"/>
<rect x="120" y="4230" width="4560" height="1520" rx="36" fill="#eef9f8" stroke="#b8c7dc" stroke-width="4"/>
<text x="154" y="4300" class="section-title">5. 运行时运动数据流与 Vismach 显示</text>
<rect id="gcode" x="240" y="4390" width="520" height="150" rx="18" fill="#ffffff" stroke="#2b7a78" stroke-width="4"/>
<text x="500.0" y="4434" class="box-title" text-anchor="middle">G-code XYZBC</text>
<text x="268" y="4484" class="box-small">程序指令</text>
<rect id="interp" x="920" y="4390" width="520" height="150" rx="18" fill="#ffffff" stroke="#2b7a78" stroke-width="4"/>
<text x="1180.0" y="4434" class="box-title" text-anchor="middle">interpreter</text>
<text x="948" y="4484" class="box-small">RS274NGC</text>
<rect id="task" x="1600" y="4390" width="520" height="150" rx="18" fill="#ffffff" stroke="#2b7a78" stroke-width="4"/>
<text x="1860.0" y="4434" class="box-title" text-anchor="middle">milltask</text>
<text x="1628" y="4484" class="box-small">任务层</text>
<rect id="tp" x="2280" y="4390" width="620" height="150" rx="18" fill="#ffffff" stroke="#2b7a78" stroke-width="4"/>
<text x="2590.0" y="4434" class="box-title" text-anchor="middle">trajectory planner</text>
<text x="2308" y="4484" class="box-small">生成 carte_pos_cmd</text>
<rect id="inv" x="3060" y="4390" width="760" height="150" rx="18" fill="#ffffff" stroke="#2b7a78" stroke-width="4"/>
<text x="3440.0" y="4434" class="box-title" text-anchor="middle">kinematicsInverse()</text>
<text x="3088" y="4484" class="box-small">按当前 type 分派</text>
<rect id="joint" x="3980" y="4390" width="560" height="150" rx="18" fill="#ffffff" stroke="#2b7a78" stroke-width="4"/>
<text x="4260.0" y="4434" class="box-title" text-anchor="middle">joint targets</text>
<text x="4008" y="4484" class="box-small">X/Y/Z/B/C joint 目标</text>
<path d="M 760 4465 L 920 4465" fill="none" stroke="#1f3b57" stroke-width="6" marker-end="url(#arrow)"/>
<path d="M 1440 4465 L 1600 4465" fill="none" stroke="#1f3b57" stroke-width="6" marker-end="url(#arrow)"/>
<path d="M 2120 4465 L 2280 4465" fill="none" stroke="#1f3b57" stroke-width="6" marker-end="url(#arrow)"/>
<path d="M 2900 4465 L 3060 4465" fill="none" stroke="#1f3b57" stroke-width="6" marker-end="url(#arrow)"/>
<path d="M 3820 4465 L 3980 4465" fill="none" stroke="#1f3b57" stroke-width="6" marker-end="url(#arrow)"/>
<rect id="idinv" x="540" y="4840" width="760" height="150" rx="18" fill="#ffffff" stroke="#2b7a78" stroke-width="4"/>
<text x="920.0" y="4884" class="box-title" text-anchor="middle">type 0: identity</text>
<text x="568" y="4934" class="box-small">一一映射</text>
<rect id="trtinv" x="1510" y="4840" width="1040" height="210" rx="18" fill="#ffffff" stroke="#2b7a78" stroke-width="4"/>
<text x="2030.0" y="4884" class="box-title" text-anchor="middle">type 1: xyzbcKinematicsInverse</text>
<text x="1538" y="4934" class="box-small">使用 B/C</text>
<text x="1538" y="4968" class="box-small">角度、x-offset=-20、z-offset=-15、tool-offset、旋转</text>
<text x="1538" y="5002" class="box-small">中心计算 joint</text>
<rect id="userkinv" x="2760" y="4840" width="760" height="150" rx="18" fill="#ffffff" stroke="#2b7a78" stroke-width="4"/>
<text x="3140.0" y="4884" class="box-title" text-anchor="middle">type 2: userk</text>
<text x="2788" y="4934" class="box-small">模板示例</text>
<path d="M 3440 4540 L 3440 4720 L 920 4720 L 920 4840" fill="none" stroke="#1f3b57" stroke-width="6" marker-end="url(#arrow)"/>
<path d="M 3440 4540 L 3440 4720 L 2030 4720 L 2030 4840" fill="none" stroke="#1f3b57" stroke-width="6" marker-end="url(#arrow)"/>
<path d="M 3440 4540 L 3440 4720 L 3140 4720 L 3140 4840" fill="none" stroke="#1f3b57" stroke-width="6" marker-end="url(#arrow)"/>
<rect id="fb" x="820" y="5260" width="1000" height="160" rx="18" fill="#ffffff" stroke="#2b7a78" stroke-width="4"/>
<text x="1320.0" y="5304" class="box-title" text-anchor="middle">仿真反馈闭环</text>
<text x="848" y="5354" class="box-small">joint.N.motor-pos-cmd -&gt; pid/mux2 -&gt;</text>
<text x="848" y="5388" class="box-small">joint.N.motor-pos-fb</text>
<rect id="vis" x="2140" y="5260" width="1000" height="160" rx="18" fill="#ffffff" stroke="#2b7a78" stroke-width="4"/>
<text x="2640.0" y="5304" class="box-title" text-anchor="middle">Vismach</text>
<text x="2168" y="5354" class="box-small">joint feedback 驱动 table/saddle/spindle/B/C 模型</text>
<rect id="panel" x="3460" y="5260" width="780" height="160" rx="18" fill="#ffffff" stroke="#2b7a78" stroke-width="4"/>
<text x="3850.0" y="5304" class="box-title" text-anchor="middle">PyVCP 状态</text>
<text x="3488" y="5354" class="box-small">kinstype.is-N 显示当前运动学</text>
<path d="M 4260 4540 L 1320 5260" fill="none" stroke="#1f3b57" stroke-width="6" marker-end="url(#arrow)"/>
<path d="M 1820 5340 L 2140 5340" fill="none" stroke="#1f3b57" stroke-width="6" marker-end="url(#arrow)"/>
<path d="M 2980 3870 L 3850 5260" fill="none" stroke="#1f3b57" stroke-width="6" marker-end="url(#arrow)" stroke-dasharray="18 12"/>
<rect x="3265.0" y="4517.0" width="300" height="46" rx="12" fill="#ffffff" opacity="0.92"/>
<text x="3415.0" y="4549.0" class="arrow-label" text-anchor="middle">kinstype.is-N</text>
<rect x="120" y="5860" width="4560" height="1060" rx="36" fill="#f7f7f7" stroke="#b8c7dc" stroke-width="4"/>
<text x="154" y="5930" class="section-title">6. 自动打开的演示 G-code 流程</text>
<rect id="d1" x="250" y="6020" width="660" height="150" rx="18" fill="#ffffff" stroke="#5b6570" stroke-width="4"/>
<text x="580.0" y="6064" class="box-title" text-anchor="middle">xyzbc_switchkins.ngc</text>
<text x="278" y="6114" class="box-small">AXIS OPEN_FILE</text>
<rect id="d2" x="1080" y="6020" width="760" height="150" rx="18" fill="#ffffff" stroke="#5b6570" stroke-width="4"/>
<text x="1460.0" y="6064" class="box-title" text-anchor="middle">xyzbc_switchkins_sub</text>
<text x="1108" y="6114" class="box-small">四个象限重复</text>
<rect id="d3" x="2010" y="6020" width="620" height="150" rx="18" fill="#ffffff" stroke="#5b6570" stroke-width="4"/>
<text x="2320.0" y="6064" class="box-title" text-anchor="middle">M429 identity</text>
<text x="2038" y="6114" class="box-small">安全定位 / 重设 G54</text>
<rect id="d4" x="2800" y="6020" width="620" height="150" rx="18" fill="#ffffff" stroke="#5b6570" stroke-width="4"/>
<text x="3110.0" y="6064" class="box-title" text-anchor="middle">helix_bc</text>
<text x="2828" y="6114" class="box-small">准备螺旋插补</text>
<rect id="d5" x="3590" y="6020" width="700" height="150" rx="18" fill="#ffffff" stroke="#5b6570" stroke-width="4"/>
<text x="3940.0" y="6064" class="box-title" text-anchor="middle">M428 xyzbc TRT</text>
<text x="3618" y="6114" class="box-small">B/C 倾斜后加工</text>
<path d="M 910 6095 L 1080 6095" fill="none" stroke="#1f3b57" stroke-width="6" marker-end="url(#arrow)"/>
<path d="M 1840 6095 L 2010 6095" fill="none" stroke="#1f3b57" stroke-width="6" marker-end="url(#arrow)"/>
<path d="M 2630 6095 L 2800 6095" fill="none" stroke="#1f3b57" stroke-width="6" marker-end="url(#arrow)"/>
<path d="M 3420 6095 L 3590 6095" fill="none" stroke="#1f3b57" stroke-width="6" marker-end="url(#arrow)"/>
<rect x="360" y="6380" width="1780" height="330" rx="18" fill="#fff7e6" stroke="#d49b32" stroke-width="4"/>
<text x="388" y="6426" class="note-title">演示循环</text>
<text x="388" y="6470" class="note-text">每个象限先 M429 切回 identity。</text>
<text x="388" y="6502" class="note-text">G53 回机床安全位置G10 L20 P0 重设 G54。</text>
<text x="388" y="6534" class="note-text">移动到象限中心后调用 helix_bc。</text>
<rect x="2540" y="6380" width="1780" height="330" rx="18" fill="#fff7e6" stroke="#d49b32" stroke-width="4"/>
<text x="2568" y="6426" class="note-title">helix_bc 核心</text>
<text x="2568" y="6470" class="note-text">M428 切换到 xyzbc TRT。</text>
<text x="2568" y="6502" class="note-text">G0 B#&lt;b&gt; C#&lt;c&gt; 设置转台角度。</text>
<text x="2568" y="6534" class="note-text">G2 I#&lt;r&gt; Z#&lt;zmin&gt; P#&lt;n&gt; 执行螺旋插补。</text>
<text x="2400" y="7080" class="legend" text-anchor="middle">输出文件: 项目分析/5axis-xyzbc-trt-sim高清流程图.png源文件: 项目分析/5axis-xyzbc-trt-sim高清流程图.svg</text>
</svg>

After

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -0,0 +1,597 @@
# LinuxCNC 数据系统核心原理
本文从本质上解释 LinuxCNC 的数据系统。这里的“数据系统”不是单个数据库,也不是单个消息队列,而是由多个不同实时等级、不同生命周期、不同所有权的数据通道组合而成。
LinuxCNC 的核心设计目标是:把非实时的解释、界面、配置、任务调度,与实时运动控制隔离开,同时又让它们能以受控方式交换命令、状态和信号。
## 1. 一句话理解
LinuxCNC 的数据系统本质上分为五层:
1. **INI 配置数据**启动时读取决定机器拓扑、模块、限位、速度、GUI、HAL 文件。
2. **NML 命令/状态数据**跨进程通信总线GUI、halui、task、server 通过它交换命令和状态。
3. **Task/Interpreter 数据**:解释 G-code维护模态状态、坐标系、刀补、运行队列并把规范动作转成 motion/io 命令。
4. **Motion 实时共享数据**task 和实时 motion 模块之间的 command/status/config 共享内存。
5. **HAL 信号数据**:实时和用户态组件共享的 pin/signal/parameter 网络,连接 motion、驱动、GUI、Vismach 和自定义组件。
简化图:
```text
INI
-> linuxcnc 启动脚本
-> NML 通道 / HAL 模块 / GUI / task / motion
GUI / halui
-> NML command
-> milltask
-> interpreter / canonical commands
-> motion command shared memory
-> realtime motion
-> HAL pins/signals
-> feedback/status
-> EMC_STAT / GUI 显示
```
## 2. 为什么 LinuxCNC 要分成这些数据系统
CNC 控制同时有两类完全不同的需求:
- **人机界面、G-code 解释、文件读取、坐标系管理**:逻辑复杂,但不要求微秒级确定性。
- **伺服周期、轨迹插补、关节输出、限位、探针采样**:必须稳定、周期性、可预测。
因此 LinuxCNC 不能把所有数据都放在一个普通进程里处理。它将系统拆成多个进程和实时模块:
- GUI 可以慢一些,甚至短暂卡顿。
- task 可以做解释器、队列、状态同步。
- motion 必须按 servo period 周期运行。
- HAL 负责把实时数据以 pin/signal 方式连接起来。
本质原则:
```text
非实时层负责“决定要做什么”
实时层负责“按确定周期执行”
HAL 负责“把实时变量接到机器/仿真/GUI”
NML 负责“进程之间传命令和状态”
```
## 3. INI启动配置数据
INI 是 LinuxCNC 的静态配置入口。它不是运行时主数据通道,而是启动时的装配说明书。
典型职责:
- 选择 GUI`[DISPLAY] DISPLAY = axis`
- 指定 G-code 自动打开文件:`[DISPLAY] OPEN_FILE = ...`
- 指定运动学:`[KINS] KINEMATICS = ...`
- 指定 joint 数:`[KINS] JOINTS = ...`
- 指定坐标字母:`[TRAJ] COORDINATES = ...`
- 指定实时 motion 模块参数:`[EMCMOT] SERVO_PERIOD = ...`
- 指定 HAL 文件:`[HAL] HALFILE = ...`
- 指定单条 HAL 命令:`[HAL] HALCMD = ...`
- 指定 NML 文件:`[EMC] NML_FILE = ...`,默认常见为 `configs/common/linuxcnc.nml`
`scripts/linuxcnc` 使用 `inivar` 从 INI 中读取这些配置,然后按顺序启动 server、task、halui、HAL、GUI。
INI 的特点:
- 启动时影响很大。
- 运行中多数值不会自动重新读取。
- 很多 INI 项会被 task/motion 初始化代码转写到 NML 状态或 HAL pin 中。
- INI 本身不负责实时连接,实时连接由 HAL 完成。
## 4. NML跨进程命令/状态总线
NML 是 LinuxCNC 的进程间通信系统。它把 GUI、halui、task、server 等非实时进程连接起来。
默认 NML 配置可见:
```text
configs/common/linuxcnc.nml
```
其中定义了三个顶层 buffer
```text
emcCommand
emcStatus
emcError
```
它们的本质职责:
- `emcCommand`GUI/halui 发命令给 task。
- `emcStatus`task 汇总系统状态供 GUI/halui 读取。
- `emcError`:错误和操作信息通道。
`linuxcncsvr` 是这些 NML channel 的 master/server。启动脚本注释也说明`linuxcncsvr` 默认第一个启动,因为它创建/持有 NML channel。
## 5. EMC_STATGUI 看到的系统状态
LinuxCNC 顶层状态结构是 `EMC_STAT`,定义在:
```text
src/emc/nml_intf/emc_nml.hh
```
它聚合了:
```text
EMC_STAT
├─ EMC_TASK_STAT task
├─ EMC_MOTION_STAT motion
└─ EMC_IO_STAT io
```
其中 motion 部分又包含:
```text
EMC_MOTION_STAT
├─ EMC_TRAJ_STAT traj
├─ EMC_JOINT_STAT joint[]
├─ EMC_AXIS_STAT axis[]
├─ EMC_SPINDLE_STAT spindle[]
├─ synch_di[]
├─ synch_do[]
├─ analog_input[]
└─ analog_output[]
```
GUI 看到的大部分状态都来自 `EMC_STAT`
- 当前模式manual/mdi/auto
- 当前任务状态estop/off/on
- 当前执行状态done/exec/error
- 当前文件、当前行、读到哪一行
- 当前 G-code/M-code 模态
- 当前坐标系偏置、G92、刀补
- 当前 commanded position
- 当前 actual position
- joint 状态
- spindle 状态
- motion queue 状态
AXIS 的 Python 扩展通过 `RCS_STAT_CHANNEL` 读取 `emcStatus`poll 时将 `EMC_STAT` 复制到本地 Python 对象中。
## 6. EMC_COMMANDGUI/halui 发出的命令
GUI、halui、外部客户端一般不会直接写 motion 共享内存,而是向 `emcCommand` NML channel 写命令。
例如:
- 上电/下电
- 解除急停
- 切换模式
- MDI 命令
- 打开程序
- cycle start
- pause/resume
- jog
这些命令先进入 task。task 决定命令是否合法、当前状态是否允许执行,以及是否需要调用 interpreter 或 motion。
本质上:
```text
GUI/halui 不直接控制伺服周期
GUI/halui 发送意图
task 负责调度和状态一致性
motion 负责实时执行
```
## 7. Task/Interpreter命令解释和调度层
`milltask` 是 LinuxCNC 的任务协调进程。它同时处理:
- NML command
- interpreter G-code 解释
- motion command 发送
- IO/tool/spindle/coolant 命令
- task 状态同步
- EMC_STAT 汇总更新
关键文件:
```text
src/emc/task/emctaskmain.cc
src/emc/task/taskintf.cc
src/emc/rs274ngc/
src/emc/task/emccanon.cc
```
典型链路:
```text
GUI cycle start
-> NML emcCommand
-> milltask
-> interpreter 读取 G-code
-> canonical commands
-> taskintf.cc
-> usrmotWriteEmcmotCommand()
-> realtime motion shared memory
```
解释器维护的不是简单的“当前行文本”,而是一套 CNC 模态状态:
- G 模态组
- M 模态组
- 坐标系 G54/G55/...
- G92 偏置
- 工件平面
- 距离模式
- 进给模式
- 刀具长度补偿
- 半径补偿
- 子程序调用层级
- 参数文件变量
- remap 状态
这些数据在 task/interpreter 层处理,然后转成 motion 能理解的动作。
## 8. Canonical command解释器和 motion 之间的语义桥
解释器不会直接操作 joint。它输出更抽象的“规范动作”
- 直线移动
- 圆弧移动
- 设置速度
- 设置主轴
- 设置 IO
- 设置 motion analog output
- 等待输入
- 换刀
例如 `M68 E3 Q1` 的链路是:
```text
RS274NGC 解释 M68
-> SET_AUX_OUTPUT_VALUE(3, 1)
-> emccanon.cc 创建 EMC_MOTION_SET_AOUT
-> taskintf.cc: emcMotionSetAout()
-> usrmotWriteEmcmotCommand()
-> motion command shared memory
-> motion.analog-out-03 = 1
```
这说明 G-code 不是直接写 HAL pin而是通过解释器、canonical 层、task、motion再由 motion 暴露 HAL pin。
## 9. Motion 共享内存task 与实时 motion 的边界
实时 motion 的核心共享结构是:
```text
src/emc/motion/motion_struct.h
```
核心结构:
```c
emcmot_struct_t {
command_mutex;
emcmot_command_t command;
emcmot_status_t status;
emcmot_config_t config;
emcmot_error_t error;
emcmot_internal_t internal;
}
```
这是一块 task 和 realtime motion 都能访问的共享内存区域。
其中:
- `emcmot_command_t`task 写入motion 读取。
- `emcmot_status_t`motion 周期更新task 读取。
- `emcmot_config_t`:机器配置和 motion 参数。
- `emcmot_internal_t`motion 内部轨迹规划/调试状态。
task 写 motion command 的路径:
```text
taskintf.cc
-> usrmotWriteEmcmotCommand()
-> 加 command_mutex
-> 复制 emcmot_command_t 到共享内存
-> 等待 motion 回显 commandNumEcho
```
motion status 读取有一个重要细节:`emcmot_status_t``head`/`tail` 字段。motion 更新状态时先改 `head`,完成后设置 `tail=head`。读取方复制后检查 `head == tail`,避免读到半更新的数据。
这是 LinuxCNC 实时数据一致性的关键手段之一。
## 10. Motion 实时循环:周期性状态机
motion 控制循环位于:
```text
src/emc/motion/control.c
```
每个 servo cycle 处理大致顺序:
```text
读取 homing/input pins
处理 switchkins 切换
读取 HAL 输入
执行 forward kinematics
处理 probe
检查 fault/limit
确定运行模式
处理 jog/homing
轨迹规划取点
inverse kinematics
插补到 joint
输出到 HAL
更新 status
heartbeat++
```
它的本质职责:
- 在固定周期内维护运动状态。
- 从轨迹规划器获取下一个笛卡尔点。
- 调用运动学把笛卡尔位置转换为 joint 位置。
- 输出 joint 命令、spindle、IO、状态 HAL pin。
- 读取反馈、探针、限位、外部 offset 等 HAL pin。
- 更新 `emcmot_status_t` 给 task 读取。
motion 不读取 G-code 文件,也不关心 AXIS 界面。它只执行已经被 task/interpreter 转换过的命令。
## 11. HAL实时信号网络
HAL 是 LinuxCNC 最容易被误解的部分。它不是 NML也不是普通配置文件。HAL 的本质是一个共享内存对象图:
```text
HAL shared memory
├─ component list
├─ pin list
├─ signal list
├─ parameter list
├─ function list
└─ thread list
```
这些结构定义在:
```text
src/hal/hal_priv.h
```
关键概念:
### Component
组件是 pin/function/parameter 的拥有者。例如:
- `motmod`
- `pid`
- `mux2`
- `halui`
- `xyzbc-trt-gui`
- 自定义 `.comp`
组件调用 `hal_init()` 注册到 HAL。
### Pin
pin 是组件暴露的数据端口。每个 pin 有:
- 名字
- 类型
- 方向
- 所属 component
- 连接的 signal
方向包括:
- input
- output
- io
### Signal
signal 是多个 pin 共享的一块数据值。`net` 命令本质上是:
```text
把多个 pin 的 data pointer 指向同一个 signal value
```
例如:
```hal
net :kinstype-select <= motion.analog-out-03 => motion.switchkins-type
```
表示:
```text
motion.analog-out-03 写 signal :kinstype-select
motion.switchkins-type 读 signal :kinstype-select
```
### Parameter
parameter 通常是配置量或调试量,可用 `setp` 设置。
### Function 和 Thread
实时组件导出 functionHAL thread 周期调用这些 function。
例如:
```hal
addf motion-command-handler servo-thread
addf motion-controller servo-thread
addf J0_pid.do-pid-calcs servo-thread
```
这决定了每个 servo period 内函数执行顺序。
## 12. HAL 与 NML 的本质区别
| 项目 | NML | HAL |
| --- | --- | --- |
| 本质 | 跨进程消息/状态通道 | 共享内存信号网络 |
| 主要用途 | GUI、task、server 通信 | 实时组件连接 |
| 数据形态 | command/status/error 消息 | pin/signal/parameter 值 |
| 时间特性 | 非实时或软实时 | 可用于实时线程 |
| 典型读写者 | AXIS、halui、milltask | motmod、驱动、pid、Vismach、halui |
| 示例 | `emcCommand`, `emcStatus` | `motion.analog-out-03`, `joint.0.motor-pos-cmd` |
重要结论:
```text
NML 表达“系统命令与系统状态”
HAL 表达“机器信号与实时变量”
```
## 13. 数据所有权原则
LinuxCNC 的数据系统依赖清晰的所有权。
典型规则:
- GUI 不直接写 joint command。
- task 不直接修改 HAL 伺服输出。
- motion 不解释 G-code。
- HAL signal 通常只能有一个 writer。
- motion status 由 realtime motion 写task/GUI 读。
- EMC_STAT 由 task 汇总写GUI/halui 读。
- INI 是启动配置,不是周期数据通道。
如果违反这些边界,系统会变得不可预测。
## 14. 数据刷新频率和一致性
不同数据有不同刷新频率:
- servo-thread通常 1 ms 或更快。
- task cycle常见 10 ms 左右。
- GUI poll通常几十毫秒级。
- NML status由 task 汇总后供 GUI 读取。
- HAL pin实时线程中按 thread 周期更新。
所以 GUI 看到的位置不是“每个伺服周期的每一个点”,而是 task/GUI poll 后的状态快照。
这也是为什么实时控制必须在 motion/HAL 内完成,不能依赖 GUI。
## 15. xyzbc-trt 配置中的具体体现
`xyzbc-trt.ini` 为例:
### INI 层
```ini
[KINS]
KINEMATICS = xyzbc-trt-kins sparm=identityfirst
JOINTS = 5
[TRAJ]
COORDINATES = XYZBC
[HAL]
HALFILE = LIB:basic_sim.tcl
HALCMD = net :kinstype-select <= motion.analog-out-03 => motion.switchkins-type
```
INI 决定:
- 加载什么运动学模块。
- 有多少 joint。
- HAL 怎样连接。
- GUI 加载什么面板。
### HAL 层
```hal
motion.analog-out-03 -> motion.switchkins-type
joint.N.pos-fb -> xyzbc-trt-gui.*
motion.tooloffset.z -> xyzbc-trt-kins.tool-offset
```
HAL 决定:
- M68 输出如何进入 switchkins。
- joint feedback 如何驱动 Vismach。
- 刀长补偿如何进入运动学。
### NML/task/interpreter 层
按下 PyVCP 按钮:
```text
pyvcp button
-> halui MDI command
-> NML command
-> milltask
-> interpreter 执行 M428/M429/M430 remap
```
### motion 层
`M68 E3 Q1` 最终写入:
```text
motion.analog-out-03 = 1
```
HAL 将其连接到:
```text
motion.switchkins-type = 1
```
motion servo cycle 检测到变化:
```text
handle_kinematicsSwitch()
-> kinematicsSwitch(1)
-> 激活 xyzbcKinematicsInverse / Forward
```
### Vismach 层
Vismach 不参与实时控制,只读取 HAL pin
```text
joint feedback + offset pins -> 三维模型变换
```
它是 HAL 数据消费者,不是运动控制源。
## 16. 本质总结
LinuxCNC 数据系统的核心不是“一个中心数据库”,而是四种不同性质的数据机制协作:
1. **INI**:装配系统。
2. **NML**:让进程交换命令、状态、错误。
3. **Task/Interpreter**:把人的意图和 G-code 转换成规范动作。
4. **Motion shared memory**:连接非实时 task 和实时 motion。
5. **HAL**:把实时变量连接成机器信号网络。
最终形成一条严格分层的数据链:
```text
人/程序意图
-> NML command
-> task/interpreter
-> canonical command
-> motion command shared memory
-> realtime motion
-> HAL pins/signals
-> 机器/仿真反馈
-> motion status
-> EMC_STAT
-> GUI/halui 显示
```
理解这条链,就能解释 LinuxCNC 中大多数现象:
- 为什么 GUI 按钮通常不是直接控制实时变量。
- 为什么 `M68` 可以改变 HAL pin。
- 为什么 `motion.switchkins-type` 要通过 `motion.analog-out-03` 控制。
- 为什么 Vismach 只需要连 HAL pin 就能显示机床。
- 为什么 task 和 motion 之间要有 command/status 共享内存。
- 为什么 HAL signal 通常要求单 writer。
- 为什么实时动作不能依赖 GUI poll。

Binary file not shown.

After

Width:  |  Height:  |  Size: 947 KiB

View File

@@ -0,0 +1,197 @@
<svg xmlns="http://www.w3.org/2000/svg" width="5200" height="6600" viewBox="0 0 5200 6600">
<defs>
<marker id="arrow" markerWidth="18" markerHeight="18" refX="14" refY="6" orient="auto" markerUnits="strokeWidth">
<path d="M2,2 L14,6 L2,10 Z" fill="#203a53"/>
</marker>
<style>
svg { background: #ffffff; }
text { font-family: Noto Sans CJK SC, DejaVu Sans, sans-serif; fill: #17212b; }
.title { font-size: 78px; font-weight: 850; }
.subtitle { font-size: 35px; fill: #53606c; }
.section { font-size: 43px; font-weight: 850; fill: #233d56; }
.boxtitle { font-size: 35px; font-weight: 850; }
.boxbody { font-size: 27px; fill: #33495d; }
.smalltitle { font-size: 29px; font-weight: 850; }
.smallbody { font-size: 24px; fill: #35495e; }
.label { font-size: 25px; font-weight: 750; fill: #25415d; }
.foot { font-size: 26px; fill: #52606d; }
</style>
</defs>
<rect x="0" y="0" width="5200" height="6600" fill="#ffffff"/>
<text x="2600" y="118" class="title" text-anchor="middle">LinuxCNC 数据系统核心原理</text>
<text x="2600" y="176" class="subtitle" text-anchor="middle">INI / NML / Task / Interpreter / Motion Shared Memory / HAL / GUI</text>
<rect x="130" y="260" width="4940" height="660" rx="36" fill="#eef6ff" stroke="#9fb9d2" stroke-width="4"/>
<text x="164" y="326" class="section">1. 静态装配层INI 决定系统如何启动</text>
<rect x="250" y="390" width="960" height="250" rx="18" fill="#ffffff" stroke="#386d9d" stroke-width="4"/>
<text x="730.0" y="434" class="boxtitle" text-anchor="middle">INI 文件</text>
<text x="278" y="474" class="boxbody">机器拓扑、KINS、JOINTS、TRAJ、DISPLAY、HALFILE、HA</text>
<text x="278" y="505" class="boxbody">LCMD、NML_FILE。INI</text>
<text x="278" y="536" class="boxbody">是启动装配说明,不是实时数据通道。</text>
<rect x="1510" y="390" width="920" height="250" rx="18" fill="#ffffff" stroke="#386d9d" stroke-width="4"/>
<text x="1970.0" y="434" class="boxtitle" text-anchor="middle">scripts/linuxcnc</text>
<text x="1538" y="474" class="boxbody">读取 INI启动</text>
<text x="1538" y="505" class="boxbody">linuxcncsvr、realtime、milltask、halui、HAL</text>
<text x="1538" y="536" class="boxbody">配置和 GUI。</text>
<rect x="2730" y="390" width="900" height="250" rx="18" fill="#ffffff" stroke="#386d9d" stroke-width="4"/>
<text x="3180.0" y="434" class="boxtitle" text-anchor="middle">模块装配</text>
<text x="2758" y="474" class="boxbody">loadrt motmod / kinematics / pid /</text>
<text x="2758" y="505" class="boxbody">驱动loadusr halui / Vismach / GUI 组件。</text>
<rect x="3910" y="390" width="920" height="250" rx="18" fill="#ffffff" stroke="#386d9d" stroke-width="4"/>
<text x="4370.0" y="434" class="boxtitle" text-anchor="middle">初始参数落地</text>
<text x="3938" y="474" class="boxbody">INI 值被转写到 task 状态、motion config、HAL</text>
<text x="3938" y="505" class="boxbody">pin/param 或组件启动参数。</text>
<path d="M 1210 515 L 1510 515" fill="none" stroke="#203a53" stroke-width="6" marker-end="url(#arrow)"/>
<path d="M 2430 515 L 2730 515" fill="none" stroke="#203a53" stroke-width="6" marker-end="url(#arrow)"/>
<path d="M 3630 515 L 3910 515" fill="none" stroke="#203a53" stroke-width="6" marker-end="url(#arrow)"/>
<rect x="130" y="1020" width="4940" height="940" rx="36" fill="#f8f3ff" stroke="#b59ad7" stroke-width="4"/>
<text x="164" y="1086" class="section">2. NML跨进程命令 / 状态 / 错误总线</text>
<rect x="250" y="1180" width="930" height="250" rx="18" fill="#ffffff" stroke="#7a4eb0" stroke-width="4"/>
<text x="715.0" y="1224" class="boxtitle" text-anchor="middle">emcCommand</text>
<text x="278" y="1264" class="boxbody">GUI、halui、外部客户端写入命令。典型命令MDI、A</text>
<text x="278" y="1295" class="boxbody">UTO_RUN、SET_MODE、SET_STATE、ABORT。</text>
<rect x="1520" y="1180" width="900" height="250" rx="18" fill="#ffffff" stroke="#7a4eb0" stroke-width="4"/>
<text x="1970.0" y="1224" class="boxtitle" text-anchor="middle">milltask</text>
<text x="1548" y="1264" class="boxbody">读取 NML 命令,检查状态合法性,调度</text>
<text x="1548" y="1295" class="boxbody">interpreter、motion、IO、tool、spindle。</text>
<rect x="2760" y="1180" width="930" height="250" rx="18" fill="#ffffff" stroke="#7a4eb0" stroke-width="4"/>
<text x="3225.0" y="1224" class="boxtitle" text-anchor="middle">emcStatus</text>
<text x="2788" y="1264" class="boxbody">task 汇总 EMC_STATtask、motion、io。GUI/halui</text>
<text x="2788" y="1295" class="boxbody">读取该状态。</text>
<rect x="4030" y="1180" width="780" height="250" rx="18" fill="#ffffff" stroke="#7a4eb0" stroke-width="4"/>
<text x="4420.0" y="1224" class="boxtitle" text-anchor="middle">emcError</text>
<text x="4058" y="1264" class="boxbody">错误、操作信息、诊断消息通道。</text>
<path d="M 1180 1305 L 1520 1305" fill="none" stroke="#203a53" stroke-width="6" marker-end="url(#arrow)"/>
<rect x="1160.0" y="1257.0" width="380" height="42" rx="10" fill="#ffffff" opacity="0.94"/>
<text x="1350.0" y="1287.0" class="label" text-anchor="middle">命令</text>
<path d="M 2420 1305 L 2760 1305" fill="none" stroke="#203a53" stroke-width="6" marker-end="url(#arrow)"/>
<rect x="2400.0" y="1257.0" width="380" height="42" rx="10" fill="#ffffff" opacity="0.94"/>
<text x="2590.0" y="1287.0" class="label" text-anchor="middle">状态汇总</text>
<path d="M 3690 1305 L 4030 1305" fill="none" stroke="#203a53" stroke-width="6" marker-end="url(#arrow)"/>
<rect x="3670.0" y="1257.0" width="380" height="42" rx="10" fill="#ffffff" opacity="0.94"/>
<text x="3860.0" y="1287.0" class="label" text-anchor="middle">错误/信息</text>
<rect x="580" y="1600" width="1180" height="190" rx="14" fill="#ffffff" stroke="#7a4eb0" stroke-width="3"/>
<text x="1170.0" y="1636" class="smalltitle" text-anchor="middle">linuxcncsvr</text>
<text x="602" y="1668" class="smallbody">NML channel master/server通常最先启动。</text>
<rect x="2260" y="1600" width="1420" height="190" rx="14" fill="#ffffff" stroke="#7a4eb0" stroke-width="3"/>
<text x="2970.0" y="1636" class="smalltitle" text-anchor="middle">EMC_STAT</text>
<text x="2282" y="1668" class="smallbody">EMC_TASK_STAT + EMC_MOTION_STAT + EMC_IO_STAT。</text>
<rect x="130" y="2060" width="4940" height="1000" rx="36" fill="#fff8eb" stroke="#d49a35" stroke-width="4"/>
<text x="164" y="2126" class="section">3. Task / Interpreter把人的意图转成规范动作</text>
<rect x="250" y="2220" width="880" height="260" rx="18" fill="#ffffff" stroke="#b36b00" stroke-width="4"/>
<text x="690.0" y="2264" class="boxtitle" text-anchor="middle">GUI / halui 意图</text>
<text x="278" y="2304" class="boxbody">按钮、MDI、自动运行、暂停、jog、模式切换。它</text>
<text x="278" y="2335" class="boxbody">们表达意图,不直接控制 servo 周期。</text>
<rect x="1420" y="2220" width="900" height="260" rx="18" fill="#ffffff" stroke="#b36b00" stroke-width="4"/>
<text x="1870.0" y="2264" class="boxtitle" text-anchor="middle">Interpreter</text>
<text x="1448" y="2304" class="boxbody">读取</text>
<text x="1448" y="2335" class="boxbody">G-code维护模态组、坐标系、G92、刀补、参数、r</text>
<text x="1448" y="2366" class="boxbody">emap、子程序调用。</text>
<rect x="2610" y="2220" width="920" height="260" rx="18" fill="#ffffff" stroke="#b36b00" stroke-width="4"/>
<text x="3070.0" y="2264" class="boxtitle" text-anchor="middle">Canonical Commands</text>
<text x="2638" y="2304" class="boxbody">直线、圆弧、主轴、IO、M68 analog</text>
<text x="2638" y="2335" class="boxbody">output、换刀等规范动作。</text>
<rect x="3820" y="2220" width="900" height="260" rx="18" fill="#ffffff" stroke="#b36b00" stroke-width="4"/>
<text x="4270.0" y="2264" class="boxtitle" text-anchor="middle">taskintf.cc</text>
<text x="3848" y="2304" class="boxbody">把规范动作转成 emcmot_command_t 或</text>
<text x="3848" y="2335" class="boxbody">IO/tool/spindle 命令。</text>
<path d="M 1130 2350 L 1420 2350" fill="none" stroke="#203a53" stroke-width="6" marker-end="url(#arrow)"/>
<path d="M 2320 2350 L 2610 2350" fill="none" stroke="#203a53" stroke-width="6" marker-end="url(#arrow)"/>
<path d="M 3530 2350 L 3820 2350" fill="none" stroke="#203a53" stroke-width="6" marker-end="url(#arrow)"/>
<rect x="640" y="2700" width="1240" height="190" rx="14" fill="#ffffff" stroke="#b36b00" stroke-width="3"/>
<text x="1260.0" y="2736" class="smalltitle" text-anchor="middle">M68 E3 Q1</text>
<text x="662" y="2768" class="smallbody">解释器 -&gt; SET_AUX_OUTPUT_VALUE -&gt; EMC_MOTION_SET_AOUT。</text>
<rect x="2350" y="2700" width="1320" height="190" rx="14" fill="#ffffff" stroke="#b36b00" stroke-width="3"/>
<text x="3010.0" y="2736" class="smalltitle" text-anchor="middle">关键边界</text>
<text x="2372" y="2768" class="smallbody">G-code 不直接写 HAL它先进入 task/interpreter。</text>
<rect x="130" y="3160" width="4940" height="1020" rx="36" fill="#eefaf1" stroke="#65a36f" stroke-width="4"/>
<text x="164" y="3226" class="section">4. Motion Shared Memory非实时 task 与实时 motion 的边界</text>
<rect x="250" y="3320" width="1050" height="300" rx="18" fill="#ffffff" stroke="#3f7d4a" stroke-width="4"/>
<text x="775.0" y="3364" class="boxtitle" text-anchor="middle">emcmot_command_t</text>
<text x="278" y="3404" class="boxbody">task 写入motion 读取。包含 command</text>
<text x="278" y="3435" class="boxbody">code、pos、vel、acc、tool_offset、AOUT/DOUT、spindle</text>
<text x="278" y="3466" class="boxbody">等命令参数。</text>
<rect x="1580" y="3320" width="1030" height="300" rx="18" fill="#ffffff" stroke="#3f7d4a" stroke-width="4"/>
<text x="2095.0" y="3364" class="boxtitle" text-anchor="middle">emcmot_status_t</text>
<text x="1608" y="3404" class="boxbody">motion 周期更新task 读取。包含</text>
<text x="1608" y="3435" class="boxbody">carte_pos_cmd/fb、joint/axis/spindle、queue、analog_ou</text>
<text x="1608" y="3466" class="boxbody">tput、heartbeat。</text>
<rect x="2890" y="3320" width="920" height="300" rx="18" fill="#ffffff" stroke="#3f7d4a" stroke-width="4"/>
<text x="3350.0" y="3364" class="boxtitle" text-anchor="middle">emcmot_config_t</text>
<text x="2918" y="3404" class="boxbody">实时 motion 配置joint 数、kinematics</text>
<text x="2918" y="3435" class="boxbody">type、速度/加速度限制等。</text>
<rect x="4090" y="3320" width="760" height="300" rx="18" fill="#ffffff" stroke="#3f7d4a" stroke-width="4"/>
<text x="4470.0" y="3364" class="boxtitle" text-anchor="middle">head / tail</text>
<text x="4118" y="3404" class="boxbody">读取 status 时检查 head ==</text>
<text x="4118" y="3435" class="boxbody">tail避免读到半更新快照。</text>
<path d="M 1300 3470 L 1580 3470" fill="none" stroke="#203a53" stroke-width="6" marker-end="url(#arrow)"/>
<rect x="1250.0" y="3422.0" width="380" height="42" rx="10" fill="#ffffff" opacity="0.94"/>
<text x="1440.0" y="3452.0" class="label" text-anchor="middle">执行后反馈</text>
<path d="M 2610 3470 L 2890 3470" fill="none" stroke="#203a53" stroke-width="6" marker-end="url(#arrow)"/>
<path d="M 3810 3470 L 4090 3470" fill="none" stroke="#203a53" stroke-width="6" marker-end="url(#arrow)"/>
<rect x="670" y="3840" width="1260" height="190" rx="14" fill="#ffffff" stroke="#3f7d4a" stroke-width="3"/>
<text x="1300.0" y="3876" class="smalltitle" text-anchor="middle">usrmotWriteEmcmotCommand()</text>
<text x="692" y="3908" class="smallbody">加 command_mutex复制 command等待 commandNumEcho。</text>
<rect x="2530" y="3840" width="1320" height="190" rx="14" fill="#ffffff" stroke="#3f7d4a" stroke-width="3"/>
<text x="3190.0" y="3876" class="smalltitle" text-anchor="middle">usrmotReadEmcmotStatus()</text>
<text x="2552" y="3908" class="smallbody">复制 status检查 head/tail 一致性。</text>
<rect x="130" y="4280" width="4940" height="1080" rx="36" fill="#eef9f8" stroke="#4b9c9a" stroke-width="4"/>
<text x="164" y="4346" class="section">5. Realtime Motion + HAL周期执行与机器信号网络</text>
<rect x="250" y="4440" width="920" height="300" rx="18" fill="#ffffff" stroke="#2b7a78" stroke-width="4"/>
<text x="710.0" y="4484" class="boxtitle" text-anchor="middle">servo-thread</text>
<text x="278" y="4524" class="boxbody">固定周期运行。执行</text>
<text x="278" y="4555" class="boxbody">motion-controller、PID、驱动、仿真组件等 HAL</text>
<text x="278" y="4586" class="boxbody">function。</text>
<rect x="1450" y="4440" width="980" height="300" rx="18" fill="#ffffff" stroke="#2b7a78" stroke-width="4"/>
<text x="1940.0" y="4484" class="boxtitle" text-anchor="middle">motion-controller</text>
<text x="1478" y="4524" class="boxbody">轨迹取点、switchkins、forward/inverse</text>
<text x="1478" y="4555" class="boxbody">kinematics、limits、probe、jog、homing、status。</text>
<rect x="2710" y="4440" width="900" height="300" rx="18" fill="#ffffff" stroke="#2b7a78" stroke-width="4"/>
<text x="3160.0" y="4484" class="boxtitle" text-anchor="middle">HAL pins</text>
<text x="2738" y="4524" class="boxbody">motion、joint、spindle、驱动、Vismach、halui</text>
<text x="2738" y="4555" class="boxbody">都通过 pin 暴露实时变量。</text>
<rect x="3890" y="4440" width="900" height="300" rx="18" fill="#ffffff" stroke="#2b7a78" stroke-width="4"/>
<text x="4340.0" y="4484" class="boxtitle" text-anchor="middle">HAL signals</text>
<text x="3918" y="4524" class="boxbody">net 命令将 pin 连接到同一 signal。通常一个</text>
<text x="3918" y="4555" class="boxbody">writer多个 reader。</text>
<path d="M 1170 4590 L 1450 4590" fill="none" stroke="#203a53" stroke-width="6" marker-end="url(#arrow)"/>
<path d="M 2430 4590 L 2710 4590" fill="none" stroke="#203a53" stroke-width="6" marker-end="url(#arrow)"/>
<path d="M 3610 4590 L 3890 4590" fill="none" stroke="#203a53" stroke-width="6" marker-end="url(#arrow)"/>
<rect x="520" y="5020" width="1200" height="190" rx="14" fill="#ffffff" stroke="#2b7a78" stroke-width="3"/>
<text x="1120.0" y="5056" class="smalltitle" text-anchor="middle">HAL shared memory</text>
<text x="542" y="5088" class="smallbody">component list / pin list / signal list / param list / function list /</text>
<text x="542" y="5115" class="smallbody">thread list。</text>
<rect x="2080" y="5020" width="1200" height="190" rx="14" fill="#ffffff" stroke="#2b7a78" stroke-width="3"/>
<text x="2680.0" y="5056" class="smalltitle" text-anchor="middle">net 的本质</text>
<text x="2102" y="5088" class="smallbody">把 pin data pointer 指向同一个 signal value。</text>
<rect x="3620" y="5020" width="1040" height="190" rx="14" fill="#ffffff" stroke="#2b7a78" stroke-width="3"/>
<text x="4140.0" y="5056" class="smalltitle" text-anchor="middle">实时原则</text>
<text x="3642" y="5088" class="smallbody">控制闭环必须在 motion/HAL 内,不能依赖 GUI poll。</text>
<rect x="130" y="5460" width="4940" height="900" rx="36" fill="#f6f7f9" stroke="#9da8b3" stroke-width="4"/>
<text x="164" y="5526" class="section">6. 状态回流与 GUI 显示</text>
<rect x="250" y="5620" width="920" height="250" rx="18" fill="#ffffff" stroke="#5b6570" stroke-width="4"/>
<text x="710.0" y="5664" class="boxtitle" text-anchor="middle">motion status</text>
<text x="278" y="5704" class="boxbody">实时 motion 更新 emcmot_status_t。</text>
<rect x="1470" y="5620" width="920" height="250" rx="18" fill="#ffffff" stroke="#5b6570" stroke-width="4"/>
<text x="1930.0" y="5664" class="boxtitle" text-anchor="middle">task update</text>
<text x="1498" y="5704" class="boxbody">emcMotionUpdate() 将 motion status 汇总到</text>
<text x="1498" y="5735" class="boxbody">EMC_MOTION_STAT。</text>
<rect x="2690" y="5620" width="920" height="250" rx="18" fill="#ffffff" stroke="#5b6570" stroke-width="4"/>
<text x="3150.0" y="5664" class="boxtitle" text-anchor="middle">EMC_STAT</text>
<text x="2718" y="5704" class="boxbody">顶层状态task + motion + io。写入 emcStatus</text>
<text x="2718" y="5735" class="boxbody">NML。</text>
<rect x="3910" y="5620" width="920" height="250" rx="18" fill="#ffffff" stroke="#5b6570" stroke-width="4"/>
<text x="4370.0" y="5664" class="boxtitle" text-anchor="middle">GUI / halui</text>
<text x="3938" y="5704" class="boxbody">poll</text>
<text x="3938" y="5735" class="boxbody">emcStatus显示位置、模式、状态、错误、队列、模</text>
<text x="3938" y="5766" class="boxbody">态。</text>
<path d="M 1170 5745 L 1470 5745" fill="none" stroke="#203a53" stroke-width="6" marker-end="url(#arrow)"/>
<path d="M 2390 5745 L 2690 5745" fill="none" stroke="#203a53" stroke-width="6" marker-end="url(#arrow)"/>
<path d="M 3610 5745 L 3910 5745" fill="none" stroke="#203a53" stroke-width="6" marker-end="url(#arrow)"/>
<path d="M 4360 5620 L 4360 5350 L 390 5350 L 390 4740" fill="none" stroke="#203a53" stroke-width="6" marker-end="url(#arrow)" stroke-dasharray="18 12"/>
<rect x="190" y="5298" width="400" height="42" rx="10" fill="#ffffff" opacity="0.94"/>
<text x="390" y="5328" class="label" text-anchor="middle">HAL/Vismach 也可直接读 HAL</text>
<text x="2600" y="6460" class="foot" text-anchor="middle">核心原则NML 表达系统命令/状态HAL 表达实时机器信号motion shared memory 是 task 与实时控制的边界。</text>
<text x="2600" y="6505" class="foot" text-anchor="middle">输出文件:项目分析/LinuxCNC数据系统核心原理高清流程图.png源文件项目分析/LinuxCNC数据系统核心原理高清流程图.svg</text>
</svg>

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -0,0 +1,184 @@
#!/usr/bin/env python3
from html import escape
W = 5200
H = 6600
FONT = "Noto Sans CJK SC, DejaVu Sans, sans-serif"
def units(s):
return sum(2 if ord(ch) > 127 else 1 for ch in s)
def wrap(text, max_units):
result = []
line = ""
for token in text.split(" "):
parts = [token]
if units(token) > max_units:
parts = []
cur = ""
for ch in token:
if units(cur + ch) > max_units and cur:
parts.append(cur)
cur = ch
else:
cur += ch
if cur:
parts.append(cur)
for part in parts:
cand = part if not line else line + " " + part
if units(cand) <= max_units:
line = cand
else:
if line:
result.append(line)
line = part
if line:
result.append(line)
return result
class SVG:
def __init__(self):
self.out = []
def add(self, s):
self.out.append(s)
def section(self, x, y, w, h, title, fill, stroke="#bdcbd8"):
self.add(f'<rect x="{x}" y="{y}" width="{w}" height="{h}" rx="36" fill="{fill}" stroke="{stroke}" stroke-width="4"/>')
self.add(f'<text x="{x+34}" y="{y+66}" class="section">{escape(title)}</text>')
def box(self, x, y, w, h, title, body, stroke, fill="#ffffff"):
self.add(f'<rect x="{x}" y="{y}" width="{w}" height="{h}" rx="18" fill="{fill}" stroke="{stroke}" stroke-width="4"/>')
self.add(f'<text x="{x+w/2}" y="{y+44}" class="boxtitle" text-anchor="middle">{escape(title)}</text>')
ty = y + 84
for line in wrap(body, max(12, int((w - 56) / 18))):
self.add(f'<text x="{x+28}" y="{ty}" class="boxbody">{escape(line)}</text>')
ty += 31
def small(self, x, y, w, h, title, body, stroke, fill="#ffffff"):
self.add(f'<rect x="{x}" y="{y}" width="{w}" height="{h}" rx="14" fill="{fill}" stroke="{stroke}" stroke-width="3"/>')
self.add(f'<text x="{x+w/2}" y="{y+36}" class="smalltitle" text-anchor="middle">{escape(title)}</text>')
ty = y + 68
for line in wrap(body, max(10, int((w - 42) / 16))):
self.add(f'<text x="{x+22}" y="{ty}" class="smallbody">{escape(line)}</text>')
ty += 27
def arrow(self, x1, y1, x2, y2, label=None, color="#203a53", dashed=False):
dash = ' stroke-dasharray="18 12"' if dashed else ""
self.add(f'<path d="M {x1} {y1} L {x2} {y2}" fill="none" stroke="{color}" stroke-width="6" marker-end="url(#arrow)"{dash}/>')
if label:
lx, ly = (x1 + x2) / 2, (y1 + y2) / 2
self.add(f'<rect x="{lx-190}" y="{ly-48}" width="380" height="42" rx="10" fill="#ffffff" opacity="0.94"/>')
self.add(f'<text x="{lx}" y="{ly-18}" class="label" text-anchor="middle">{escape(label)}</text>')
def poly(self, pts, label=None, color="#203a53", dashed=False):
dash = ' stroke-dasharray="18 12"' if dashed else ""
d = "M " + " L ".join(f"{x} {y}" for x, y in pts)
self.add(f'<path d="{d}" fill="none" stroke="{color}" stroke-width="6" marker-end="url(#arrow)"{dash}/>')
if label:
x, y = pts[len(pts)//2]
self.add(f'<rect x="{x-200}" y="{y-52}" width="400" height="42" rx="10" fill="#ffffff" opacity="0.94"/>')
self.add(f'<text x="{x}" y="{y-22}" class="label" text-anchor="middle">{escape(label)}</text>')
def render(self):
defs = f"""
<defs>
<marker id="arrow" markerWidth="18" markerHeight="18" refX="14" refY="6" orient="auto" markerUnits="strokeWidth">
<path d="M2,2 L14,6 L2,10 Z" fill="#203a53"/>
</marker>
<style>
svg {{ background: #ffffff; }}
text {{ font-family: {FONT}; fill: #17212b; }}
.title {{ font-size: 78px; font-weight: 850; }}
.subtitle {{ font-size: 35px; fill: #53606c; }}
.section {{ font-size: 43px; font-weight: 850; fill: #233d56; }}
.boxtitle {{ font-size: 35px; font-weight: 850; }}
.boxbody {{ font-size: 27px; fill: #33495d; }}
.smalltitle {{ font-size: 29px; font-weight: 850; }}
.smallbody {{ font-size: 24px; fill: #35495e; }}
.label {{ font-size: 25px; font-weight: 750; fill: #25415d; }}
.foot {{ font-size: 26px; fill: #52606d; }}
</style>
</defs>
"""
return f'<svg xmlns="http://www.w3.org/2000/svg" width="{W}" height="{H}" viewBox="0 0 {W} {H}">\n{defs}\n' + "\n".join(self.out) + "\n</svg>\n"
s = SVG()
s.add(f'<rect x="0" y="0" width="{W}" height="{H}" fill="#ffffff"/>')
s.add('<text x="2600" y="118" class="title" text-anchor="middle">LinuxCNC 数据系统核心原理</text>')
s.add('<text x="2600" y="176" class="subtitle" text-anchor="middle">INI / NML / Task / Interpreter / Motion Shared Memory / HAL / GUI</text>')
# Layers
s.section(130, 260, 4940, 660, "1. 静态装配层INI 决定系统如何启动", "#eef6ff", "#9fb9d2")
s.box(250, 390, 960, 250, "INI 文件", "机器拓扑、KINS、JOINTS、TRAJ、DISPLAY、HALFILE、HALCMD、NML_FILE。INI 是启动装配说明,不是实时数据通道。", "#386d9d")
s.box(1510, 390, 920, 250, "scripts/linuxcnc", "读取 INI启动 linuxcncsvr、realtime、milltask、halui、HAL 配置和 GUI。", "#386d9d")
s.box(2730, 390, 900, 250, "模块装配", "loadrt motmod / kinematics / pid / 驱动loadusr halui / Vismach / GUI 组件。", "#386d9d")
s.box(3910, 390, 920, 250, "初始参数落地", "INI 值被转写到 task 状态、motion config、HAL pin/param 或组件启动参数。", "#386d9d")
s.arrow(1210, 515, 1510, 515)
s.arrow(2430, 515, 2730, 515)
s.arrow(3630, 515, 3910, 515)
s.section(130, 1020, 4940, 940, "2. NML跨进程命令 / 状态 / 错误总线", "#f8f3ff", "#b59ad7")
s.box(250, 1180, 930, 250, "emcCommand", "GUI、halui、外部客户端写入命令。典型命令MDI、AUTO_RUN、SET_MODE、SET_STATE、ABORT。", "#7a4eb0")
s.box(1520, 1180, 900, 250, "milltask", "读取 NML 命令,检查状态合法性,调度 interpreter、motion、IO、tool、spindle。", "#7a4eb0")
s.box(2760, 1180, 930, 250, "emcStatus", "task 汇总 EMC_STATtask、motion、io。GUI/halui 读取该状态。", "#7a4eb0")
s.box(4030, 1180, 780, 250, "emcError", "错误、操作信息、诊断消息通道。", "#7a4eb0")
s.arrow(1180, 1305, 1520, 1305, "命令")
s.arrow(2420, 1305, 2760, 1305, "状态汇总")
s.arrow(3690, 1305, 4030, 1305, "错误/信息")
s.small(580, 1600, 1180, 190, "linuxcncsvr", "NML channel master/server通常最先启动。", "#7a4eb0")
s.small(2260, 1600, 1420, 190, "EMC_STAT", "EMC_TASK_STAT + EMC_MOTION_STAT + EMC_IO_STAT。", "#7a4eb0")
s.section(130, 2060, 4940, 1000, "3. Task / Interpreter把人的意图转成规范动作", "#fff8eb", "#d49a35")
s.box(250, 2220, 880, 260, "GUI / halui 意图", "按钮、MDI、自动运行、暂停、jog、模式切换。它们表达意图不直接控制 servo 周期。", "#b36b00")
s.box(1420, 2220, 900, 260, "Interpreter", "读取 G-code维护模态组、坐标系、G92、刀补、参数、remap、子程序调用。", "#b36b00")
s.box(2610, 2220, 920, 260, "Canonical Commands", "直线、圆弧、主轴、IO、M68 analog output、换刀等规范动作。", "#b36b00")
s.box(3820, 2220, 900, 260, "taskintf.cc", "把规范动作转成 emcmot_command_t 或 IO/tool/spindle 命令。", "#b36b00")
s.arrow(1130, 2350, 1420, 2350)
s.arrow(2320, 2350, 2610, 2350)
s.arrow(3530, 2350, 3820, 2350)
s.small(640, 2700, 1240, 190, "M68 E3 Q1", "解释器 -> SET_AUX_OUTPUT_VALUE -> EMC_MOTION_SET_AOUT。", "#b36b00")
s.small(2350, 2700, 1320, 190, "关键边界", "G-code 不直接写 HAL它先进入 task/interpreter。", "#b36b00")
s.section(130, 3160, 4940, 1020, "4. Motion Shared Memory非实时 task 与实时 motion 的边界", "#eefaf1", "#65a36f")
s.box(250, 3320, 1050, 300, "emcmot_command_t", "task 写入motion 读取。包含 command code、pos、vel、acc、tool_offset、AOUT/DOUT、spindle 等命令参数。", "#3f7d4a")
s.box(1580, 3320, 1030, 300, "emcmot_status_t", "motion 周期更新task 读取。包含 carte_pos_cmd/fb、joint/axis/spindle、queue、analog_output、heartbeat。", "#3f7d4a")
s.box(2890, 3320, 920, 300, "emcmot_config_t", "实时 motion 配置joint 数、kinematics type、速度/加速度限制等。", "#3f7d4a")
s.box(4090, 3320, 760, 300, "head / tail", "读取 status 时检查 head == tail避免读到半更新快照。", "#3f7d4a")
s.arrow(1300, 3470, 1580, 3470, "执行后反馈")
s.arrow(2610, 3470, 2890, 3470)
s.arrow(3810, 3470, 4090, 3470)
s.small(670, 3840, 1260, 190, "usrmotWriteEmcmotCommand()", "加 command_mutex复制 command等待 commandNumEcho。", "#3f7d4a")
s.small(2530, 3840, 1320, 190, "usrmotReadEmcmotStatus()", "复制 status检查 head/tail 一致性。", "#3f7d4a")
s.section(130, 4280, 4940, 1080, "5. Realtime Motion + HAL周期执行与机器信号网络", "#eef9f8", "#4b9c9a")
s.box(250, 4440, 920, 300, "servo-thread", "固定周期运行。执行 motion-controller、PID、驱动、仿真组件等 HAL function。", "#2b7a78")
s.box(1450, 4440, 980, 300, "motion-controller", "轨迹取点、switchkins、forward/inverse kinematics、limits、probe、jog、homing、status。", "#2b7a78")
s.box(2710, 4440, 900, 300, "HAL pins", "motion、joint、spindle、驱动、Vismach、halui 都通过 pin 暴露实时变量。", "#2b7a78")
s.box(3890, 4440, 900, 300, "HAL signals", "net 命令将 pin 连接到同一 signal。通常一个 writer多个 reader。", "#2b7a78")
s.arrow(1170, 4590, 1450, 4590)
s.arrow(2430, 4590, 2710, 4590)
s.arrow(3610, 4590, 3890, 4590)
s.small(520, 5020, 1200, 190, "HAL shared memory", "component list / pin list / signal list / param list / function list / thread list。", "#2b7a78")
s.small(2080, 5020, 1200, 190, "net 的本质", "把 pin data pointer 指向同一个 signal value。", "#2b7a78")
s.small(3620, 5020, 1040, 190, "实时原则", "控制闭环必须在 motion/HAL 内,不能依赖 GUI poll。", "#2b7a78")
s.section(130, 5460, 4940, 900, "6. 状态回流与 GUI 显示", "#f6f7f9", "#9da8b3")
s.box(250, 5620, 920, 250, "motion status", "实时 motion 更新 emcmot_status_t。", "#5b6570")
s.box(1470, 5620, 920, 250, "task update", "emcMotionUpdate() 将 motion status 汇总到 EMC_MOTION_STAT。", "#5b6570")
s.box(2690, 5620, 920, 250, "EMC_STAT", "顶层状态task + motion + io。写入 emcStatus NML。", "#5b6570")
s.box(3910, 5620, 920, 250, "GUI / halui", "poll emcStatus显示位置、模式、状态、错误、队列、模态。", "#5b6570")
s.arrow(1170, 5745, 1470, 5745)
s.arrow(2390, 5745, 2690, 5745)
s.arrow(3610, 5745, 3910, 5745)
s.poly([(4360, 5620), (4360, 5350), (390, 5350), (390, 4740)], "HAL/Vismach 也可直接读 HAL", dashed=True)
s.add('<text x="2600" y="6460" class="foot" text-anchor="middle">核心原则NML 表达系统命令/状态HAL 表达实时机器信号motion shared memory 是 task 与实时控制的边界。</text>')
s.add('<text x="2600" y="6505" class="foot" text-anchor="middle">输出文件:项目分析/LinuxCNC数据系统核心原理高清流程图.png源文件项目分析/LinuxCNC数据系统核心原理高清流程图.svg</text>')
with open("项目分析/LinuxCNC数据系统核心原理高清流程图.svg", "w", encoding="utf-8") as f:
f.write(s.render())

View File

@@ -0,0 +1,310 @@
#!/usr/bin/env python3
from html import escape
W = 4800
H = 7200
FONT = "Noto Sans CJK SC, DejaVu Sans, sans-serif"
def text_width_units(s):
units = 0
for ch in s:
units += 2 if ord(ch) > 127 else 1
return units
def wrap_text(text, max_units):
words = []
for part in text.split(" "):
if text_width_units(part) <= max_units:
words.append(part)
continue
current = ""
for ch in part:
if text_width_units(current + ch) > max_units and current:
words.append(current)
current = ch
else:
current += ch
if current:
words.append(current)
lines = []
current = ""
for word in words:
candidate = word if not current else current + " " + word
if text_width_units(candidate) <= max_units:
current = candidate
else:
if current:
lines.append(current)
current = word
if current:
lines.append(current)
return lines
class Svg:
def __init__(self):
self.items = []
def add(self, s):
self.items.append(s)
def section(self, x, y, w, h, title, color="#eef5ff"):
self.add(
f'<rect x="{x}" y="{y}" width="{w}" height="{h}" rx="36" '
f'fill="{color}" stroke="#b8c7dc" stroke-width="4"/>'
)
self.add(
f'<text x="{x + 34}" y="{y + 70}" class="section-title">{escape(title)}</text>'
)
def box(self, node_id, x, y, w, h, title, body=None, fill="#ffffff", stroke="#416788"):
self.add(
f'<rect id="{node_id}" x="{x}" y="{y}" width="{w}" height="{h}" rx="18" '
f'fill="{fill}" stroke="{stroke}" stroke-width="4"/>'
)
lines = wrap_text(title, max(10, int(w / 24)))
ty = y + 44
for i, line in enumerate(lines):
klass = "box-title" if i == 0 else "box-text"
self.add(f'<text x="{x + w / 2}" y="{ty}" class="{klass}" text-anchor="middle">{escape(line)}</text>')
ty += 42
if body:
ty += 8
for line in wrap_text(body, max(10, int(w / 20))):
self.add(f'<text x="{x + 28}" y="{ty}" class="box-small">{escape(line)}</text>')
ty += 34
def note(self, x, y, w, h, title, lines, fill="#fff7e6"):
self.add(
f'<rect x="{x}" y="{y}" width="{w}" height="{h}" rx="18" '
f'fill="{fill}" stroke="#d49b32" stroke-width="4"/>'
)
self.add(f'<text x="{x + 28}" y="{y + 46}" class="note-title">{escape(title)}</text>')
ty = y + 90
for line in lines:
for wrapped in wrap_text(line, max(10, int((w - 56) / 19))):
self.add(f'<text x="{x + 28}" y="{ty}" class="note-text">{escape(wrapped)}</text>')
ty += 32
def arrow(self, x1, y1, x2, y2, label=None, color="#1f3b57", dashed=False):
dash = ' stroke-dasharray="18 12"' if dashed else ""
self.add(
f'<path d="M {x1} {y1} L {x2} {y2}" fill="none" stroke="{color}" '
f'stroke-width="6" marker-end="url(#arrow)"{dash}/>'
)
if label:
lx = (x1 + x2) / 2
ly = (y1 + y2) / 2 - 16
self.add(
f'<rect x="{lx - 150}" y="{ly - 32}" width="300" height="46" rx="12" '
f'fill="#ffffff" opacity="0.92"/>'
)
self.add(f'<text x="{lx}" y="{ly}" class="arrow-label" text-anchor="middle">{escape(label)}</text>')
def poly_arrow(self, points, label=None, color="#1f3b57", dashed=False):
dash = ' stroke-dasharray="18 12"' if dashed else ""
d = "M " + " L ".join(f"{x} {y}" for x, y in points)
self.add(
f'<path d="{d}" fill="none" stroke="{color}" stroke-width="6" '
f'marker-end="url(#arrow)"{dash}/>'
)
if label:
x, y = points[len(points) // 2]
self.add(
f'<rect x="{x - 170}" y="{y - 54}" width="340" height="46" rx="12" '
f'fill="#ffffff" opacity="0.92"/>'
)
self.add(f'<text x="{x}" y="{y - 22}" class="arrow-label" text-anchor="middle">{escape(label)}</text>')
def render(self):
defs = f"""
<defs>
<marker id="arrow" markerWidth="18" markerHeight="18" refX="14" refY="6" orient="auto" markerUnits="strokeWidth">
<path d="M2,2 L14,6 L2,10 Z" fill="#1f3b57"/>
</marker>
<style>
svg {{ background: #ffffff; }}
text {{ font-family: {FONT}; fill: #17212b; }}
.title {{ font-size: 76px; font-weight: 800; }}
.subtitle {{ font-size: 34px; fill: #52606d; }}
.section-title {{ font-size: 44px; font-weight: 800; fill: #24415f; }}
.box-title {{ font-size: 34px; font-weight: 800; }}
.box-text {{ font-size: 30px; font-weight: 650; }}
.box-small {{ font-size: 27px; fill: #35495e; }}
.note-title {{ font-size: 34px; font-weight: 800; fill: #7a4b00; }}
.note-text {{ font-size: 27px; fill: #5c440b; }}
.arrow-label {{ font-size: 26px; font-weight: 700; fill: #24415f; }}
.legend {{ font-size: 28px; fill: #34495e; }}
</style>
</defs>
"""
return (
f'<svg xmlns="http://www.w3.org/2000/svg" width="{W}" height="{H}" viewBox="0 0 {W} {H}">\n'
+ defs
+ "\n".join(self.items)
+ "\n</svg>\n"
)
svg = Svg()
svg.add(f'<rect x="0" y="0" width="{W}" height="{H}" fill="#ffffff"/>')
svg.add('<text x="2400" y="120" class="title" text-anchor="middle">5axis-xyzbc-trt-sim 执行流程图</text>')
svg.add('<text x="2400" y="178" class="subtitle" text-anchor="middle">LinuxCNC: xyzbc-trt.ini / switchkins / Vismach / PyVCP / M428-M429-M430</text>')
# Section 1: startup
svg.section(120, 260, 4560, 780, "1. 总体启动链路", "#eef6ff")
startup = [
("s1", 210, 390, 500, 150, "xyzbc-trt.desktop", "快捷方式入口"),
("s2", 820, 390, 520, 150, "rip-environment", "设置 RIP 环境"),
("s3", 1450, 390, 560, 150, "scripts/linuxcnc", "读取 xyzbc-trt.ini"),
("s4", 2120, 390, 520, 150, "linuxcncsvr", "NML 通道"),
("s5", 2750, 390, 560, 150, "realtime / HAL", "加载 RTAPI/HAL"),
("s6", 3420, 390, 500, 150, "milltask / halui", "任务与 MDI"),
("s7", 4030, 390, 520, 150, "AXIS GUI", "前台显示"),
]
for args in startup:
svg.box(*args)
for i in range(len(startup) - 1):
x1 = startup[i][1] + startup[i][3]
y1 = startup[i][2] + startup[i][4] / 2
x2 = startup[i + 1][1]
y2 = startup[i + 1][2] + startup[i + 1][4] / 2
svg.arrow(x1, y1, x2, y2)
svg.box("ini", 450, 690, 1050, 230, "INI 核心项", "[KINS] xyzbc-trt-kins sparm=identityfirst; [TRAJ] XYZBC; [DISPLAY] axis + PyVCP; [HAL] basic_sim.tcl")
svg.box("halcmd", 1760, 690, 1050, 230, "HAL 加载", "basic_sim.tcl 建立仿真闭环; HALCMD 启动 Vismach 并连接 pins")
svg.box("postgui", 3070, 690, 1050, 230, "GUI 后置 HAL", "AXIS 创建 PyVCP 后执行 switchkins_postgui.hal")
svg.arrow(1500, 805, 1760, 805)
svg.arrow(2810, 805, 3070, 805)
# Section 2: HAL and kins
svg.section(120, 1140, 2200, 1490, "2. basic_sim.tcl / 仿真 HAL", "#f2fbf2")
svg.box("b1", 230, 1280, 520, 150, "basic_sim.tcl", "读取 coordinates/joints/servo period", "#ffffff", "#3f7d4a")
svg.box("b2", 910, 1280, 560, 150, "setup_kins", "loadrt xyzbc-trt-kins sparm=identityfirst", "#ffffff", "#3f7d4a")
svg.box("b3", 1590, 1280, 560, 150, "motmod", "num_joints=5 servo=1ms", "#ffffff", "#3f7d4a")
svg.arrow(750, 1355, 910, 1355)
svg.arrow(1470, 1355, 1590, 1355)
for i, label in enumerate(["pid J0..J4", "mux2 J0..J4", "sim_home_switch", "sim_spindle", "hal_manualtoolchange"]):
x = 250 + (i % 2) * 870
y = 1600 + (i // 2) * 230
svg.box(f"bc{i}", x, y, 720, 150, label, "basic_sim.tcl 创建/连接", "#ffffff", "#3f7d4a")
svg.poly_arrow([(1870, 1430), (1870, 1530), (610, 1530), (610, 1600)])
svg.poly_arrow([(1870, 1430), (1870, 1530), (1480, 1530), (1480, 1600)])
svg.box("loop", 250, 2300, 1720, 190, "理想伺服仿真闭环", "joint.N.motor-pos-cmd -> JN_pid.command -> JN_mux.in1 -> joint.N.motor-pos-fb", "#ffffff", "#3f7d4a")
svg.arrow(1110, 2210, 1110, 2300)
svg.section(2480, 1140, 2200, 1490, "3. switchkins 初始化", "#f9f3ff")
svg.box("k1", 2590, 1280, 620, 150, "xyzbc-trt-kins", "switchkinsSetup()", "#ffffff", "#7246a3")
svg.box("k2", 3350, 1280, 620, 150, "sparm=identityfirst", "改变 type 顺序", "#ffffff", "#7246a3")
svg.arrow(3210, 1355, 3350, 1355)
svg.box("t0", 2630, 1620, 520, 150, "type 0", "identity kinematics", "#ffffff", "#7246a3")
svg.box("t1", 3280, 1620, 520, 150, "type 1", "xyzbc TRT kinematics", "#ffffff", "#7246a3")
svg.box("t2", 3930, 1620, 520, 150, "type 2", "userk kinematics", "#ffffff", "#7246a3")
svg.poly_arrow([(3660, 1430), (3660, 1530), (2890, 1530), (2890, 1620)])
svg.poly_arrow([(3660, 1430), (3660, 1530), (3540, 1530), (3540, 1620)])
svg.poly_arrow([(3660, 1430), (3660, 1530), (4190, 1530), (4190, 1620)])
svg.box("pins", 2700, 1940, 1560, 180, "HAL pins", "kinstype.is-0/1/2; x/y/z-rot-point; x/y/z-offset; tool-offset; conventional-directions", "#ffffff", "#7246a3")
svg.arrow(3540, 1770, 3480, 1940)
svg.box("default", 2920, 2280, 1120, 160, "启动默认状态", "switchkins_type = 0 -> identity", "#ffffff", "#7246a3")
svg.arrow(3480, 2120, 3480, 2280)
# Section 4: switching
svg.section(120, 2730, 4560, 1400, "4. PyVCP / M-code / motion.switchkins-type 切换链路", "#fff8ee")
svg.box("p1", 240, 2880, 680, 160, "PyVCP SWITCHKINS 面板", "IDENTITY / TCP:XYZBC / userk", "#ffffff", "#ba7a20")
svg.box("p2", 1100, 2880, 620, 160, "switchkins_postgui.hal", "按钮接入 halui.mdi-command", "#ffffff", "#ba7a20")
svg.box("p3", 1900, 2880, 580, 160, "halui MDI", "执行 M429 / M428 / M430", "#ffffff", "#ba7a20")
svg.box("p4", 2660, 2880, 640, 160, "remap 子程序", "429/428/430remap.ngc", "#ffffff", "#ba7a20")
svg.box("p5", 3480, 2880, 520, 160, "M68 E3 Qn", "设置 analog out", "#ffffff", "#ba7a20")
svg.box("p6", 4160, 2880, 420, 160, "M66 E0 L0", "同步解释器与 motion", "#ffffff", "#ba7a20")
for x1, x2 in [(920, 1100), (1720, 1900), (2480, 2660), (3300, 3480), (4000, 4160)]:
svg.arrow(x1, 2960, x2, 2960)
svg.box("m429", 520, 3260, 660, 150, "M429", "type 0: identity", "#ffffff", "#ba7a20")
svg.box("m428", 1480, 3260, 660, 150, "M428", "type 1: xyzbc TRT", "#ffffff", "#ba7a20")
svg.box("m430", 2440, 3260, 660, 150, "M430", "type 2: userk", "#ffffff", "#ba7a20")
svg.box("aout", 3400, 3260, 720, 150, "motion.analog-out-03", "HAL net :kinstype-select", "#ffffff", "#ba7a20")
svg.arrow(850, 3040, 850, 3260, "M429")
svg.arrow(2200, 3040, 1810, 3260, "M428")
svg.arrow(2200, 3040, 2770, 3260, "M430")
svg.arrow(1180, 3335, 3400, 3335, "Q0")
svg.arrow(2140, 3335, 3400, 3335, "Q1")
svg.arrow(3100, 3335, 3400, 3335, "Q2")
svg.box("swpin", 1580, 3700, 760, 170, "motion.switchkins-type", "float HAL input, 被截断为整数 type", "#ffffff", "#ba7a20")
svg.box("handle", 2580, 3700, 820, 170, "handle_kinematicsSwitch()", "servo-thread 每周期检测并调用 kinematicsSwitch(type)", "#ffffff", "#ba7a20")
svg.arrow(3760, 3410, 1960, 3700)
svg.arrow(2340, 3785, 2580, 3785)
# Section 5: motion data
svg.section(120, 4230, 4560, 1520, "5. 运行时运动数据流与 Vismach 显示", "#eef9f8")
svg.box("gcode", 240, 4390, 520, 150, "G-code XYZBC", "程序指令", "#ffffff", "#2b7a78")
svg.box("interp", 920, 4390, 520, 150, "interpreter", "RS274NGC", "#ffffff", "#2b7a78")
svg.box("task", 1600, 4390, 520, 150, "milltask", "任务层", "#ffffff", "#2b7a78")
svg.box("tp", 2280, 4390, 620, 150, "trajectory planner", "生成 carte_pos_cmd", "#ffffff", "#2b7a78")
svg.box("inv", 3060, 4390, 760, 150, "kinematicsInverse()", "按当前 type 分派", "#ffffff", "#2b7a78")
svg.box("joint", 3980, 4390, 560, 150, "joint targets", "X/Y/Z/B/C joint 目标", "#ffffff", "#2b7a78")
for x1, x2 in [(760, 920), (1440, 1600), (2120, 2280), (2900, 3060), (3820, 3980)]:
svg.arrow(x1, 4465, x2, 4465)
svg.box("idinv", 540, 4840, 760, 150, "type 0: identity", "一一映射", "#ffffff", "#2b7a78")
svg.box("trtinv", 1510, 4840, 1040, 210, "type 1: xyzbcKinematicsInverse", "使用 B/C 角度、x-offset=-20、z-offset=-15、tool-offset、旋转中心计算 joint", "#ffffff", "#2b7a78")
svg.box("userkinv", 2760, 4840, 760, 150, "type 2: userk", "模板示例", "#ffffff", "#2b7a78")
svg.poly_arrow([(3440, 4540), (3440, 4720), (920, 4720), (920, 4840)])
svg.poly_arrow([(3440, 4540), (3440, 4720), (2030, 4720), (2030, 4840)])
svg.poly_arrow([(3440, 4540), (3440, 4720), (3140, 4720), (3140, 4840)])
svg.box("fb", 820, 5260, 1000, 160, "仿真反馈闭环", "joint.N.motor-pos-cmd -> pid/mux2 -> joint.N.motor-pos-fb", "#ffffff", "#2b7a78")
svg.box("vis", 2140, 5260, 1000, 160, "Vismach", "joint feedback 驱动 table/saddle/spindle/B/C 模型", "#ffffff", "#2b7a78")
svg.box("panel", 3460, 5260, 780, 160, "PyVCP 状态", "kinstype.is-N 显示当前运动学", "#ffffff", "#2b7a78")
svg.arrow(4260, 4540, 1320, 5260)
svg.arrow(1820, 5340, 2140, 5340)
svg.arrow(2980, 3870, 3850, 5260, "kinstype.is-N", dashed=True)
# Section 6: demo
svg.section(120, 5860, 4560, 1060, "6. 自动打开的演示 G-code 流程", "#f7f7f7")
svg.box("d1", 250, 6020, 660, 150, "xyzbc_switchkins.ngc", "AXIS OPEN_FILE", "#ffffff", "#5b6570")
svg.box("d2", 1080, 6020, 760, 150, "xyzbc_switchkins_sub", "四个象限重复", "#ffffff", "#5b6570")
svg.box("d3", 2010, 6020, 620, 150, "M429 identity", "安全定位 / 重设 G54", "#ffffff", "#5b6570")
svg.box("d4", 2800, 6020, 620, 150, "helix_bc", "准备螺旋插补", "#ffffff", "#5b6570")
svg.box("d5", 3590, 6020, 700, 150, "M428 xyzbc TRT", "B/C 倾斜后加工", "#ffffff", "#5b6570")
for x1, x2 in [(910, 1080), (1840, 2010), (2630, 2800), (3420, 3590)]:
svg.arrow(x1, 6095, x2, 6095)
svg.note(
360,
6380,
1780,
330,
"演示循环",
[
"每个象限先 M429 切回 identity。",
"G53 回机床安全位置G10 L20 P0 重设 G54。",
"移动到象限中心后调用 helix_bc。",
],
)
svg.note(
2540,
6380,
1780,
330,
"helix_bc 核心",
[
"M428 切换到 xyzbc TRT。",
"G0 B#<b> C#<c> 设置转台角度。",
"G2 I#<r> Z#<zmin> P#<n> 执行螺旋插补。",
],
)
svg.add('<text x="2400" y="7080" class="legend" text-anchor="middle">输出文件: 项目分析/5axis-xyzbc-trt-sim高清流程图.png源文件: 项目分析/5axis-xyzbc-trt-sim高清流程图.svg</text>')
with open("项目分析/5axis-xyzbc-trt-sim高清流程图.svg", "w", encoding="utf-8") as f:
f.write(svg.render())