C# 固高 GTS 运动控制卡入门:伺服上下电、Jog 点动与二维插补(WinForms 实战)

C# 固高 GTS 运动控制卡入门:伺服上下电、Jog 点动与二维插补(WinForms 实战)

导语:运动控制卡是插在电脑上的"电机专用指挥官",它自己带处理器,负责实时计算电机每个时刻该转到哪,电脑只需要把"走到哪、多快"这些指令发给它。固高 GTS 系列卡通过一个 gts.dll 提供一整套 C 风格函数,C# 用 DllImport 直接调用。本文基于我自己写的轴调试程序,按"开卡 → 上下电 → 点动 → 建坐标系 → 插补画线"的顺序走一遍,不深入底层,面向第一次接触运动控制的同学。

一、先搞懂几个基础概念

入门前先把这几个词弄明白,后面代码就不难了:

  • 轴(Axis):一套"伺服电机 + 驱动器"就叫一个轴,卡可以同时控制多个轴。
  • 脉冲(pulse):位置指令的基本单位,发多少个脉冲就走多少位置,一个脉冲对应电机转多少由电子齿轮比决定。本文代码里的位置数字单位都是脉冲。
  • 规划器(Profile):卡内部的"轨迹计算器"。你给目标位置、速度、加速度,它自动算好加减速曲线,不用自己操心每个时刻的速度。
  • 插补(Interpolation):让多个轴配合着同时动,走出直线、圆弧。比如画斜线时 X、Y 两个轴要按比例一起走;如果各走各的,只会走折线。
  • FIFO:插补指令的"排队队列",先压进去的轨迹段先执行,像排队上车一样。

二、环境准备

2.1 三个必备文件

文件 作用 放哪里
gts.dll 厂商的原生函数库,真正干活的东西 程序输出目录 bin\Debug 下
gts.cs C# 调用声明(DllImport 封装),让我们能在 C# 里写函数名 项目里,通过"添加现有项"导入
GTS800.cfg 卡的配置文件(轴、限位、齿轮比等),用 MCT2008 软件生成 bin\Debug 下,和 dll 同目录

2.2 项目情况

  • 项目类型:Windows 窗体应用(WinForms),.NET Framework 4.7.2。
  • 控制卡装好驱动后,设备管理器里能看到 GoogolTech GT-800-PCI Ver 1.0。
  • 注意配置文件名要和代码里 GT_LoadConfig("GTS800.cfg") 的名字完全一致,否则加载失败。

三、程序整体结构

我把代码分成了三层,职责分开,界面里不直接写底层函数:

text 复制代码
Form1.cs            界面层:按钮点击事件,负责和用户交互
MotionGTS.cs        封装层:把固高的函数按"开卡、上下电、点动、插补"封装成好懂的方法
Googol/gts.cs       厂商层:固高提供的 API 声明,不用改

界面上一个按钮对应一个动作,Form1 里只调我封装好的方法:

csharp 复制代码
int axisIndex = 1;              // 记录当前选的是哪个轴,轴号从 1 开始
MotionGTS motion = new MotionGTS();

【插图 1:从开卡到插补的完整流程】

四、第一步:打开控制卡

程序一启动就要先开卡,这是所有动作的前提。我把四个初始化函数封装在 InitCard 里:

csharp 复制代码
public int InitCard()
{
    sRtn = gts.mc.GT_Open(0, 1);            // 打开 0 号卡(一台卡时固定写 0)
    sRtn = gts.mc.GT_Reset();               // 复位控制卡,清掉上次的异常状态
    sRtn = gts.mc.GT_LoadConfig("GTS800.cfg"); // 加载配置文件,告诉卡轴怎么接
    sRtn = gts.mc.GT_ClrSts(1, 8);          // 清除 1~8 号轴的状态
    return sRtn;
}

窗体加载时调用,返回值不是 0 就说明失败了:

csharp 复制代码
private void Form1_Load(object sender, EventArgs e)
{
    if (motion.InitCard() != 0)
    {
        MessageBox.Show("卡失败!!!");
    }
}

四个函数的顺序不能乱:先开卡、再复位、再加载配置、最后清状态。

五、伺服上下电与状态读取

5.1 上电和下电

伺服电机要先"上电"(使能)才会转,轴上有保持力矩:

csharp 复制代码
// 伺服上电
public int SvOn(int Axis)
{
    // 注意这里传的是位掩码:1 左移(轴号-1)位,不是直接传轴号
    sRtn = gts.mc.GT_AxisOn((short)(1 << (Axis - 1)));
    return sRtn;
}

// 伺服下电
public int SvOff(int Axis)
{
    sRtn = gts.mc.GT_AxisOff((short)(1 << (Axis - 1)));
    return sRtn;
}

这里有个初学者容易懵的点:GT_AxisOn 要的不是轴号,而是位掩码 。1 号轴对应二进制第 1 位(值 1),2 号轴对应第 2 位(值 2),所以写成 1 << (Axis - 1)。

5.2 怎么知道当前是上电还是下电

调用 GT_GetSts 会返回一个 32 位的轴状态字 ,每个 bit 代表一种状态(报警、限位、使能、运动......)。其中第 9 位(bit 9,十六进制 0x200,十进制 512)是电机使能标志:

csharp 复制代码
public bool GetSts(int Axis)
{
    // 拿到 32 位状态字 pSts
    sRtn = gts.mc.GT_GetSts((short)Axis, out int pSts, 1, out uint pClock);

    // 按位与 0x200:结果不为 0 说明第 9 位是 1,电机处于使能状态
    if ((pSts & 0x200) != 0)
    {
        return true;
    }
    return false;
}

【插图 2:轴状态字与 0x200 使能位判断】

一个按钮实现上电/下电切换:

csharp 复制代码
private void button1_Click(object sender, EventArgs e)
{
    if (!motion.GetSts(axisIndex))   // 没上电就上电
        motion.SvOn(axisIndex);
    else                             // 已上电就下电
        motion.SvOff(axisIndex);
}

六、Jog 点动

Jog 就是按住按钮一直走、松开就停,用来手动调试轴的方向和响应。点动需要先切换到 Jog 模式,再设参数,最后统一更新:

csharp 复制代码
public int JogPostive(int Axis, double Acc, double Dec, double Vel, short Smooth = 1)
{
    sRtn = gts.mc.GT_ClrSts((short)Axis, 8);   // 先清轴状态
    sRtn = gts.mc.GT_PrfJog((short)Axis);      // 切换到 Jog 规划模式

    // Jog 参数用专门的结构体:加速度、减速度、平滑系数
    gts.mc.TJogPrm jogPrm = new gts.mc.TJogPrm();
    jogPrm.acc = Acc;
    jogPrm.dec = Dec;
    jogPrm.smooth = Smooth;
    sRtn = gts.mc.GT_SetJogPrm((short)Axis, ref jogPrm);  // 下发加减速参数

    sRtn = gts.mc.GT_SetVel((short)Axis, Vel);            // 设置目标速度
    sRtn = gts.mc.GT_Update(1 << (Axis - 1));             // 关键:更新才真正生效
    return sRtn;
}

注意最后一步 GT_Update:前面都是在"填参数",调用 Update 后指令才会真正下发,漏掉它轴不会动。速度传负数就反方向转:

csharp 复制代码
motion.JogPostive(axisIndex, 0.5, 0.5, -10);   // 负方向
motion.JogPostive(axisIndex, 0.5, 0.5, 10);    // 正方向

七、二维插补:先建立坐标系

插补运动前必须先建一个坐标系,规定哪个规划轴当 X、哪个当 Y。不建系直接写插补指令是没有效果的:

csharp 复制代码
const short Crd = 1;   // 固定使用 1 号坐标系

public int BuildCrdSystem()
{
    gts.mc.TCrdPrm crdPrm = new gts.mc.TCrdPrm();
    crdPrm.dimension = 2;          // 二维,即 XY 平面
    crdPrm.profile1 = 1;           // 规划轴 1 映射为 X
    crdPrm.profile2 = 2;           // 规划轴 2 映射为 Y
    crdPrm.synVelMax = 500;        // 坐标系允许的最大合成速度
    crdPrm.synAccMax = 1;          // 最大合成加速度
    crdPrm.evenTime = 50;          // 最小匀速时间,减小起步停止的冲击
    crdPrm.setOriginFlag = 0;      // 不设原点偏移,把当前位置当 (0,0)

    sRtn = gts.mc.GT_SetCrdPrm(Crd, ref crdPrm);
    return sRtn;
}

建系成功后才解锁插补按钮,避免没建系就点:

csharp 复制代码
if (motion.BuildCrdSystem() != 0)
{
    MessageBox.Show("建立坐标系失败!!!");
    return;
}
button3.Enabled = true;   // 画直线
button4.Enabled = true;   // 画矩形
button6.Enabled = true;   // 画爱心

八、插补实战:直线、矩形、爱心

8.1 画直线

GT_LnXY 向 FIFO 写入一段直线,GT_CrdStart 启动:

csharp 复制代码
public int DrawLine(int x, int y)
{
    sRtn = gts.mc.GT_CrdClear(Crd, 0);   // 先清掉 FIFO 里的旧轨迹
    // 写入一段二维直线:终点 (x,y)、合成速度、加速度、终点速度 0、FIFO 0
    sRtn = gts.mc.GT_LnXY(Crd, x, y, CrdVel, CrdAcc, 0, 0);
    short startRtn = gts.mc.GT_CrdStart(1, 0);   // 启动 1 号坐标系
    WaitCrdDone();                                // 等这一段跑完
    return startRtn;
}

8.2 画矩形

矩形就是 4 段直线,按顺序全部压进 FIFO,一次启动后连续走出完整矩形:

csharp 复制代码
public int DrawRectangle(int length, int width)
{
    sRtn = gts.mc.GT_CrdClear(Crd, 0);
    sRtn = gts.mc.GT_LnXY(Crd, length, 0,     CrdVel, CrdAcc, 0, 0); // 第1边:X 正方向
    sRtn = gts.mc.GT_LnXY(Crd, length, width, CrdVel, CrdAcc, 0, 0); // 第2边:Y 正方向
    sRtn = gts.mc.GT_LnXY(Crd, 0, width,      CrdVel, CrdAcc, 0, 0); // 第3边:X 负方向
    sRtn = gts.mc.GT_LnXY(Crd, 0, 0,          CrdVel, CrdAcc, 0, 0); // 第4边:回原点
    short startRtn = gts.mc.GT_CrdStart(1, 0);
    WaitCrdDone();
    return startRtn;
}

路径:(0,0) → (长,0) → (长,宽) → (0,宽) → (0,0),正好一圈。

8.3 画爱心

爱心没有直线方程,我的做法是用心形参数方程算出一圈点,再用多段短直线折线逼近,点越多越平滑:

csharp 复制代码
public int DrawHeart()
{
    sRtn = gts.mc.GT_CrdClear(Crd, 0);
    const int segCount = 36;     // 一圈分 36 段
    const double scale = 400;    // 放大到脉冲量级
    const int offsetX = 10000;   // 平移到工作台合适位置
    const int offsetY = 8000;

    for (int i = 0; i <= segCount; i++)
    {
        double t = 2 * Math.PI * i / segCount;
        // 心形参数方程
        double px = 16 * Math.Pow(Math.Sin(t), 3);
        double py = 13 * Math.Cos(t) - 5 * Math.Cos(2 * t)
                    - 2 * Math.Cos(3 * t) - Math.Cos(4 * t);
        int x = (int)(px * scale) + offsetX;
        int y = (int)(py * scale) + offsetY;
        sRtn = gts.mc.GT_LnXY(Crd, x, y, CrdVel, CrdAcc, 0, 0);
    }
    short heartStart = gts.mc.GT_CrdStart(1, 0);
    WaitCrdDone();
    return heartStart;
}

【插图 3:插补 FIFO 多段连续执行模型】

8.4 怎么知道轨迹跑完了

插补启动后是异步 的,函数立刻返回,电机还在走。我用循环查询 GT_CrdStatus,等运行标志变成 0:

csharp 复制代码
private void WaitCrdDone()
{
    short run;      // 1 表示正在运动
    int segment;    // 已完成的段数
    do
    {
        sRtn = gts.mc.GT_CrdStatus(Crd, out run, out segment, 0);
        Thread.Sleep(10);   // 歇 10ms 再查,避免空转占满 CPU
    } while (run == 1);
}

【插图 4:软件界面截图】


【插图 5:实际运行轨迹效果】

九、常见坑

  1. 运行报 DllNotFoundException,或者加载配置失败。 八成是 gts.dll 和 GTS800.cfg 没放到 bin\Debug 输出目录,或者文件名和代码里写的不一致。先确认这两个文件和 exe 在同一目录。

  2. 没建坐标系就调用插补函数,电机完全没反应。 GT_SetCrdPrm 建系是插补的前提,而且要确认建系返回成功,再允许执行后面的画线操作。

  3. 参数设置完轴不动。 Jog 模式最后必须调 GT_Update、插补最后必须调 GT_CrdStart,前面的函数只是在填参数,没有这一步指令不会真正下发。

  4. 插补启动后立刻点别的按钮,轨迹互相干扰。 插补是异步的,启动后要用 GT_CrdStatus 等 run 变成 0、确认上一段跑完,再下发下一段;并且每次写新轨迹前先 GT_CrdClear 清 FIFO。

  5. 把轴号直接传给 GT_AxisOn 这类函数。 它们要的是位掩码(1 << (轴号-1)),不是轴号本身;另外轴号从 1 开始数,不是从 0 开始。调试时 Jog 一般还要在按钮松开时调用停止函数,这一点可以在后续版本里补上。

十、小结

  1. 运动控制卡是电机的专用指挥官,入门先记住几个词:轴、脉冲、规划器、插补、FIFO。
  2. 固定流程:GT_Open/Reset/LoadConfig 开卡 → GT_AxisOn/Off 上下电 → GT_GetSts 读状态字(0x200 判断使能)→ Jog 点动(最后 GT_Update)→ GT_SetCrdPrm 建坐标系 → GT_LnXY 写轨迹、GT_CrdStart 启动、GT_CrdStatus 等待完成。
  3. 复杂图形(矩形、爱心)本质都是"多段直线压进 FIFO 连续执行",曲线靠折线逼近。
  4. 代码分层写:厂商 API 不动,自己再封一层 MotionGTS,界面只调封装方法,结构清晰也好维护。
相关推荐
海绵宝宝转agent1 小时前
LeetCode100个人题解+java面经总结
java·开发语言·笔记·学习·算法
chenbingjie_c1 小时前
C++ 继承全解析(从三种继承方式到菱形继承与内存布局)
开发语言·c++
山东孙燕姿1 小时前
前端框架升级:不敢动的组件怎么办
大数据·运维·服务器·网络·人工智能·前端框架
何以解忧唯有撸码8 小时前
c# + YOLODotNet+OpenCVSharp 实现物品轨迹追踪
计算机视觉·c#·winform·yolov8·opencvsharp
学逆向的9 小时前
C 堆区内存管理
c语言·开发语言·网络安全
huoyueyi10 小时前
《融天AI-APP从0到1开发实录》
开发语言·lua
琥珀.10 小时前
mysql中json数组转列表查询
linux·运维·服务器
OpenAnolis小助手11 小时前
PHP 两个严重漏洞曝光,龙蜥社区 24 小时完成 3 版本修复
开发语言·操作系统·php·龙蜥社区
阿尼卡~12 小时前
VS Code 配置 C 语言开发环境(Windows 10/11):从 GCC 安装到运行、断点调试,一篇搞定
c语言·开发语言·windows