Files
cnc_wams/项目分析/LinuxCNC数据系统核心原理.md
2026-07-05 22:13:40 -04:00

598 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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。