1. 背景
在高精度PZT扫描系统中,经常需要实现这样一种功能:
PZT按照固定速度运动,每移动固定距离触发一次相机采集,从而获得等间隔空间采样数据。
例如:
PZT扫描范围:100 μm
触发步长:0.076 μm
相机帧率:150 FPS
理想情况下,希望得到:
也就是说:
空间采样间隔应该由PZT位移决定,而不是由时间决定。
2. 传统触发方案的问题
在项目初期,实现PZT扫描触发通常有几种方式。
2.1 软件循环触发
最简单的方案是由上位机软件控制:
这种方案实现简单,但存在明显问题。
通信延迟
整个链路:
会受到:
Windows线程调度
程序执行时间
通信周期
SDK响应时间
影响。
因此很难保证触发位置严格对应实际空间位置。
高速扫描能力有限
当扫描速度提高后:
PZT移动速度增加
触发间隔缩短
软件判断周期不足
容易出现:
触发位置偏移
数据间距不均匀
丢触发
软件循环更适合低速定位场景,而不适合作为高精度PZT扫描触发方案。
2.2 外部硬件时间触发
为了改善软件触发的稳定性,项目中曾采用过一种硬件时间触发方案。
由硬件工程师设计了一块独立触发板:
触发板按照固定时间间隔输出脉冲。
每 6.67ms 输出一次Trigger
≈150 FPS
例如:
相比软件循环:
优势:
不依赖Windows系统调度
触发周期更加稳定
延迟更低
相机采集更加可靠
实际效果明显优于软件轮询。
但是这种方案仍然存在一个本质问题:
它控制的是时间间隔,而不是空间间隔。
例如:
PZT速度 = 10 μm/s
Trigger周期 = 10ms
理论采样间隔:
10 × 0.01 = 0.1 μm
但是实际运行过程中,如果PZT受到:
环境振动
机械共振
负载变化
微小速度波动
那么:
最终可能出现:
对于普通成像影响不大,但对于:
纳米级位移扫描
表面轮廓测量
共聚焦扫描
会导致:
点云比例失真
局部拉伸/压缩
测量误差增加
2.3 PI控制器位置触发
相比时间触发,PI PZT控制器提供了硬件级位置触发功能。
触发逻辑:
此时触发条件由:
时间
变成:
空间位置
例如设置:
Trigger Step = 0.076 μm
控制器自动:
0
0.076
0.152
0.228
...
每经过固定距离输出一次Trigger。
这种方式:
不依赖PC实时性
不受Windows调度影响
不受通信周期影响
触发点与PZT位置同步
对环境振动更加不敏感
因此对于高精度扫描系统:
最理想的触发方式不是固定时间触发,而是固定空间距离触发。
3. PI GCS触发功能简介
PI的GCS(General Command Set)中,Trigger Output主要由两个接口完成。
CTO:Configure Trigger Output
CTO用于配置触发规则。
可以设置:
Trigger模式
参考轴
触发步长
起始位置
结束位置
简单理解:
CTO负责告诉控制器什么时候产生Trigger。
TRO:Trigger Output
TRO用于开启或者关闭触发输出。
简单理解:
TRO负责打开Trigger输出开关。
完整流程:
4. PI位置触发模式
本文使用:
Position Distance
即:
每经过固定距离输出一次Trigger。
例如:
设置:
Trigger Step = 0.076 μm
控制器产生:
0
0.076
0.152
0.228
...
每到达一个位置点:
输出一次触发。
5. 触发参数说明
主要参数:
例如:
扫描:
Start = 10 μm
End = 20 μm
Step = 0.1 μm
控制器产生:
10.1
10.2
...
20.0
共100次触发。
6. SDK调用流程
6.1 设置触发模式
PI.GCS2.CTO(
handle,
new int[] { Digital_OUT_1 },
new int[] { P_TriggerMode },
new double[] { PositionDistance },
1);
6.2 设置触发轴
PI.GCS2.CTOString(
handle,
new int[] { Digital_OUT_1 },
new int[] { P_Axis },
"1",
1);
6.3 设置触发间隔
例如:
TriggerStep = 0.076;
表示:
每移动:
0.076 μm
输出一次触发。
6.4 设置触发范围
例如:
Start = 0
End = 100
对应:
StartThreshold = 0
StopThreshold = 100
控制器只在该范围内输出Trigger。
6.5 开启触发
PI.GCS2.TRO(
handle,
new int[]{Digital_OUT_1},
new int[]{1},
1);
7. 实际项目代码
以下代码用于配置PI PZT位置触发。
功能:
设置触发步长
设置扫描范围
开启/关闭Trigger Output
public static int Digital_OUT_1 = 1;
public static int Digital_OUT_2 = 2;
public static int Digital_OUT_3 = 3;
public enum PztTriggerMode
{
PositionDistance = 0,
OnTarget = 2,
MinMaxThreshold = 3,
GeneratorTrigger = 4,
InMotion = 6
};
public enum PztTriggerParameter
{
P_TriggerStep = 1,
P_Axis = 2,
P_TriggerMode = 3,
P_MinThreshold = 5,
P_MaxThreshold = 6,
P_StartThreshold = 8,
P_StopThreshold = 9
};
/// <summary>
/// 配置触发输出
/// </summary>
/// <param name="step">步长</param>
/// <param name="start">起始位置</param>
/// <param name="end">结束位置</param>
/// <param name="isenable">是否启用</param>
public void SetTriOutConfigure(
float step,
double start,
double end,
bool isenable)
{
if (!isenable)
{
// 设置Trigger模式关闭
double[] CTOparamsoff =
{
(double)PztTriggerMode.InMotion
};
int result =
PI.GCS2.CTO(
handle,
new int[] { Digital_OUT_1 },
new int[] {
(int)PztTriggerParameter.P_TriggerMode
},
CTOparamsoff,
1);
if(result != 1)
{
LogHelper.Debug(
"PI_CTO error : PositionDistance");
return;
}
// 设置触发轴
result =
PI.GCS2.CTOString(
handle,
new int[] {Digital_OUT_1},
new int[]{
(int)PztTriggerParameter.P_Axis
},
axes,
1);
if(result != 1)
{
LogHelper.Debug(
"PI_CTOString error : Axis");
return;
}
// 关闭Trigger输出
result =
PI.GCS2.TRO(
handle,
new int[]{Digital_OUT_1},
new int[]{0},
1);
if(result != 1)
{
LogHelper.Debug(
"PI_TRO error : disable");
}
return;
}
// ==========================
// Enable Trigger
// ==========================
// 1. Position Distance模式
double[] CTOparams =
{
(double)PztTriggerMode.PositionDistance
};
int isok =
PI.GCS2.CTO(
handle,
new int[]{Digital_OUT_1},
new int[]{
(int)PztTriggerParameter.P_TriggerMode
},
CTOparams,
1);
if(isok != 1)
{
LogHelper.Debug(
"PI_CTO error : PositionDistance");
return;
}
// 2. 设置触发轴
isok =
PI.GCS2.CTOString(
handle,
new int[]{Digital_OUT_1},
new int[]{
(int)PztTriggerParameter.P_Axis
},
"1",
1);
if(isok != 1)
{
LogHelper.Debug(
"PI_CTOString error : Axis");
return;
}
// 3. 设置Trigger Step
isok =
PI.GCS2.CTO(
handle,
new int[]{Digital_OUT_1},
new int[]{
(int)PztTriggerParameter.P_TriggerStep
},
new double[]{step},
1);
if(isok != 1)
{
LogHelper.Debug(
"PI_CTO error : TriggerStep");
return;
}
// 4. 设置触发范围
isok =
PI.GCS2.CTO(
handle,
new int[]{Digital_OUT_1},
new int[]{
(int)PztTriggerParameter.P_StartThreshold
},
new double[]{start},
1);
if(isok != 1)
return;
isok =
PI.GCS2.CTO(
handle,
new int[]{Digital_OUT_1},
new int[]{
(int)PztTriggerParameter.P_StopThreshold
},
new double[]{end},
1);
if(isok != 1)
return;
// 5. 开启Trigger Output
isok =
PI.GCS2.TRO(
handle,
new int[]{Digital_OUT_1},
new int[]{1},
1);
if(isok != 1)
{
LogHelper.Debug(
"PI_TRO error : enable");
}
}8. 扫描时序建议
推荐流程:
注意:
不要在PZT加速阶段开始采集。
更推荐:
也就是说在触发范围前后各留一段距离。
这样可以避免扫描前后数据间距异常。
9. 速度与相机帧率匹配
Trigger步长决定空间采样率。
计算公式:
PZT速度 = TriggerStep × CameraFPS
例如:
步长:
0.076 μm
相机:
150 FPS
理论速度:
0.076 × 150 = 11.4 μm/s
实际工程中需要留出余量。
推荐:
9~10 μm/s
避免:
相机丢帧
Trigger堆积
PZT速度波动造成采样误差
实际相机的极限帧率应当根据测试情况来决定。
10. 总结
PI PZT硬件触发核心只有两个接口:
对于高精度扫描:
相比:
软件轮询触发
固定时间硬件触发
位置触发能够保证:
更高同步精度
更稳定空间采样间隔
更低系统延迟
适用于:
共聚焦扫描
白光干涉
表面轮廓测量
PZT纳米级位移扫描
注:
本文中的CTO参数编号、Trigger模式枚举等内容与具体PI控制器型号有关,实际开发时需要参考对应控制器手册。
评论