chore: finalize remaining project artifacts
This commit is contained in:
597
项目分析/LinuxCNC数据系统核心原理.md
Normal file
597
项目分析/LinuxCNC数据系统核心原理.md
Normal 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_STAT:GUI 看到的系统状态
|
||||
|
||||
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_COMMAND:GUI/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
|
||||
|
||||
实时组件导出 function,HAL 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。
|
||||
Reference in New Issue
Block a user