一、前言
毕业一年,辗转在各电子厂之间,现在在一家主做手柄的智能控制公司,刚入职经理让尝试下个人编写吉时利2306模拟电源的控制与判断电流值的软件,只给了一个设备让我从零开发,本人很感兴趣,于是接下这一任务并从百度识图到NI-VISA官网查看C语言示例终于被我摸索出来了,以下是本人在摸索中的重点,希望能帮到各位与我一样的本科电子信息工程毕业月薪3k、梦想是全栈工程师的核动力牛马们
二、项目需求
写项目是离不开需求的,以下是暂时完成了的需求,仅供参考
1.Windows电脑与K2306(吉时利2306,以下略为K2306)通信并控制;
2.K2306返回目前电流数值给Windows电脑;
3.Windows电脑判断电流值是否在标准内;
4.需要一个Windows程序界面,并且需要输入16位SN码与点击按钮
5.各测试项的电流数值与PASS/FAIL的显示
6.需要从“./”的绝对路径中的Set.txt文件内读到测试设定的标准值
7.需要在"./Log"的绝对路径文件夹内生产命名为“now年-now月-now日 XX机型电流测试.xls”的XLS表格文件
8.Log文件内需要自动生成测试序号,测试数据,测试时间等等
三、前期开发准备
1.找到设备的可编程接口(一般在设备的手册内有写),一般为GPIB通信协议
2.购买对应的接口连接线,K2306使用GPIB-USB采集卡
3.找到使用的编程语言对应接口的库,K2306推荐VISA库:下载NI-488.2 - NI
4.查看设备控制语言(目前使用SCPI命令为通用命令):通用SCPI命令与详细
5.查看常用的VISA库函数:常用VISA库函数
四、正式开发
1.使用Visual Studio 2022编写
2.使用EasyX图形库
①.确认设备正确连接
(1)设备与电脑连接
右键window图标打开设备管理器,其中GPIB-USB-HS图标显示为即为正常连接,打开NI-MAX软件并找到你的设备与其主地址
(2)设备与被测电路连接
ps:Source与Sense的正负极是必接的,DVM的正负极为充电电路,可以并联到Source与Sense一起当作恒压电池使用
②.代码编写
1.连接visa库并连接其静态库
(1)在VS2022中点击上方的“项目”—“属性”—“VC++目录”—“包含目录”中添加:“C:\Program Files (x86)\IVI Foundation\VISA\WinNT\Include”
(2)在代码上方添加预编译指令:
#pragma comment(lib, "C://Program Files (x86)//IVI Foundation//VISA//WinNT//Lib_x64//msc//visa64.lib")
(ps:我使用DEBUG X64编译所以使用visa64的静态库,32位可连接visa32)
2. 使用VISA库函数连接设备
(1)定义连接设备函数
ViSession DefaultRM;//定义缺省设备资源
ViSession power_supply;//定义缺省设备资源
long ErrorStatus;//接受VISA函数的返回值,语句输出成功返回VI_SUCCESS
(2)打开VISA默认资源管理器
ErrorStatus=viOpenDefaultRM(&DefaultRM);
(3) 输入设备主地址连接
ErrorStatus=viOpen(DefaultRM, "GPIB0::1::INSTR", 0, 0, &power_supply);//中间的1为设备的子地址,可在设备上更改
(4) 输出SCPI命令并接受返回语句
ErrorStatus = viQueryf(power_supply, "*IDN?\n", "%100c", IDN);
viQuery(vi,writeFmt,readFmt,arg1,arg2…)按设定格式对器件进行数据读写。
这里设定%100c读取格式是为方便读取其中的\s \t等格式
*IDN? 为通用SCPI命令,意思是询问设备详细信息
(5)连接设备详细代码(自编写可能不太严谨)
void OpenPort()
{
char VISA_address[40];
char IDN[1024] = "";
char SetVoltage[20];
char Voltage_address[40];
if (bGPIB)//定义了int bGPIB=1防止越界
{
strcpy(VISA_address, "GPIB0::");
strcat(VISA_address, GPIB_address);//这里在后面编写了Fopen读取txt内参数储存
strcat(VISA_address, "::INSTR");
ErrorStatus = viOpenDefaultRM(&DefaultRM);
CheckError("viOpenDefaultRM Failed!");
ErrorStatus = viOpen(DefaultRM, VISA_address, 0, 0, &power_supply);
CheckError("Unable to OpenPort!");
if (ErrorStatus != VI_SUCCESS)
{
return;
}
ErrorStatus = viQueryf(power_supply, "*IDN?\n", "%100c", IDN);
CheckError("Unable to Link!");
if (ErrorStatus == VI_SUCCESS)
{
set_Device();//自写设置设备具体数值的函数
}
if (!bGPIB)
viPrintf(power_supply, "System:Remote");
}
//printf("VISA_address:%s\n",VISA_address);
}
// 2024-8-1 12:00编写,后续会更新,先写到这里
(6) 检查ERROR的自定义函数CheckError()的详细代码
void CheckError(const char* pMassage)
{
if (ErrorStatus != VI_SUCCESS)
{
MessageBox(GetHWnd(), pMassage, "错误!", MB_OK);//弹一个确认按钮的提示窗口
}
}//用的EasyX图形库
3.SCPI命令设置设备
(1) 输出命令前的注意事项
!!!重要的事情说三遍: 输出命令后面一定要带‘\n’
!!!重要的事情说三遍: 输出命令后面一定要带‘\n’
!!!重要的事情说三遍: 输出命令后面一定要带‘\n’
(2)设置设备通道输出电压值
首先,SCPI设置设备电压的命令为SOUR1:VOLT 3.5\n (其中1为通道口,切换2通道即为SOUR2:VOLT 3.5\n,特别注意VOLT与数值中间有空格!)
而后,VISA库的命令输出函数为viPrintf(vi,arg1)
因此设置电压命令的代码:
ErrorStatus = viPrintf(power_supply, "SOUR1:VOLT 1.25\n");
power_supply 在 2.(1)已经定义过了
(3)设置设备通道输出电流最大限值
与上方电压值设置大同小异,SCPI设置设备电流的命令为SOUR1:CURR 1.5\n (其中1为通道口,切换2通道即为SOUR2:CURR 1.5\n,特别注意CURR与数值中间有空格!)
因此设置电流命令的代码:
ErrorStatus = viPrintf(power_supply, "SOUR1:CURR 1.5\n");
power_supply 在 2.(1)已经定义过了
(4)设置设备的模块化详细代码(自写不严谨)
void set_Device()
{
char SetVoltageA[20]="";
char SetVoltageB[20] = "";
char VoltageA_address[40]="";
char VoltageB_address[40] = "";
strcpy(SetVoltageA, txtVolt.Text4());//此处由后面fopen获取的电流范围值设置
strcpy(SetVoltageB, txtVolt.Text2());//此处由后面fopen获取的电流范围值设置
strcpy(VoltageA_address, "SOUR1:VOLT ");
strcat(VoltageA_address, SetVoltageA);
strcat(VoltageA_address, "\n");
strcpy(VoltageB_address, "SOUR2:VOLT ");
strcat(VoltageB_address, SetVoltageB);
strcat(VoltageB_address, "\n");
ErrorStatus = viPrintf(power_supply, VoltageA_address);
ErrorStatus = viPrintf(power_supply, "SOUR1:CURR 1.25\n");
ErrorStatus = viPrintf(power_supply, VoltageB_address);
ErrorStatus = viPrintf(power_supply, "SOUR2:CURR 1.25\n");
CheckError("仪器设置失败");
}
PS:我在这里设置电流最大值为1.25是因为通常开机需要一个强电流,如果后续连接设备电源发现板子不开机可能会是这里的电流限制了
4.使用SCPI命令询问设备返回当前电流值
询问电流的SCPI命令为:MEAS2:CURR?\n(其中2为通道,修改为1即切换到通道1询问)
询问设备当前通道电流的代码:
double current=0;
ErrorStatus = viQueryf(power_supply, "MEAS2:CURR?\n", "%lf", ¤t);
// 2024-8-3 10:00编写,其实到这里关于K2306的连接、输出及询问已经写完了,可以根据自己需要编写其中的逻辑了,后续还会更新通过类传参避免出现全局变量、如何通过fopen和fclose读取txt文件中的范围值并传参入代码、如何创建以当天日期生成LOG文件的xls格式并自动输出每一个的测试序号,测试数据,测试时间等等
//后续会更新
5.显示界面与交互编程
(1)确定所使用的图形库并下载
我所使用图形库为EasyX:下载EasyX
下载后直接运行点击安装就行了,需要先安装好Visual Studio
(现在在学习WINAPI的图形界面编程,但是我推荐如果不是和我一样只喜欢C语言的话还是用JAVA或者使用QT的C++图形库会更加便捷)
(2)EasyX界面编写
*1* 添加EasyX图形库头文件
#include<easy.h>
*2* 创建窗口界面
EasyX的创建窗口界面分为三部曲:
1设置窗口大小: HWND initgraph(int width, int height, int flag = 0);//width为长,height为宽,flag=0则不显示cmd命令行窗口,为=1则显示
2清屏显示:void cleardevice();
3关闭窗口:void closegraph();
详细代码为:
#include<stdio.h>
#include<easyx.h>
int main()
{
initgraph(800, 600);//设置窗口大小
setbkcolor(WHITE);//设置窗口背景颜色
cleardevice();//清屏
system("pause");//需要暂停函数以便显示观察,不然则窗口一闪而过关闭
closegraph();//关闭窗口
}
*3* 封装图形库的按钮及文本框
这一部分是核心内容,要讲的比较多,后面我会专门写一个文章讲这里,目前暂时先放上整个的详细代码略过
输入文本框详细代码:
class EasyTextBox
{
private:
int left = 0, top = 0, right = 0, bottom = 0; // 控件坐标
TCHAR* text = NULL; // 控件内容
size_t maxlen = 0; // 文本框最大内容长度
public:
void Create(int x1, int y1, int x2, int y2, int max)
{
maxlen = max;
text = new TCHAR[maxlen];
text[0] = 0;
left = x1, top = y1, right = x2, bottom = y2;
// 绘制用户界面
Show();
}
~EasyTextBox()
{
if (text != NULL)
delete[] text;
}
TCHAR* Text()
{
return text;
}
/*char* Text2()
{
return text2;
}
char* Text3()
{
return text3;
}
char* Text4()
{
return text4;
}*/
bool Check(int x, int y)
{
return (left <= x && x <= right && top <= y && y <= bottom);
}
// 绘制界面
void Show()
{
// 备份环境值
int oldlinecolor = getlinecolor();
int oldbkcolor = getbkcolor();
int oldfillcolor = getfillcolor();
setlinecolor(LIGHTGRAY); // 设置画线颜色
setbkcolor(0xeeeeee); // 设置背景颜色
setfillcolor(0xeeeeee); // 设置填充颜色
fillrectangle(left, top, right, bottom);
outtextxy(left + 10, top + 5, text);
/*outtextxy(left + (right - left - textwidth(text) + 1) / 2, top + (bottom - top - textheight(text) + 1) / 2, text);*/
// 恢复环境值
setlinecolor(oldlinecolor);
setbkcolor(oldbkcolor);
setfillcolor(oldfillcolor);
}
void GetMessage1(int max,char *str)
{
maxlen = max;
text = new TCHAR[maxlen];
strcpy(text, str);
}
void ClearMessage(int max)
{
maxlen = max;
text[maxlen] = NULL;
size_t len = _tcslen(text);
int width = textwidth(text);
for(;len<=maxlen&&len>0;len--)
{
text[len - 1] = 0;
width = textwidth(text);
clearrectangle(left + 10 + width, top + 1, right - 1, bottom - 1);
}
Show();
}
int TXT_MAXreturn()
{
size_t len = _tcslen(text);
if (len == maxlen - 1)
return 1;
else
return 0;
}
void Result(int input)
{
success +=input;
}
int Result_get()
{
return success;
}
void Clear_Result()
{
success =0;
}
void OnMessage()
{
// 备份环境值
int oldlinecolor = getlinecolor();
int oldbkcolor = getbkcolor();
int oldfillcolor = getfillcolor();
setlinecolor(BLACK); // 设置画线颜色
setbkcolor(WHITE); // 设置背景颜色
setfillcolor(WHITE); // 设置填充颜色
fillrectangle(left, top, right, bottom);
outtextxy(left + 10, top + 5, text);
int width = textwidth(text); // 字符串总宽度
int counter = 0; // 光标闪烁计数器
bool binput = true; // 是否输入中
ExMessage msg;
while (binput)
{
while (binput && peekmessage(&msg, EX_MOUSE | EX_CHAR, false)) // 获取消息,但不从消息队列拿出
{
if (msg.message == WM_LBUTTONDOWN)
{
// 如果鼠标点击文本框外面,结束文本输入
if (msg.x < left || msg.x > right || msg.y < top || msg.y > bottom)
{
binput = false;
break;
}
}
else if (msg.message == WM_CHAR)
{
size_t len = _tcslen(text);
switch (msg.ch)
{
case '\b': // 用户按退格键,删掉一个字符
if (len > 0)
{
text[len - 1] = 0;
width = textwidth(text);
counter = 0;
clearrectangle(left + 10 + width, top + 1, right - 1, bottom - 1);
}
break;
case '\r': // 用户按回车键,结束文本输入
case '\n':
binput = false;
break;
default: // 用户按其它键,接受文本输入
if (len < maxlen - 1)
{
text[len++] = msg.ch;
text[len] = 0;
clearrectangle(left + 10 + width + 1, top + 3, left + 10 + width + 1, bottom - 3); // 清除画的光标
width = textwidth(text); // 重新计算文本框宽度
counter = 0;
outtextxy(left + 10, top + 5, text); // 输出新的字符串
}
}
}
peekmessage(NULL, EX_MOUSE | EX_CHAR); // 从消息队列抛弃刚刚处理过的一个消息
}
// 绘制光标(光标闪烁周期为 20ms * 32)
counter = (counter + 1) % 32;
if (counter < 16)
line(left + 10 + width + 1, top + 3, left + 10 + width + 1, bottom - 3); // 画光标
else
clearrectangle(left + 10 + width + 1, top + 3, left + 10 + width + 1, bottom - 3); // 擦光标
// 延时 20ms
Sleep(20);
}
clearrectangle(left + 10 + width + 1, top + 3, left + 10 + width + 1, bottom - 3); // 擦光标
// 恢复环境值
setlinecolor(oldlinecolor);
setbkcolor(oldbkcolor);
setfillcolor(oldfillcolor);
Show();
}
};//文本框控件
按钮封装详细代码:
class EasyButton
{
private:
int left = 0, top = 0, right = 0, bottom = 0; // 控件坐标
TCHAR* text = NULL; // 控件内容
void (*userfunc)() = NULL; // 控件消息
//float scale=1.0f; //缩放比例
bool checkMouse; //是否在按钮上方
public:
void Create(int x1, int y1, int x2, int y2, const TCHAR* title, void (*func)())
{
text = new TCHAR[_tcslen(title) + 1];
_tcscpy(text, title);
left = x1, top = y1, right = x2, bottom = y2;
userfunc = func;
// 绘制用户界面
Show();
}
~EasyButton()
{
if (text != NULL)
delete[] text;
}
bool Check(int x, int y)
{
/*checkMouse= (left <= x && x <= right && top <= y && y <= bottom);
Show();*/
/*checkMouse = (left <= x && x <= right && top <= y && y <= bottom);*/
return (left <= x && x <= right && top <= y && y <= bottom);
}
// 绘制界面
void Show()
{
int oldlinecolor = getlinecolor();
int oldbkcolor = getbkcolor();
int oldfillcolor = getfillcolor();
// setlinecolor(BLACK); // 设置画线颜色
//setbkcolor(WHITE); // 设置背景颜色
//setfillcolor(BLACK); // 设置填充颜色
// setlinestyle(PS_SOLID, 2);
// setfillcolor(WHITE);
if (checkMouse ==true)
{
setlinecolor(RGB(0, 120, 215));
setfillcolor(RGB(229, 241, 251));
//scale = 0.8f;
}
else
{
setlinecolor(RGB(173, 173, 173));
setfillcolor(RGB(225, 225, 225));
//scale = 1.0f;
}
fillrectangle(left, top, right, bottom);
settextcolor(BLACK);
setbkmode(TRANSPARENT);
outtextxy(left + (right - left - textwidth(text) + 1) / 2, top + (bottom - top - textheight(text) + 1) / 2, text);
setlinecolor(oldlinecolor);
setbkcolor(oldbkcolor);
setfillcolor(oldfillcolor);
}
void OnMessage()//双运行实现点击效果
{
checkMouse = true;
Show();
Sleep(20);
if (userfunc != NULL)
userfunc();
checkMouse = false;
Show();
}
int ExitMessage()
{
checkMouse = true;
Show();
Sleep(20);
if (userfunc != NULL)
userfunc();
checkMouse = false;
clearrectangle(left, top, right, bottom);
return 0;
}
//void OnMessage()
//{
// bool checkMouse=true;
// ExMessage msg;
// while (checkMouse)
// {
// while (checkMouse && peekmessage(&msg, EX_MOUSE))
// {
// if (msg.message == WM_LBUTTONDOWN)
// {
//
// if (msg.x < left || msg.x > right || msg.y < top || msg.y > bottom)//移动到外面停止
// {
// checkMouse = false;
// break;
// }
// else
// {
// if (userfunc != NULL)
// userfunc();
// }
// }
// Show();
// }
//
// }
//}
};//按钮控件
*4* 界面文本显示及怎么使用封装的文本框和按钮
首先,文本显示函数为 void outtextxy(int x, int y, TCHAR c);//x轴坐标,y轴坐标,TCHAR类型字符串
而在EasyX界面中,坐标轴为Y轴是正数的第四象限:
其次,在输出文字显示之前,可以设置文字颜色、背景模式等等
设置文字颜色:void settextcolor(COLORREF color);
其中COLORREF 已在.h宏定义:
设置文字背景模式:void setbkmode(int mode);
OPAQUE为不透明,默认使用当前背景颜色
TRANSPARENT为透明色
//2024-8-5 17:15 编写,后续会更新