4. PLC读取LinuxCNC坐标位置

任务目标

在LinuxCNC运行过程中,PLC需要实时获取机床各轴坐标(如X、Y、Z轴位置)。本案例通过LinuxCNC的DBus接口实现坐标读取,并在PLC侧通过Modbus TCP协议暴露坐标值,使外部PLC主站(或HMI)能够读取当前坐标。

实现方案

LinuxCNC本身不直接提供PLC通信模块,但可以通过其DBus接口获取轴位置。本案例采用以下链路:

  1. LinuxCNC通过DBus发布轴坐标(需启用dbus模块);
  2. Python脚本订阅DBus信号,周期读取坐标;
  3. 脚本作为Modbus TCP从站,将坐标映射到保持寄存器;
  4. PLC作为Modbus主站读取这些寄存器。

环境依赖

  • LinuxCNC(已启用DBus模块,通常在.ini中配置DBUS = 1)
  • Python 3 + dbus-python、pymodbus库
sudo apt install python3-dbus python3-pip
pip3 install pymodbus==2.5.3

关键完整代码

以下Python脚本实现坐标读取与Modbus TCP从站功能:

#!/usr/bin/env python3
import dbus
import threading
import time
from pymodbus.server import StartTcpServer
from pymodbus.datastore import ModbusSlaveContext, ModbusDataBlock
from pymodbus.datastore import ModbusServerContext

# ---------- LinuxCNC DBus 坐标读取 ----------
class AxisReader:
    def __init__(self):
        self.bus = dbus.SessionBus()
        # LinuxCNC DBus 服务名与路径(需与hal配置一致)
        self.obj = self.bus.get_object('org.linuxcnc', '/org/linuxcnc/axis')
        self.iface = dbus.Interface(self.obj, 'org.linuxcnc.Axis')
        self.coords = [0.0, 0.0, 0.0]  # X, Y, Z

    def update(self):
        try:
            # 获取当前坐标(单位:mm)
            self.coords[0] = self.iface.GetAxisPosition(0)  # X轴
            self.coords[1] = self.iface.GetAxisPosition(1)  # Y轴
            self.coords[2] = self.iface.GetAxisPosition(2)  # Z轴
        except Exception as e:
            print(f"DBus读取失败: {e}")

# ---------- Modbus 寄存器映射 ----------
# 保持寄存器地址规划:
# 0-1: X轴坐标(float32,大端)
# 2-3: Y轴坐标
# 4-5: Z轴坐标
# 6: 数据有效标志(1=有效)

class ModbusDataStore:
    def __init__(self, reader):
        self.reader = reader
        # 初始化寄存器块(保持寄存器,共7个)
        self.store = ModbusSlaveContext(
            di=ModbusDataBlock.create([0]*10),
            co=ModbusDataBlock.create([0]*10),
            hr=ModbusDataBlock.create([0]*7),
            ir=ModbusDataBlock.create([0]*10)
        )

    def update_registers(self):
        # 将float32拆分为两个16位寄存器(大端序)
        for idx, val in enumerate(self.reader.coords):
            import struct
            packed = struct.pack('>f', val)  # 大端float32
            reg1, reg2 = struct.unpack('>HH', packed)
            self.store.setValues(3, idx*2, [reg1, reg2])  # 3=保持寄存器
        # 设置有效标志
        self.store.setValues(3, 6, [1])

# ---------- 主程序 ----------
def main():
    reader = AxisReader()
    datastore = ModbusDataStore(reader)
    context = ModbusServerContext(slaves=datastore, single=True)

    # 启动Modbus TCP服务(端口5020,避免权限问题)
    t = threading.Thread(target=StartTcpServer, args=(context,), kwargs={'address': ('0.0.0.0', 5020)})
    t.daemon = True
    t.start()
    print("Modbus TCP 从站已启动,端口5020")

    # 周期更新坐标(10Hz)
    while True:
        reader.update()
        datastore.update_registers()
        time.sleep(0.1)

if __name__ == '__main__':
    main()

运行与验证

  1. 启动LinuxCNC并加载包含DBus支持的配置(如模拟器配置sim/axis.ini)。
  2. 运行上述脚本:python3 axis_to_modbus.py
  3. PLC侧配置:作为Modbus TCP主站,连接IP为LinuxCNC主机IP,端口5020。
  4. 读取寄存器:地址0-5为X/Y/Z坐标(float32大端),地址6为有效标志。

验证方法:在LinuxCNC中手动移动轴(如MDI输入G0 X50),观察PLC读取到的X坐标值是否同步变化。若PLC显示值与LinuxCNC界面坐标一致(误差在运动精度范围内),且有效标志为1,则判定通过。

常见问题

  • DBus连接失败:确认LinuxCNC已启用DBus(检查.ini中[DBUS]节),且运行在同一用户会话。
  • 寄存器字节序:本实现使用大端序(网络字节序),PLC侧需匹配。若PLC使用小端,可修改struct.pack('<f', val)。
  • 坐标单位:LinuxCNC默认返回毫米(mm),若使用英寸配置需在PLC侧换算。