基于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运动控制器缓冲区程序管理的各个方面:

  1. 5种下载程序方法 - 从简单到复杂,满足各种需求
  2. 完整的ACSPL+语法 - 运动控制、流程控制、I/O操作
  3. 3个实战案例 - 点胶机、切割机、多任务并行
  4. 高级应用技巧 - 程序通信、数据记录、错误处理
  5. 调试和最佳实践 - 帮助快速定位问题和优化代码
  6. 完整工具类 - 可直接使用的C#代码

基于:ACS SPiiPlus .NET Library v2.50

Logo

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

更多推荐