ACS SPiiPlus .NET库 - C#上位机开发指南

版本 1.0
基于官方Demo源代码


目录

  1. 简介
  2. 系统架构
  3. 环境配置
  4. 核心API类
  5. 连接管理
  6. 轴控制
  7. 运动控制
  8. 状态监控
  9. I/O操作
  10. 错误处理
  11. 完整示例
  12. 最佳实践
  13. 常见问题

1. 简介

1.1 概述

ACS SPiiPlus .NET库是一个功能强大的软件开发工具包(SDK),用于在Windows平台上通过.NET Framework与ACS运动控制器进行通信。本指南基于官方Demo源代码,为C#上位机开发者提供详细的使用说明和代码示例。

1.2 主要特性

  • 多种通信方式:串口、以太网(TCP/UDP)、PCI总线、模拟器
  • 完整的运动控制:点到点运动、电子齿轮、插补等
  • 实时状态监控:轴使能状态、运动状态、位置反馈
  • 异步/同步操作:支持同步和异步API调用
  • 多线程支持:可在后台线程中监控轴状态
  • MVVM架构支持:提供完整的MVVM模式实现示例

1.3 支持的控制器

  • SPiiPlusSC系列
  • SPiiPlusEC系列
  • SPiiPlusUD系列
  • SPiiPlusCM系列
  • SPiiPlusHP系列

2. 系统架构

2.1 核心类层次结构

┌─────────────────────────────────────┐
│         Application Layer           │
│   (WinForms / WPF / Avalonia)   │
└──────────────┬──────────────────┘
               │
┌──────────────▼──────────────────┐
│      CommunicationManager          │  ← 通信管理(单例)
├─────────────────────────────────┤
│ - OpenCommSerialAsync()         │
│ - OpenCommEthernetAsync()       │
│ - OpenCommPCIAsync()           │
│ - EnableAxisAsync()            │
│ - ToPointAsync()               │
│ - KillAsync()                 │
│ - StartMonitorAxis...()        │
└──────────────┬──────────────────┘
               │
┌──────────────▼──────────────────┐
│           Api Class             │  ← 核心API库
│      (ACS.SPiiPlusNET)         │
├─────────────────────────────────┤
│ - OpenCommSerial()             │
│ - OpenCommEthernet()           │
│ - Enable() / Disable()         │
│ - ToPoint() / Kill()          │
│ - GetFPosition()              │
│ - GetMotorState()             │
└─────────────────────────────────┘

2.2 MVVM架构(WPF Demo)

┌─────────────────────────────────────┐
│           MainWindow.xaml          │  ← View层
└──────────────┬──────────────────┘
               │ (DataContext)
┌──────────────▼──────────────────┐
│        MainViewModel            │  ← ViewModel层
├─────────────────────────────────┤
│ - ConnectCommand              │
│ - ConnectAxesCommand          │
│ - StartMoveMotorCommand       │
│ - SelectedAxes               │
│ - IsConnected               │
└──────────────┬──────────────────┘
               │
┌──────────────▼──────────────────┐
│     CommunicationManager        │  ← Model层
│  (Singleton Pattern)          │
└──────────────┬──────────────────┘
               │
┌──────────────▼──────────────────┐
│         Api Class              │  ← 数据访问层
└─────────────────────────────────┘

2.3 WinForms架构

┌─────────────────────────────────────┐
│        COMLibDemoFrm            │  ← Form主类
├─────────────────────────────────┤
│ - Ch (Api)                  │  ← Api实例
│ - Axis (Axis枚举)            │  ← 当前轴
│ - MotorStateThr (Thread)     │  ← 状态监控线程
│ - ConnectBtn_Click()          │
│ - EnableBtn_Click()           │
│ - StartBtn_Click()           │
└──────────────┬──────────────────┘
               │
┌──────────────▼──────────────────┐
│         Api Class              │  ← 核心API
└─────────────────────────────────┘

3. 环境配置

3.1 项目创建

WinForms项目
// 1. 创建WinForms项目
// 2. 添加引用:ACS.SPiiPlusNET.dll
// 3. 引用命名空间
using ACS.SPiiPlusNET;
WPF项目
// 1. 创建WPF项目
// 2. 添加引用:ACS.SPiiPlusNET.dll
// 3. 引用命名空间
using ACS.SPiiPlusNET;
using NET.Library.Demo.CS.WPF.VS2017.Model.Manager;

3.2 添加DLL引用

方法1:通过NuGet
# 在Package Manager Console中执行
Install-Package ACS.SPiiPlusNET
方法2:手动添加DLL
  1. ACS.SPiiPlusNET.dll复制到项目目录
  2. 在Visual Studio中右键"引用" → “添加引用”
  3. 选择"浏览"并选择DLL文件

3.3 必需命名空间

using System;
using System.Threading;
using System.Threading.Tasks;
using ACS.SPiiPlusNET;              // 核心库

4. 核心API类

4.1 Api类

Api类是所有通信和运动控制的核心类。

构造函数
// 创建Api实例
Api Ch = new Api();
主要方法
方法说明返回值
OpenCommSerial(int channel, int rate)打开串口连接void
OpenCommEthernet(string address, int port)打开以太网连接void
OpenCommPCI(int slotNumber)打开PCI总线连接void
OpenCommSimulator()打开模拟器连接void
CloseComm()关闭连接void
Enable(Axis axis)使能轴void
Disable(Axis axis)禁用轴void
ToPoint(MotionFlags flags, Axis axis, double point)点到点运动void
Kill(Axis axis)停止运动void
GetFPosition(Axis axis)获取反馈位置double
GetMotorState(Axis axis)获取电机状态MotorStates
SetFPosition(Axis axis, double position)设置反馈位置void
Commut(Axis axis)电机换向void
Transaction(string command)发送ACSPL+命令string

4.2 Axis枚举

public enum Axis
{
    ACSC_SYSTEM = -3,
	ACSC_PAR_ALL = -2,
	ACSC_NONE = -1,
	ACSC_AXIS_0 = 0,
	ACSC_AXIS_1 = 1,
	ACSC_AXIS_2 = 2,
	ACSC_AXIS_3 = 3,
	ACSC_AXIS_4 = 4,
	ACSC_AXIS_5 = 5,
	ACSC_AXIS_6 = 6,
	ACSC_AXIS_7 = 7,
	ACSC_AXIS_8 = 8,
	ACSC_AXIS_9 = 9,
	ACSC_AXIS_10 = 10,
    // ... 更多轴
}

4.3 MotorStates枚举

[Flags]
public enum MotorStates
{
	ACSC_ALL = -1,
	ACSC_NONE = 0,
	ACSC_MST_ENABLE = 1,
	ACSC_MST_INPOS = 16,
	ACSC_MST_MOVE = 32,
	ACSC_MST_ACC = 64
}

5. 连接管理

5.1 串口连接

同步方式
Api Ch = new Api();

try
{
    // 打开串口连接
    // channel: COM端口号(1=COM1, 2=COM2...)
    // rate: 波特率(-1表示自动检测)
    Ch.OpenCommSerial(1, 115200);
    Console.WriteLine("串口连接成功");
}
catch (ACSException ex)
{
    Console.WriteLine("连接失败: " + ex.Message);
}
异步方式(推荐)
public async Task ConnectSerialAsync(int port, int baudRate)
{
    return await Task.Run(() =>
    {
        try
        {
            Ch.OpenCommSerial(port, baudRate);
            return true;
        }
        catch (ACSException ex)
        {
            Console.WriteLine("连接失败: " + ex.Message);
            return false;
        }
    });
}

// 使用示例
bool connected = await ConnectSerialAsync(1, 115200);

5.2 以太网连接

Api Ch = new Api();

try
{
    // 点对点UDP连接
    int protocol = (int)EthernetCommOption.ACSC_SOCKET_DGRAM_PORT;
    Ch.OpenCommEthernet("192.168.1.100", protocol);
    Console.WriteLine("以太网连接成功(UDP)");
}
catch (ACSException ex)
{
    Console.WriteLine("连接失败: " + ex.Message);
}

// TCP连接
try
{
    int protocol = (int)EthernetCommOption.ACSC_SOCKET_STREAM_PORT;
    Ch.OpenCommEthernet("192.168.1.100", protocol);
    Console.WriteLine("以太网连接成功(TCP)");
}
catch (ACSException ex)
{
    Console.WriteLine("连接失败: " + ex.Message);
}

5.3 PCI总线连接

Api Ch = new Api();

try
{
    // 打开PCI总线连接
    // slotNumber: PCI插槽号(-1表示自动检测)
    Ch.OpenCommPCI(-1);
    Console.WriteLine("PCI总线连接成功");
}
catch (ACSException ex)
{
    Console.WriteLine("连接失败: " + ex.Message);
}

5.4 模拟器连接

Api Ch = new Api();

try
{
    // 打开模拟器连接(用于测试)
    Ch.OpenCommSimulator();
    Console.WriteLine("模拟器连接成功");
}
catch (ACSException ex)
{
    Console.WriteLine("连接失败: " + ex.Message);
}

5.5 断开连接

// 断开所有类型的连接
try
{
    Ch.CloseComm();
    Console.WriteLine("已断开连接");
}
catch (ACSException ex)
{
    Console.WriteLine("断开连接失败: " + ex.Message);
}

5.6 检查连接状态

// 使用异常检测连接状态
public bool IsConnected()
{
    try
    {
        // 尝试读取位置,如果失败则未连接
        Ch.GetFPosition(Axis.ACSC_AXIS_X);
        return true;
    }
    catch (ACSException)
    {
        return false;
    }
}

6. 轴控制

6.1 获取轴列表

public List<Axis> GetAvailableAxes()
{
    var axes = new List<Axis>();

    try
    {
        // 使用Transaction命令获取轴列表
        string response = Ch.Transaction("#PR/AXES/INTERNALDRIVES/");
        if (!string.IsNullOrEmpty(response))
        {
            var axisNumbers = response.Trim().Split(',');
            foreach (var num in axisNumbers)
            {
                if (int.TryParse(num, out int axisNum))
                {
                    axes.Add((Axis)axisNum);
                }
            }
        }
    }
    catch (ACSException ex)
    {
        Console.WriteLine("获取轴列表失败: " + ex.Message);
    }

    return axes;
}

6.2 使能轴

同步使能
try
{
    // 使能X轴
    Ch.Enable(Axis.ACSC_AXIS_X);

    // 等待使能完成
    Ch.WaitMotorEnabled(Axis.ACSC_AXIS_X, 1, 30000);

    Console.WriteLine("轴已使能");
}
catch (ACSException ex)
{
    Console.WriteLine("使能失败: " + ex.Message);
}
异步使能
public async Task<bool> EnableAxisAsync(Axis axis)
{
    return await Task.Run(() =>
    {
        try
        {
            Ch.Enable(axis);
            Ch.WaitMotorEnabled(axis, 1, 30000);
            return true;
        }
        catch (ACSException ex)
        {
            Console.WriteLine("使能失败: " + ex.Message);
            return false;
        }
    });
}

// 使用示例
bool result = await EnableAxisAsync(Axis.ACSC_AXIS_X);

6.3 禁用轴

try
{
    // 禁用X轴
    Ch.Disable(Axis.ACSC_AXIS_X);
    Console.WriteLine("轴已禁用");
}
catch (ACSException ex)
{
    Console.WriteLine("禁用失败: " + ex.Message);
}

6.4 检查轴使能状态

public bool IsAxisEnabled(Axis axis)
{
    try
    {
        MotorStates state = Ch.GetMotorState(axis);
        return (state & MotorStates.ACSC_MST_ENABLE) != 0;
    }
    catch (ACSException ex)
    {
        Console.WriteLine("读取状态失败: " + ex.Message);
        return false;
    }
}

6.5 电机换向(针对无刷电机)

public bool CommutMotor(Axis axis)
{
    try
    {
        // 检查是否为无刷电机
        string isBrush = Ch.Transaction($"?MFLAGS({(int)axis}).#BRUSHL").Trim();
        if (isBrush != "1")
        {
            Console.WriteLine("该轴不是无刷电机,无需换向");
            return true;
        }

        // 检查是否已换向
        string isBrushOk = Ch.Transaction($"?MFLAGS({(int)axis}).#BRUSHOK").Trim();
        if (isBrushOk == "1")
        {
            Console.WriteLine("电机已换向");
            return true;
        }

        // 执行换向
        Ch.Commut(axis);
        Ch.WaitMotorCommutated(axis, 1, 30000);

        Console.WriteLine("电机换向完成");
        return true;
    }
    catch (ACSException ex)
    {
        Console.WriteLine("换向失败: " + ex.Message);
        return false;
    }
}

7. 运动控制

7.1 点到点运动(PTP)

相对运动
try
{
    // 相对运动1000个单位
    double increment = 1000;

    Ch.ToPoint(
        MotionFlags.ACSC_AMF_RELATIVE,  // 相对位置标志
        Axis.ACSC_AXIS_X,                 // 目标轴
        increment                          // 移动距离
    );

    Console.WriteLine("已启动相对运动");
}
catch (ACSException ex)
{
    Console.WriteLine("运动失败: " + ex.Message);
}
绝对运动
try
{
    // 移动到绝对位置1000
    double targetPosition = 1000;

    Ch.ToPoint(
        MotionFlags.ACSC_NONE,  		// 默认绝对位置
        Axis.ACSC_AXIS_X,                 // 目标轴
        targetPosition                    // 目标位置
    );

    Console.WriteLine("已启动绝对运动");
}
catch (ACSException ex)
{
    Console.WriteLine("运动失败: " + ex.Message);
}

7.2 停止运动

try
{
    // 停止X轴运动
    Ch.Kill(Axis.ACSC_AXIS_X);
    Console.WriteLine("运动已停止");
}
catch (ACSException ex)
{
    Console.WriteLine("停止失败: " + ex.Message);
}

7.3 等待运动完成

public async Task<bool> WaitForMotionCompleteAsync(Axis axis, int timeoutMs = 30000)
{
    return await Task.Run(() =>
    {
        int startTime = Environment.TickCount;
        while (Environment.TickCount - startTime < timeoutMs)
        {
            try
            {
                MotorStates state = Ch.GetMotorState(axis);
                if ((state & MotorStates.ACSC_MST_MOVE) == 0)
                {
                    Console.WriteLine("运动已完成");
                    return true;
                }
            }
            catch (ACSException ex)
            {
                Console.WriteLine("读取状态失败: " + ex.Message);
                return false;
            }

            Thread.Sleep(50);
        }
        Console.WriteLine("等待超时");
        return false;
    });
}

7.4 设置反馈位置

try
{
    // 将X轴的反馈位置置零
    Ch.SetFPosition(Axis.ACSC_AXIS_X, 0.0);
    Console.WriteLine("反馈位置已置零");
}
catch (ACSException ex)
{
    Console.WriteLine("设置失败: " + ex.Message);
}

8. 状态监控

8.1 单线程状态监控

public class AxisMonitor
{
    private readonly Api _api;
    private readonly Axis _axis;
    private readonly Thread _monitorThread;
    private readonly ManualResetEvent _stopEvent;

    public event Action<double> PositionChanged;
    public event Action<bool> EnableStateChanged;
    public event Action<bool> MoveStateChanged;

    public AxisMonitor(Api api, Axis axis)
    {
        _api = api;
        _axis = axis;
        _stopEvent = new ManualResetEvent(false);
        _monitorThread = new Thread(MonitorLoop)
        {
            IsBackground = true
        };
    }

    public void Start()
    {
        _stopEvent.Reset();
        _monitorThread.Start();
    }

    public void Stop()
    {
        _stopEvent.Set();
        _monitorThread.Join(1000);
    }

    private void MonitorLoop()
    {
        while (!_stopEvent.WaitOne(100))
        {
            try
            {
                // 读取反馈位置
                double position = _api.GetFPosition(_axis);
                PositionChanged?.Invoke(position);

                // 读取电机状态
                MotorStates state = _api.GetMotorState(_axis);

                // 使能状态
                bool isEnabled = (state & MotorStates.ACSC_MST_ENABLE) != 0;
                EnableStateChanged?.Invoke(isEnabled);

                // 运动状态
                bool isMoving = (state & MotorStates.ACSC_MST_MOVE) != 0;
                MoveStateChanged?.Invoke(isMoving);
            }
            catch (ACSException ex)
            {
                Console.WriteLine($"监控出错: {ex.Message}");
            }
        }
    }
}

8.2 使用状态监控

Api Ch = new Api();
Ch.OpenCommEthernet("192.168.1.100", 801);

// 创建监控器
var monitor = new AxisMonitor(Ch, Axis.ACSC_AXIS_X);

// 订阅事件
monitor.PositionChanged += (pos) =>
{
    Console.WriteLine($"位置: {pos:F2}");
};

monitor.EnableStateChanged += (enabled) =>
{
    Console.WriteLine($"使能状态: {(enabled ? "已使能" : "已禁用")}");
};

monitor.MoveStateChanged += (moving) =>
{
    Console.WriteLine($"运动状态: {(moving ? "运动中" : "停止")}");
};

// 启动监控
monitor.Start();

// ... 执行运动 ...

// 停止监控
monitor.Stop();

8.3 多轴监控

public class MultiAxisMonitor
{
    private readonly Api _api;
    private readonly List<Axis> _axes;
    private readonly Thread _monitorThread;
    private readonly ManualResetEvent _stopEvent;

    public Dictionary<Axis, double> Positions { get; } = new();
    public Dictionary<Axis, MotorStates> States { get; } = new();

    public event Action AxisStatesUpdated;

    public MultiAxisMonitor(Api api, IEnumerable<Axis> axes)
    {
        _api = api;
        _axes = axes.ToList();
        _stopEvent = new ManualResetEvent(false);
        _monitorThread = new Thread(MonitorLoop)
        {
            IsBackground = true
        };
    }

    public void Start()
    {
        _stopEvent.Reset();
        _monitorThread.Start();
    }

    public void Stop()
    {
        _stopEvent.Set();
        _monitorThread.Join(1000);
    }

    private void MonitorLoop()
    {
        while (!_stopEvent.WaitOne(100))
        {
            foreach (var axis in _axes)
            {
                try
                {
                    Positions[axis] = _api.GetFPosition(axis);
                    States[axis] = _api.GetMotorState(axis);
                }
                catch (ACSException ex)
                {
                    Console.WriteLine($"读取轴{axis}状态失败: {ex.Message}");
                }
            }

            AxisStatesUpdated?.Invoke();
        }
    }
}

9. I/O操作

9.1 数字输入

public bool GetDigitalInput(int ioNumber)
{
    try
    {
        string response = Ch.Transaction($"?DIN({ioNumber})");
        return int.Parse(response) != 0;
    }
    catch (ACSException ex)
    {
        Console.WriteLine($"读取数字输入失败: {ex.Message}");
        return false;
    }
}

9.2 数字输出

public bool SetDigitalOutput(int ioNumber, bool state)
{
    try
    {
        string command = $"DOUT({ioNumber})={(state ? 1 : 0)}";
        Ch.Transaction(command);
        return true;
    }
    catch (ACSException ex)
    {
        Console.WriteLine($"设置数字输出失败: {ex.Message}");
        return false;
    }
}

9.3 模拟输入

public double GetAnalogInput(int channelNumber)
{
    try
    {
        string response = Ch.Transaction($"?AIN({channelNumber})");
        return double.Parse(response);
    }
    catch (ACSException ex)
    {
        Console.WriteLine($"读取模拟输入失败: {ex.Message}");
        return 0.0;
    }
}

9.4 模拟输出

public bool SetAnalogOutput(int channelNumber, double value)
{
    try
    {
        string command = $"AOUT({channelNumber})={value}";
        Ch.Transaction(command);
        return true;
    }
    catch (ACSException ex)
    {
        Console.WriteLine($"设置模拟输出失败: {ex.Message}");
        return false;
    }
}

10. 错误处理

10.1 异常类型

ACSException

ACS库抛出的主要异常类型。

try
{
    Ch.Enable(Axis.ACSC_AXIS_X);
}
catch (ACSException ex)
{
    Console.WriteLine($"ACS错误: {ex.Message}");
    Console.WriteLine($"错误代码: 0x{ex.ErrorCode:X}");
    Console.WriteLine($"错误源: {ex.Source}");
}
COMException

旧版COM接口使用的异常类型。

try
{
    Ch.EnableAsync(Axis.ACSC_AXIS_X);
}
catch (COMException ex)
{
    Console.WriteLine($"COM错误: {ex.Message}");
    Console.WriteLine($"错误代码: 0x{ex.ErrorCode:X}");
}

10.2 错误代码

错误代码描述处理方式
0x80040001参数错误检查传入参数是否有效
0x80040002轴号超出范围检查轴号是否在有效范围内
0x80040003连接未建立先建立连接再执行操作
0x80040004轴未使能先使能轴再执行运动
0x80040005轴在运动中使用Kill停止或等待完成
0x80040006运动失败检查限位、错误标志等

10.3 错误处理包装器

public static class ApiExtensions
{
    public static bool TryExecute(this Api api, Action action, string operationName = "")
    {
        try
        {
            action();
            return true;
        }
        catch (ACSException ex)
        {
            Console.WriteLine($"{operationName}失败: {ex.Message}");
            Console.WriteLine($"错误代码: 0x{ex.ErrorCode:X}");
            return false;
        }
        catch (Exception ex)
        {
            Console.WriteLine($"{operationName}发生未预期的错误: {ex.Message}");
            return false;
        }
    }

    public static T TryExecute<T>(this Api api, Func<T> func, T defaultValue = default, string operationName = "")
    {
        try
        {
            return func();
        }
        catch (ACSException ex)
        {
            Console.WriteLine($"{operationName}失败: {ex.Message}");
            Console.WriteLine($"错误代码: 0x{ex.ErrorCode:X}");
            return defaultValue;
        }
        catch (Exception ex)
        {
            Console.WriteLine($"{operationName}发生未预期的错误: {ex.Message}");
            return defaultValue;
        }
    }
}

10.4 使用错误处理包装器

// 不使用包装器
try
{
    Ch.Enable(Axis.ACSC_AXIS_X);
}
catch (ACSException ex)
{
    Console.WriteLine($"使能失败: {ex.Message}");
}

// 使用包装器
bool success = Ch.TryExecute(() =>
{
    Ch.Enable(Axis.ACSC_AXIS_X);
}, "使能轴");

double position = Ch.TryExecute(() =>
{
    return Ch.GetFPosition(Axis.ACSC_AXIS_X);
}, 0.0, "读取位置");

11. 完整示例

11.1 WinForms完整示例

using System;
using System.Threading;
using System.Windows.Forms;
using ACS.SPiiPlusNET;

namespace AcsControllerDemo
{
    public partial class MainForm : Form
    {
        private Api _api;
        private Axis _currentAxis = Axis.ACSC_AXIS_X;
        private Thread _monitorThread;
        private volatile bool _isMonitoring = false;

        public MainForm()
        {
            InitializeComponent();
            _api = new Api();
            _monitorThread = new Thread(MonitorLoop)
            {
                IsBackground = true
            };
        }

        private void btnConnect_Click(object sender, EventArgs e)
        {
            try
            {
                // 以太网连接
                _api.OpenCommEthernet(txtIpAddress.Text, 801);
                btnConnect.Enabled = false;
                btnDisconnect.Enabled = true;

                // 启动监控
                _isMonitoring = true;
                _monitorThread.Start();

                MessageBox.Show("连接成功!");
            }
            catch (ACSException ex)
            {
                MessageBox.Show($"连接失败: {ex.Message}");
            }
        }

        private void btnDisconnect_Click(object sender, EventArgs e)
        {
            try
            {
                // 停止监控
                _isMonitoring = false;
                _monitorThread.Join(1000);

                // 断开连接
                _api.CloseComm();
                btnConnect.Enabled = true;
                btnDisconnect.Enabled = false;

                MessageBox.Show("已断开连接");
            }
            catch (ACSException ex)
            {
                MessageBox.Show($"断开失败: {ex.Message}");
            }
        }

        private void btnEnable_Click(object sender, EventArgs e)
        {
            try
            {
                if (btnEnable.Text == "使能")
                {
                    _api.Enable(_currentAxis);
                    _api.WaitMotorEnabled(_currentAxis, 1, 30000);
                    btnEnable.Text = "禁用";
                }
                else
                {
                    _api.Disable(_currentAxis);
                    btnEnable.Text = "使能";
                }
            }
            catch (ACSException ex)
            {
                MessageBox.Show($"操作失败: {ex.Message}");
            }
        }

        private void btnMove_Click(object sender, EventArgs e)
        {
            try
            {
                double distance = double.Parse(txtDistance.Text);
                _api.ToPoint(MotionFlags.ACSC_AMF_RELATIVE, _currentAxis, distance);
            }
            catch (ACSException ex)
            {
                MessageBox.Show($"运动失败: {ex.Message}");
            }
        }

        private void btnStop_Click(object sender, EventArgs e)
        {
            try
            {
                _api.Kill(_currentAxis);
            }
            catch (ACSException ex)
            {
                MessageBox.Show($"停止失败: {ex.Message}");
            }
        }

        private void btnZeroPosition_Click(object sender, EventArgs e)
        {
            try
            {
                _api.SetFPosition(_currentAxis, 0.0);
            }
            catch (ACSException ex)
            {
                MessageBox.Show($"置零失败: {ex.Message}");
            }
        }

        private void MonitorLoop()
        {
            while (_isMonitoring)
            {
                try
                {
                    if (this.InvokeRequired)
                    {
                        this.Invoke(new Action(UpdateStatus));
                    }
                    else
                    {
                        UpdateStatus();
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"监控错误: {ex.Message}");
                }

                Thread.Sleep(100);
            }
        }

        private void UpdateStatus()
        {
            try
            {
                // 读取位置
                double position = _api.GetFPosition(_currentAxis);
                lblPosition.Text = position.ToString("F2");

                // 读取状态
                MotorStates state = _api.GetMotorState(_currentAxis);

                // 使能状态
                bool isEnabled = (state & MotorStates.ACSC_MST_ENABLE) != 0;
                lblEnabled.Text = isEnabled ? "已使能" : "已禁用";
                picEnabled.BackColor = isEnabled ? System.Drawing.Color.Green : System.Drawing.Color.Gray;

                // 运动状态
                bool isMoving = (state & MotorStates.ACSC_MST_MOVE) != 0;
                lblMoving.Text = isMoving ? "运动中" : "停止";
                picMoving.BackColor = isMoving ? System.Drawing.Color.Green : System.Drawing.Color.Gray;
            }
            catch (ACSException ex)
            {
                Console.WriteLine($"更新状态失败: {ex.Message}");
            }
        }

        protected override void OnFormClosing(FormClosingEventArgs e)
        {
            try
            {
                _isMonitoring = false;
                if (_monitorThread.IsAlive)
                {
                    _monitorThread.Join(1000);
                }
                _api.CloseComm();
            }
            catch { }
            base.OnFormClosing(e);
        }
    }
}

11.2 WPF MVVM完整示例

using System;
using System.Threading.Tasks;
using System.Windows.Input;
using ACS.SPiiPlusNET;

namespace AcsController.WPF
{
    public class MainViewModel : ObservableObject
    {
        private readonly Api _api;
        private bool _isConnected;
        private Axis _selectedAxis = Axis.ACSC_AXIS_X;
        private double _currentPosition;
        private bool _isAxisEnabled;
        private bool _isAxisMoving;
        private double _moveDistance = 1000;

        public MainViewModel()
        {
            _api = new Api();
        }

        #region Properties

        public bool IsConnected
        {
            get => _isConnected;
            set
            {
                _isConnected = value;
                OnPropertyChanged();
                ConnectCommand.RaiseCanExecuteChanged();
                DisconnectCommand.RaiseCanExecuteChanged();
            }
        }

        public Axis SelectedAxis
        {
            get => _selectedAxis;
            set
            {
                _selectedAxis = value;
                OnPropertyChanged();
            }
        }

        public double CurrentPosition
        {
            get => _currentPosition;
            set
            {
                _currentPosition = value;
                OnPropertyChanged();
            }
        }

        public bool IsAxisEnabled
        {
            get => _isAxisEnabled;
            set
            {
                _isAxisEnabled = value;
                OnPropertyChanged();
                EnableCommand.RaiseCanExecuteChanged();
                MoveCommand.RaiseCanExecuteChanged();
            }
        }

        public bool IsAxisMoving
        {
            get => _isAxisMoving;
            set
            {
                _isAxisMoving = value;
                OnPropertyChanged();
                StopCommand.RaiseCanExecuteChanged();
            }
        }

        public double MoveDistance
        {
            get => _moveDistance;
            set
            {
                _moveDistance = value;
                OnPropertyChanged();
            }
        }

        #endregion

        #region Commands

        public RelayCommand ConnectCommand { get; }
        public RelayCommand DisconnectCommand { get; }
        public RelayCommand EnableCommand { get; }
        public RelayCommand MoveCommand { get; }
        public RelayCommand StopCommand { get; }
        public RelayCommand ZeroPositionCommand { get; }

        public MainViewModel()
        {
            ConnectCommand = new RelayCommand(ExecuteConnect, CanExecuteConnect);
            DisconnectCommand = new RelayCommand(ExecuteDisconnect, CanExecuteDisconnect);
            EnableCommand = new RelayCommand(ExecuteEnable, CanExecuteEnable);
            MoveCommand = new RelayCommand(ExecuteMove, CanExecuteMove);
            StopCommand = new RelayCommand(ExecuteStop, CanExecuteStop);
            ZeroPositionCommand = new RelayCommand(ExecuteZeroPosition, CanExecuteZeroPosition);
        }

        #endregion

        #region Command Handlers

        private bool CanExecuteConnect()
        {
            return !IsConnected;
        }

        private void ExecuteConnect()
        {
            try
            {
                _api.OpenCommEthernet("192.168.1.100", 801);
                IsConnected = true;
            }
            catch (ACSException ex)
            {
                MessageBox.Show($"连接失败: {ex.Message}");
            }
        }

        private bool CanExecuteDisconnect()
        {
            return IsConnected;
        }

        private void ExecuteDisconnect()
        {
            try
            {
                _api.CloseComm();
                IsConnected = false;
            }
            catch (ACSException ex)
            {
                MessageBox.Show($"断开失败: {ex.Message}");
            }
        }

        private bool CanExecuteEnable()
        {
            return IsConnected;
        }

        private void ExecuteEnable()
        {
            try
            {
                if (IsAxisEnabled)
                {
                    _api.Disable(SelectedAxis);
                }
                else
                {
                    _api.Enable(SelectedAxis);
                    _api.WaitMotorEnabled(SelectedAxis, 1, 30000);
                }
            }
            catch (ACSException ex)
            {
                MessageBox.Show($"操作失败: {ex.Message}");
            }
        }

        private bool CanExecuteMove()
        {
            return IsConnected && IsAxisEnabled && !IsAxisMoving;
        }

        private void ExecuteMove()
        {
            try
            {
                _api.ToPoint(MotionFlags.ACSC_AMF_RELATIVE, SelectedAxis, MoveDistance);
            }
            catch (ACSException ex)
            {
                MessageBox.Show($"运动失败: {ex.Message}");
            }
        }

        private bool CanExecuteStop()
        {
            return IsConnected && IsAxisMoving;
        }

        private void ExecuteStop()
        {
            try
            {
                _api.Kill(SelectedAxis);
            }
            catch (ACSException ex)
            {
                MessageBox.Show($"停止失败: {ex.Message}");
            }
        }

        private bool CanExecuteZeroPosition()
        {
            return IsConnected;
        }

        private void ExecuteZeroPosition()
        {
            try
            {
                _api.SetFPosition(SelectedAxis, 0.0);
            }
            catch (ACSException ex)
            {
                MessageBox.Show($"置零失败: {ex.Message}");
            }
        }

        #endregion
    }

    // ObservableObject基类
    public abstract class ObservableObject : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    // RelayCommand基类
    public class RelayCommand : ICommand
    {
        private readonly Action _execute;
        private readonly Func<bool> _canExecute;

        public RelayCommand(Action execute, Func<bool> canExecute = null)
        {
            _execute = execute;
            _canExecute = canExecute;
        }

        public event EventHandler CanExecuteChanged;

        public bool CanExecute(object parameter)
        {
            return _canExecute?.Invoke() ?? true;
        }

        public void Execute(object parameter)
        {
            _execute?.Invoke();
        }

        public void RaiseCanExecuteChanged()
        {
            CanExecuteChanged?.Invoke(this, EventArgs.Empty);
        }
    }
}

12. 最佳实践

12.1 连接管理

  1. 使用using语句管理连接
using (var api = new Api())
{
    api.OpenCommEthernet("192.168.1.100", 801);
    // 执行操作
} // 自动调用CloseComm()
  1. 添加连接超时处理
public async Task<bool> ConnectWithTimeout(string address, int port, int timeoutMs)
{
    var connectTask = Task.Run(() =>
    {
        try
        {
            _api.OpenCommEthernet(address, port);
            return true;
        }
        catch { return false; }
    });

    var timeoutTask = Task.Delay(timeoutMs);
    var completedTask = await Task.WhenAny(connectTask, timeoutTask);

    return completedTask == connectTask && await connectTask;
}

12.2 错误处理

  1. 集中错误处理
public class ErrorHandler
{
    public static bool HandleException(Exception ex, string operation)
    {
        if (ex is ACSException acsEx)
        {
            LogError(acsEx);
            return ShouldRetry(acsEx);
        }
        LogError(ex);
        return false;
    }

    private static void LogError(Exception ex)
    {
        // 记录到日志文件
        File.AppendAllText("error.log",
            $"{DateTime.Now}: {ex.Message}\n{ex.StackTrace}\n");
    }

    private static bool ShouldRetry(ACSException ex)
    {
        // 判断是否应该重试
        return ex.ErrorCode == 0x80040003; // 连接未建立
    }
}
  1. 使用重试机制
public async Task<bool> ExecuteWithRetry(Func<Task<bool>> action, int maxRetries = 3)
{
    for (int i = 0; i < maxRetries; i++)
    {
        try
        {
            return await action();
        }
        catch (ACSException ex) when (ShouldRetry(ex) && i < maxRetries - 1)
        {
            Console.WriteLine($"重试 {i + 1}/{maxRetries}...");
            await Task.Delay(1000 * (i + 1));
        }
    }
    return false;
}

12.3 线程安全

  1. 使用锁保护共享资源
private readonly object _apiLock = new object();

public bool EnableAxis(Axis axis)
{
    lock (_apiLock)
    {
        try
        {
            _api.Enable(axis);
            return true;
        }
        catch (ACSException ex)
        {
            Console.WriteLine($"使能失败: {ex.Message}");
            return false;
        }
    }
}
  1. 使用CancellationToken取消操作
public async Task<bool> EnableAxisAsync(Axis axis, CancellationToken ct)
{
    try
    {
        await Task.Run(() =>
        {
            ct.ThrowIfCancellationRequested();
            _api.Enable(axis);
            _api.WaitMotorEnabled(axis, 1, 30000);
        }, ct);
        return true;
    }
    catch (OperationCanceledException)
    {
        Console.WriteLine("操作已取消");
        return false;
    }
}

12.4 性能优化

  1. 批量读取变量
public Dictionary<Axis, double> GetAllPositions(IEnumerable<Axis> axes)
{
    var result = new Dictionary<Axis, double>();

    // 使用Transaction批量读取
    string command = string.Join(",", axes.Select(a => $"?FPOS({(int)a})"));
    string response = _api.Transaction(command);

    var values = response.Split(',');
    for (int i = 0; i < axes.Count() && i < values.Length; i++)
    {
        result[axes.ElementAt(i)] = double.Parse(values[i]);
    }

    return result;
}
  1. 使用适当的状态刷新频率
public class AdaptiveMonitor
{
    private int _refreshInterval = 100;

    public void AdjustRefreshRate(bool isMoving)
    {
        // 运动时提高刷新率
        _refreshInterval = isMoving ? 50 : 100;
    }

    public void Monitor(Action updateCallback)
    {
        while (_isMonitoring)
        {
            updateCallback();
            Thread.Sleep(_refreshInterval);
        }
    }
}

13. 常见问题

Q1: 连接失败怎么办?

A: 检查以下几点:

  1. 确认控制器IP地址正确
  2. 确认网络连接正常(尝试ping控制器)
  3. 检查防火墙设置
  4. 确认控制器已上电并正常工作
  5. 检查端口是否被其他程序占用

Q2: 如何处理运动超时?

A: 使用异步方式并添加超时处理:

public async Task<bool> MoveWithTimeout(Axis axis, double position, int timeoutMs)
{
    var moveTask = Task.Run(() =>
    {
        _api.ToPoint(MotionFlags.ACSC_AMF_ABSOLUTE, axis, position);

        // 等待运动完成
        int startTime = Environment.TickCount;
        while (Environment.TickCount - startTime < timeoutMs)
        {
            var state = _api.GetMotorState(axis);
            if ((state & MotorStates.ACSC_MST_MOVE) == 0)
                return true;
            Thread.Sleep(50);
        }
        return false;
    });

    var timeoutTask = Task.Delay(timeoutMs);
    var completedTask = await Task.WhenAny(moveTask, timeoutTask);

    if (completedTask == timeoutTask)
    {
        _api.Kill(axis);
        return false;
    }

    return await moveTask;
}

Q3: 如何实现多轴同步运动?

A: 使用ACSPL+的GROUP命令或分别启动多轴运动:

public void MoveAxesSimultaneously(Dictionary<Axis, double> targets)
{
    // 方法1: 使用GROUP命令
    string groupCmd = "GROUP(X,Y,Z)";
    _api.Transaction(groupCmd);

    string moveCmd = string.Join(",",
        targets.Select(kvp => $"PTP({(int)kvp.Key},{kvp.Value})"));
    _api.Transaction(moveCmd);

    // 方法2: 分别启动运动
    foreach (var target in targets)
    {
        _api.ToPoint(MotionFlags.ACSC_AMF_ABSOLUTE, target.Key, target.Value);
    }
}

Q4: 如何实现电子齿轮?

A: 使用ACPL+命令设置电子齿轮关系:

public void SetGearRatio(Axis master, Axis slave, double ratio, double offset)
{
    try
    {
        // 设置主从关系
        string gearCmd = $"GEAR({(int)slave},{(int)master},{ratio},{offset})";
        _api.Transaction(gearCmd);

        // 启动电子齿轮
        _api.Transaction($"ENABLEGEAR({(int)slave})");

        Console.WriteLine($"电子齿轮已设置: 轴{(int)slave}跟随轴{(int)master},比例{ratio}");
    }
    catch (ACSException ex)
    {
        Console.WriteLine($"设置电子齿轮失败: {ex.Message}");
    }
}

Q5: 如何处理限位触发?

A: 定期检查轴的错误状态:

public bool IsLimitHit(Axis axis)
{
    try
    {
        string response = _api.Transaction($"?MFLAGS({(int)axis}).#LIMITS");
        return response == "1";
    }
    catch (ACSException ex)
    {
        Console.WriteLine($"检查限位失败: {ex.Message}");
        return false;
    }
}

附录

A. 完整API参考

详见官方文档:《SPiiPlus .NET Library Programmer’s Guide》

B. 技术支持

  • 官方网站: https://www.acsmotioncontrol.com
  • 技术支持: support@acsmotioncontrol.com

C. 示例代码

完整示例代码位于Demo目录:

  • .NET Library Demo CS - WinForms C#示例
  • .NET Library Demo WPF - WPF C#示例
  • .NET Library Demo VB - WinForms VB示例
  • .NET Library Demo C++ - WinForms C++示例
  • AvaloniaLinuxDemo - Linux Avalonia示例

文档版本:1.0
基于:ACS SPiiPlus .NET Library Demo源代码

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐