ACS运动控制:ACS SPiiPlus .NET库 - C#上位机开发01-绪论
·
ACS SPiiPlus .NET库 - C#上位机开发指南
版本 1.0
基于官方Demo源代码
目录
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
- 将
ACS.SPiiPlusNET.dll复制到项目目录 - 在Visual Studio中右键"引用" → “添加引用”
- 选择"浏览"并选择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 连接管理
- 使用using语句管理连接
using (var api = new Api())
{
api.OpenCommEthernet("192.168.1.100", 801);
// 执行操作
} // 自动调用CloseComm()
- 添加连接超时处理
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 错误处理
- 集中错误处理
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; // 连接未建立
}
}
- 使用重试机制
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 线程安全
- 使用锁保护共享资源
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;
}
}
}
- 使用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 性能优化
- 批量读取变量
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;
}
- 使用适当的状态刷新频率
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: 检查以下几点:
- 确认控制器IP地址正确
- 确认网络连接正常(尝试ping控制器)
- 检查防火墙设置
- 确认控制器已上电并正常工作
- 检查端口是否被其他程序占用
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源代码
更多推荐
所有评论(0)