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:实际运行轨迹效果】
九、常见坑
-
运行报 DllNotFoundException,或者加载配置失败。 八成是
gts.dll和GTS800.cfg没放到bin\Debug输出目录,或者文件名和代码里写的不一致。先确认这两个文件和 exe 在同一目录。 -
没建坐标系就调用插补函数,电机完全没反应。
GT_SetCrdPrm建系是插补的前提,而且要确认建系返回成功,再允许执行后面的画线操作。 -
参数设置完轴不动。 Jog 模式最后必须调
GT_Update、插补最后必须调GT_CrdStart,前面的函数只是在填参数,没有这一步指令不会真正下发。 -
插补启动后立刻点别的按钮,轨迹互相干扰。 插补是异步的,启动后要用
GT_CrdStatus等run变成 0、确认上一段跑完,再下发下一段;并且每次写新轨迹前先GT_CrdClear清 FIFO。 -
把轴号直接传给 GT_AxisOn 这类函数。 它们要的是位掩码(
1 << (轴号-1)),不是轴号本身;另外轴号从 1 开始数,不是从 0 开始。调试时 Jog 一般还要在按钮松开时调用停止函数,这一点可以在后续版本里补上。
十、小结
- 运动控制卡是电机的专用指挥官,入门先记住几个词:轴、脉冲、规划器、插补、FIFO。
- 固定流程:
GT_Open/Reset/LoadConfig开卡 →GT_AxisOn/Off上下电 →GT_GetSts读状态字(0x200判断使能)→ Jog 点动(最后GT_Update)→GT_SetCrdPrm建坐标系 →GT_LnXY写轨迹、GT_CrdStart启动、GT_CrdStatus等待完成。 - 复杂图形(矩形、爱心)本质都是"多段直线压进 FIFO 连续执行",曲线靠折线逼近。
- 代码分层写:厂商 API 不动,自己再封一层 MotionGTS,界面只调封装方法,结构清晰也好维护。




