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

15 KiB
Raw Permalink Blame History

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 和自定义组件。

简化图:

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

它们的本质职责:

  • emcCommandGUI/halui 发命令给 task。
  • emcStatustask 汇总系统状态供 GUI/halui 读取。
  • emcError:错误和操作信息通道。

linuxcncsvr 是这些 NML channel 的 master/server。启动脚本注释也说明linuxcncsvr 默认第一个启动,因为它创建/持有 NML channel。

5. EMC_STATGUI 看到的系统状态

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 读取 emcStatuspoll 时将 EMC_STAT 复制到本地 Python 对象中。

6. EMC_COMMANDGUI/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_ttask 写入motion 读取。
  • emcmot_status_tmotion 周期更新task 读取。
  • emcmot_config_t:机器配置和 motion 参数。
  • emcmot_internal_tmotion 内部轨迹规划/调试状态。

task 写 motion command 的路径:

taskintf.cc
  -> usrmotWriteEmcmotCommand()
  -> 加 command_mutex
  -> 复制 emcmot_command_t 到共享内存
  -> 等待 motion 回显 commandNumEcho

motion status 读取有一个重要细节:emcmot_status_thead/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 的拥有者。例如:

  • motmod
  • pid
  • mux2
  • halui
  • xyzbc-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

实时组件导出 functionHAL 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 数据系统的核心不是“一个中心数据库”,而是四种不同性质的数据机制协作:

  1. INI:装配系统。
  2. NML:让进程交换命令、状态、错误。
  3. Task/Interpreter:把人的意图和 G-code 转换成规范动作。
  4. Motion shared memory:连接非实时 task 和实时 motion。
  5. 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。