串口操作类

tjpu_spl 2011-12-09 09:28:07
串口,一种古老的设备,虽然能力有限,但因其简单,在计算机与单片机通信场合,有着较多应用。本文介绍的串口类,并非本人独自完成,框架思路继承了一位国际友人的源代码,在此基础上扩充了功能,修正了bug。一并列出原文代码,以示敬意。现在说一下如何使用。

Part one:简单使用
新建一个对话框程序,包含SerialPort.h头文件,加入一个类变量,如m_Port。在对话框的初始化函数中加入如下代码:


m_Port.m_PortParameter.SetPortNumber(4);
m_Port.m_PortParameter.SetPortBaudRate(9600);
m_Port.SetCaptureReceive(false); //无视此行
m_Port.SetCaptureOnly(false); //无视此行
m_Port.SetBatchReceive(false); //无视此行
m_Port.SetDynamicLengthReceive(false); //无视此行
if( m_Port.OpenPort(this) == false )
{
/*
错误处理函数
*/
return TRUE;
}
m_Port.StartMonitoring();

return TRUE; // 除非将焦点设置到控件,否则返回TRUE
额外加入的代码含义是,配置串口4,波特率9600,中间4行暂时无视。OpenPort(this)函数需要一个CWnd指针参数,代表用于接收消息的主界面,填入this。检查一下返回值,如果是false,则不能继续后续操作,表明你配置的参数有问题,或者打算使用的串口被占用等。如果是true,调用StartMonitoring()开启一个工作线程,串口在后台进行收发,有数据到来就发送消息到主界面。是多线程的处理,主界面不会被阻塞。
按以上代码,默认的行为是当串口有数据到来就会接收(哪怕只有一个)。当有数据到来的时候,工作线程会向主界面投递消息(刚才提到的OpenPort的那个参数),消息是WM_PORT_RXCHAR,拦截一下消息即可(以下所有消息都是自定义的)。

LRESULT CTestDlg::OnChar(WPARAM wParam,LPARAM lParam)
{
/*
处理函数
*/
return 0;
}
消息处理函数有两个参数,第一个wParam包含接收到的字符,只有最低一个字节有效,强行转换为char即可,每次只能传送一个字节。第二个参数lParam代表从哪个串口接收到数据,如果只开启一个串口,则这个参数没什么意义。
最基本的操作就这么简单啦,到此结束。

Part two:基本使用方法的补充说明。

首先要说明的是,我加入了很多错误检查,一旦你配置的参数或者程序运行出现错误,我会采取四种方法通知:
第一种:发生错误的那个函数返回值是非正常的(如果有返回值的话),需要在代码中检查返回值才能决定下一步做什么。如果函数的返回值是bool类型,那么true代表正确,如果函数返回一个整型,那么0代表正确(多数情况下是如此,自己看源代码)。这种错误提示方法总是生效的。
第二种:弹出一个对话框,告诉你发生了错误。这种错误提示方法大多数情况下是生效的。如果想屏蔽讨人厌的对话框,在SerialPort.h头文件有一个宏定义,设置为0,重新编译即可,如下,
#define PMB_ONOFF 1
但是,有些对话框是无法屏蔽的,我指的是特别重要的那些信息。
第三种:向主界面投递消息WM_SERIOUS_ERROR,拦截一下消息即可,消息的wParam代表了不同的错误类型,lParam代表发生错误的串口号。这种方法不是总有效的,多数对应串口接收和发送的意外错误(但是这类错误基本不发生)。如果想屏蔽,头文件同样有一个宏定义PWM_ONOFF。屏蔽只针对WM_SERIOUS_ERROR消息,不针对任何串口正常的事件。
第四种:我调用SetLastError()设置错误码,见SerialPort.h头文件我声明的错误码。这种方法大多数下有效。

其次,除了串口号和波特率,还有哪些参数可以设置。CSerialPort类含有一个类型为CParameter的变量m_PortParameter,其含有一个public型的DCB结构m_PortDcb,诸如奇偶校验、停止位长度等都可以设定,因为是public型,所以可以直接修改。默认是串口1,9600波特,8bit长度、一个停止位、无奇偶校验,有数据到来即投递消息。另外还可以设置握手协议SetHandShakeType,这个用于较为复杂的场合,我按照多数下合理的逻辑做了默认设置,默认有三种协议,无握手、软件握手、硬件握手。

最后,这个串口类还可以处理哪些串口事件呢?硬件有一种功能,可以设定一个标志字节FLAG,当串口在数据流中检测到有这个匹配字节时,发生中断。这个类继承了此功能,在OpenPort之前,调用
m_Port.m_PortParameter.SetPortEvent(EV_RXCHAR | EV_RXFLAG);
凡是想监控的事件都可以用或运算设置。当检测到这个标志字符时,这个类会向主界面投递WM_PORT_RXFLAG消息。因为这个FLAG是已知的,所以不被接收,消息处理函数的wParam参数总是0(这个要特别注意),lParam参数是发生此事件的串口号。如果同时设定EV_RXCHAR和EV_FLAG,则会收到两个消息。使用这个功能前还要设定m_PortParameter里的m_PortDcb里的EvtChar,即想要监控的标志字节。
微软定义了九个事件(其实是硬件具有的),都有对应消息,事件集见MSDN。我定义的消息参见头文件声明。对这些事件只发送消息,因为我无法预见,需要自行处理。一共有九个事件,我只能说说自己的理解。
EV_BREAK:帧中断错误,比如串口正在发送一个字符,突然间线路故障或者人为拔掉串口,则接收方会检测到,判定为线路中断。
EV_CTS:检测到串口的CTS(清除发送)引脚产生一个有效电平。
EV_DSR:检测到串口的DSR(数据设备准备好)引脚产生一个有效电平。
EV_ERR:线路故障,包括奇偶校验错误、超速(收发速度不一样)和帧格式错误(收发格式不一样)。
EV_RING:振铃指示引脚产生一个有效电平。
EV_RLSD:RLSD线改变状态,不懂,是否类似载波检测?
EV_RXCHAR:有字符到来。
EV_RXFLAG:有匹配的标志字符到来。
EV_TXEMPTY:发送缓冲器空。

BUG的补救
北航龚建伟老师,写过一本串口的著作,同样引用了这位外国友人的原始代码。提到这个代码不能同时运行2个串口,或者只运行一个串口,在程序运行中途更换串口号会发生错误。我进行了补救。目测,补救之后可以同时开2个或者更多串口,也可以中途更换串口号。我忘了改动了什么函数,可以使得同时运行两个串口。这个代码是我三年前改进的,现在才写说明。看来及时写注释非常必要,否则自己也回忆不起来。
补救后的BUG
我喜欢穷举和恶意攻击,补救之后,好像我又检测到一些错误。大约是同时开启两个串口,然后各自同时收发数据,好像会丢数据。如果两个不同时接收,则不会发生丢包现象。这个问题,现在没时间研究,看看哪位高手给补救吧,顺便告诉我。
Part three:增强的数据接收。
一个土豆吃不饱。
微软的串口控件有种能力,可以设定一个接收门限值,比如100,那么每到100个字节才发送消息,而不是每个字节到来都发送消息,这样可以批量接收,提高消息处理函数的效率。这个功能我山寨上了。
但是,我们考虑一下,如果我设定100个字节的门限,当100个字节到来时,我去处理它,但是windows是非实时的,有可能(虽然可能性很小)系统还没来得及处理,新的数据又从串口接收到了,用户的目标处理程序和这个类的工作线程同时操作缓冲区,要么发生同步阻塞,要么还未及时处理的老数据可能被覆盖。所以,我启用了两个缓冲区,以下有一幅很漂亮的图示。
(我不会粘贴图片,在我上传的资料里有)
每个缓冲区的大小相同,每个最大容量用宏定义MAXINPUTBUFFERSIZE定义,默认8192字节。如果设定接收门限为100,则当第一个缓冲区达到100个字节时,投递消息WM_DATA_RTHRESHOLD,然后工作线程使用缓冲区2继续接收,用户程序操作缓冲区1处理,当第二个缓冲区达到100个字节时,再次投递消息WM_DATA_RTHRESHOLD,然后工作线程使用缓冲区1继续接收,用户程序操作缓冲区2处理,如此循环。这样做虽然不敢保证100%不发生数据覆盖,但是总比一个缓冲区好。最好的办法是在消息处理函数中,及时拷贝数据再处理,而不是用我提供的缓冲区。这种方法,我称之为批量接收,注意此时投递的消息是WM_DATA_RTHRESHOLD,拦截消息即可。

LRESULT CTestDlg::OnBatchReceive(WPARAM wParam,LPARAM lParam)
{
/*
处理函数
*/
return 0;
}
消息函数的wParam参数如果非零,代表第一个缓冲区有数据,需要处理。为零则代表第二个,lParam代表发生此消息的串口号。那么缓冲区在哪里?CSerialPort类有一个CInputBuffer类型的成员m_PortInputBuffer,pubilc型。
class CInputBuffer
{
public:
CInputBuffer(void);
public:
virtual ~CInputBuffer(void);
public:
void ClearBuffer(void);
public:
char* m_pBuffer1;
public:
char* m_pBuffer2;
};
使用批量接收,需要在打开串口之前,执行以下配置(请与最开始的对比不同点):
m_Port.SetCaptureReceive(false);
m_Port.SetCaptureOnly(false);
m_Port.SetBatchReceive(true);
m_Port.SetRThresholdrSize(100);
m_Port.SetDynamicLengthReceive(false);
注意批量接收门限不能为零,也不能超过最大缓冲区长度。如果门限值为1,则是每到一个数据就发送消息,但是和前面提到非批量方式的有所不同,首先消息不同,其次批量接收时数据放在双缓冲区里,非批量接收,随着消息参数发送。

我好像还想写点什么,一时间忘了,好像是我对临界区的理解有些不明白。

继续前行。
考虑以下两种情况:
第一种:假设我的串口总线挂上很多串口,其中一个始终发,其他的始终收,接收的怎么区分呢?对了,需要配一个地址码或者其他什么码,每个从机地址码都不同。然后主机每次发送数据都附加上地址码,从机在数据流中不断搜索,有与自己地址匹配者,开始接收,没有则放弃。
第二种:数据流就是要有一个固定的前缀作为起始(比如我的名字),即使它没有意义。
所以,我增加了捕捉接收方式(匹配接收、捕获接收,叫什么都行)。但是有一种可能,真正的数据流中可能包含与地址码相同的数据,如何处理呢?我规定了,使用捕捉接收方式,真正的数据必须要有固定长度,代码在数据流中不断匹配,直到发生与设定的地址码一致的时候,开始接收固定长度的数据(如果这期间再出现地址码,则忽略),直到接收完成,然后开始下一次匹配。启用捕捉方式,使用以下配置(请对比):
m_Port.SetCaptureReceive(true);
m_Port.SetCaptureOnly(false);
m_Port.SetBatchReceive(true);
m_Port.SetRThresholdrSize(100);
m_Port.SetDynamicLengthReceive(false);
m_Port.SetMatchBuffer("your address");
最后一行代表设定的匹配前缀字符串,最大256字节(最好不要等于这个数量),最小1字节。接收的门限仍旧用SetRThresholdrSize设定。当数据接收到时(正确匹配和收到指定长度数据),发送WM_DATA_RTHRESHOLD消息,拦截即可。消息函数的参数与批量接收时含义相同,wParam代表缓冲区指示,lParam代表串口号。使用捕捉接收,匹配前缀不包含在接收缓冲区中,因为这是已知的,不是真正数据。
在捕捉方式下,可以是批量接收,也可以是非批量接收(来一个数据就收下),根据SetBatchReceive()的参数而定,非批量的时候忽略门限值。这两种方式投递的消息不同。批量接收是WM_DATA_RTHRESHOLD消息,非批量是WM_PORT_RXCHAR。

再接再厉。
刚才提到,硬件有一种能力,可以拦截和你设定的标志相同的字符,然后发送消息,但是只能拦截一个字节,如果我只想检测串口数据流有无我感兴趣的字符串,但我不接收任何数据(相当于一种通知提示),那么这就是仅捕捉接收方式,CaptrueOnly,如下配置:
m_Port.SetCaptureReceive(true);
m_Port.SetCaptureOnly(true);
m_Port.SetBatchReceive(true);
m_Port.SetDynamicLengthReceive(false);
这时,当发生匹配时,发送WM_DATA_CAPTURE消息作为通知,但是没有任何数据放入缓冲区。注意上述代码第三行和第四行的参数是true或者false无关紧要。

有点乱,整理一下。
首先捕捉接收方式具有最高流程优先级。如果是捕捉方式,则有三种可能:仅捕捉,批量接收和非批量接收。在程序流程判断上,仅捕捉优先级更高,非批量接收最低,程序只采用一种方式。
如果不是捕捉接收方式,则有两种可能:批量接收和非批量接收,批量接收具有较高优先级。
还没完。
刚才的捕捉接收方式很好,你觉得呢?不过我觉得有缺陷,每次的数据长度是固定的(我指的是真正想要的数据),如果数据长度不同,需要每次调用SetRThresholdrSize设定门限值。我希望数据长度能够写在数据流中,程序动态改正。这样,我在捕捉接收方式之上又设定了是否启用动态长度接收。先说一下,动态长度接收必须是捕捉接收方式下才可启用(想一下就知道,没有帧头,我怎么知道那个数据代表长度)。第二,在捕捉接收方式下,如果启用动态长度接收,则优先级低于仅捕捉方式,高于批量接收方式,程序流程进行不二选择。也就是按优先级排列是:仅捕捉,动态长度批量接收、固定长度批量接收和非批量接收
如上图,启用动态长度接收,必须遵循以下格式:帧头是你随意设定的(长度不超过256字节,最少一字节),然后两个字节是长度指示,表明这一次要接收多少字节(不包括帧头和长度指示符),最后是数据。只有真正数据放入缓冲区。两个字节长度指示符可以表示最大65535个字节,好像足够了吧。对,足够了,即使TCP数据包也没有这许多。长度指示符不能是零,否则意味着动态长度是65536,而不是没有数据,这是一种错误方式。这个类的代码
无法做检查,因为检查出来也为时已晚。

这个类允许在程序运行期间更改接收方式。但是仅仅更改接收方式不能正确执行,因为很多内部状态变量没有更新,还处于原先状态。所以我建议(或者说没有别的选择)按照以下方法:
1 调用CSerialPort的ResetSomeKeyValue函数,复位关键变量,包括清空缓冲区,指示位置和迭代搜索的变量归零。
2 设定新的方式。
在更换方式下,串口不能正确收发数据,这一点要自己保证。

如果中途更换串口号和波特率,那么必须按照以下步骤进行:
1 先关闭串口ClosePort。
2 执行Initialization函数
3 因为串口号和波特率等所有变量复位,需要重新设定所有变量和工作方式
4 调用OpenPort函数
5 如果没有错误,调用StartMonitor函数

这个类支持物理串口和虚拟串口,虚拟串口,我接触过两类,把USB弄成串口(笔记本),纯软件虚拟的,在一台机器上完成类似仿真的任务。把USB弄成串口,我没有测试过,其他两种测试过。有一个宏定义SCANIO_ONOFF,如果非零,则每次构造实例的时候或者调用Initialization函数的时候,搜索一下计算机上所有的串口,写在一个变量数组里m_bPortNumberExist[254];
如果取值为true,则有对应串口,否则没有。下标为0的元素代表串口1,下标为1的元素代表串口2等等,最多支持254个。但是这个类无法区分物理串口和纯虚拟串口的重叠使用,另外我判定的方式就是循环打开所有串口,这样需要1-2秒,主界面会卡住。因为我不会其他方法。如果不需要这个辅助功能,把SCANIO_ONOFF定义为0即可。按理说,可以支持255个串口,我人为去掉了最后一个。

完结。

Part four:增强的数据发送。
不知道你有这经验没,发送永远比接收简单,就像花钱永远比赚钱容易一样。
如果发送字符串,调用以下函数即可。
public:
void WriteStringToPort(char* strText,
bool bWithCapture = false, bool bWithDynamicLength = false,
char* strCapture = "tjpu_spl", bool bInstruction = false, _int8 nInstruction = 0);
strText是发送的字符串,其他的参数待会解释。
这个类可以发送汉字,只能发送多字节汉字,Unicode汉字需要自己转换一下。
这个类不能接收汉字。解释一下,汉字无非是多个字节流,没有什么可以接收与不可以接收的,我的意思是串口只能一个字节一个字节的发送与接收,如果是汉字,需要自己拼在一起,而不是靠我拼装,程序只收发数据不处理。
这个类可以发送浮点数和整型数,有多个WriteToPort函数,名称一样,参数类型不同。我特意和发送字符串的函数名区分开了,仅举一例:
public:
void WriteToPort(double* pValue, unsigned int nCount,
bool bWithCapture = false, bool bWithDynamicLength = false,
char* strCapture = "tjpu_spl", bool bInstruction = false, _int8 nInstruction = 0);
其中pValue代表要发送的多个浮点数,nCount表示要发送多少个,这个得自己如实填写,pValue必须有这么多数据才行,否则崩溃,而且数据量不得超过最大输出缓冲区容量,默认8192字节,用宏定义MAXOUTPUTBUFFERSIZE表示。nCount以发送的类型计算,不以字节计算,我在代码中进行换算,不超过最大发送缓冲区容量以 发送的数量*发送类型 大小计算,这要自己保证,如果检测到超出容量,拒绝发送。
现在来看看其他参数的含义。
刚才提到,有一种捕捉的接收方式,那么就得有一种带前缀(地址码)的发送才对称,bWithCapture表明发送的时是否附加前缀,str_Capture表示前缀字符串。字符串不得超过256字节,最好少点。默认前缀是tjpu_spl,tjpu是天津工业大学的缩写,spl是我名字汉语拼音首字母缩写。
刚才提到,有一种动态长度的捕捉接收方式,那么就得有一种带长度的发送才对称,bWithDynamicLength表示是否附加长度码(2字节,小端字序),最大可以指示65535字节,但是我限定了最大32768字节,我限定它肯定有道理。长度指示必须在bWithCapture为true时才生效,否则无视。无前缀,怎么界定长度指示符在哪里?

你马上就要获得解放了。
我设想,从串口来的数据可能表示不同含义,比如2点10分的时候代表温度,2点11分的时候表示压力等。我可否在发送数据流中插入这种含义指示符呢,这样一同发送,让接收方在数据起始的时候先取出含义指示,然后采取不同对策,这个我称为指令码。bInstruction表示是否附加指令码,nInstruction是一字节指令码,一个字节对我最够了,对你不够的话,自己改代码。


这个代码我继承了老外的框架,就是多线程的方式。我不是搞软件的,实话说,没有这个框架,我不知道该怎么弄串口。但是我做了巨大修改,增加了很多功能,军功章上有我的一半,这不为过。出于对原作者敬意,我的代码头部仍旧有这个老外的名字。这个类允许教学用、学习用,自己私人用,拒绝商用。

有什么BUG,请反馈 tjpu_spl@126.com

...全文
295 3 打赏 收藏 转发到动态 举报
写回复
用AI写文章
3 条回复
切换为时间正序
请发表友善的回复…
发表回复
tjpu_spl 2011-12-09
  • 打赏
  • 举报
回复
第一次发帖,不知道怎么粘贴本地图片,不知道怎么插入代码,不知道怎么修改帖子。
tjpu_spl 2011-12-09
  • 打赏
  • 举报
回复
001、VB串口通讯视频教程源码41个 002、Visual Basic串口通信工程开发实例导航随书源码7个 003、Visual Basic串口通信与测控应用技术实战详解 源代码(15个全) 004、GE PLC串口通讯,VB编制,读取内存单元 005、PC机与51单片机之间的串口通讯,VB编的,分PC和单片机两部分 006、VB6的串口通信程序,还有crc校验 007、VB Modbus RTU源码,其中协议部分已生成DLL,可直接调用 008、VB.net开发的串口调试程序 009、VB.net实现串口编程,希望大家有用 010、VB版串口调试程序,包含VB源码及安装文件,适合调试串口 011、VB编程RS232串口控制DA数模转换 012、VB编程实现的串口调试工具源码 013、VB编写的RS232串口通信测试程序,以txt格式接受,可定义发送字符 014、VB编写的SouthStar串口测试与51串口烧写器V1.0版 015、VB编写的串口调试助手1.0的源码 016、VB编写的串口短信发送程序,需要数据线支持 017、VB编写的串口通信程序,实现多机通信 018、VB编写的串口通信程序,主要用于上位机与下位机间的通信 019、VB编写的串口通信程序界面参考网上的程序较简单 020、VB编写的串口通讯界面,主要面向51单片机的串口通信 021、VB编写的单片机和PC串口通信的调试程序 022、VB编写的仿真实电子琴操作界面,包含与FPGA串口通信的功能 023、VB串口API通讯,附带BAS文件全部源码,实现与饭卡读卡器通讯 024、VB串口编程,关于上位机的应用,特别适合初级学习VB的学员 025、VB串口编程调试精灵源码 026、VB串口编程实现完整的多费率电表读数软件 027、VB串口程序,,是一个串口使用例程,对初学者有用,特别是工控的 028、VB串口传输文本,实现2台PC间的通信,似简单的聊天工具 029、VB串口的一个电子称的项目 030、VB串口调试程序,用于通过串口控制松下空调测试 031、VB串口调试程序及源码 032、VB串口调试软件源代码,可以参考修改为其它通讯程序 033、VB串口调试软件源文件 034、VB串口控制步进电机程序完整源码 035、VB串口通信 6路10位AD转换数据采集源程序 036、VB串口通信,API串口通信模块源码 037、VB串口通信,适用简单,适合初学者 038、VB串口通信操作界面,进行数据采集,画实时曲线 039、VB串口通信程序,可以读取串口并显示保存数据,且能显示数据曲线 040、VB串口通信的源码,学习的好资料 041、VB串口通信调试器的源码程序 042、VB串口通信设计视频演示源码 043、VB串口通信示例 044、VB串口通信数据源码 045、VB串口通信之串口接收程序 046、VB串口通讯测试源代码,有文本和图形两种端口数据观察方式 047、VB串口通讯程序,用来跟单片机通讯 048、VB串口通讯代码(部分) 049、VB串口通讯的参考源程序 050、VB串口通讯实例 高精度电压表(24bit) VB源程序 051、vb串口通讯示例 052、VB串口与伺服电机DSP2407通讯完整代码源程序 053、VB串口源码,动力电池检测数据采集,内含电导巡检模块通讯报文,可,读写,保存,备份数据 054、VB串口字节通信程序,包括:1字节发送子程序,n字节接收子程序 055、VB串行口通信测试示例 056、VB串行通信试验程序 057、VB的MODEM通信源代码,智能化水电远端数据读取系统 058、VB的串口源程序,包括串口的配置界面,接收功能和发送功能 059、VB访问串口,并读取电子秤上显示的数据 060、VB和西门子S7-300 PLC串口通讯程序能实现读写功能 061、VB检测串口工作状态 062、VB简单的串口短信收发功能,使用短信猫测试通过 063、VB开发串口通信,关于生物医学工程专业的血氧饱和度的设计 064、VB开发串口通信软件,利用按钮控件控制高清晰数字展示台 065、VB开发的RS232串口图像处理器驱动(摄像头驱动) 066、VB开发的串口通信源码 067、VB开发的串口与三菱FX PLC通讯源码 068、VB控制串口232通讯,对飞利浦M1卡内数据进行处理,支持密码修改等 069、VB利用Mscomm控件编写的通讯终端,可做串口通讯编程参考示例 070、VB平台单片机与PC机串口通信的PC端程序。小巧易用,功能丰富 071、VB嵌入式串口通讯波形分析显示软件 072、VB实现串口调试LED信息显示屏设备主要代码 073、VB实现串口调试工具的完整源码 074、vb实现串口通信 文件传送系统,用vb以及mscomm控件实现 075、VB实现串口通信,发送命令从而接收相应数据 076、VB使用mscom控件实现PC机与单片机串口通信 077、VB通过COM串口读取条形码设备 078、VB通过串口控制单片机读写24C02源代码 079、VB通讯程序,连接串口可在电脑显示来电号码 080、VB下的串口发短信程序,可选择端口,设置短信中心号码 081、VB写的串口通信,发送和接收实例 082、VB写的串口通信分析程序源码 083、VB写的串口通讯,通过串口对单片机进行控制 084、VB写的串口通讯软件,简单易学,适合初学者 085、VB写的通过串口与考勤机连接通讯的程序 086、vb用控件的写的串口程序,是vb的经典之作 087、VB与USB转串口的通讯完整程序,有详细说明,不需要安装驱动 088、vb与串口通信的关于回路测试的小程序很实用 089、vb语言开发的串口通信,可实现拨号传送文件等 090、VB中串口事件处理函数的示例 091、VB中的串口通讯,串口通讯作为一种古老而又灵活的通讯方式,被广泛地应用 092、VB自动枚举系统串口加摄象头图象采集,坐标系变换 093、Visual Basic2005与自动化系统监控(串并行控制)光盘

15,979

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC 界面
社区管理员
  • 界面
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧