ACS运动控制:ACS SPiiPlus .NET库 - C#上位机开发02-Buffer缓冲区管理
·
基于ACS SPiiPlus .NET Library的Buffer缓冲区编程
目录
1. 缓冲区程序概述
1.1 什么是缓冲区程序?
┌─────────────────────────────────────────────┐
│ ACS运动控制器 │
│ ┌──────────────────────────────────────┐ │
│ │ 缓冲区0 (Buffer 0) │ │
│ │ ┌────────────────────────────────┐ │ │
│ │ │ 程序代码1 │ │ │
│ │ │ - 运动控制 │ │ │
│ │ │ - 逻辑判断 │ │ │
│ │ │ - I/O控制 │ │ │
│ │ └────────────────────────────────┘ │ │
│ └──────────────────────────────────────┘ │
│ ┌──────────────────────────────────────┐ │
│ │ 缓冲区1 (Buffer 1) │ │
│ │ ┌────────────────────────────────┐ │ │
│ │ │ 程序代码2 │ │ │
│ │ └────────────────────────────────┘ │ │
│ └──────────────────────────────────────┘ │
│ ... │
│ ┌──────────────────────────────────────┐ │
│ │ 缓冲区63 (Buffer 63) │ │
│ └──────────────────────────────────────┘ │
└─────────────────────────────────────────────┘
1.2 为什么要使用缓冲区程序?
传统方式 vs 缓冲区方式
| 特性 | 传统上位机控制 | 缓冲区程序 |
|---|---|---|
| 网络依赖 | 持续需要 | 下载后独立运行 |
| 通信延迟 | 每条命令都有延迟 | 无延迟,控制器自主运行 |
| CPU占用 | 上位机持续占用 | 控制器内部运行 |
| 复杂运动 | 需要拆分成多条命令 | 一个程序完成 |
| 故障处理 | 通信中断立即停止 | 可继续执行完 |
| 适用场景 | 简单运动、实时交互 | 复杂序列、离线运行 |
典型应用场景
场景1:复杂轨迹加工
└─→ 画圆、螺旋、多段路径
└─→ 使用缓冲区程序一气呵成
场景2:生产流水线
└─→ 预先下载动作序列
└─→ 触发后自动执行,上位机可做其他事
场景3:安全备份
└─→ 上位机故障时,缓冲区程序可继续运行
└─→ 完成当前动作后安全停止
场景4:多任务并行
└─→ 缓冲区0:主运动轨迹
└─→ 缓冲区1:辅助轴控制
└─→ 缓冲区2:I/O逻辑控制
1.3 缓冲区数量限制
// Form1.cs 第21行
private const int MAX_BUFFER_CNT = 64;
- 最多支持64个缓冲区(0-63)
- 每个缓冲区可以独立运行
- 缓冲区之间可以相互通信
2. 下载程序到缓冲区
2.1 方法一:WriteBuffer(推荐)
基础用法
/// <summary>
/// 下载程序到缓冲区
/// </summary>
/// <param name="bufferNo">缓冲区编号(0-63)</param>
/// <param name="program">程序内容字符串</param>
public bool DownloadProgram(int bufferNo, string program)
{
try
{
// 步骤1:停止正在运行的程序(如果有)
_ACS.StopBuffer((ProgramBuffer)bufferNo);
// 步骤2:清除缓冲区旧内容
_ACS.ClearBuffer((ProgramBuffer)bufferNo);
// 步骤3:写入新程序
_ACS.WriteBuffer((ProgramBuffer)bufferNo, program);
Console.WriteLine($"程序已成功下载到缓冲区{bufferNo}");
return true;
}
catch (ACS.SPiiPlusNET.ACSException ex)
{
Console.WriteLine($"下载失败: {ex.Message}");
Console.WriteLine($"错误代码: 0x{ex.ErrorCode:X}");
return false;
}
}
使用示例
// 示例1:简单往返运动
string program1 = @"
# Simple back-and-forth motion
ENABLE(0)
LABEL_START:
PTP(0, 1000) # Move to position 1000
DELAY 500 # Wait 0.5 second
PTP(0, 0) # Move back to 0
DELAY 500 # Wait 0.5 second
GOTO LABEL_START # Loop forever
";
DownloadProgram(0, program1);
// 示例2:带条件判断的程序
string program2 = @"
ENABLE(0)
LABEL_MAIN:
# 检查位置,决定运动方向
?RPOS(0) < 5000
PTP(0, 10000) # If position < 5000, move to 10000
ELSE
PTP(0, 0) # Else, move to 0
ENDIF
DELAY 1000
GOTO LABEL_MAIN
";
DownloadProgram(1, program2);
2.2 方法二:WriteProgram(带编译)
/// <summary>
/// 下载并编译程序
/// </summary>
public bool DownloadAndCompileProgram(int bufferNo, string program)
{
try
{
_ACS.StopBuffer((ProgramBuffer)bufferNo);
_ACS.ClearBuffer((ProgramBuffer)bufferNo);
// WriteProgram会自动编译,检查语法错误
_ACS.WriteProgram(
(ProgramBuffer)bufferNo,
program,
null // 编译选项(null使用默认设置)
);
Console.WriteLine($"程序已下载并编译到缓冲区{bufferNo}");
return true;
}
catch (ACS.SPiiPlusNET.ACSException ex)
{
if (ex.ErrorCode == 0x80040706)
{
Console.WriteLine("编译错误:程序语法有误");
Console.WriteLine("请检查:");
Console.WriteLine("1. 标签名是否正确(必须_或A-Z开头)");
Console.WriteLine("2. 括号是否匹配");
Console.WriteLine("3. 命令拼写是否正确");
}
else
{
Console.WriteLine($"下载失败: {ex.Message}");
}
return false;
}
}
2.3 方法三:从文件下载
/// <summary>
/// 从文件加载程序到缓冲区
/// </summary>
public bool LoadProgramFromFile(int bufferNo, string filePath)
{
try
{
// 检查文件是否存在
if (!File.Exists(filePath))
{
Console.WriteLine($"文件不存在: {filePath}");
return false;
}
// 读取文件内容
string program = File.ReadAllText(filePath, Encoding.UTF8);
// 下载到缓冲区
return DownloadProgram(bufferNo, program);
}
catch (Exception ex)
{
Console.WriteLine($"加载文件失败: {ex.Message}");
return false;
}
}
// 使用示例
LoadProgramFromFile(0, @"C:\ACS_Programs\Motion1.txt");
LoadProgramFromFile(1, @"D:\Scripts\CircleCutting.acs");
2.4 方法四:逐行下载(大程序)
/// <summary>
/// 逐行下载大程序(适合大型程序)
/// </summary>
public bool DownloadProgramLineByLine(int bufferNo, string filePath)
{
try
{
_ACS.StopBuffer((ProgramBuffer)bufferNo);
_ACS.ClearBuffer((ProgramBuffer)bufferNo);
// 读取所有行
string[] lines = File.ReadAllLines(filePath);
// 逐行写入
for (int i = 0; i < lines.Length; i++)
{
string line = lines[i].Trim();
// 跳过空行和注释
if (string.IsNullOrEmpty(line) || line.StartsWith("#"))
continue;
// 写入一行
_ACS.WriteBuffer((ProgramBuffer)bufferNo, line);
// 显示进度
Console.Write($"\r正在写入: {i + 1}/{lines.Length} 行");
// 避免写入过快
Thread.Sleep(5);
}
Console.WriteLine("\n下载完成!");
return true;
}
catch (Exception ex)
{
Console.WriteLine($"\n下载失败: {ex.Message}");
return false;
}
}
2.5 方法五:使用Transaction命令
/// <summary>
/// 使用Transaction命令下载程序
/// </summary>
public bool DownloadViaTransaction(int bufferNo, string program)
{
try
{
// 先清除缓冲区
_ACS.Transaction($"CLEAR({bufferNo})");
// 逐行发送
string[] lines = program.Split(new[] { '\r', '\n' },
StringSplitOptions.RemoveEmptyEntries);
foreach (string line in lines)
{
string trimmedLine = line.Trim();
if (!string.IsNullOrEmpty(trimmedLine))
{
// 使用>>符号追加到缓冲区
_ACS.Transaction($"{bufferNo}>>{trimmedLine}");
}
}
return true;
}
catch (ACS.SPiiPlusNET.ACSException ex)
{
Console.WriteLine($"下载失败: {ex.Message}");
return false;
}
}
3. 运行和控制程序
3.1 运行程序
从头运行
/// <summary>
/// 从程序开头开始运行
/// </summary>
public bool RunProgram(int bufferNo)
{
try
{
_ACS.RunBuffer((ProgramBuffer)bufferNo, null);
Console.WriteLine($"缓冲区{bufferNo}程序已启动");
return true;
}
catch (ACS.SPiiPlusNET.ACSException ex)
{
Console.WriteLine($"启动失败: {ex.Message}");
return false;
}
}
从指定标签运行
/// <summary>
/// 从指定标签位置开始运行
/// </summary>
public bool RunProgramFromLabel(int bufferNo, string labelName)
{
try
{
// 验证标签名
if (string.IsNullOrEmpty(labelName))
return RunProgram(bufferNo);
labelName = labelName.Trim().ToUpper();
// 标签名必须以_或A-Z开头
if (labelName[0] != '_' && (labelName[0] < 'A' || labelName[0] > 'Z'))
{
Console.WriteLine("标签名无效!必须以_或A-Z开头");
return false;
}
_ACS.RunBuffer((ProgramBuffer)bufferNo, labelName);
Console.WriteLine($"缓冲区{bufferNo}从标签{labelName}开始运行");
return true;
}
catch (ACS.SPiiPlusNET.ACSException ex)
{
Console.WriteLine($"启动失败: {ex.Message}");
return false;
}
}
3.2 停止程序
/// <summary>
/// 停止缓冲区程序
/// </summary>
public void StopProgram(int bufferNo)
{
try
{
_ACS.StopBuffer((ProgramBuffer)bufferNo);
Console.WriteLine($"缓冲区{bufferNo}程序已停止");
}
catch (ACS.SPiiPlusNET.ACSException ex)
{
Console.WriteLine($"停止失败: {ex.Message}");
}
}
/// <summary>
/// 停止所有缓冲区程序
/// </summary>
public void StopAllPrograms()
{
for (int i = 0; i < 64; i++)
{
try
{
_ACS.StopBuffer((ProgramBuffer)i);
}
catch { }
}
Console.WriteLine("所有缓冲区程序已停止");
}
3.3 暂停和恢复
/// <summary>
/// 暂停程序执行
/// </summary>
public void PauseProgram(int bufferNo)
{
try
{
_ACS.Transaction($"HOLD({bufferNo})");
Console.WriteLine($"缓冲区{bufferNo}已暂停");
}
catch (ACS.SPiiPlusNET.ACSException ex)
{
Console.WriteLine($"暂停失败: {ex.Message}");
}
}
/// <summary>
/// 恢复程序执行
/// </summary>
public void ResumeProgram(int bufferNo)
{
try
{
_ACS.Transaction($"CONTINUE({bufferNo})");
Console.WriteLine($"缓冲区{bufferNo}已恢复");
}
catch (ACS.SPiiPlusNET.ACSException ex)
{
Console.WriteLine($"恢复失败: {ex.Message}");
}
}
3.4 监控程序状态
/// <summary>
/// 获取程序运行状态
/// </summary>
public ProgramStates GetProgramStatus(int bufferNo)
{
try
{
return _ACS.GetProgramState((ProgramBuffer)bufferNo);
}
catch (ACS.SPiiPlusNET.ACSException ex)
{
Console.WriteLine($"获取状态失败: {ex.Message}");
return ProgramStates.ACSC_PST_STOP;
}
}
/// <summary>
/// 监控程序状态(轮询方式)
/// </summary>
public void MonitorProgram(int bufferNo, int intervalMs = 100)
{
Console.WriteLine($"开始监控缓冲区{bufferNo}...");
while (true)
{
ProgramStates state = GetProgramStatus(bufferNo);
if ((state & ProgramStates.ACSC_PST_RUN) != 0)
{
Console.Write($"\r状态: 运行中");
}
else if ((state & ProgramStates.ACSC_PST_SUSPEND) != 0)
{
Console.Write($"\r状态: 已暂停");
}
else if ((state & ProgramStates.ACSC_PST_STOP) != 0)
{
Console.WriteLine($"\r状态: 已停止");
break;
}
Thread.Sleep(intervalMs);
}
}
/// <summary>
/// 等待程序执行完成
/// </summary>
public bool WaitForProgramComplete(int bufferNo, int timeoutMs = 30000)
{
int startTime = Environment.TickCount;
while (Environment.TickCount - startTime < timeoutMs)
{
ProgramStates state = GetProgramStatus(bufferNo);
if ((state & ProgramStates.ACSC_PST_RUN) == 0)
{
Console.WriteLine("程序执行完成");
return true;
}
Thread.Sleep(100);
}
Console.WriteLine("等待超时");
return false;
}
3.5 读取程序内容
/// <summary>
/// 读取缓冲区中的程序内容
/// </summary>
public string ReadProgramFromBuffer(int bufferNo)
{
try
{
// 方法1:使用Transaction命令
string program = _ACS.Transaction($"?PRG({bufferNo})");
// 保存到文件
string fileName = $"Buffer{bufferNo}_Program.txt";
File.WriteAllText(fileName, program);
Console.WriteLine($"程序已保存到: {fileName}");
return program;
}
catch (ACS.SPiiPlusNET.ACSException ex)
{
Console.WriteLine($"读取失败: {ex.Message}");
return string.Empty;
}
}
4. ACSPL+语言详解
4.1 基本语法
注释
# 这是单行注释
// 这也是单行注释
/* 这是
多行注释 */
标签定义
# 标签命名规则:
# 1. 必须以_或A-Z开头
# 2. 后续可以是字母、数字、下划线
# 3. 不区分大小写(内部转换为大写)
LABEL_START: # ✅ 正确
_LABEL1: # ✅ 正确
MAIN_LOOP: # ✅ 正确
1START: # ❌ 错误(数字开头)
label: # ❌ 错误(小写)
@LABEL: # ❌ 错误(特殊字符)
4.2 运动控制命令
点到点运动(PTP)
# 绝对位置运动
PTP(0, 1000) # 轴0移动到绝对位置1000
PTP(0, 1000), (1, 500) # 轴0和1同时运动
# 相对位置运动
PTP(0, 500) REL # 轴0相对移动500
PTP(0, -1000) REL # 轴0向负方向相对移动1000
# 带参数的PTP
PTP(0, 2000) VELOCITY=1000 ACCELERATION=50000
圆弧插补(CIRCLE)
# 标准圆弧
CIRCLE(0, 1, 0, 0, 500) # 轴0和1画圆
# 圆心(0, 0)
# 半径500
# 带方向的圆弧
CIRCLE(0, 1, 1000, 1000, 500) CW # 顺时针
CIRCLE(0, 1, 1000, 1000, 500) CCW # 逆时针
# 螺旋线
HELIX(0, 1, 2, 0, 0, 500, 1000) # 三轴螺旋
# 圆心(0, 0)
# 半径500
# Z轴上升1000
连续运动(JOG)
# 速度模式点动
JOG(0) VELOCITY=500 # 轴0以速度500持续运动
# 方向点动
JOG(0) POSITIVE # 正方向
JOG(0) NEGATIVE # 负方向
# 停止点动
HALT(0)
4.3 参数设置
# 速度和加速度
VELOCITY(0) = 1000 # 设置轴0速度为1000
ACCELERATION(0) = 50000 # 设置轴0加速度为50000
DECELERATION(0) = 50000 # 设置轴0减速度为50000
JERK(0) = 1000 # 设置轴0加加速度
# 即时生效(On-the-fly)
VELOCITY(0) = 2000 IMM # 立即改变速度
# 查询参数
?VELOCITY(0) # 查询轴0速度
4.4 流程控制
条件判断(IF)
# 简单IF
?FPOS(0) > 1000
PTP(0, 2000)
ENDIF
# IF-ELSE
?RPOS(0) >= 5000
PTP(0, 0) # 如果位置>=5000,回零
ELSE
PTP(0, 10000) # 否则移动到10000
ENDIF
# 多重条件
?MST(0).#MOVE
# 轴0在运动中
DOUT(0) = 1
ELSE
# 轴0停止
DOUT(0) = 0
ENDIF
# 复合条件
?(FPOS(0) > 1000) AND (FPOS(1) < 5000)
PTP(0, 2000), (1, 0)
ENDIF
循环(GOTO)
# 无限循环
LABEL_LOOP:
PTP(0, 1000)
DELAY 500
PTP(0, 0)
DELAY 500
GOTO LABEL_LOOP
# 计数循环(使用变量)
VAR1 = 0
LABEL_COUNT:
PTP(0, VAR1 * 100)
DELAY 100
VAR1 = VAR1 + 1
?VAR1 < 10
GOTO LABEL_COUNT
ENDIF
子程序(CALL)
# 主程序
LABEL_MAIN:
CALL SUB_HOME # 调用回零子程序
CALL SUB_PICK # 调用抓取子程序
CALL SUB_PLACE # 调用放置子程序
GOTO LABEL_MAIN
# 回零子程序
LABEL_SUB_HOME:
PTP(0, 0)
PTP(1, 0)
PTP(2, 0)
RETURN
# 抓取子程序
LABEL_SUB_PICK:
PTP(2, -100) # Z轴下降
DOUT(0) = 1 # 打开夹爪
DELAY 500
PTP(2, 0) # Z轴上升
RETURN
# 放置子程序
LABEL_SUB_PLACE:
PTP(0, 1000), (1, 500)
PTP(2, -100) # Z轴下降
DOUT(0) = 0 # 关闭夹爪
DELAY 500
PTP(2, 0) # Z轴上升
RETURN
4.5 I/O控制
# 数字输出
DOUT(0) = 1 # 设置输出0为ON
DOUT(0) = 0 # 设置输出0为OFF
# 等待输入
WAIT DIN(0) = 1 # 等待输入0变为ON
WAIT DIN(1) = 0 # 等待输入1变为OFF
# 模拟输出
AOUT(0) = 5.0 # 设置模拟输出0为5V
# 查询输入
?DIN(0) = 1
# 输入0为ON时执行
PTP(0, 1000)
ENDIF
4.6 延时和等待
# 时间延时
DELAY 1000 # 延时1000ms
DELAY 0.5 # 延时0.5秒
# 等待运动完成
WAIT MOVES(0) # 等待轴0运动完成
WAIT MOVES(0), (1) # 等待轴0和1都完成
# 等待位置到达
WAIT FPOS(0) > 1000 # 等待轴0位置>1000
WAIT INPOS(0) # 等待轴0到位
# 等待时间
WAIT TIME = 10.0 # 等待10秒(从程序启动算)
4.7 变量操作
# 变量定义(无需声明,直接使用)
VAR1 = 100
VAR2 = 3.14
STR_VAR = "Hello"
# 数学运算
VAR1 = VAR1 + 1 # 加法
VAR2 = VAR2 * 2 # 乘法
VAR3 = VAR1 / 10 # 除法
VAR4 = VAR1 % 7 # 取模
# 位置变量
POS_VAR = FPOS(0) # 读取轴0位置
PTP(0, POS_VAR + 100) # 使用变量
# 字符串操作
?STR_VAR = "START"
GOTO LABEL_START
ENDIF
5. 完整项目示例
5.1 示例1:点胶机控制程序
# ===========================================
# 点胶机控制程序
# 功能:自动点胶路径
# 轴配置:X(0), Y(1), Z(2)
# ===========================================
# 变量定义
VAR_CURRENT_X = 0
VAR_CURRENT_Y = 0
VAR_DISPENSE_TIME = 200
# 主程序
LABEL_MAIN:
ENABLE(0)
ENABLE(1)
ENABLE(2)
# 设置运动参数
VELOCITY(0) = 2000
VELOCITY(1) = 2000
VELOCITY(2) = 500
ACCELERATION(0) = 100000
ACCELERATION(1) = 100000
ACCELERATION(2) = 50000
# 回原点
CALL SUB_HOME
# 等待启动信号
WAIT DIN(0) = 1
# 开始点胶循环
CALL SUB_DISPENSE_PATTERN
GOTO LABEL_MAIN
# ===========================================
# 子程序:回原点
# ===========================================
LABEL_SUB_HOME:
# Z轴先抬起
PTP(2, 50)
WAIT MOVES(2)
# XY回零
PTP(0, 0), (1, 0)
WAIT MOVES(0), (1)
# Z轴回零
PTP(2, 0)
WAIT MOVES(2)
RETURN
# ===========================================
# 子程序:点胶图案
# ===========================================
LABEL_SUB_DISPENSE_PATTERN:
# 点1
CALL SUB_MOVE_AND_DISPENSE(100, 100)
# 点2
CALL SUB_MOVE_AND_DISPENSE(200, 100)
# 点3
CALL SUB_MOVE_AND_DISPENSE(200, 200)
# 点4
CALL SUB_MOVE_AND_DISPENSE(100, 200)
RETURN
# ===========================================
# 子程序:移动并点胶
# 参数:VAR_X, VAR_Y
# ===========================================
LABEL_SUB_MOVE_AND_DISPENSE:
# Z轴抬起
PTP(2, 20)
WAIT MOVES(2)
# XY移动
PTP(0, VAR_X), (1, VAR_Y)
WAIT MOVES(0), (1)
# Z轴下压
PTP(2, -5)
WAIT MOVES(2)
# 打开点胶阀
DOUT(0) = 1
DELAY VAR_DISPENSE_TIME
# 关闭点胶阀
DOUT(0) = 0
DELAY 100
RETURN
C#调用代码:
public class DispensingMachineController
{
private Api _acs;
public DispensingMachineController(Api acs)
{
_acs = acs;
}
public void LoadProgramAndRun()
{
// 读取程序文件
string program = File.ReadAllText(@"C:\Programs\Dispensing.txt");
// 下载到缓冲区0
_acs.StopBuffer((ProgramBuffer)0);
_acs.ClearBuffer((ProgramBuffer)0);
_acs.WriteBuffer((ProgramBuffer)0, program);
// 设置点胶时间变量
_acs.Transaction("VAR_DISPENSE_TIME = 300");
// 运行程序
_acs.RunBuffer((ProgramBuffer)0, null);
Console.WriteLine("点胶程序已启动");
}
public void EmergencyStop()
{
// 停止程序
_acs.StopBuffer((ProgramBuffer)0);
// 关闭点胶阀
_acs.SetOutput(0, 0, 0);
// 停止所有轴
_acs.KillAll();
Console.WriteLine("紧急停止已执行");
}
}
5.2 示例2:切割机控制程序
# ===========================================
# 切割机控制程序
# 功能:圆形和方形切割
# 轴配置:X(0), Y(1)
# ===========================================
# 主程序
LABEL_MAIN:
ENABLE(0)
ENABLE(1)
# 设置高速参数
VELOCITY(0) = 3000
VELOCITY(1) = 3000
ACCELERATION(0) = 200000
ACCELERATION(1) = 200000
# 等待切割模式选择
?DIN(0) = 1
# DIN(0)=1: 圆形切割
CALL SUB_CIRCLE_CUT
ENDIF
?DIN(1) = 1
# DIN(1)=1: 方形切割
CALL SUB_SQUARE_CUT
ENDIF
DELAY 100
GOTO LABEL_MAIN
# ===========================================
# 子程序:圆形切割
# ===========================================
LABEL_SUB_CIRCLE_CUT:
# 移动到起点
PTP(0, -500), (1, 0)
WAIT MOVES(0), (1)
# 降低速度(切割速度)
VELOCITY(0) = 500
VELOCITY(1) = 500
# 打开切割刀具
DOUT(0) = 1
DELAY 200
# 画圆(圆心在原点,半径500)
CIRCLE(0, 1, 0, 0, 500)
WAIT MOVES(0), (1)
# 关闭切割刀具
DOUT(0) = 0
DELAY 200
# 恢复高速
VELOCITY(0) = 3000
VELOCITY(1) = 3000
# 移动到安全位置
PTP(0, 0), (1, 0)
WAIT MOVES(0), (1)
RETURN
# ===========================================
# 子程序:方形切割
# ===========================================
LABEL_SUB_SQUARE_CUT:
# 移动到起点
PTP(0, 0), (1, 0)
WAIT MOVES(0), (1)
# 降低速度
VELOCITY(0) = 500
VELOCITY(1) = 500
# 打开切割刀具
DOUT(0) = 1
DELAY 200
# 切割正方形(边长1000)
# 边1:下边
PTP(0, 1000), (1, 0)
WAIT MOVES(0), (1)
# 边2:右边
PTP(0, 1000), (1, 1000)
WAIT MOVES(0), (1)
# 边3:上边
PTP(0, 0), (1, 1000)
WAIT MOVES(0), (1)
# 边4:左边
PTP(0, 0), (1, 0)
WAIT MOVES(0), (1)
# 关闭切割刀具
DOUT(0) = 0
DELAY 200
# 恢复高速
VELOCITY(0) = 3000
VELOCITY(1) = 3000
RETURN
5.3 示例3:多任务并行
# ===========================================
# 缓冲区0:主运动轨迹
# ===========================================
ENABLE(0)
ENABLE(1)
LABEL_MAIN_PATH:
# 复杂轨迹运动
CIRCLE(0, 1, 0, 0, 1000)
DELAY 500
CIRCLE(0, 1, 0, 0, 1000) CCW
DELAY 500
GOTO LABEL_MAIN_PATH
# ===========================================
# 缓冲区1:Z轴上下控制
# ===========================================
ENABLE(2)
LABEL_Z_CONTROL:
# 检测传感器
?DIN(0) = 1
# 有工件,下降
PTP(2, -100)
WAIT MOVES(2)
DELAY 500
PTP(2, 0)
WAIT MOVES(2)
ENDIF
DELAY 100
GOTO LABEL_Z_CONTROL
# ===========================================
# 缓冲区2:I/O逻辑控制
# ===========================================
LABEL_IO_MONITOR:
# 监控安全状态
?DIN(7) = 0
# 急停触发
DOUT(7) = 1 # 报警灯
HALT(0)
HALT(1)
HALT(2)
WAIT DIN(7) = 1 # 等待急停复位
DOUT(7) = 0
ENDIF
# 状态指示
?MST(0).#MOVE
DOUT(1) = 1 # 运行指示灯
ELSE
DOUT(1) = 0
ENDIF
DELAY 50
GOTO LABEL_IO_MONITOR
C#调用代码:
public void StartMultiTaskProgram()
{
string prog0 = File.ReadAllText(@"C:\Programs\MainPath.txt");
string prog1 = File.ReadAllText(@"C:\Programs\ZControl.txt");
string prog2 = File.ReadAllText(@"C:\Programs\IOMonitor.txt");
// 下载三个程序
DownloadProgram(0, prog0);
DownloadProgram(1, prog1);
DownloadProgram(2, prog2);
// 同时启动三个任务
_acs.RunBuffer((ProgramBuffer)0, null);
_acs.RunBuffer((ProgramBuffer)1, null);
_acs.RunBuffer((ProgramBuffer)2, null);
Console.WriteLine("多任务并行程序已启动");
}
6. 高级应用技巧
6.1 程序间通信
# ===========================================
# 缓冲区0:主程序,设置共享变量
# ===========================================
LABEL_MASTER:
# 设置共享变量
PVAR(0) = 1000 # 全局变量0
PVAR(1) = 500 # 全局变量1
# 通知缓冲区1
DOUT(5) = 1 # 触发信号
DELAY 100
GOTO LABEL_MASTER
# ===========================================
# 缓冲区1:从程序,读取共享变量
# ===========================================
LABEL_SLAVE:
# 等待触发信号
WAIT DIN(5) = 1
# 读取共享变量
VAR_TARGET = PVAR(0)
# 使用变量
PTP(0, VAR_TARGET)
WAIT MOVES(0)
# 确认完成
DOUT(6) = 1
WAIT DIN(5) = 0
DOUT(6) = 0
GOTO LABEL_SLAVE
6.2 数据记录
# ===========================================
# 程序:数据采集和记录
# ===========================================
LABEL_DATA_LOGGER:
# 开启数据记录
# RECORD格式:变量列表,采样率,文件名
RECORD FPOS(0), FPOS(1), FVEL(0), TIME(0) RATE=10 FILE="DATA.LOG"
# 执行运动
PTP(0, 5000)
WAIT MOVES(0)
PTP(0, 0)
WAIT MOVES(0)
# 停止记录
ENDRECORD
DELAY 1000
GOTO LABEL_DATA_LOGGER
6.3 错误处理
# ===========================================
# 程序:带错误处理的运动
# ===========================================
LABEL_ERROR_HANDLER:
ENABLE(0)
LABEL_MOTION_LOOP:
# 检查错误状态
?MST(0).#ERROR
# 有错误,跳转到错误处理
GOTO LABEL_HANDLE_ERROR
ENDIF
# 检查限位
?MFLAGS(0).#LIMITS
# 限位触发
DOUT(7) = 1 # 报警
GOTO LABEL_CLEAR_LIMITS
ENDIF
# 正常运动
PTP(0, 1000)
WAIT MOVES(0)
PTP(0, 0)
WAIT MOVES(0)
GOTO LABEL_MOTION_LOOP
# ===========================================
# 错误处理子程序
# ===========================================
LABEL_HANDLE_ERROR:
# 读取错误代码
VAR_ERROR = MST(0)
# 显示错误(通过输出)
DOUT(0) = 1 # 错误指示
# 尝试恢复
DISABLE(0)
DELAY 1000
ENABLE(0)
WAIT MST(0).#ENABLE
DOUT(0) = 0
GOTO LABEL_MOTION_LOOP
# ===========================================
# 限位清除子程序
# ===========================================
LABEL_CLEAR_LIMITS:
# 向反方向移动脱离限位
?MFLAGS(0).#LL
# 左限位触发,向右移
PTP(0, 100) REL
WAIT MOVES(0)
ENDIF
?MFLAGS(0).#RL
# 右限位触发,向左移
PTP(0, -100) REL
WAIT MOVES(0)
ENDIF
DOUT(7) = 0 # 清除报警
GOTO LABEL_MOTION_LOOP
6.4 电子齿轮配合
# ===========================================
# 程序:主轴旋转+从轴跟随
# ===========================================
LABEL_GEAR_SYSTEM:
ENABLE(0) # 主轴
ENABLE(1) # 从轴
# 设置电子齿轮
GEAR(1, 0, 2.0, 0) # 从轴1跟随主轴0,比例2:1
# 使能齿轮
ENABLEGEAR(1)
# 主轴旋转
JOG(0) VELOCITY=1000
# 等待停止信号
WAIT DIN(0) = 1
# 停止
HALT(0)
# 禁用齿轮
DISABLEGEAR(1)
DELAY 1000
GOTO LABEL_GEAR_SYSTEM
7. 调试和错误处理
7.1 读取程序内容
/// <summary>
/// 读取并显示缓冲区程序
/// </summary>
public void DisplayBufferProgram(int bufferNo)
{
try
{
string program = _ACS.Transaction($"?PRG({bufferNo})");
Console.WriteLine($"=== 缓冲区{bufferNo}程序内容 ===");
Console.WriteLine(program);
Console.WriteLine($"=== 程序结束 ===");
// 保存到文件
string fileName = $"Buffer{bufferNo}_Dump.txt";
File.WriteAllText(fileName, program);
Console.WriteLine($"已保存到: {fileName}");
}
catch (ACS.SPiiPlusNET.ACSException ex)
{
Console.WriteLine($"读取失败: {ex.Message}");
}
}
7.2 单步执行
# 在程序中插入断点
ENABLE(0)
LABEL_STEP1:
PTP(0, 1000)
BREAK # ← 断点,程序暂停
LABEL_STEP2:
PTP(0, 2000)
BREAK # ← 断点
LABEL_STEP3:
PTP(0, 0)
// C#控制单步执行
public void StepExecution()
{
// 运行到第一个断点
_ACS.RunBuffer((ProgramBuffer)0, null);
// 等待断点
Thread.Sleep(2000);
// 继续执行(到下一个断点)
_ACS.Transaction("CONTINUE(0)");
Thread.Sleep(2000);
// 再次继续
_ACS.Transaction("CONTINUE(0)");
}
7.3 变量监控
/// <summary>
/// 实时监控程序变量
/// </summary>
public void MonitorProgramVariables(int bufferNo)
{
Console.WriteLine("开始监控程序变量...");
while (true)
{
try
{
// 读取程序变量
double var0 = (double)_ACS.ReadVariable("PVAR(0)",
ProgramBuffer.ACSC_NONE, 0, 0);
double var1 = (double)_ACS.ReadVariable("PVAR(1)",
ProgramBuffer.ACSC_NONE, 0, 0);
// 读取轴状态
double pos0 = _ACS.GetFPosition(Axis.ACSC_AXIS_0);
double vel0 = (double)_ACS.ReadVariable("FVEL",
ProgramBuffer.ACSC_NONE, 0, 0);
Console.WriteLine($"PVAR0={var0}, PVAR1={var1}, " +
$"POS={pos0:F2}, VEL={vel0:F2}");
}
catch (Exception ex)
{
Console.WriteLine($"监控出错: {ex.Message}");
}
Thread.Sleep(500);
}
}
7.4 常见错误及解决
错误1:编译错误(0x80040706)
# 错误示例
1START: # ❌ 数字开头
PTP(0, 1000)
# 正确示例
LABEL_START: # ✅ 字母开头
PTP(0, 1000)
错误2:标签未找到
# 错误:标签名拼写不一致
LABEL_START:
PTP(0, 1000)
GOTO LABLE_START # ❌ 拼写错误
# 正确
LABEL_START:
PTP(0, 1000)
GOTO LABEL_START # ✅ 拼写正确
错误3:括号不匹配
# 错误
?(FPOS(0) > 1000
PTP(0, 2000)
ENDIF # ❌ 缺少右括号
# 正确
?(FPOS(0) > 1000)
PTP(0, 2000)
ENDIF # ✅ 括号匹配
错误4:轴未使能
# 错误
PTP(0, 1000) # ❌ 轴0未使能
# 正确
ENABLE(0) # ✅ 先使能
PTP(0, 1000)
8. 最佳实践
8.1 程序结构规范
# ===========================================
# 程序名称:XXXX控制系统
# 作者:XXX
# 日期:2024-XX-XX
# 版本:1.0
# ===========================================
# 功能描述:
# 1. XXX功能
# 2. XXX功能
# ===========================================
# 轴配置:
# 轴0:X轴
# 轴1:Y轴
# 轴2:Z轴
# ===========================================
# I/O配置:
# DIN(0):启动按钮
# DIN(1):停止按钮
# DOUT(0):运行指示
# ===========================================
# ===== 全局变量定义 =====
VAR_MODE = 0
VAR_STEP = 0
# ===== 主程序 =====
LABEL_MAIN:
CALL SUB_INIT
CALL SUB_AUTO_RUN
GOTO LABEL_MAIN
# ===== 初始化子程序 =====
LABEL_SUB_INIT:
ENABLE(0)
ENABLE(1)
ENABLE(2)
RETURN
# ===== 自动运行子程序 =====
LABEL_SUB_AUTO_RUN:
# 具体功能实现
RETURN
8.2 注释规范
# 好的注释示例
LABEL_HOME:
# Z轴先抬起,避免碰撞
PTP(2, 50)
WAIT MOVES(2)
# XY轴回零
PTP(0, 0), (1, 0)
WAIT MOVES(0), (1)
# Z轴回零
PTP(2, 0)
WAIT MOVES(2)
RETURN
# 不好的注释(无注释或注释不清晰)
LABEL_HOME:
PTP(2, 50)
WAIT MOVES(2)
PTP(0, 0), (1, 0)
WAIT MOVES(0), (1)
PTP(2, 0)
WAIT MOVES(2)
RETURN
8.3 错误处理规范
# 完整的错误处理示例
LABEL_MAIN:
ENABLE(0)
LABEL_MOTION:
# 检查错误
?MST(0).#ERROR
CALL SUB_ERROR_HANDLER
ENDIF
# 检查限位
?MFLAGS(0).#LIMITS
CALL SUB_LIMIT_HANDLER
ENDIF
# 正常运动
PTP(0, 1000)
GOTO LABEL_MOTION
# 错误处理子程序
LABEL_SUB_ERROR_HANDLER:
DOUT(7) = 1 # 报警灯
DISABLE(0)
DELAY 1000
ENABLE(0)
DOUT(7) = 0
RETURN
# 限位处理子程序
LABEL_SUB_LIMIT_HANDLER:
# 向反方向移动
PTP(0, -100) REL
WAIT MOVES(0)
RETURN
8.4 性能优化
# 优化前:重复查询
LABEL_LOOP:
?FPOS(0) > 1000
PTP(0, 2000)
ENDIF
?FPOS(0) < 500
PTP(0, 0)
ENDIF
DELAY 10
GOTO LABEL_LOOP
# 优化后:减少查询
LABEL_LOOP:
VAR_POS = FPOS(0) # 一次查询
?VAR_POS > 1000
PTP(0, 2000)
ENDIF
?VAR_POS < 500
PTP(0, 0)
ENDIF
DELAY 10
GOTO LABEL_LOOP
9. 常见问题FAQ
Q1: 如何知道缓冲区有多少程序?
// 查询系统信息
string bufferCount = _ACS.Transaction("?SYSINFO(10)");
Console.WriteLine($"缓冲区数量: {bufferCount}");
Q2: 如何清空所有缓冲区?
public void ClearAllBuffers()
{
for (int i = 0; i < 64; i++)
{
try
{
_ACS.StopBuffer((ProgramBuffer)i);
_ACS.ClearBuffer((ProgramBuffer)i);
}
catch { }
}
Console.WriteLine("所有缓冲区已清空");
}
Q3: 程序运行太慢怎么办?
# 方法1:提高速度
VELOCITY(0) = 5000 # 提高速度
ACCELERATION(0) = 200000 # 提高加速度
# 方法2:减少DELAY
DELAY 100 # 从500减少到100
# 方法3:优化逻辑
# 合并多个运动
PTP(0, 1000), (1, 500) # 同时运动,而非分两次
Q4: 如何实现暂停/恢复功能?
public void PauseResumeProgram(int bufferNo)
{
ProgramStates state = _ACS.GetProgramState((ProgramBuffer)bufferNo);
if ((state & ProgramStates.ACSC_PST_RUN) != 0)
{
// 正在运行,暂停
_ACS.Transaction($"HOLD({bufferNo})");
Console.WriteLine("程序已暂停");
}
else if ((state & ProgramStates.ACSC_PST_SUSPEND) != 0)
{
// 已暂停,恢复
_ACS.Transaction($"CONTINUE({bufferNo})");
Console.WriteLine("程序已恢复");
}
}
Q5: 如何在程序中使用循环计数器?
# 方法1:使用变量
VAR_COUNTER = 0
LABEL_LOOP:
PTP(0, VAR_COUNTER * 100)
VAR_COUNTER = VAR_COUNTER + 1
?VAR_COUNTER < 10
GOTO LABEL_LOOP
ENDIF
# 方法2:使用REPEAT(如果支持)
REPEAT 10
PTP(0, 1000)
DELAY 500
PTP(0, 0)
DELAY 500
ENDREPEAT
Q6: 如何调试缓冲区程序?
# 技巧1:使用输出指示状态
LABEL_DEBUG:
DOUT(0) = 1 # 到这里
PTP(0, 1000)
DOUT(0) = 0 # 完成第一步
PTP(0, 2000)
DOUT(1) = 1 # 完成第二步
# 技巧2:插入断点
LABEL_STEP1:
PTP(0, 1000)
BREAK # 断点
LABEL_STEP2:
PTP(0, 2000)
# 技巧3:使用变量监控
VAR_DEBUG = FPOS(0) # 保存位置用于调试
Q7: 如何实现多轴同步启动?
# 方法1:使用MOVE命令
MOVE(0, 1000), (1, 500), (2, -100)
WAIT MOVES(0), (1), (2)
# 方法2:分别启动(几乎同时)
ENABLE(0)
ENABLE(1)
ENABLE(2)
PTP(0, 1000) # 第一条命令
PTP(1, 500) # 立即第二条
PTP(2, -100) # 立即第三条
附录:完整工具类代码
using System;
using System.IO;
using System.Threading;
using ACS.SPiiPlusNET;
namespace ACS.BufferProgramManager
{
/// <summary>
/// 缓冲区程序管理器 - 完整版
/// </summary>
public class BufferProgramManager
{
private readonly Api _acs;
public BufferProgramManager(Api acs)
{
_acs = acs;
}
#region 基础操作
/// <summary>
/// 下载程序到缓冲区
/// </summary>
public bool Download(int bufferNo, string program)
{
try
{
_acs.StopBuffer((ProgramBuffer)bufferNo);
_acs.ClearBuffer((ProgramBuffer)bufferNo);
_acs.WriteBuffer((ProgramBuffer)bufferNo, program);
return true;
}
catch (ACS.SPiiPlusNET.ACSException ex)
{
Console.WriteLine($"下载失败: {ex.Message}");
return false;
}
}
/// <summary>
/// 从文件下载程序
/// </summary>
public bool DownloadFromFile(int bufferNo, string filePath)
{
try
{
if (!File.Exists(filePath))
{
Console.WriteLine($"文件不存在: {filePath}");
return false;
}
string program = File.ReadAllText(filePath);
return Download(bufferNo, program);
}
catch (Exception ex)
{
Console.WriteLine($"加载文件失败: {ex.Message}");
return false;
}
}
/// <summary>
/// 运行程序
/// </summary>
public bool Run(int bufferNo, string label = null)
{
try
{
if (string.IsNullOrEmpty(label))
_acs.RunBuffer((ProgramBuffer)bufferNo, null);
else
_acs.RunBuffer((ProgramBuffer)bufferNo, label);
return true;
}
catch (ACS.SPiiPlusNET.ACSException ex)
{
Console.WriteLine($"运行失败: {ex.Message}");
return false;
}
}
/// <summary>
/// 停止程序
/// </summary>
public void Stop(int bufferNo)
{
try
{
_acs.StopBuffer((ProgramBuffer)bufferNo);
}
catch (ACS.SPiiPlusNET.ACSException ex)
{
Console.WriteLine($"停止失败: {ex.Message}");
}
}
/// <summary>
/// 获取程序状态
/// </summary>
public ProgramStates GetStatus(int bufferNo)
{
try
{
return _acs.GetProgramState((ProgramBuffer)bufferNo);
}
catch (ACS.SPiiPlusNET.ACSException ex)
{
Console.WriteLine($"获取状态失败: {ex.Message}");
return ProgramStates.ACSC_PST_STOP;
}
}
/// <summary>
/// 清除缓冲区
/// </summary>
public void Clear(int bufferNo)
{
try
{
_acs.StopBuffer((ProgramBuffer)bufferNo);
_acs.ClearBuffer((ProgramBuffer)bufferNo);
}
catch (ACS.SPiiPlusNET.ACSException ex)
{
Console.WriteLine($"清除失败: {ex.Message}");
}
}
/// <summary>
/// 读取程序内容
/// </summary>
public string ReadProgram(int bufferNo)
{
try
{
return _acs.Transaction($"?PRG({bufferNo})");
}
catch (ACS.SPiiPlusNET.ACSException ex)
{
Console.WriteLine($"读取失败: {ex.Message}");
return string.Empty;
}
}
#endregion
#region 高级操作
/// <summary>
/// 暂停程序
/// </summary>
public void Pause(int bufferNo)
{
try
{
_acs.Transaction($"HOLD({bufferNo})");
}
catch (ACS.SPiiPlusNET.ACSException ex)
{
Console.WriteLine($"暂停失败: {ex.Message}");
}
}
/// <summary>
/// 恢复程序
/// </summary>
public void Resume(int bufferNo)
{
try
{
_acs.Transaction($"CONTINUE({bufferNo})");
}
catch (ACS.SPiiPlusNET.ACSException ex)
{
Console.WriteLine($"恢复失败: {ex.Message}");
}
}
/// <summary>
/// 等待程序完成
/// </summary>
public bool WaitComplete(int bufferNo, int timeoutMs = 30000)
{
int startTime = Environment.TickCount;
while (Environment.TickCount - startTime < timeoutMs)
{
var state = GetStatus(bufferNo);
if ((state & ProgramStates.ACSC_PST_RUN) == 0)
return true;
Thread.Sleep(100);
}
return false;
}
#endregion
#region 批量操作
/// <summary>
/// 停止所有缓冲区
/// </summary>
public void StopAll()
{
for (int i = 0; i < 64; i++)
{
try
{
_acs.StopBuffer((ProgramBuffer)i);
}
catch { }
}
}
/// <summary>
/// 清除所有缓冲区
/// </summary>
public void ClearAll()
{
for (int i = 0; i < 64; i++)
{
try
{
_acs.StopBuffer((ProgramBuffer)i);
_acs.ClearBuffer((ProgramBuffer)i);
}
catch { }
}
}
#endregion
}
}
总结
本文档详细介绍了ACS运动控制器缓冲区程序管理的各个方面:
- ✅ 5种下载程序方法 - 从简单到复杂,满足各种需求
- ✅ 完整的ACSPL+语法 - 运动控制、流程控制、I/O操作
- ✅ 3个实战案例 - 点胶机、切割机、多任务并行
- ✅ 高级应用技巧 - 程序通信、数据记录、错误处理
- ✅ 调试和最佳实践 - 帮助快速定位问题和优化代码
- ✅ 完整工具类 - 可直接使用的C#代码
基于:ACS SPiiPlus .NET Library v2.50
更多推荐
所有评论(0)