工业控制核心:S7.Net 深度指南与固高、雷赛运动控制卡全景实战

Siemens PLC 通信协议解析与多轴运动控制底层 API、参数配置及应用场景全解

Posted by Comet on October 20, 2024

工业控制核心:S7.Net 与固高/雷赛运动控制卡全景实战

在现代上位机开发(无论是老牌的 WinForm 还是现代化的 WPF)中,工控软件工程师往往需要面对两大类核心硬件:PLC(可编程逻辑控制器,主要管逻辑与低速IO)运动控制卡(主要管伺服电机的高速、高精度位移)

本文将摒弃空洞的理论,直接切入核心:为你全景展现如何通过 C# 与 西门子 PLC(借助 S7netplus)、固高科技 (Googol) 控制卡、雷赛智能 (Leadshine) 控制卡进行深度通讯与控制。每一段代码、每一个 API 我们都会掰开揉碎地讲解其参数含义与使用流程。


1. S7netplus (S7.Net) 深度实战指南

S7netplus 是开源的 C# 西门子通讯库。相比昂贵的 Kepware 或庞大的 PROFINET 官方库,它直接通过 TCP 建立 S7 协议的 Socket 连接,极其轻量。

1.1 核心连接 API 与参数解析

在 WinForm 或 WPF 启动时,我们决不能在 UI 主线程(比如 Form_Load)里直接去连接 PLC,因为 Socket 连接可能面临网络不通而导致长达几秒的阻塞(超时),这会让整个界面白屏假死。

【API 详解】

  • new Plc(CpuType cpu, string ip, short rack, short slot): 构造函数。
    • cpu: 目标型号枚举(S71200, S71500, S7300, S7200Smart等)。
    • ip: 设备的局域网 IPv4 地址。
    • rack: 机架号。绝大多数单背板系统填 0
    • slot: 插槽号。S7-1200/1500 的 CPU 通常在槽 1,S7-300 在槽 2
  • plc.OpenAsync(): 异步打开连接。这不会阻塞主线程。

【WPF / WinForm 异步连接场景示例】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
using S7.Net;
using System.Threading.Tasks;
using System.Windows; // WPF 示例

public partial class MainWindow : Window
{
    private Plc _siemensPlc;

    public MainWindow()
    {
        InitializeComponent();
        // 绑定窗口加载事件
        this.Loaded += MainWindow_Loaded; 
    }

    private async void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        // UI 显示提示语
        lblStatus.Text = "正在连接设备...";
        
        // 初始化对象
        _siemensPlc = new Plc(CpuType.S71200, "192.168.0.10", 0, 1);

        try
        {
            // 使用 await 异步等待连接,避免界面假死
            await _siemensPlc.OpenAsync();
            
            if(_siemensPlc.IsConnected)
            {
                lblStatus.Text = "PLC 连接成功!";
                lblStatus.Foreground = System.Windows.Media.Brushes.Green;
                
                // 连接成功后,开启后台轮询数据的任务
                StartPollingData();
            }
        }
        catch (Exception ex)
        {
            lblStatus.Text = "连接失败,请检查网线!";
            lblStatus.Foreground = System.Windows.Media.Brushes.Red;
        }
    }
}

🔥 避坑指南 (博图配置):针对 1200/1500,必须在博图中勾选 “允许从远程伙伴使用 PUT/GET 通信访问”,且要访问的 DB 块必须取消勾选“优化的块访问”

1.2 数据读写流与内存切片映射

S7.Net 支持绝对地址寻址,语法规则类似于 DB1.DBX0.1, MD20, Q0.0。 然而,在实际上位机应用中(如 SCADA 看板),我们需要实时刷新几十上百个数据。千万不要使用多次 plc.Read("DB1..."),因为每一次调用都会触发一次 TCP 握手和 S7 报文收发,极易造成网络风暴。

正确使用流程(大块读取 + 内存切片)

【API 详解】

  • ReadBytes(DataType dataType, int db, int startByteAdr, int count): 一次性读取整块字节流。
    • dataType: 数据区枚举。常用 DataType.DataBlock (DB块), DataType.Memory (M区)。
    • db: DB块编号。读 DB10 就填 10(如果是非DB区此参数填0)。
    • startByteAdr: 起始字节偏移量。
    • count: 要读取的总字节数。

【WPF 实时数据轮询场景示例】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
using S7.Net;
using S7.Net.Types; // 需要用到辅助转换类

// 开启一个后台死循环任务,用于监控产线数据
private void StartPollingData()
{
    Task.Run(async () =>
    {
        while (true)
        {
            try
            {
                // 一次性读取 DB10 中,从偏移量 0 开始的 100 字节数据
                byte[] buffer = _siemensPlc.ReadBytes(DataType.DataBlock, 10, 0, 100);

                // --- 开始在 C# 内存中高速解析 ---
                
                // 1. 解析 Bool (位): 假设需求是读取 DB10.DBX0.2 (系统启动信号)
                // SelectBit(2) 表示取第一个字节 (buffer[0]) 的第 2 个比特位
                bool isSystemRunning = buffer[0].SelectBit(2); 

                // 2. 解析 Int16 (短整型): 假设需求是读取 DB10.DBW2 (气缸压力值)
                // Skip(2).Take(2) 截取从字节 2 开始的连续 2 个字节
                // FromByteArray 负责处理西门子(大端)与 Windows(小端)的字节序翻转
                short airPressure = Int.FromByteArray(buffer.Skip(2).Take(2).ToArray());

                // 3. 解析 Float/Real (单精度浮点数): 假设需求读取 DB10.DBD4 (熔炉温度)
                // 截取连续 4 个字节,转换为浮点数
                float temperature = Real.FromByteArray(buffer.Skip(4).Take(4).ToArray());

                // --- 切换回 UI 线程刷新界面 ---
                Application.Current.Dispatcher.Invoke(() => 
                {
                    chkRunning.IsChecked = isSystemRunning;
                    txtPressure.Text = airPressure.ToString() + " Mpa";
                    txtTemp.Text = temperature.ToString("F2") + " ℃";
                });
            }
            catch(Exception)
            {
                // 通讯中断处理,可在此重连
            }

            // 严禁无限光速轮询,必须休眠,保护网卡与 CPU
            await Task.Delay(100); 
        }
    });
}

2. 固高科技 (Googol) 运动控制卡实战

固高控制卡是国内运控领域的元老,动态链接库通常为 gts.dll。它的 API 风格非常偏向底层和 C 语言(全是返回 short 类型的错误码,0 代表成功,非 0 代表故障)。

2.1 核心初始化与伺服上电流程

固高卡强依赖于外部配置文件(.cfg)。机电工程师在装配好机器后,会用固高提供的 MCT2008 辅助软件,把电机的极性、脉冲当量、限位传感器等配置好,并导出一个 .cfg 文件供 C# 上位机调用。

【API 详解】

  • GT_Open(short channel, short param): 打开板卡底层驱动。channel 常填 0(指控第一张卡)。
  • GT_Reset(): 给板卡的 DSP 芯片发一个复位信号,清空内部一切残留的运行状态。
  • GT_LoadConfig(string pFile): 将配置好的 cfg 文件刷入板卡内部寄存器。
  • GT_ClrSts(short axis, short count): 清除指定轴上的错误状态(比如刚才碰到了急停、或者掉电报警)。
  • GT_AxisOn(short axis): 给驱动器发送 Servo On(伺服使能)信号。驱动器听到后就会抱死电机轴,不允许手掰。

【WinForm 窗口加载初始化场景示例】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
using gts;

private void FormMain_Load(object sender, EventArgs e)
{
    // 1. 尝试打开板卡,如果返回不是 0,说明驱动没装好或板卡没插稳
    short rtn = mc.GT_Open(0, 1);
    if (rtn != 0) 
    {
        MessageBox.Show("PCIe总线未检测到固高控制卡!错误码:" + rtn);
        return;
    }

    // 2. 深度复位
    mc.GT_Reset();

    // 3. 加载存放在程序根目录的机器配置文件
    rtn = mc.GT_LoadConfig("MachineConfig.cfg");
    if(rtn != 0)
    {
        MessageBox.Show("配置文件加载失败,请确认 MachineConfig.cfg 是否存在!");
        return;
    }

    // 4. 假设我们的设备是一个三轴系统 (X=1, Y=2, Z=3)
    // 我们需要循环清除每个轴的报警,并开启使能
    for (short axis = 1; axis <= 3; axis++)
    {
        mc.GT_ClrSts(1, axis); // 清除轴状态
        mc.GT_AxisOn(axis);    // 伺服通电,听到“哒”的一声继电器响,电机锁死
    }

    MessageBox.Show("机台底层伺服初始化完成,等待运动指令。");
}

2.2 Jog 模式 (点动:用于手动调试面板)

场景描述:在调试阶段,工人需要按住界面上的“X+”按钮,电机就开始转;松开按钮,电机就停。这叫做 Jog 模式。

【API 详解】

  • GT_PrfJog(short axis): 告知板卡,这个轴接下来的运动属于 Jog 模式。
  • GT_SetJogPrm(short axis, ref TJogPrm prm): 配置点动的加速度和减速度。
  • GT_SetVel(short axis, double vel): 设置点动的方向与速度(正数为正转,负数为反转)。
  • GT_Update(int mask): 【极度重要】固高卡的大多数设置指令只是放在缓冲区,必须调用 Update 发送掩码,才会真正起跑!掩码采用位运算:轴1是 1<<0(1),轴2是 1<<1(2),轴3是 1<<2(4)。

【WPF 键盘点动与安全失焦防护场景示例】

🔥 避坑指南 (极度危险的 MouseDown 陷阱): 很多新手喜欢用鼠标的 MouseDownMouseUp 来做 Jog。但如果在鼠标按下期间,窗口被其他软件覆盖、弹出了弹窗导致焦点丢失,或者鼠标拖到窗体外松开,MouseUp 事件将永远不会触发,机器轴会像脱缰的野马一样直接撞向机械硬限位! 工业级标准做法是:使用 PreviewKeyDown / PreviewKeyUp(原生支持物理按键操作)配合窗口或按钮的 LostFocus 事件,一旦失去焦点必须强制发送停轴指令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// 键盘/鼠标【按下】事件 (开始正向转动)
private void btnXPositive_PreviewKeyDown(object sender, KeyEventArgs e)
{
    // 拦截长按键盘时产生的连续重复触发信号
    if (e.IsRepeat) return; 

    short axisX = 1; // 假设 X 轴映射在物理轴 1
    
    // 1. 设定为点动模式
    mc.GT_PrfJog(axisX);
    
    // 2. 配置点动参数
    mc.TJogPrm jogPrm;
    mc.GT_GetJogPrm(axisX, out jogPrm);
    // 【TJogPrm 结构体参数详解】
    // acc (double): 加速度,单位通常为 pulse/ms^2 (脉冲每平方毫秒)
    // dec (double): 减速度,单位为 pulse/ms^2
    // smooth (double): 平滑系数(0~1),用于在加减速拐点做 S 型曲线平滑,值越大越平滑但跟随滞后越长
    jogPrm.acc = 0.5; 
    jogPrm.dec = 0.5; 
    jogPrm.smooth = 0.5; // 可选的平滑过渡
    mc.GT_SetJogPrm(axisX, ref jogPrm);

    // 3. 设定恒定速度 (假设 100 脉冲/毫秒,正数代表正向跑)
    mc.GT_SetVel(axisX, 100);

    // 4. 触发物理运行!(1 << (1-1) 即 1)
    mc.GT_Update(1 << (axisX - 1)); 
}

// 键盘【抬起】事件 (要求平滑停止)
private void btnXPositive_PreviewKeyUp(object sender, KeyEventArgs e)
{
    StopJogAxis(1);
}

// 【保命核心】控件或窗口失去焦点时,无条件强制刹车!
private void btnXPositive_LostFocus(object sender, RoutedEventArgs e)
{
    StopJogAxis(1); // 哪怕没有触发 KeyUp,失焦也立刻停机
}

// 独立的平滑停止函数
private void StopJogAxis(short axis)
{
    // 在 Jog 模式下,将目标速度设为 0 并 Update,电机就会根据设定的 dec (减速度) 完美平滑刹车
    mc.GT_SetVel(axis, 0);
    mc.GT_Update(1 << (axis - 1));
}

2.3 Trap 模式 (点位定位:最常用的绝对/相对运动)

场景描述:这是工控中最核心的模式。机台要从当前工位“走到”绝对坐标 50000 的位置,要求起步有加速,中途匀速,快到终点时自动减速刹车(呈现梯形速度曲线)。

【API 与参数详解】

  • GT_PrfTrap(short axis): 设定轴为梯形点位模式。
  • GT_SetTrapPrm(short axis, ref TTrapPrm prm): 配置梯形曲线的核心参数。
    • 【TTrapPrm 结构体详解】:
      • acc (double): 加速度 (pulse/ms^2),决定起步有多猛。
      • dec (double): 减速度 (pulse/ms^2),决定刹车有多急。
      • smoothTime (short): 平滑时间 (ms)。极其重要!如果不设置,电机会在加速瞬间产生极其刺耳的“嘎噔”异响。设为 10~50ms 可在加减速拐点形成 S 曲线,大幅度保护机械寿命。
      • velStart (double): 起步初速,通常设为 0 以防刚性冲击。
  • GT_SetPos(short axis, int pos): 设定目标位置的绝对刻度(或者相对刻度,取决于底层配置)。
  • GT_SetVel(short axis, double vel): 设定此段路程的最高巡航速度。

【梯形点位移动场景示例】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void MoveToAbsolutePosition(short axis, int targetPos)
{
    // 1. 设置该轴为梯形点位定位模式
    mc.GT_PrfTrap(axis);

    // 2. 灌入梯形结构体参数
    mc.TTrapPrm trapPrm;
    mc.GT_GetTrapPrm(axis, out trapPrm); // 先获取底层默认参数
    trapPrm.acc = 1.0;          // 设定加速度
    trapPrm.dec = 1.0;          // 设定减速度
    trapPrm.smoothTime = 25;    // 设定 25ms 的 S 型平滑过度,让起步如丝般顺滑
    mc.GT_SetTrapPrm(axis, ref trapPrm);

    // 3. 压入终点坐标与速度
    mc.GT_SetPos(axis, targetPos);
    mc.GT_SetVel(axis, 50.0); // 巡航速度设定为 50 pulse/ms

    // 4. 呼叫底层起跑
    mc.GT_Update(1 << (axis - 1));
}

2.4 PT 模式 (Position-Time 位置-时间模式)

场景描述:当传统的 Trap 模式无法满足需求(例如:你需要让电机的位移曲线呈现出一种极为变态的非线性自定义波形,或者用于电子凸轮追剪、飞锯),你可以使用 PT 模式。它允许你直接向板卡发送一连串的 (位置增量, 耗时) 数据对,板卡会在底层严格按照你的时间表无缝切过这些点。

【API 与参数详解】

  • GT_PrfPt(short axis, short mode): 设置为 PT 模式。
  • GT_PtSpace(short axis, out short pSpace): 查询底层 PT 缓冲区的剩余空间。因为 FIFO 通常很小(只能容纳 32 组点),长路径必须边跑边塞。
  • GT_PtData(short axis, double pos, short time, short type): 压入数据点。
    • pos (double): 在这段时间内要求电机走的位移增量(脉冲数)。
    • time (short): 走完这段位移必须花费的绝对时间(毫秒)。
    • type (short): 线型规划枚举。PT_MODE_STATIC (普通匀速) 或 PT_MODE_DYNAMIC
  • GT_PtStart(int mask, int option): 启动指定轴的 PT 引擎开始消耗队列。

【PT 模式自定义正弦波震动曲线示例】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public void StartCustomPtCurve(short axis)
{
    // 1. 清空底层缓冲并设置为 PT 模式 (mode 1 指静态模式)
    mc.GT_PtClear(axis);
    mc.GT_PrfPt(axis, 1);

    // 2. 压入自定义数据
    // 假设我们要模拟一段正弦波振动,自行计算出每次 10ms 需要走的增量 posDelta
    for(int i = 0; i < 50; i++)
    {
        short space;
        mc.GT_PtSpace(axis, out space); // 查询剩余空间
        
        if (space > 0) // 确保板卡 FIFO 还没塞满
        {
            // 通过数学公式计算位移:一个周期波的增量
            double posDelta = Math.Sin(i * 0.1) * 100; 
            
            // 压入指令:强制驱动器花费恰好 10ms 走完 posDelta 的距离
            mc.GT_PtData(axis, posDelta, 10, mc.PT_MODE_STATIC);
        }
    }

    // 3. 开始严格按时间序列执行
    mc.GT_PtStart(1 << (axis - 1), 0);
}

2.5 多轴插补模式 (Interpolation / Crd)

场景描述:自动点胶机,需要控制胶枪在 XY 平面上走一个完美的矩形或圆弧路径。此时单独控制 X 和 Y 是没用的(跑出来是锯齿),必须让固高底层 DSP 芯片将两轴联动(插补)。

【API 详解】

  • GT_SetCrdPrm(short crd, ref TCrdPrm prm): 初始化坐标系。告诉板卡,几号坐标系由哪几个轴构成。
  • GT_CrdClear(short crd, short fifo): 清空插补 FIFO 缓冲队列。
  • GT_LnXY(short crd, int x, int y, double synVel, double synAcc, double velEnd, short fifo): 将一段 XY 直线推入缓冲队列。
    • x, y: 直线终点的绝对脉冲坐标。
    • synVel: 这一段直线的巡航合成速度。
    • velEnd: 终点速度。如果填 0,代表走到这会停;如果填非 0,代表直接滑过这个点连接下一段线(前瞻)。
  • GT_CrdStart(short mask, short option): 一键启动坐标系的插补引擎。

【插补点胶作业场景示例】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public void StartDispensingSquare()
{
    // 1. 构建一个二维坐标系配置
    mc.TCrdPrm crdPrm = new mc.TCrdPrm();
    crdPrm.dimension = 2;            // 我们需要联动两个轴 (X,Y)
    crdPrm.profile[0] = 1;           // 该坐标系的 X 绑定到物理轴 1
    crdPrm.profile[1] = 2;           // 该坐标系的 Y 绑定到物理轴 2
    
    // 初始化 1 号坐标系
    mc.GT_SetCrdPrm(1, ref crdPrm); 
    
    // 启动前必须清空 1 号坐标系的 0 号 FIFO 数据缓冲区
    mc.GT_CrdClear(1, 0);

    // 2. 依次推入矩形的四条边
    // 参数含义:坐标系1, X点, Y点, 合成速度100, 加速度1, 终点速度(50代表不停车滑过拐角), FIFO区号0
    
    // 走线段1: 到达 (10000, 0)
    mc.GT_LnXY(1, 10000, 0, 100, 1, 50, 0); 
    
    // 走线段2: 到达 (10000, 10000)
    mc.GT_LnXY(1, 10000, 10000, 100, 1, 50, 0); 
    
    // 走线段3: 到达 (0, 10000)
    mc.GT_LnXY(1, 0, 10000, 100, 1, 50, 0); 
    
    // 走线段4: 闭合回到起点 (0, 0),此时要求停车,故终点速度给 0
    mc.GT_LnXY(1, 0, 0, 100, 1, 0, 0); 

    // 3. 所有路径下发完毕,呼叫底层的 DSP 插补引擎起跑!(掩码位1代表坐标系1)
    mc.GT_CrdStart(1, 0); 
}

3. 雷赛智能 (Leadshine) 运动控制卡实战

雷赛在 3C 消费电子制造装备(如贴片机、视觉分拣分板机)领域占有率极高,底层通信库通常为 LTDMC.dll。它的 API 以 dmc_ 开头,比起固高,它的功能分类更加直白。

3.1 核心初始化与引脚配置

雷赛的架构与固高不同,可以直接通过 API 强行设定很多硬件信号,而不过分依赖于外部配置文件。

【API 详解】

  • dmc_board_init(): 扫描电脑的插槽并初始化所有雷赛控制卡,返回检测到的可用卡数量。
  • dmc_write_sevon_pin(ushort CardNo, ushort NodeId, ushort on_off): 强行拉高/拉低 Servo 针脚的电平。1 为使能(电机抱死)。
  • dmc_set_pulse_outmode(ushort CardNo, ushort NodeId, ushort outmode): 设置输出脉冲的电气模式。大多数市面驱动器采用 1,即 脉冲(Pulse) + 方向(Dir) 的单端输出方式。

【WinForm/WPF 极简初始化】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
using csLTDMC; // 引用雷赛官方 C# 封装类

public void InitLeadshineCard()
{
    // 1. 初始化引擎,这步如果没插卡会卡住一小会,所以返回 <=0 代表失败
    short cardCount = LTDMC.dmc_board_init();
    if(cardCount <= 0) 
    {
        MessageBox.Show("未检测到任何雷赛控制卡在位!");
        return;
    }

    ushort cardNo = 0; // 板卡标识序号(第一张卡是 0)
    
    // 假设卡上接了 4 个电机轴 (索引 0~3)
    for (ushort axis = 0; axis < 4; axis++)
    {
        // 2. 发送 Servo 使能信号,1 表示强电吸合
        LTDMC.dmc_write_sevon_pin(cardNo, axis, 1); 
        
        // 3. 设置输出模式:1 代表“脉冲+方向”模式
        LTDMC.dmc_set_pulse_outmode(cardNo, axis, 1);
    }
}

3.2 P-Move 绝对定位与完美 S 曲线柔化

在大多数定长工位间移动时,我们需要机器既跑得快,起步刹车又要稳(防止机器猛然颤抖导致零件掉落)。雷赛通过两组 API 分解了速度曲线。

【API 详解】

  • dmc_set_profile(CardNo, axis, Min_Vel, Max_Vel, Tacc, Tdec): 设定梯形主心骨。起步速度、巡航最大速度、加速耗时、减速耗时。
  • dmc_set_s_profile(CardNo, axis, s_mode, s_time): 灵魂配置! 在起步、加速到顶、减速、刹车的四个拐点处,增加 S 形的弧形过渡柔化时间。
  • dmc_pmove(CardNo, axis, Dist, posi_mode): 触发运动指令。
    • Dist: 目标脉冲数值。
    • posi_mode: 1 代表绝对坐标(走到刻度10000),0 代表相对坐标(在当前基础上再往前走10000)。

【移动至组装工位场景示例】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public void MoveToAssembleStation(ushort axis, int targetPulsePos)
{
    ushort card = 0;

    // 1. 配置梯形速度主骨架
    double startVel = 500;   // 刚起步瞬间的速度 (pulse/s)
    double maxVel = 50000;   // 允许飚到的最高速度 (pulse/s)
    double timeAcc = 0.2;    // 用 0.2 秒从 500 飚到 50000
    double timeDec = 0.2;    // 刹车时间
    LTDMC.dmc_set_profile(card, axis, startVel, maxVel, timeAcc, timeDec);

    // 2. 叠加 S 曲线柔化 (消除机械“点头”抖动)
    // s_mode 填 0 (时间模式),0.05 代表 S 段柔化耗时占用 0.05 秒
    LTDMC.dmc_set_s_profile(card, axis, 0, 0.05); 

    // 3. 触发指令!参数 1 代表绝对运动,目标绝对位置为 targetPulsePos
    LTDMC.dmc_pmove(card, axis, targetPulsePos, 1); 

    // 4. 等待运动结束 (如果是基于 Task 的后台作业,在此处阻塞等待即可)
    // dmc_check_done 返回 1 代表轴彻底停稳了,返回 0 代表正在跑
    while(LTDMC.dmc_check_done(card, axis) == 0)
    {
        Thread.Sleep(10); // 【铁律】这里必须休眠释放 CPU 时间片,否则程序卡死
    }
    
    // 抵达工位后,触发后续的吹气阀、气缸等动作...
}

3.3 高阶应用:Position Compare (硬件飞拍比较输出)

场景描述:在极速运行的 SMT 贴片流水线或 CCD 视觉检测站中,如果通过 C# 代码不断询问板卡“你到目标点了吗?”,一旦确认到了再调用相机拍照 API,这中间经过了 USB、Windows 线程调度,至少会产生 10 毫秒的误差。在 2m/s 的运动下,10毫秒意味着相机拍出来的画面完全错位并发虚。

雷赛的杀手锏是 Position Compare (飞拍比较):你提前把坐标写入板卡的 DSP 芯片。当高速直线电机掠过该坐标点时,DSP 芯片会在内部产生一个纳秒级的硬中断,瞬间导通板卡外部的特定引脚发出一道电流脉冲给相机。整个过程零软件延迟,上位机只管事后去取照片

【API 详解】

  • dmc_compare_clear_points(CardNo, cmp): 清空某个比较通道内部的点位阵列。
  • dmc_compare_add_point(CardNo, cmp, pos, dir, action, actpos): 将坐标推入比较器。
    • pos: 要触发相机的坐标刻度。
    • dir: 运动方向(0为正向碰触触发)。
    • action: 1 为发出有效电平。
  • dmc_compare_start(CardNo, cmp): 使能该比较通道,开始监听飞速转动的编码器值。

【飞拍检测线视觉触发示例】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public void SetupFlyCameraTrigger(ushort axis)
{
    ushort card = 0;
    ushort cmpChannel = 0; // 假定使用 0 号比较通道连接了相机

    // 1. 清空历史触发点
    LTDMC.dmc_compare_clear_points(card, cmpChannel);

    // 2. 设置比较参数配置: 绑定当前 axis,选择使用“规划位置”进行比较
    LTDMC.dmc_compare_set_config(card, cmpChannel, 1, axis, 0, 0);

    // 3. 我们在长条形流水线上,需要在 X 坐标为 10000, 20000, 30000 的地方进行高速无感抓拍
    int[] triggerPositions = new int[] { 10000, 20000, 30000 };
    
    foreach(var pos in triggerPositions)
    {
        // 向 DSP 压入触发点。
        // 参数含义: 卡号, 通道号, 触发位置pos, 方向(0=正向有效), 动作(1=输出有效脉冲), 动作保持时间(无视填0)
        LTDMC.dmc_compare_add_point(card, cmpChannel, pos, 0, 1, 0);
    }

    // 4. 激活监听
    LTDMC.dmc_compare_start(card, cmpChannel);

    // 设置完后,上位机只管让电机狂飙跑过这段路程,硬件板卡在路过时会自动电击相机触发快门
    LTDMC.dmc_pmove(card, axis, 50000, 1); 
}

4. 总结:写给 C# 上位机开发者的“血泪箴言”

无论你是去跟西门子网线打交道,还是在机箱里去插固高和雷赛的 PCIe 板卡,在这条路上踩过的坑总结起来就只有这三点,必须全文背诵:

  1. 绝对隔离 UI 线程与硬件总线通讯 坚决禁止在 WPF/WinForm 的 Button_Click 里直接撰写 plc.Write() 或阻塞等待的 while(dmc_check_done(..) == 0)。 所有硬件交涉必须通过 Command 将意图投递到后端的独立 Task 或状态机线程池中执行。主线程一旦卡死,工控机在车间看起来就跟“死机”了一样,极为凶险。

  2. 警惕致命的死循环轮询陷阱 在读取 PLC 或者板卡底层状态(如 dmc_check_done)时,必须加上 Thread.Sleep(5)await Task.Delay(5)。 如果你不加休眠进行极速空转,它会以每秒千万次的频率向底层驱动或网卡发起访问,瞬间导致 PCIe 总线阻塞、通讯超时、CPU 单核满载甚至操作系统蓝屏宕机。

  3. 软件只是辅助,安全硬急停才是最后底线 软件写得再精妙,也防不了现场网线被老鼠咬断、车间遭遇瞬间强电磁干扰、或者 Windows 突然后台更新弹窗卡死。 作为上位机开发,你永远只能把电脑当作“大脑指挥中枢”。你必须强烈要求电气硬件工程师在电柜外面串联一个绝对独立的“大红色蘑菇头物理急停按键”,用来在发生撞机前,瞬间用物理开关切断所有驱动器的 220V 供电回路。人身安全,重于一切!