Post
LOM 纸叠层三维打印机五轴控制程序技术文档
针对 LOM_GTS800_5Axis_NewWiring.cpp 的完整技术说明,涵盖五轴轴号分配、限位回零设计、I/O 定义、关键参数、软件结构、状态机流程及调试顺序,基于固高 GTS-800 系列运动控制卡。
LOM纸叠层三维打印机五轴控制程序技术文档
1. 文档说明
本文档针对 LOM_GTS800_5Axis_NewWiring.cpp 控制程序进行说明。程序用于 LOM 纸叠层三维打印机的五轴运动控制,控制对象包括 X 轴、Y 轴、Z 轴、送料轴和热压滚筒轴,并配合安全输入、驱动继电器、加热控制和 CO₂ 激光 TTL 控制完成纸张送料、热压、激光切割和逐层下降等加工流程。
程序基于固高 GTS-800 系列运动控制卡编写,使用 gts.h 中的固高运动控制 API。当前代码中 USE_MOCK_IO 默认为 1,表示程序处于仿真 IO 模式;实机运行前需要改为 0,并根据现场端子和 SDK 补全 IO 读写函数。
2. 系统功能概述
本程序实现的主要功能包括:
- 固高 GTS-800 控制卡初始化;
- 五轴轴号分配和运动控制;
- 仅使用限位开关的回零流程;
- 安全输入检测和运行保护;
- 驱动继电器、热压滚筒加热、激光输出控制;
- 送料、热压滚筒滚压、激光轮廓切割、Z 轴逐层下降;
- 采用状态机组织整机加工流程;
- 在故障或安全异常时关闭激光、加热和驱动输出,并停止五轴运动。
3. 硬件与轴号分配
| 轴号 | 程序宏定义 | 设备对象 | 驱动类型 |
|---|---|---|---|
| Axis1 | AXIS_X | X轴 | 松下 A6 伺服驱动器 |
| Axis2 | AXIS_Y | Y轴 | 松下 A6 伺服驱动器 |
| Axis3 | AXIS_Z | Z轴 | 步进驱动器 |
| Axis4 | AXIS_FEED | 送料轴 | 步进驱动器 |
| Axis5 | AXIS_ROLLER | 热压滚筒轴 | 步进驱动器 |
程序中使用 AXIS_MASK_5 = 0x1F 表示同时控制 1~5 号轴,常用于急停或整体停止。
4. 限位与回零设计
系统没有单独设置原点开关,因此程序采用限位开关作为回零参考。限位分配如下:
| 限位端子 | 对应轴 | 含义 |
|---|---|---|
LIMIT0+ | X轴 | X轴正限位 |
LIMIT0- | X轴 | X轴负限位 |
LIMIT1+ | Y轴 | Y轴正限位 |
LIMIT1- | Y轴 | Y轴负限位 |
LIMIT2+ | Z轴 | Z轴上限位 |
LIMIT2- | Z轴 | Z轴下限位 |
LIMIT3+ | 送料轴 | 送料轴正限位 |
LIMIT3- | 送料轴 | 送料轴负限位 |
LIMIT4+ | 热压滚筒轴 | 热压滚筒轴正限位 |
LIMIT4- | 热压滚筒轴 | 热压滚筒轴负限位 |
回零方式为:
| 轴 | 回零参考 | 程序函数 |
|---|---|---|
| X轴 | LIMIT0- | HomeAxisByNegLimit(AXIS_X) |
| Y轴 | LIMIT1- | HomeAxisByNegLimit(AXIS_Y) |
| Z轴 | LIMIT2+ | HomeZByUpLimit() |
| 送料轴 | LIMIT3- | HomeAxisByNegLimit(AXIS_FEED) |
| 热压滚筒轴 | LIMIT4- | HomeAxisByNegLimit(AXIS_ROLLER) |
限位回零流程采用“两次找限位”的方式:先快速寻找限位,退出限位后再慢速寻找限位,最后离开限位一小段距离并清零当前位置。这样做可以减少单次触发带来的误差,提高回零重复性。
5. 输入输出定义
| 类型 | 端子/宏定义 | 功能 |
|---|---|---|
| 输入 | EXI0 / EXI_SAFE_OK | 安全输入 |
| 输出 | EXO0 / EXO_DRIVE_RELAY | 驱动继电器输出 |
| 输出 | EXO1 / EXO_HEATER_EN | 加热控制输出 |
| 输出 | LASER+ / LASER- | CO₂ 激光电源 TTL 控制端 |
程序中将安全输入、驱动继电器、加热输出和激光输出都做了函数封装:
| 函数 | 作用 |
|---|---|
ReadRawEXI() | 读取 EXI 输入原始状态 |
WriteEXO() | 输出 EXO 信号 |
IsSafeOK() | 判断安全输入是否正常 |
DriveRelayOn() / DriveRelayOff() | 控制驱动继电器 |
HeaterOn() / HeaterOff() | 控制热压滚筒加热允许信号 |
LaserOn() / LaserOff() | 控制激光开关 |
实机运行前需要根据固高控制卡 SDK 和实际接线补全 ReadRawEXI()、WriteEXO()、ReadLimitTerminal()、LaserOn()、LaserOff()。
6. 关键参数说明
6.1 脉冲当量
| 参数 | 含义 | 当前值 |
|---|---|---|
X_PULSE_PER_MM | X轴每毫米脉冲数 | 80.0 |
Y_PULSE_PER_MM | Y轴每毫米脉冲数 | 80.0 |
Z_PULSE_PER_MM | Z轴每毫米脉冲数 | 640.0 |
FEED_PULSE_PER_MM | 送料轴每毫米脉冲数 | 33.9 |
R_PULSE_PER_MM | 热压滚筒轴每毫米脉冲数 | 80.0 |
这些参数直接影响运动距离和速度换算。实机调试时应根据丝杆导程、同步带传动比、驱动器细分、电子齿轮比等参数重新校核。
6.2 工件坐标偏置
程序中定义:
#define X_WORK_OFFSET 230.0
#define Y_WORK_OFFSET 230.0
工件坐标通过以下关系转换为机床坐标:
机床X坐标 = 工件X坐标 + X_WORK_OFFSET
机床Y坐标 = 工件Y坐标 + Y_WORK_OFFSET
例如工件坐标 (-100, -100),转换后机床坐标为 (130, 130)。
6.3 加工参数
| 参数 | 含义 | 当前值 |
|---|---|---|
LAYER_THICKNESS | 单层层厚 | 0.20 mm |
FEED_LENGTH | 每层送料长度 | 520.0 mm |
R_WAIT_POS | 热压滚筒等待位置 | 0.0 mm |
R_START_POS | 热压滚筒滚压起点 | 30.0 mm |
R_END_POS | 热压滚筒滚压终点 | 630.0 mm |
PREHEAT_TIME_MS | 预热等待时间 | 30000 ms |
这些参数需要结合实际纸张尺寸、滚筒行程、热压区域和层厚要求调整。
6.4 回零参数
| 参数 | 含义 | 当前值 |
|---|---|---|
HOME_SEARCH_VEL | 第一次找限位速度 | 10.0 mm/s |
HOME_CREEP_VEL | 慢速找限位速度 | 3.0 mm/s |
HOME_BACK_DIST | 退出限位距离 | 5.0 mm |
HOME_OFFSET_DIST | 回零完成后离开限位距离 | 2.0 mm |
调试初期建议适当降低回零速度,确认方向正确后再提高速度。
7. 软件结构说明
程序按功能分为以下模块:
| 模块 | 主要内容 |
|---|---|
| 调试开关 | USE_MOCK_IO 控制仿真/实机 IO 模式 |
| 轴号与限位定义 | 定义五轴编号、限位编号、输入输出编号 |
| 机械参数 | 定义脉冲当量、层厚、送料长度、滚筒位置等 |
| 单位换算 | 完成 mm、pulse、速度单位之间的换算 |
| IO 封装 | 封装 EXI、EXO、限位、激光、加热等控制 |
| 安全保护 | 安全输入检测、限位保护、急停处理 |
| 运动控制 | 单轴绝对/相对运动、Jog、XY 联动运动 |
| 限位回零 | X/Y/送料/滚筒负限位回零,Z 上限位回零 |
| 工艺动作 | 预热、送料、热压、激光切割、Z 轴下降 |
| 控制卡初始化 | 打开控制卡、加载配置、轴使能、输出初始化 |
| 主状态机 | 按加工顺序组织整机运行流程 |
8. 主要函数说明
8.1 单位换算函数
| 函数 | 功能 |
|---|---|
mm_to_pulse(short axis, double mm) | 将毫米换算为对应轴脉冲数 |
pulse_to_mm(short axis, double pulse) | 将脉冲数换算为毫米 |
vel_to_sdk(short axis, double vel_mm_s) | 将 mm/s 换算为 SDK 速度单位 |
WorkToMachineX(double x_work) | 工件 X 坐标转机床 X 坐标 |
WorkToMachineY(double y_work) | 工件 Y 坐标转机床 Y 坐标 |
8.2 安全保护函数
| 函数 | 功能 |
|---|---|
SafetyCheck() | 判断安全输入是否正常,异常时关闭激光和加热 |
LimitProtectCheck() | 加工运行阶段检查各轴限位是否触发 |
EmergencyStop() | 关闭激光、加热、驱动继电器,并停止五轴 |
LimitProtectCheck() 不在回零阶段调用,避免回零找限位时被误判为故障。
8.3 运动控制函数
| 函数 | 功能 |
|---|---|
StopOneAxis(short axis) | 停止单轴并清除状态 |
IsAxisMoving(short axis) | 判断单轴是否处于规划运动状态 |
WaitAxisStop(short axis) | 等待单轴停止,期间执行安全和限位保护 |
WaitAxesStop(long mask) | 等待多个轴停止,期间执行安全和限位保护 |
MoveAbsMM(short axis, double pos_mm, double vel_mm_s) | 单轴绝对位置运动 |
MoveRelMM(short axis, double distance_mm, double vel_mm_s) | 单轴相对位移运动 |
JogAxis(short axis, int dir, double vel_mm_s) | 单轴连续 Jog 运动 |
MoveXYMachineMM(double x_mm, double y_mm, double vel_mm_s) | X/Y 机床坐标同步移动 |
MoveXYWorkMM(double x_work, double y_work, double vel_mm_s) | X/Y 工件坐标移动 |
8.4 回零函数
| 函数 | 功能 |
|---|---|
HomeAxisByNegLimit(short axis) | 使用负限位对指定轴回零 |
HomeZByUpLimit() | 使用 Z 轴上限位回零 |
HomeAllAxisByLimit() | 按 Z、X、Y、送料、滚筒顺序完成五轴回零 |
HomeAllAxisByLimit() 中先执行 Z 轴上限位回零,使 Z 轴先回到上方安全位置,再执行其他轴回零,降低平台或工具与机构发生干涉的风险。
8.5 工艺动作函数
| 函数 | 功能 |
|---|---|
PreheatRoller() | 开启加热输出并等待预热时间 |
PaperFeed() | 执行送料轴送料动作 |
HotPress() | 移动到安全位置后执行热压滚筒滚压 |
EnsureRollerSafe() | 确保热压滚筒回到等待位置 |
LaserCutLine() | 执行一条直线切割动作 |
LaserCutCurrentLayer() | 执行当前层矩形轮廓切割示例 |
ZDownOneLayer() | Z 轴下降一个层厚 |
当前激光切割路径为矩形轮廓示例,实际加工时应由切片文件或 G 代码生成加工路径。
9. 状态机流程
主程序使用 MachineState 枚举定义整机运行状态:
| 状态 | 含义 |
|---|---|
S_POWER_ON | 系统上电初始化 |
S_SELF_CHECK | 安全自检 |
S_LIMIT_HOME | 五轴限位回零 |
S_PREHEAT | 热压滚筒预热 |
S_LAYER_PREPARE | 当前层加工准备 |
S_PAPER_FEED | 送料 |
S_HOT_PRESS | 热压滚筒滚压 |
S_LASER_CUT | 激光切割 |
S_Z_DOWN | Z 轴下降一个层厚 |
S_NEXT_LAYER | 层数累加并进入下一层 |
S_FINISH | 加工完成 |
S_FAULT | 系统故障 |
S_EMERGENCY | 安全保护触发 |
程序主流程如下:
系统上电初始化
→ 安全自检
→ 五轴限位回零
→ 热压滚筒预热
→ 层加工准备
→ 送料
→ 热压滚筒滚压
→ 激光切割
→ Z轴下降一个层厚
→ 下一层
→ 达到总层数后加工完成
其中 currentLayer 表示当前层数,totalLayer 表示总层数。当前代码中总层数为 10。
10. 安全逻辑说明
程序中的安全保护主要包括以下几类:
- 安全输入异常时,关闭激光和加热,并进入保护状态;
- 加工运行阶段检测各轴正负限位,触发后立即停止;
- 激光切割前确认安全输入正常;
- 激光切割前确认热压滚筒已经退回等待位置;
- 空移阶段激光关闭,只有切割运动时才开启激光;
- 故障或急停时执行
EmergencyStop(),同时关闭激光、加热、驱动继电器并停止五轴。
需要注意的是,程序目前只提供了安全保护逻辑框架,实机运行时必须确保安全输入、限位信号、激光 TTL 信号和驱动继电器输出已经在硬件层面正确接线并完成测试。
11. 编译与运行说明
11.1 编译环境
程序需要在 Windows 环境下编译,并正确配置固高 GTS-800 系列控制卡 SDK。工程中应包含:
gts.h头文件;- 固高 SDK 对应的
.lib静态库或导入库; - 运行时需要的 DLL 文件;
- 控制卡配置文件
GTS800.cfg。
11.2 实机运行前修改
当前代码中:
#define USE_MOCK_IO 1
表示程序处于仿真 IO 模式。实机运行前应改为:
#define USE_MOCK_IO 0
并补全以下函数中的实际 IO 读写代码:
ReadRawEXI()
WriteEXO()
ReadLimitTerminal()
LaserOn()
LaserOff()
11.3 推荐调试顺序
建议按以下顺序调试:
- 不接激光电源,只测试运动轴;
- 测试
EXI0安全输入有效电平; - 测试所有正负限位输入;
- 单独测试 Z 轴上限位回零;
- 单独测试 X 轴负限位回零;
- 单独测试 Y 轴负限位回零;
- 单独测试送料轴负限位回零;
- 单独测试热压滚筒轴负限位回零;
- 测试
EXO0驱动继电器输出; - 测试
EXO1加热允许输出; - 测试送料动作;
- 测试热压滚筒动作;
- 测试 X/Y 空移;
- 确认激光 TTL 输出默认关闭;
- 最后接入激光电源并测试完整流程。
12. 需要现场确认的内容
实机使用前,应重点确认以下内容:
- Axis1~Axis5 是否与实际电机接线一致;
- 各轴正方向是否与程序逻辑一致;
- Z 轴正方向是否为向上,若相反需要修改
HomeZByUpLimit()和ZDownOneLayer()中的方向; - 各限位触发电平是否与
LIMIT_TRIGGER_LEVEL一致; - 安全输入正常电平是否与
EXI_OK_LEVEL一致; GTS800.cfg是否为现场调试软件生成的正确配置文件;- 每个轴的脉冲当量是否正确;
- 送料长度、层厚、滚筒起点和终点是否符合实际机械尺寸;
- 激光 TTL 输出是否为低电平安全关闭状态;
- 温控器、固态继电器、热压滚筒加热回路是否能独立安全工作。
13. 代码全文
/*
* LOM纸叠层三维打印机五轴控制程序
* 控制卡:固高 GTS-800 系列运动控制卡
*
* 轴定义:
* Axis1:X轴,松下A6伺服驱动器
* Axis2:Y轴,松下A6伺服驱动器
* Axis3:Z轴,步进驱动器
* Axis4:送料轴,步进驱动器
* Axis5:热压滚筒轴,步进驱动器
*
* 限位定义:
* LIMIT0+ / LIMIT0-:X轴正限位 / 负限位
* LIMIT1+ / LIMIT1-:Y轴正限位 / 负限位
* LIMIT2+ / LIMIT2-:Z轴上限位 / 下限位
* LIMIT3+ / LIMIT3-:送料轴正限位 / 负限位
* LIMIT4+ / LIMIT4-:热压滚筒轴正限位 / 负限位
*
* 输入输出定义:
* EXI0:安全输入
* EXO0:驱动继电器输出
* EXO1:加热控制输出
* LASER+ / LASER-:CO2激光电源TTL控制端
*
* 回零方式:
* X轴、Y轴、送料轴、热压滚筒轴采用负限位回零。
* Z轴采用上限位回零。
*/
#include <stdio.h>
#include <windows.h>
#include "gts.h"
/******************** 0. 调试开关 ********************/
/*
* 1:仿真IO,用于查看程序流程。
* 0:实机IO,需要补全 ReadRawEXI、WriteEXO、ReadLimitTerminal、LaserOn、LaserOff。
*/
#define USE_MOCK_IO 1
/******************** 1. 轴号定义 ********************/
#define AXIS_X 1
#define AXIS_Y 2
#define AXIS_Z 3
#define AXIS_FEED 4
#define AXIS_ROLLER 5
#define AXIS_MASK_5 0x1F
/******************** 2. 限位编号 ********************/
#define LIMIT_X 0
#define LIMIT_Y 1
#define LIMIT_Z 2
#define LIMIT_FEED 3
#define LIMIT_ROLLER 4
#define LIMIT_NEG 0
#define LIMIT_POS 1
/*
* 光电限位触发电平。
* 在调试软件中观察光电开关动作状态:
* 遮挡时显示为1,则保持1;
* 遮挡时显示为0,则改为0。
*/
#define LIMIT_TRIGGER_LEVEL 1
/******************** 3. 输入输出编号 ********************/
#define EXI_SAFE_OK 0
#define EXO_DRIVE_RELAY 0
#define EXO_HEATER_EN 1
/*
* EXI有效电平。
* 安全输入正常时,若调试软件显示为1,则保持1;
* 若显示为0,则改为0。
*/
#define EXI_OK_LEVEL 1
/******************** 4. 机械参数 ********************/
#define X_PULSE_PER_MM 80.0
#define Y_PULSE_PER_MM 80.0
#define Z_PULSE_PER_MM 640.0
#define FEED_PULSE_PER_MM 33.9
#define R_PULSE_PER_MM 80.0
#define X_WORK_OFFSET 230.0
#define Y_WORK_OFFSET 230.0
#define LAYER_THICKNESS 0.20
#define FEED_LENGTH 520.0
#define R_WAIT_POS 0.0
#define R_START_POS 30.0
#define R_END_POS 630.0
#define XY_SAFE_WORK_X 0.0
#define XY_SAFE_WORK_Y 0.0
/*
* 加热预热时间。
* 如果温控器已经独立完成恒温控制,该时间用于等待热压滚筒达到工艺温度。
* 实际调试时按温度上升速度修改。
*/
#define PREHEAT_TIME_MS 30000
/******************** 5. 回零参数 ********************/
#define HOME_SEARCH_VEL 10.0
#define HOME_CREEP_VEL 3.0
#define HOME_BACK_DIST 5.0
#define HOME_OFFSET_DIST 2.0
/******************** 6. 运动状态位 ********************/
/*
* 常见固高示例中,0x400表示规划运动中。
* 实际值以所用SDK和编程手册为准。
*/
#define STS_PROFILE_MOVING 0x400
/******************** 7. 状态机 ********************/
typedef enum
{
S_POWER_ON = 0,
S_SELF_CHECK,
S_LIMIT_HOME,
S_PREHEAT,
S_LAYER_PREPARE,
S_PAPER_FEED,
S_HOT_PRESS,
S_LASER_CUT,
S_Z_DOWN,
S_NEXT_LAYER,
S_FINISH,
S_FAULT,
S_EMERGENCY
} MachineState;
/******************** 8. 单位换算 ********************/
long mm_to_pulse(short axis, double mm)
{
switch(axis)
{
case AXIS_X:
return (long)(mm * X_PULSE_PER_MM);
case AXIS_Y:
return (long)(mm * Y_PULSE_PER_MM);
case AXIS_Z:
return (long)(mm * Z_PULSE_PER_MM);
case AXIS_FEED:
return (long)(mm * FEED_PULSE_PER_MM);
case AXIS_ROLLER:
return (long)(mm * R_PULSE_PER_MM);
default:
return 0;
}
}
double pulse_to_mm(short axis, double pulse)
{
switch(axis)
{
case AXIS_X:
return pulse / X_PULSE_PER_MM;
case AXIS_Y:
return pulse / Y_PULSE_PER_MM;
case AXIS_Z:
return pulse / Z_PULSE_PER_MM;
case AXIS_FEED:
return pulse / FEED_PULSE_PER_MM;
case AXIS_ROLLER:
return pulse / R_PULSE_PER_MM;
default:
return 0.0;
}
}
double vel_to_sdk(short axis, double vel_mm_s)
{
/*
* 这里按 pulse/ms 换算。
* 若所用SDK速度单位不同,在此统一换算。
*/
switch(axis)
{
case AXIS_X:
return vel_mm_s * X_PULSE_PER_MM / 1000.0;
case AXIS_Y:
return vel_mm_s * Y_PULSE_PER_MM / 1000.0;
case AXIS_Z:
return vel_mm_s * Z_PULSE_PER_MM / 1000.0;
case AXIS_FEED:
return vel_mm_s * FEED_PULSE_PER_MM / 1000.0;
case AXIS_ROLLER:
return vel_mm_s * R_PULSE_PER_MM / 1000.0;
default:
return 0.0;
}
}
double WorkToMachineX(double x_work)
{
return x_work + X_WORK_OFFSET;
}
double WorkToMachineY(double y_work)
{
return y_work + Y_WORK_OFFSET;
}
/******************** 9. IO接口封装 ********************/
int ReadRawEXI(int exi_no)
{
#if USE_MOCK_IO
if(exi_no == EXI_SAFE_OK) return EXI_OK_LEVEL;
return 0;
#else
/*
* 按实际固高SDK读取EXI输入。
* 返回值要求:0或1。
*/
long value = 0;
/*
* 示例:
* GT_GetDi(MC_GPI, &value);
*/
return (value & (1 << exi_no)) ? 1 : 0;
#endif
}
void WriteEXO(int exo_no, int value)
{
#if USE_MOCK_IO
printf("[EXO] EXO%d = %d\n", exo_no, value);
#else
/*
* 按实际固高SDK输出EXO信号。
*/
/*
* 示例:
* GT_SetDoBit(MC_GPO, exo_no, value);
*/
(void)exo_no;
(void)value;
#endif
}
int ReadLimitTerminal(int limit_no, int pos_or_neg)
{
#if USE_MOCK_IO
/*
* 仿真状态下,默认所有限位未触发。
*/
return 0;
#else
/*
* 按实际固高SDK读取LIMIT输入。
*
* limit_no:
* 0 = LIMIT0
* 1 = LIMIT1
* 2 = LIMIT2
* 3 = LIMIT3
* 4 = LIMIT4
*
* pos_or_neg:
* LIMIT_POS = 正限位
* LIMIT_NEG = 负限位
*
* 返回值要求:
* 1 = 限位触发
* 0 = 限位未触发
*/
int raw_level = 0;
(void)limit_no;
(void)pos_or_neg;
return raw_level == LIMIT_TRIGGER_LEVEL ? 1 : 0;
#endif
}
int IsSafeOK()
{
return ReadRawEXI(EXI_SAFE_OK) == EXI_OK_LEVEL ? 1 : 0;
}
void DriveRelayOn()
{
WriteEXO(EXO_DRIVE_RELAY, 1);
}
void DriveRelayOff()
{
WriteEXO(EXO_DRIVE_RELAY, 0);
}
void HeaterOn()
{
WriteEXO(EXO_HEATER_EN, 1);
}
void HeaterOff()
{
WriteEXO(EXO_HEATER_EN, 0);
}
void LaserOn()
{
#if USE_MOCK_IO
printf("[LASER] ON\n");
#else
/*
* 按实际固高激光端子或数字输出端子打开激光。
*/
#endif
}
void LaserOff()
{
#if USE_MOCK_IO
printf("[LASER] OFF\n");
#else
/*
* 按实际固高激光端子或数字输出端子关闭激光。
*/
#endif
}
/******************** 10. 限位判断 ********************/
int AxisToLimitNo(short axis)
{
switch(axis)
{
case AXIS_X:
return LIMIT_X;
case AXIS_Y:
return LIMIT_Y;
case AXIS_Z:
return LIMIT_Z;
case AXIS_FEED:
return LIMIT_FEED;
case AXIS_ROLLER:
return LIMIT_ROLLER;
default:
return -1;
}
}
int IsPosLimitOn(short axis)
{
return ReadLimitTerminal(AxisToLimitNo(axis), LIMIT_POS);
}
int IsNegLimitOn(short axis)
{
return ReadLimitTerminal(AxisToLimitNo(axis), LIMIT_NEG);
}
int IsZUpLimitOn()
{
return ReadLimitTerminal(LIMIT_Z, LIMIT_POS);
}
int IsZDownLimitOn()
{
return ReadLimitTerminal(LIMIT_Z, LIMIT_NEG);
}
/******************** 11. 安全保护 ********************/
int SafetyCheck()
{
if(!IsSafeOK())
{
printf("安全输入异常,停止运行。\n");
LaserOff();
HeaterOff();
return 0;
}
return 1;
}
/*
* 加工运行阶段调用。
* 回零阶段不调用该函数,避免将找限位动作误判为故障。
*/
int LimitProtectCheck()
{
if(IsNegLimitOn(AXIS_X) || IsPosLimitOn(AXIS_X))
{
printf("X轴限位触发。\n");
return 0;
}
if(IsNegLimitOn(AXIS_Y) || IsPosLimitOn(AXIS_Y))
{
printf("Y轴限位触发。\n");
return 0;
}
if(IsZUpLimitOn() || IsZDownLimitOn())
{
printf("Z轴限位触发。\n");
return 0;
}
if(IsNegLimitOn(AXIS_FEED) || IsPosLimitOn(AXIS_FEED))
{
printf("送料轴限位触发。\n");
return 0;
}
if(IsNegLimitOn(AXIS_ROLLER) || IsPosLimitOn(AXIS_ROLLER))
{
printf("热压滚筒轴限位触发。\n");
return 0;
}
return 1;
}
void EmergencyStop()
{
LaserOff();
HeaterOff();
DriveRelayOff();
GT_Stop(AXIS_MASK_5, 0);
}
/******************** 12. 运动控制函数 ********************/
void StopOneAxis(short axis)
{
GT_Stop(1 << (axis - 1), 0);
Sleep(100);
GT_ClrSts(axis, 1);
}
int IsAxisMoving(short axis)
{
long sts = 0;
unsigned long clk = 0;
GT_GetSts(axis, &sts, 1, &clk);
return (sts & STS_PROFILE_MOVING) ? 1 : 0;
}
int WaitAxisStop(short axis)
{
while(IsAxisMoving(axis))
{
if(!SafetyCheck() || !LimitProtectCheck())
{
EmergencyStop();
return 0;
}
Sleep(10);
}
return 1;
}
int WaitAxesStop(long mask)
{
int moving = 1;
while(moving)
{
moving = 0;
for(short axis = 1; axis <= 5; axis++)
{
if(mask & (1 << (axis - 1)))
{
if(IsAxisMoving(axis))
{
moving = 1;
}
}
}
if(!SafetyCheck() || !LimitProtectCheck())
{
EmergencyStop();
return 0;
}
Sleep(10);
}
return 1;
}
int MoveAbsMM(short axis, double pos_mm, double vel_mm_s)
{
TTrapPrm trap;
long pos_pulse = mm_to_pulse(axis, pos_mm);
double vel = vel_to_sdk(axis, vel_mm_s);
GT_PrfTrap(axis);
GT_GetTrapPrm(axis, &trap);
trap.acc = 0.25;
trap.dec = 0.25;
trap.smoothTime = 20;
GT_SetTrapPrm(axis, &trap);
GT_SetVel(axis, vel);
GT_SetPos(axis, pos_pulse);
GT_Update(1 << (axis - 1));
return 1;
}
int MoveRelMM(short axis, double distance_mm, double vel_mm_s)
{
double current_pos = 0.0;
unsigned long clk = 0;
double current_mm = 0.0;
double target_mm = 0.0;
GT_GetPrfPos(axis, ¤t_pos, 1, &clk);
current_mm = pulse_to_mm(axis, current_pos);
target_mm = current_mm + distance_mm;
return MoveAbsMM(axis, target_mm, vel_mm_s);
}
void JogAxis(short axis, int dir, double vel_mm_s)
{
TJogPrm jog;
double vel = vel_to_sdk(axis, vel_mm_s);
GT_PrfJog(axis);
GT_GetJogPrm(axis, &jog);
jog.acc = 0.1;
jog.dec = 0.1;
GT_SetJogPrm(axis, &jog);
GT_SetVel(axis, dir < 0 ? -vel : vel);
GT_Update(1 << (axis - 1));
}
void MoveXYMachineMM(double x_mm, double y_mm, double vel_mm_s)
{
TTrapPrm trapX, trapY;
long x_pulse = mm_to_pulse(AXIS_X, x_mm);
long y_pulse = mm_to_pulse(AXIS_Y, y_mm);
GT_PrfTrap(AXIS_X);
GT_PrfTrap(AXIS_Y);
GT_GetTrapPrm(AXIS_X, &trapX);
trapX.acc = 0.25;
trapX.dec = 0.25;
trapX.smoothTime = 20;
GT_SetTrapPrm(AXIS_X, &trapX);
GT_GetTrapPrm(AXIS_Y, &trapY);
trapY.acc = 0.25;
trapY.dec = 0.25;
trapY.smoothTime = 20;
GT_SetTrapPrm(AXIS_Y, &trapY);
GT_SetVel(AXIS_X, vel_to_sdk(AXIS_X, vel_mm_s));
GT_SetVel(AXIS_Y, vel_to_sdk(AXIS_Y, vel_mm_s));
GT_SetPos(AXIS_X, x_pulse);
GT_SetPos(AXIS_Y, y_pulse);
GT_Update((1 << (AXIS_X - 1)) | (1 << (AXIS_Y - 1)));
}
void MoveXYWorkMM(double x_work, double y_work, double vel_mm_s)
{
MoveXYMachineMM(WorkToMachineX(x_work), WorkToMachineY(y_work), vel_mm_s);
}
/******************** 13. 限位回零 ********************/
int HomeAxisByNegLimit(short axis)
{
printf("Axis %d 负限位回零。\n", axis);
LaserOff();
if(!SafetyCheck())
{
return 0;
}
/*
* 若轴已压在负限位上,先向正方向退出限位。
*/
if(IsNegLimitOn(axis))
{
JogAxis(axis, 1, HOME_CREEP_VEL);
while(IsNegLimitOn(axis))
{
if(!SafetyCheck())
{
StopOneAxis(axis);
return 0;
}
Sleep(10);
}
StopOneAxis(axis);
}
/*
* 第一次寻找负限位。
*/
JogAxis(axis, -1, HOME_SEARCH_VEL);
while(!IsNegLimitOn(axis))
{
if(!SafetyCheck())
{
StopOneAxis(axis);
return 0;
}
Sleep(10);
}
StopOneAxis(axis);
/*
* 退出限位。
*/
MoveRelMM(axis, HOME_BACK_DIST, HOME_CREEP_VEL);
while(IsAxisMoving(axis))
{
if(!SafetyCheck())
{
EmergencyStop();
return 0;
}
Sleep(10);
}
/*
* 第二次慢速寻找负限位。
*/
JogAxis(axis, -1, HOME_CREEP_VEL);
while(!IsNegLimitOn(axis))
{
if(!SafetyCheck())
{
StopOneAxis(axis);
return 0;
}
Sleep(10);
}
StopOneAxis(axis);
/*
* 离开限位一小段距离,并将当前位置清零。
*/
MoveRelMM(axis, HOME_OFFSET_DIST, HOME_CREEP_VEL);
while(IsAxisMoving(axis))
{
if(!SafetyCheck())
{
EmergencyStop();
return 0;
}
Sleep(10);
}
GT_ZeroPos(axis, 1);
printf("Axis %d 回零完成。\n", axis);
return 1;
}
int HomeZByUpLimit()
{
printf("Z轴上限位回零。\n");
LaserOff();
if(!SafetyCheck())
{
return 0;
}
/*
* 默认Z轴正方向为向上。
* 若实际方向相反,需要调整本函数中的运动方向。
*/
if(IsZUpLimitOn())
{
JogAxis(AXIS_Z, -1, HOME_CREEP_VEL);
while(IsZUpLimitOn())
{
if(!SafetyCheck())
{
StopOneAxis(AXIS_Z);
return 0;
}
Sleep(10);
}
StopOneAxis(AXIS_Z);
}
/*
* 第一次寻找上限位。
*/
JogAxis(AXIS_Z, 1, HOME_SEARCH_VEL);
while(!IsZUpLimitOn())
{
if(!SafetyCheck())
{
StopOneAxis(AXIS_Z);
return 0;
}
Sleep(10);
}
StopOneAxis(AXIS_Z);
/*
* 向下退出上限位。
*/
MoveRelMM(AXIS_Z, -HOME_BACK_DIST, HOME_CREEP_VEL);
while(IsAxisMoving(AXIS_Z))
{
if(!SafetyCheck())
{
EmergencyStop();
return 0;
}
Sleep(10);
}
/*
* 第二次慢速寻找上限位。
*/
JogAxis(AXIS_Z, 1, HOME_CREEP_VEL);
while(!IsZUpLimitOn())
{
if(!SafetyCheck())
{
StopOneAxis(AXIS_Z);
return 0;
}
Sleep(10);
}
StopOneAxis(AXIS_Z);
/*
* 离开上限位一小段距离,并将当前位置清零。
*/
MoveRelMM(AXIS_Z, -HOME_OFFSET_DIST, HOME_CREEP_VEL);
while(IsAxisMoving(AXIS_Z))
{
if(!SafetyCheck())
{
EmergencyStop();
return 0;
}
Sleep(10);
}
GT_ZeroPos(AXIS_Z, 1);
printf("Z轴回零完成。\n");
return 1;
}
int HomeAllAxisByLimit()
{
/*
* 先让Z轴回到上方安全位置,再进行其他轴回零。
*/
if(!HomeZByUpLimit())
{
return 0;
}
if(!HomeAxisByNegLimit(AXIS_X))
{
return 0;
}
if(!HomeAxisByNegLimit(AXIS_Y))
{
return 0;
}
if(!HomeAxisByNegLimit(AXIS_FEED))
{
return 0;
}
if(!HomeAxisByNegLimit(AXIS_ROLLER))
{
return 0;
}
return 1;
}
/******************** 14. 工艺动作 ********************/
int PreheatRoller()
{
unsigned long start = GetTickCount();
HeaterOn();
while(GetTickCount() - start < PREHEAT_TIME_MS)
{
if(!SafetyCheck())
{
return 0;
}
Sleep(100);
}
return 1;
}
int PaperFeed()
{
LaserOff();
MoveRelMM(AXIS_FEED, FEED_LENGTH, 40.0);
if(!WaitAxisStop(AXIS_FEED))
{
return 0;
}
return 1;
}
int HotPress()
{
LaserOff();
MoveXYWorkMM(XY_SAFE_WORK_X, XY_SAFE_WORK_Y, 100.0);
if(!WaitAxesStop((1 << (AXIS_X - 1)) | (1 << (AXIS_Y - 1))))
{
return 0;
}
MoveAbsMM(AXIS_ROLLER, R_START_POS, 40.0);
if(!WaitAxisStop(AXIS_ROLLER))
{
return 0;
}
MoveAbsMM(AXIS_ROLLER, R_END_POS, 30.0);
if(!WaitAxisStop(AXIS_ROLLER))
{
return 0;
}
MoveAbsMM(AXIS_ROLLER, R_WAIT_POS, 50.0);
if(!WaitAxisStop(AXIS_ROLLER))
{
return 0;
}
return 1;
}
int EnsureRollerSafe()
{
MoveAbsMM(AXIS_ROLLER, R_WAIT_POS, 50.0);
if(!WaitAxisStop(AXIS_ROLLER))
{
return 0;
}
return 1;
}
int LaserCutLine(double x1, double y1, double x2, double y2)
{
LaserOff();
MoveXYWorkMM(x1, y1, 120.0);
if(!WaitAxesStop((1 << (AXIS_X - 1)) | (1 << (AXIS_Y - 1))))
{
LaserOff();
return 0;
}
LaserOn();
/*
* 此处为直线切割示例。
* 实机轮廓切割建议使用固高二维直线插补函数。
*/
MoveXYWorkMM(x2, y2, 30.0);
if(!WaitAxesStop((1 << (AXIS_X - 1)) | (1 << (AXIS_Y - 1))))
{
LaserOff();
return 0;
}
LaserOff();
return 1;
}
int LaserCutCurrentLayer()
{
if(!IsSafeOK())
{
printf("安全输入异常,禁止激光切割。\n");
return 0;
}
if(!EnsureRollerSafe())
{
printf("热压滚筒未退回等待位置,禁止激光切割。\n");
return 0;
}
/*
* 示例路径:矩形轮廓。
* 实际路径可由切片文件或G代码生成。
*/
if(!LaserCutLine(-100, -100, 100, -100)) return 0;
if(!LaserCutLine( 100, -100, 100, 100)) return 0;
if(!LaserCutLine( 100, 100, -100, 100)) return 0;
if(!LaserCutLine(-100, 100, -100, -100)) return 0;
LaserOff();
return 1;
}
int ZDownOneLayer()
{
LaserOff();
/*
* Z轴上限位回零后,默认向下为负方向。
*/
MoveRelMM(AXIS_Z, -LAYER_THICKNESS, 5.0);
if(!WaitAxisStop(AXIS_Z))
{
return 0;
}
return 1;
}
/******************** 15. 控制卡初始化 ********************/
int ControllerInit()
{
short rtn;
rtn = GT_Open();
if(rtn != 0)
{
printf("固高控制卡打开失败,错误码:%d\n", rtn);
return 0;
}
GT_Reset();
Sleep(200);
/*
* 配置文件由固高调试软件生成,文件名按现场配置填写。
*/
GT_LoadConfig("GTS800.cfg");
GT_ClrSts(1, 5);
GT_AxisOn(AXIS_X);
GT_AxisOn(AXIS_Y);
GT_AxisOn(AXIS_Z);
GT_AxisOn(AXIS_FEED);
GT_AxisOn(AXIS_ROLLER);
LaserOff();
HeaterOff();
DriveRelayOn();
return 1;
}
/******************** 16. 主程序 ********************/
int main()
{
MachineState state = S_POWER_ON;
int currentLayer = 0;
int totalLayer = 10;
while(1)
{
switch(state)
{
case S_POWER_ON:
printf("S0:系统上电初始化。\n");
if(ControllerInit())
{
state = S_SELF_CHECK;
}
else
{
state = S_FAULT;
}
break;
case S_SELF_CHECK:
printf("S1:安全自检。\n");
if(!SafetyCheck())
{
state = S_EMERGENCY;
break;
}
state = S_LIMIT_HOME;
break;
case S_LIMIT_HOME:
printf("S2:五轴限位回零。\n");
if(!HomeAllAxisByLimit())
{
state = S_FAULT;
break;
}
MoveAbsMM(AXIS_ROLLER, R_WAIT_POS, 50.0);
if(!WaitAxisStop(AXIS_ROLLER))
{
state = S_FAULT;
break;
}
MoveXYWorkMM(XY_SAFE_WORK_X, XY_SAFE_WORK_Y, 100.0);
if(!WaitAxesStop((1 << (AXIS_X - 1)) | (1 << (AXIS_Y - 1))))
{
state = S_FAULT;
break;
}
state = S_PREHEAT;
break;
case S_PREHEAT:
printf("S3:热压滚筒预热。\n");
if(PreheatRoller())
{
state = S_LAYER_PREPARE;
}
else
{
state = S_EMERGENCY;
}
break;
case S_LAYER_PREPARE:
printf("S4:第 %d 层准备。\n", currentLayer + 1);
if(currentLayer >= totalLayer)
{
state = S_FINISH;
}
else
{
state = S_PAPER_FEED;
}
break;
case S_PAPER_FEED:
printf("S5:送料。\n");
if(PaperFeed())
{
state = S_HOT_PRESS;
}
else
{
state = S_FAULT;
}
break;
case S_HOT_PRESS:
printf("S6:热压滚筒滚压。\n");
if(HotPress())
{
state = S_LASER_CUT;
}
else
{
state = S_FAULT;
}
break;
case S_LASER_CUT:
printf("S7:激光切割。\n");
if(LaserCutCurrentLayer())
{
state = S_Z_DOWN;
}
else
{
state = S_FAULT;
}
break;
case S_Z_DOWN:
printf("S8:Z轴下降一个层厚。\n");
if(ZDownOneLayer())
{
state = S_NEXT_LAYER;
}
else
{
state = S_FAULT;
}
break;
case S_NEXT_LAYER:
printf("S9:进入下一层。\n");
currentLayer++;
state = S_LAYER_PREPARE;
break;
case S_FINISH:
printf("S10:加工完成。\n");
LaserOff();
HeaterOff();
DriveRelayOff();
GT_Stop(AXIS_MASK_5, 0);
GT_Close();
return 0;
case S_FAULT:
printf("SF:系统故障。\n");
EmergencyStop();
GT_Close();
return -1;
case S_EMERGENCY:
printf("SE:安全保护触发。\n");
EmergencyStop();
GT_Close();
return -1;
default:
state = S_FAULT;
break;
}
Sleep(50);
}
}
Discussion
Comments
Share questions, corrections, or extra notes about this post.