Files
XplorePlane/XP.Hardware.MotionControl/Documents/AxisControlView_PLC_Communication.md
T

15 KiB
Raw Blame History

AxisControlView PLC 通信机制 | AxisControlView PLC Communication

概述 | Overview

AxisControlView 是运动控制模块的核心用户控件,通过 AxisControlViewModel 与 PLC 进行双向通信。本文档详细说明摇杆 Jog 操作、轴位置输入框操作以及射线源/探测器Z轴联动状态下的 PLC 信号下发机制。

通信架构 | Communication Architecture

AxisControlView (XAML)
    ↓ (数据绑定)
AxisControlViewModel (C#)
    ↓ (调用)
IMotionControlService
    ↓ (调用)
ISignalDataService
    ↓ (写入队列)
PLC (DB31, WriteCommon 组)

1. 摇杆 Jog 操作 | Joystick Jog Operation

1.1 双轴摇杆(圆形)| Dual Axis Joystick (Circular)

左键拖拽(默认模式)

  • X 轴输出 → StageX Jog(正向/反向)
  • Y 轴输出 → StageY Jog(正向/反向)

右键拖拽(默认模式)

  • X 轴输出 → DetectorSwing Jog(正向/反向)
  • Y 轴输出 → StageRotation/FixtureRotation Jog(正向/反向)

信号下发流程

// 1. 摇杆输出变化触发 HandleDualJoystickOutput()
private void HandleDualJoystickOutput()
{
    switch (DualJoystickActiveButton)
    {
        case MouseButtonType.Left:
            UpdateLinearJog(AxisId.StageX, DualJoystickOutputX);
            UpdateLinearJog(AxisId.StageY, DualJoystickOutputY);
            break;
        case MouseButtonType.Right:
            UpdateRotaryJog(RotaryAxisId.DetectorSwing, DualJoystickOutputX);
            var rotationAxisId = GetEnabledRotationAxisId();
            if (rotationAxisId.HasValue)
                UpdateRotaryJog(rotationAxisId.Value, DualJoystickOutputY);
            break;
    }
}

// 2. UpdateLinearJog/UpdateRotaryJog 处理 Jog 启动/速度更新/停止
private void UpdateLinearJog(AxisId axisId, double output)
{
    if (output != 0)
    {
        var speedPercent = Math.Abs(output) * 100;
        var positive = output > 0;

        if (!_linearJogActive[axisId])
        {
            // 首次非零:设置速度并启动 Jog
            _motionControlService.SetJogSpeed(axisId, speedPercent);
            _motionControlService.JogStart(axisId, positive);
            _linearJogActive[axisId] = true;
        }
        else
        {
            // 已在 Jog:仅更新速度
            _motionControlService.SetJogSpeed(axisId, speedPercent);
        }
    }
    else
    {
        // 松开鼠标:停止 Jog
        if (_linearJogActive[axisId])
        {
            _motionControlService.JogStop(axisId);
            _linearJogActive[axisId] = false;
        }
    }
}

// 3. IMotionControlService 调用 ISignalDataService.EnqueueWrite 写入 PLC
public MotionResult JogStart(AxisId axisId, bool positive)
{
    var signalName = positive ? MotionSignalNames.SourceZ_JogPos : MotionSignalNames.SourceZ_JogNeg;
    var result = _signalService.EnqueueWrite(signalName, true);
    return result ? MotionResult.Ok() : MotionResult.Fail("Jog 启动写入失败");
}

PLC 信号地址

信号名 地址 说明
StageX MC_StageX_JogPos 42 正向 Jog0:缺省, 1:点动)
StageX MC_StageX_JogNeg 43 反向 Jog0:缺省, 1:点动)
StageY MC_StageY_JogPos 54 正向 Jog0:缺省, 1:点动)
StageY MC_StageY_JogNeg 55 反向 Jog0:缺省, 1:点动)
DetectorSwing MC_DetSwing_JogPos 66 正向 Jog0:缺省, 1:点动)
DetectorSwing MC_DetSwing_JogNeg 67 反向 Jog0:缺省, 1:点动)
StageRotation MC_StageRot_JogPos 78 正向 Jog0:缺省, 1:点动)
StageRotation MC_StageRot_JogNeg 79 反向 Jog0:缺省, 1:点动)
FixtureRotation MC_FixRot_JogPos 90 正向 Jog0:缺省, 1:点动)
FixtureRotation MC_FixRot_JogNeg 91 反向 Jog0:缺省, 1:点动)

1.2 单轴摇杆(腰圆)| Single Axis Joystick (Oval)

左键拖拽(默认模式)

  • Y 轴输出 → SourceZ Jog(正向/反向)
  • 若 SZDZLock=true,同时 → DetectorZ Jog(正向/反向)

右键拖拽(默认模式)

  • Y 轴输出 → DetectorZ Jog(正向/反向)
  • 若 SZDZLock=true,同时 → SourceZ Jog(正向/反向)

信号下发流程

private void HandleSingleJoystickOutput()
{
    switch (SingleJoystickActiveButton)
    {
        case MouseButtonType.Left:
            UpdateLinearJog(AxisId.SourceZ, SingleJoystickOutputY);
            if (_szdzLock)
                UpdateLinearJog(AxisId.DetectorZ, SingleJoystickOutputY);
            break;
        case MouseButtonType.Right:
            UpdateLinearJog(AxisId.DetectorZ, SingleJoystickOutputY);
            if (_szdzLock)
                UpdateLinearJog(AxisId.SourceZ, SingleJoystickOutputY);
            break;
    }
}

PLC 信号地址

信号名 地址 说明
SourceZ MC_SourceZ_JogPos 18 正向 Jog0:缺省, 1:点动)
SourceZ MC_SourceZ_JogNeg 19 反向 Jog0:缺省, 1:点动)
DetectorZ MC_DetZ_JogPos 30 正向 Jog0:缺省, 1:点动)
DetectorZ MC_DetZ_JogNeg 31 反向 Jog0:缺省, 1:点动)

1.3 Jog 速度控制 | Jog Speed Control

摇杆输出值(-1.0 ~ 1.0)映射为速度百分比(0% ~ 100%):

var speedPercent = Math.Abs(output) * 100;  // 0% ~ 100%

速度计算流程

  1. MotionControlService.SetJogSpeed 计算实际速度:

    var actualSpeed = _config.DefaultVelocity * speedPercent / 100.0;
    // 例如:DefaultVelocity=100, speedPercent=50 → actualSpeed=50 mm/s
    
  2. PlcLinearAxis.SetJogSpeed 将实际速度写入 PLC

    _signalService.EnqueueWrite(_speedSignal, (float)speedPercent);
    // 注意:参数名 speedPercent 实际接收的是 actualSpeed(已计算的实际速度值)
    

PLC 信号地址

信号名 地址 说明
SourceZ MC_SourceZ_Speed 14 速度值(mm/s,由 DefaultVelocity * speedPercent / 100 计算)
DetectorZ MC_DetZ_Speed 26 速度值(mm/s,由 DefaultVelocity * speedPercent / 100 计算)
StageX MC_StageX_Speed 38 速度值(mm/s,由 DefaultVelocity * speedPercent / 100 计算)
StageY MC_StageY_Speed 50 速度值(mm/s,由 DefaultVelocity * speedPercent / 100 计算)
DetectorSwing MC_DetSwing_Speed 62 速度值(度/s,由 DefaultVelocity * speedPercent / 100 计算)
StageRotation MC_StageRot_Speed 74 速度值(度/s,由 DefaultVelocity * speedPercent / 100 计算)
FixtureRotation MC_FixRot_Speed 86 速度值(度/s,由 DefaultVelocity * speedPercent / 100 计算)

示例

  • DefaultVelocity = 100 mm/s
  • 摇杆输出 30% → PLC 接收速度 = 100 * 30% = 30 mm/s
  • 摇杆输出 100% → PLC 接收速度 = 100 * 100% = 100 mm/s

2. 轴位置输入框操作 | Axis Position Input Box Operation

2.1 编辑状态管理 | Editing State Management

  • GotFocusSetEditing(propertyName, true) → 冻结实时更新
  • LostFocus/EscapeCancelEditing(propertyName) → 恢复实时更新,显示实际值
  • EnterConfirmPosition(propertyName) → 发送目标位置移动命令

2.2 移动命令下发流程 | Move Command Flow

public void ConfirmPosition(string propertyName)
{
    var value = GetPropertyValue(propertyName);
    SafeRun(() =>
    {
        MotionResult result;
        // 如果射线源与探测器Z轴联动,且操作的是SourceZ或DetectorZ,则同时发送两个轴
        if (SZDZLock && (propertyName == nameof(SourceZPosition) || propertyName == nameof(DetectorZPosition)))
        {
            result = SendSourceDetectorZMoveCommand(value);
        }
        else
        {
            result = SendMoveCommand(propertyName, value);
        }
        if (result.Success)
            _logger.Info("目标位置已发送:{Property}={Value}", propertyName, value);
        else
            _logger.Warn("目标位置发送失败:{Property}={Value},原因={Reason}", propertyName, value, result.ErrorMessage);
    });
    _editingFlags[propertyName] = false;
}

private MotionResult SendMoveCommand(string propertyName, double value)
{
    switch (propertyName)
    {
        case nameof(StageXPosition):
            return _motionControlService.MoveToTarget(AxisId.StageX, value);
        case nameof(SourceZPosition):
            return _motionControlService.MoveToTarget(AxisId.SourceZ, value);
        // ... 其他轴
    }
}

2.3 单轴移动 | Single Axis Move

public MotionResult MoveToTarget(AxisId axisId, double targetPosition, int? speed = null)
{
    var axis = _motionSystem.GetLinearAxis(axisId);
    var actualSpeed = speed ?? _config.DefaultVelocity;
    
    // 边界检查(使用配置中的 Min/Max)
    if (targetPosition < config.Min || targetPosition > config.Max)
        return MotionResult.Fail("目标位置超出范围");
    
    // 写入目标位置和速度
    var targetResult = _signalService.EnqueueWrite(signalName, targetPosition);
    var speedResult = _signalService.EnqueueWrite(speedSignalName, actualSpeed);
    
    // 启动移动(写入目标位置会自动触发移动)
    return targetResult && speedResult ? MotionResult.Ok() : MotionResult.Fail("移动命令写入失败");
}

PLC 信号地址

目标位置信号 地址 速度信号 地址
SourceZ MC_SourceZ_Target 10 MC_SourceZ_Speed 14
DetectorZ MC_DetZ_Target 22 MC_DetZ_Speed 26
StageX MC_StageX_Target 34 MC_StageX_Speed 38
StageY MC_StageY_Target 46 MC_StageY_Speed 50
DetectorSwing MC_DetSwing_Target 58 MC_DetSwing_Speed 62
StageRotation MC_StageRot_Target 70 MC_StageRot_Speed 74
FixtureRotation MC_FixRot_Target 82 MC_FixRot_Speed 86

2.4 步进移动 | Step Move

上下箭头键触发 StepPosition(propertyName, delta)

public void StepPosition(string propertyName, double delta)
{
    var currentValue = GetPropertyValue(propertyName);
    var newValue = currentValue + delta;
    SetPropertyValue(propertyName, newValue);
    
    // 直接发送移动命令,不进入编辑冻结
    if (SZDZLock && (propertyName == nameof(SourceZPosition) || propertyName == nameof(DetectorZPosition)))
    {
        result = SendSourceDetectorZMoveCommand(newValue);
    }
    else
    {
        result = SendMoveCommand(propertyName, newValue);
    }
}

3. 射线源/探测器Z轴联动 | Source-Detector Z-axis Linkage

3.1 联动使能 | Linkage Enable

点击"SZDZLock"按钮切换联动状态:

private void ExecuteSZDZLock()
{
    SZDZLock = !SZDZLock;
    var result = _motionControlService.SetSourceDetectorZLinkage(SZDZLock);
    
    if (!result.Success)
    {
        // 设置失败,恢复状态
        SZDZLock = !SZDZLock;
        MessageBox.Show(result.ErrorMessage);
    }
}

PLC 信号

信号名 地址 类型 说明
MC_SourceDetZ_Linkage_Enable 101 bool 联动使能(true=启用, false=禁用)

3.2 联动移动 | Linkage Move

当 SZDZLock=true 时,移动 SourceZ 或 DetectorZ 会同时移动两个轴,保持位移量相同:

private MotionResult SendSourceDetectorZMoveCommand(double targetValue)
{
    // 1. 检查联动是否在配置中启用
    if (!_config.SourceDetectorZLinkage.Enabled)
        return MotionResult.Fail("射线源与探测器Z轴联动未启用");

    // 2. 检查两个轴是否都在空闲状态
    var sourceZAxis = _motionSystem.GetLinearAxis(AxisId.SourceZ);
    var detectorZAxis = _motionSystem.GetLinearAxis(AxisId.DetectorZ);

    if (sourceZAxis.Status == AxisStatus.Moving)
        return MotionResult.Fail("射线源Z轴正在运动中,无法联动移动");

    if (detectorZAxis.Status == AxisStatus.Moving)
        return MotionResult.Fail("探测器Z轴正在运动中,无法联动移动");

    // 3. 计算位移量和目标位置
    var currentSourceZ = sourceZAxis.ActualPosition;
    var currentDetectorZ = detectorZAxis.ActualPosition;
    var sourceDelta = targetValue - currentSourceZ;
    var targetDetectorZ = currentDetectorZ + sourceDelta;

    // 4. 边界检查(使用配置中的 Min/Max)
    if (targetValue < sourceConfig.Min || targetValue > sourceConfig.Max)
        return MotionResult.Fail("射线源Z轴目标位置超出范围");

    if (targetDetectorZ < detectorConfig.Min || targetDetectorZ > detectorConfig.Max)
        return MotionResult.Fail("探测器Z轴目标位置超出范围");

    // 5. 同时发送两个轴的移动命令
    var sourceResult = sourceZAxis.MoveToTarget(targetValue, _config.DefaultVelocity);
    var detectorResult = detectorZAxis.MoveToTarget(targetDetectorZ, _config.DefaultVelocity);

    return sourceResult.Success && detectorResult.Success 
        ? MotionResult.Ok() 
        : (sourceResult.Success ? detectorResult : sourceResult);
}

PLC 信号下发

  1. SourceZ 移动

    • MC_SourceZ_Target = targetValue(地址 10
    • MC_SourceZ_Speed = DefaultVelocity(地址 14
  2. DetectorZ 移动

    • MC_DetZ_Target = currentDetectorZ + (targetValue - currentSourceZ)(地址 22
    • MC_DetZ_Speed = DefaultVelocity(地址 26

3.3 联动 Jog | Linkage Jog

当 SZDZLock=true 时,单轴摇杆的左右键会同时控制两个轴:

private void HandleSingleJoystickOutput()
{
    switch (SingleJoystickActiveButton)
    {
        case MouseButtonType.Left:
            UpdateLinearJog(AxisId.SourceZ, SingleJoystickOutputY);
            if (_szdzLock)
                UpdateLinearJog(AxisId.DetectorZ, SingleJoystickOutputY);
            break;
        case MouseButtonType.Right:
            UpdateLinearJog(AxisId.DetectorZ, SingleJoystickOutputY);
            if (_szdzLock)
                UpdateLinearJog(AxisId.SourceZ, SingleJoystickOutputY);
            break;
    }
}

PLC 信号下发

  • SourceZ Jog

    • MC_SourceZ_JogPos/MC_SourceZ_JogNeg = true(地址 18/19
    • MC_SourceZ_Speed = speedPercent(地址 14
  • DetectorZ Jog

    • MC_DetZ_JogPos/MC_DetZ_JogNeg = true(地址 30/31
    • MC_DetZ_Speed = speedPercent(地址 26

4. 虚拟摇杆使能 | Virtual Joystick Enable

点击"使能开关"按钮切换虚拟摇杆使能状态:

private void ExecuteToggleEnable()
{
    IsJoystickEnabled = !IsJoystickEnabled;
    _motionControlService.SetVirtualJoystickEnable(IsJoystickEnabled);
}

PLC 信号

信号名 地址 类型 说明
MC_VirtualJoystick_Enable 111 bool 虚拟摇杆使能(true=启用, false=禁用)

5. 信号写入队列机制 | Signal Write Queue Mechanism

所有 PLC 写入操作通过 ISignalDataService.EnqueueWrite() 进入写入队列:

public interface ISignalDataService
{
    bool EnqueueWrite(string signalName, object value);
    bool EnqueueWriteBatch(Dictionary<string, object> writes);
}

队列处理流程

  1. ViewModel 调用 EnqueueWrite(signalName, value)
  2. 信号数据服务将写入请求加入队列
  3. PLC 服务在轮询周期(默认 100ms)中批量写入
  4. 写入成功后从队列移除

优势

  • 批量写入减少 PLC 通讯次数
  • 避免频繁写入导致的通讯拥塞
  • 统一的错误处理和重试机制