15 KiB
LinuxCNC 数据系统核心原理
本文从本质上解释 LinuxCNC 的数据系统。这里的“数据系统”不是单个数据库,也不是单个消息队列,而是由多个不同实时等级、不同生命周期、不同所有权的数据通道组合而成。
LinuxCNC 的核心设计目标是:把非实时的解释、界面、配置、任务调度,与实时运动控制隔离开,同时又让它们能以受控方式交换命令、状态和信号。
1. 一句话理解
LinuxCNC 的数据系统本质上分为五层:
- INI 配置数据:启动时读取,决定机器拓扑、模块、限位、速度、GUI、HAL 文件。
- NML 命令/状态数据:跨进程通信总线,GUI、halui、task、server 通过它交换命令和状态。
- Task/Interpreter 数据:解释 G-code,维护模态状态、坐标系、刀补、运行队列,并把规范动作转成 motion/io 命令。
- Motion 实时共享数据:task 和实时 motion 模块之间的 command/status/config 共享内存。
- HAL 信号数据:实时和用户态组件共享的 pin/signal/parameter 网络,连接 motion、驱动、GUI、Vismach 和自定义组件。
简化图:
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 方式连接起来。
本质原则:
非实时层负责“决定要做什么”
实时层负责“按确定周期执行”
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 配置可见:
configs/common/linuxcnc.nml
其中定义了三个顶层 buffer:
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,定义在:
src/emc/nml_intf/emc_nml.hh
它聚合了:
EMC_STAT
├─ EMC_TASK_STAT task
├─ EMC_MOTION_STAT motion
└─ EMC_IO_STAT io
其中 motion 部分又包含:
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。
本质上:
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 汇总更新
关键文件:
src/emc/task/emctaskmain.cc
src/emc/task/taskintf.cc
src/emc/rs274ngc/
src/emc/task/emccanon.cc
典型链路:
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 的链路是:
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 的核心共享结构是:
src/emc/motion/motion_struct.h
核心结构:
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 的路径:
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 控制循环位于:
src/emc/motion/control.c
每个 servo cycle 处理大致顺序:
读取 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 的本质是一个共享内存对象图:
HAL shared memory
├─ component list
├─ pin list
├─ signal list
├─ parameter list
├─ function list
└─ thread list
这些结构定义在:
src/hal/hal_priv.h
关键概念:
Component
组件是 pin/function/parameter 的拥有者。例如:
motmodpidmux2haluixyzbc-trt-gui- 自定义
.comp
组件调用 hal_init() 注册到 HAL。
Pin
pin 是组件暴露的数据端口。每个 pin 有:
- 名字
- 类型
- 方向
- 所属 component
- 连接的 signal
方向包括:
- input
- output
- io
Signal
signal 是多个 pin 共享的一块数据值。net 命令本质上是:
把多个 pin 的 data pointer 指向同一个 signal value
例如:
net :kinstype-select <= motion.analog-out-03 => motion.switchkins-type
表示:
motion.analog-out-03 写 signal :kinstype-select
motion.switchkins-type 读 signal :kinstype-select
Parameter
parameter 通常是配置量或调试量,可用 setp 设置。
Function 和 Thread
实时组件导出 function,HAL thread 周期调用这些 function。
例如:
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 |
重要结论:
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 层
[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 层
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 按钮:
pyvcp button
-> halui MDI command
-> NML command
-> milltask
-> interpreter 执行 M428/M429/M430 remap
motion 层
M68 E3 Q1 最终写入:
motion.analog-out-03 = 1
HAL 将其连接到:
motion.switchkins-type = 1
motion servo cycle 检测到变化:
handle_kinematicsSwitch()
-> kinematicsSwitch(1)
-> 激活 xyzbcKinematicsInverse / Forward
Vismach 层
Vismach 不参与实时控制,只读取 HAL pin:
joint feedback + offset pins -> 三维模型变换
它是 HAL 数据消费者,不是运动控制源。
16. 本质总结
LinuxCNC 数据系统的核心不是“一个中心数据库”,而是四种不同性质的数据机制协作:
- INI:装配系统。
- NML:让进程交换命令、状态、错误。
- Task/Interpreter:把人的意图和 G-code 转换成规范动作。
- Motion shared memory:连接非实时 task 和实时 motion。
- HAL:把实时变量连接成机器信号网络。
最终形成一条严格分层的数据链:
人/程序意图
-> 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。