基于CAPL语法实现对程控电源的控制(通用DLL版,支持TCP协议,RS232协议,SCPI指令)
- 🍅 我是蚂蚁小兵,专注于车载诊断领域,尤其擅长于对CANoe工具的使用
- 🍅 寻找组织 ,答疑解惑,摸鱼聊天,博客源码,点击加入👉【相亲相爱一家人】
- 🍅 玩转CANoe,博客目录大全,点击跳转👉
目录
- 📙 SCPI协议
- 📙 RS232协议发送SCPI指令的场景
- 📙 RS232协议发送Hex数组指令的场景
- 📙 TCP协议发送SCPI指令的场景
- 📙 源码获取
🍅 背景:CAPL语言是支持远程控制硬件设备的,但是语法结构在我看来不够简洁,特别是获取数据需要在
RS232OnReceive
回调函数中获取十分不方便,而且台架复杂的情况下,可能需要多台程控设备,
CAPL
脚本设计起来就显得非常杂乱。基于这种背景下,我决定基于
C++
封装一个通用的脚本实现对设备的控制
📙 SCPI协议
- SCPI(Standard Commands for Programmable Instruments)即可编程仪器标准命令,是一种为了控制测试和测量仪器而设计的标准化命令语言。以下是SCPI的一些基础知识:
- 如果您要控制的仪器设备支持SCPI协议,那么您可以在设备的技术文档中或者官网中找到该设备的编程技术文档。
1. 起源与目的
SCPI起源于20世纪80年代,由惠普、泰克等公司共同开发。其目的是为了在不同厂商生产的仪器之间提供一种统一的、标准化的通信接口,方便用户对仪器进行编程控制。
2. 语法结构
SCPI命令通常由多个部分组成,遵循一定的层次结构,一般格式为:
<header>[<subheader>][:<subheader>...] <parameter>
- 头(Header):表示仪器的主要功能模块,如 SYST 代表系统功能,MEAS 代表测量功能。
- 子头(Subheader):进一步细化功能,例如 SYST:REM 表示进入远程控制模式。
- 参数(Parameter):为命令提供具体的值,如 VOLT 10.0 中的 10.0 就是设置电压的值
3. 命令类型
查询命令:以问号 ? 结尾,用于从仪器获取信息。例如 *IDN? 用于查询仪器的标识信息。
设置命令:用于向仪器发送指令并设置参数。例如 OUTP ON 用于打开仪器输出。
4. 通用命令
SCPI定义了一些通用命令,以星号 * 开头,适用于大多数仪器。常见的通用命令有:
*IDN?:查询仪器的标识信息,返回制造商、型号、序列号和固件版本等。
*RST:将仪器恢复到默认设置。
*CLS:清除仪器的状态寄存器。
*OPC?:查询操作是否完成,完成后返回 1。
5. 通信协议
SCPI本身并不指定通信协议,它可以通过多种通信接口实现,如GPIB(General Purpose Interface Bus)、RS - 232、USB、LAN(以太网)等。在实际应用中,需要根据仪器支持的接口选择合适的通信方式。
📙 RS232协议发送SCPI指令的场景
seial_scpi.dll文件中有4个函数,实现对支持串口协议的设备的远程控制:
- serial_connect:连接串口设备
- serial_disconnect:断开连接串口设备
- serial_command:发送SCPI指令,无需返回值
- serial_query:发送SCPI指令,并获取返回值
完整CAPL代码如下,不仅没有冗余复杂的CAPL结构代码, 基本上一条指令就实现了一个功能,最重要的是支持多台设备同时连接。
/*@!Encoding:936*/
includes
{
//注意这里是X86格式的DLL
#pragma library("serial_scpi.dll")
}
void MainTest ()
{
int device1 = 1;
int device2 = 2;
char response[1024];//必须是1024
long ret;
double setVoltage;
char command[1024];
//连接串口设备
ret = serial_connect(device1,"COM36",9600);
write("连接串口设备 1, ret = %d",ret);
ret = serial_connect(device2,"COM25",9600);
write("连接串口设备 2, ret = %d",ret);
//进入远程控制
ret = serial_command(device1, "SYST:REM");
write("设备 1 远程控制锁定,ret = %d",ret);
ret = serial_command(device2, "SYST:REM");;
write("设备 2 远程控制锁定, ret = %d",ret);
//返回设备信息
ret = serial_query(device1,"*IDN?",response);
write("设备 1 信息读取, ret = %d,response = %s",ret,response);
ret = serial_query(device2,"*IDN?",response);
write("设备 2 信息读取, ret = %d,response = %s",ret,response);
testWaitForTimeout(1000);
//开启输出
ret = serial_command(device1,"OUTP ON");
write( "设备 1 打开输出,ret = %d",ret);
ret = serial_command(device2, "OUTP ON");
write( "设备 2 打开输出ret = %d",ret);
testWaitForTimeout(1000);
//设置电压
setVoltage = random(10)+10;
snprintf(command,elcount(command),"VOLT %.2f",setVoltage);
ret = serial_command(device1,command);
write( "设备 1 设置电压到%.2f V,ret = %d",setVoltage,ret);
ret = serial_command(device2, command);
write( "设备 2 设置电压到%.2f V,ret = %d",setVoltage,ret);
testWaitForTimeout(1000);
//读取电压
ret = serial_query(device1, "MEAS:VOLT?", response);
write( "设备 1 读取电压,ret = %d , response = %s",ret,response );
ret = serial_query(device2, "MEAS:VOLT?", response);
write( "设备 2 读取电压,ret = %d , response = %s",ret,response );
//读取电流
ret = serial_query(device1, "MEAS:CURR?", response);
write( "设备 1 读取电流,ret = %d , response = %s",ret,response );
ret = serial_query(device2, "MEAS:CURR?", response);
write( "设备 2 读取电流,ret = %d , response = %s",ret,response );
//关闭输出
ret = serial_command(device1,"OUTP OFF");
write( "设备 1 输出关闭,ret = %d",ret);
ret = serial_command(device2, "OUTP OFF");
write( "设备 2 输出关闭,ret = %d",ret);
testWaitForTimeout(100);
//退出远程控制
ret = serial_command(device1,"SYST:LOC");
write( "设备 1 退出远程控制,ret = %d",ret);
ret = serial_command(device2, "SYST:LOC");
write( "设备 2 退出远程控制,ret = %d",ret);
testWaitForTimeout(100);
//断开串口连接
ret = serial_disconnect(device1);
write( "设备 1 断开连接,ret = %d",ret);
ret = serial_disconnect(device2);
write( "设备 2 断开连接,ret = %d",ret);
testWaitForTimeout(100);
}
下图是上面示例脚本的测试结果,函数返回值为1说明,指令执行成功,0说明指令执行失败。由于函数返回值都是字符串,在实际读取电压电流参数时还需要进一步将字符串转为数值类型等。
Program / Model 连接串口设备 1, ret = 1
Program / Model 连接串口设备 2, ret = 1
Program / Model 设备 1 远程控制锁定,ret = 1
Program / Model 设备 2 远程控制锁定, ret = 1
Program / Model 设备 1 信息读取, ret = 1,response = ITECH Electronics,IT6832A, 802035043787510120, 1.07-1.02
Program / Model 设备 2 信息读取, ret = 1,response = ITECH Electronics,IT6832A, 802035043797470019, 1.07-1.02
Program / Model 设备 1 打开输出,ret = 1
Program / Model 设备 2 打开输出ret = 1
Program / Model 设备 1 设置电压到14.00 V,ret = 1
Program / Model 设备 2 设置电压到14.00 V,ret = 1
Program / Model 设备 1 读取电压,ret = 1 , response = 13.998
Program / Model 设备 2 读取电压,ret = 1 , response = 13.998
Program / Model 设备 1 读取电流,ret = 1 , response = 0.0002
Program / Model 设备 2 读取电流,ret = 1 , response = 0
Program / Model 设备 1 输出关闭,ret = 1
Program / Model 设备 2 输出关闭,ret = 1
Program / Model 设备 1 退出远程控制,ret = 1
Program / Model 设备 2 退出远程控制,ret = 1
Program / Model 设备 1 断开连接,ret = 1
Program / Model 设备 2 断开连接,ret = 1
📙 RS232协议发送Hex数组指令的场景
因为有些设备不支持SCPI指令集,使用的是自定义的指令,所以我决定在**seial_scpi.dll** 文件中再增加如下3个函数,支持发送Hex数组指令:
- serial_connect:连接串口设备
- serial_disconnect:断开连接串口设备
- serial_command:发送SCPI指令,无需返回值
- serial_query:发送SCPI指令,并获取返回值
完整CAPL代码如下,不仅没有冗余复杂的CAPL结构代码, 基本上一条指令就实现了一个功能,**最重要的是支持多台设备同时连接。**
/*@!Encoding:936*/
includes
{
//注意这里是X86格式的DLL
#pragma library("serial_scpi.dll")
}
void MainTest ()
{
int device1 = 1;
byte response[1024];
long i,ret;
double setVoltage;
//连接串口设备
ret = serial_connect(device1,"COM37",9600);
write("连接串口设备 1, ret = %d",ret);
testWaitForTimeout(1000);
//开启输出
ret = serial_binary_command(device1,OpenPower,elCount(OpenPower));
write( "设备 1 打开输出,ret = %d",ret);
testWaitForTimeout(1000);
//设置电压
{
word crcData;
long setValue;
setVoltage = random(10)+10;
setValue = setVoltage*100;
SetVlotage[4] = setValue>>8;
SetVlotage[5] = setValue&0xff;
crcData = crc16(SetVlotage,6);
SetVlotage[6] = crcData&0xff;
SetVlotage[7] = crcData>>8;
ret = serial_binary_command(device1,SetVlotage,elCount(SetVlotage));
write( "设备 1 设置电压到%.2f V,ret = %d",setVoltage,ret);
testWaitForTimeout(1000);
}
ret = serial_clear_receive_buffer(device1);
write( "设备 1 清空接受缓存,ret = %d",ret);
//读取电压
ret = serial_binary_query(device1, ReadVlotage,elCount(ReadVlotage), response,elcount(response));
for(i = 0;i<ret;i++)
write("response[%d] = 0x%x",i,response[i]);
write( "设备 1 读取电压 = %.2f,ret = %d ",(response[3]<<8 ||response[4])/100.0,ret);
//关闭输出
ret = serial_binary_command(device1,ClosePower,elCount(ClosePower));
write( "设备 1 关闭输出,ret = %d",ret);
testWaitForTimeout(1000);
//断开串口连接
ret = serial_disconnect(device1);
write( "设备 1 断开连接,ret = %d",ret);
}
下图是上面示例脚本的测试结果,函数返回值为1说明,指令执行成功,0说明指令执行失败。由于函数返回值都是字符串,在实际读取电压电流参数时还需要进一步将字符串转为数值类型等。
Program / Model 连接串口设备 1, ret = 1
Program / Model 设备 1 打开输出,ret = 1
Program / Model 设备 1 设置电压到13.00 V,ret = 1
Program / Model 设备 1 清空接受缓存,ret = 1
Program / Model response[0] = 0x1
Program / Model response[1] = 0x3
Program / Model response[2] = 0x2
Program / Model response[3] = 0x5
Program / Model response[4] = 0x14
Program / Model response[5] = 0xbb
Program / Model response[6] = 0x1b
Program / Model 设备 1 读取电压 = 13.00,ret = 7
Program / Model 设备 1 关闭输出,ret = 1
Program / Model 设备 1 断开连接,ret = 1
📙 TCP协议发送SCPI指令的场景
下面是在CAPL中应用的Demo,tcp_scpi.dll文件中有4个函数:
- tcp_connect:连接以太网控制协议的设备
- tcp_disconnect:断开设备连接
- tcp_command:发送SCPI指令,无需返回值
- tcp_query:发送SCPI指令,并获取返回值
完整CAPL代码如下
- TCP协议设备,需要设置好设备的IP地址和端口,同时要设置好电脑或者拓展坞的IP地址,并保证和仪器设备在同一网段中,
仪器设备的IP地址:
本机的IP地址如下,和仪器设备的IP地址同属于一个网段。
- 完整的示例代码如下,tcp_scpi.dll同样支持多个设备同时连接(示例中仅仅连接单台设备)
/*@!Encoding:936*/
includes
{
//注意这里是X86格式的DLL
#pragma library("..\..\tcp_scpi_demo_vs2022\Debug\tcp_scpi.dll")
}
void MainTest ()
{
char EA_IP[20] = "172.20.1.101";
long EA_Port = 5025;
char response[1024];
long ret,deviceID = 1;
double setVoltage;
char command[1024];
ret= tcp_connect(EA_IP,EA_Port);
if(ret>0)
{
write("TCP连接成功, ret= %d",ret);
}
else
{
write("TCP连接失败, ret= %d",ret);
return;
}
ret = tcp_query(deviceID,"*IDN?",response);
write("ret = %d,设备信息 = %s",ret,response);
ret = tcp_command(deviceID,"SYSTem:LOCK ON");
write("远程控制锁定 ret = %d",ret);
testWaitForTimeout(1000);
ret = tcp_command(deviceID,"OUTP ON");
write("打开输出 ret = %d",ret);
setVoltage = random(10)+10;
snprintf(command,elcount(command),"VOLT %f",setVoltage);
ret = tcp_command(deviceID,command);
write("设置电压 = %f ,ret = %d",setVoltage,ret);
testWaitForTimeout(1000);
ret = tcp_query(deviceID,"MEAS:VOLT?", response);
write("读取电压 ret = %d, response = %s",ret,response);
ret = tcp_query(deviceID,"MEAS:CURR?", response);
write("读取电流 ret = %d, response = %s",ret,response);
ret = tcp_command(deviceID,"OUTP OFF");
write("关闭输出 ret = %d",ret);
ret = tcp_command(deviceID,"SYSTem:LOCK OFF");
write("退出远程锁定 ret = %d",ret);
ret = tcp_disconnect(deviceID );
write("断开TCP连接 ,ret = %d",ret);
}
下图是上面示例脚本的测试结果,函数返回值为1说明,指令执行成功,0说明指令执行失败。由于函数返回值都是字符串,在实际读取电压电流参数时还需要进一步将字符串转为数值类型等。
Program / Model TCP连接成功, deviceID = 2
Program / Model ret = 1,设备信息 = EA Elektro-Automatik GmbH & Co. KG, PS 9080-100, 2934691079, V2.05 04.03.2020 V3.07 06.08.2019 V1.0.22,
Program / Model 远程控制锁定 ret = 1
Program / Model 打开输出 ret = 1
Program / Model 设置电压 = 11.00 ,ret = 1
Program / Model 读取电压 ret = 1, response = 11.07 V
Program / Model 读取电流 ret = 1, response = 0.0 A
Program / Model 关闭输出 ret = 1
Program / Model 退出远程锁定 ret = 1
Program / Model 断开TCP连接 ,ret = 1
📙 源码获取
博主肝了几个通宵才得以解决在CAPL脚本中仪器控制的棘手的问题,希望对您有帮助!
- 基于CAPL语法规则生成的支持TCP协议控制仪器设备的DLL (源码和示例工程)
- 基于CAPL语法规则生成的支持RS232协议控制仪器设备的DLL(源码和示例工程)
- 基于CAPL语法规则生成的支持RS232和TCP协议控制仪器设备的DLL (源码和示例工程)
- 🚩要有最朴素的生活,最遥远的梦想,即使明天天寒地冻,路遥马亡!
- 🚩如果这篇博客对你有帮助,请 “点赞” “评论”“收藏”一键三连 哦!码字不易,大家的支持就是我坚持下去的动力。