王文娟,李绪凯 ,王欣 ,邢娜
(1.陆军工程大学石家庄校区,河北石家庄,053000;2.中国电子科技集团公司第五十四研究所,河北石家庄,050081)
关键字:Pcomm;VS2010;串口通信;多类型数据
串口通信程序设计方法多样且灵活,可适应于不同的应用环境。在VC++平台下实现串口数据传输通常有三种方式,最常用的方式是在程序中直接调用或使用类封装的Windows API(Application Program Interface)函数,通过对设备驱动程序的底层控制实现串口操作,API函数种类较多、功能分散,且使用串口较多时,需要编辑繁琐的线程同步[1]。第二种方法是使用MSComm串口通信控件[3],在VS2010中使用,需要对控件进行安装和注册,并且要将串口通信控件添加到工具箱中。数据类型转换比较复杂,并且使用时需要将控件拖入对话框中,不能满足复杂情况下的通信要求[2],例如Ribbon风格下使用就会受到限制。本文使用另外一种方法,由第三方厂商MOXA公司提供的PComm(Professional Comm Tool for PC)串口通信。PComm提供了一套封装的API函数动态链接库,可满足处理多进程/多线程等复杂情况下的串口通信,并且功能强大、传输速率高、使用方便灵活[4]。基于以上分析,在Win64位操作系统中,采用VS2010平台下的MFC进行多类型数据串口通信软件的设计和编程实现,并解决PComm模块难以在64位Windows环境中使用及不能传输多类型数据的问题。
PComm函数库中包含了多种功能的函数,包括串口设置(Port Control)、输入数据(Input Data)、输出数据(Output Data)、状态查询(Port Status Inquiry)、文件传输(File Transfer)及其他特殊设定函数(Miscellaneous),能够满足几乎所有的串口通信需求。PComm所提供的函数均以sio_开头命名,其中本软件中涉及的PComm函数如表1所示。
表1 PComm函数列表
启动VS2010,新建一个基于对话框的MFC应用程序SerialTest。MFC程序界面框架如图1所示。
图1 MFC程序界面框架
设置对话框的Caption属性为“PCOMM串口通信”,增加2个编辑框控件(Edit Control)分别用来显示接收数据和输入发送数据,3个按钮控件(Button)分别表示“清空”,“发送”和“打开串口”,并用于执行相关操作,1个组合框控件(Combo Box)显示串口号,将其Type属性设置为DropList,2个编组框控件(Group Box)表示接收和发送功能区域,1个静态字符控件。与控件相对应的成员变量[6]如表2所示。
表2 与控件对应成员变量
在VS2010环境下使用PComm函数库必须将其引用到工程中。PComm函数库中包含三个文件,PCOMM.H、PCOMM.dll和PCOMM.lib。PCOMM.H为头文件、PCOMM.dll为动态链接库,而PCOMM.lib则是静态库文件。h文件包含了函数声明,lib文件用于指定dll中各功能函数的入口及地址,真正的函数在dll中。需要注意的是,如果Windows操作系统是64位,就需要专门下载64位动态链接库。将三个文件都复制到当前工程的目录下。
打开解决方案资源管理器,在头文件中右击添加现有项PCOMM.H头文件。或者是添加工程的头文件目录,具体方法为:点击菜单项目-SerialTest属性-配置属性-C/C++-常规-附加包含目录,编辑输入头文件的存放目录。然后打开类视图,在SerialTestDlg.cpp中加入串口头文件引用,输入#include“PCOMM.h”。
除引入头文件之外,还需要在链接过程中将lib加入,即添加附加依赖项和附加库目录,否则只有函数声明,没有函数实现过程。具体方法为:点击菜单项目-SerialTest属性-配置属性-链接器-输入-附加依赖项,编辑输入PCOMM.lib,点击链接器-输入-附加库目录,编辑输入lib所在目录,对当前项目有效,这样就为PCOMM.dll动态库添加了静态链接,实现了动态库的静态调用。加入了动态库的函数声明,就可以直接使用dll中的函数。
(1)初始化
在类视图面板的CSerialTestDlg类中添加bool型变量flag用于标记串口状态,添加int型变量表示串口号。在初始化函数OnInitDialog()中对表示串口号的组合框控件的列表框添加列表项。
m_port.AddString(_T(“COM1”));
m_port.AddString(_T(“COM2”));
……
m_port.SetCurSel(0);//默认选择串口号为1
(2)串口配置
在CSerialTestDlg类中添加两个函数:打开串口函数和关闭串口函数。sio_ioctl()函数为串口设置波特率、位数据位、位停止位、无奇偶校验等参数。sio_cnt_irq用于设置中断回调函数,每当串口收到指定字节数据时,系统就会自动调用这个中断回调函数,这里的回调函数名为cntirq。程序代码如下:
//打开串口函数
void CSerialTestDlg::open_com(void)
{
n_port=m_port.GetCurSel()+1;//真正的串口从索引号加1开始
if (SIO_OK!=sio_open(n_port))
MessageBox(_T(“打开串口失败”),_T(“WARNING”),MB_ICONWARNING);
sio_ioctl(n_port,115200,BIT_8|STOP_1|P_NONE);//设置串口参数
sio_cnt_irq(n_port,cntirq,1);//设置中断函数
}
//关闭串口函数
void CSerialTestDlg::close_com(void)
{
if (SIO_OK!=sio_close(n_port))
MessageBox(_T(“无法关闭串口”),_T(“WARNING”),MB_ICONWARNING);
}
为“打开串口”按钮添加事件处理程序,或者直接双击按钮添加程序。若串口为关闭状态,则执行打开串口程序,否则执行关闭串口程序。
flag=!flag;
if (flag==TRUE)
CSerialTestDlg::open_com();
else
CSerialTestDlg::close_com();
(3)接收过程
数据的接收主要由声明回调函数实现,即 CALLBACK类型的cntirq()事件处理函数,CALLBACK类型函数一般由程序员设计,却是Windows系统调用执行的特殊函数[5]。与一般的成员函数不同,中断回调函数并不写在类里面,在调用过程中不通过任何对象,因此也并不属于某一个具体对象,所以可以在对象未产生之前被调用。中断回调函数的任务就是发送一个WM_PCOMM消息到窗口, GetMainWnd()函数功能是获得主窗口,返回类型是CWnd的指针,用AfxGetApp()->GetMainWnd()->m_hWnd语句就获得了主窗口句柄。这样,消息就可以发到主窗口了,每当串口接收了数据,就会发一个消息WM_PCOMM到窗体,WM_PCOMM是用户自定义消息。
void CALLBACK cntirq(int n_port)
{
CWnd *pgetwnd=new CWnd;
pgetwnd=AfxGetApp()->GetMainWnd();//获取主窗口
if(pgetwnd)
{
if(AfxGetApp()->GetMainWnd()->m_hWnd)
{
::PostMessage(AfxGetApp()->GetMainWnd()->m_hWnd,WM_PCOMM,0,0);}
}
}
用户自定义消息需要在CSerialTestDlg.h的宏定义后面加入定义
#define WM_PCOMM WM_USER+50
WM_USER是系统已经定义好的消息地址,从这个地址开始可以自定义消息地址,WM_USER+50表示自定义消息WM_PCOMM位于消息地址向后偏移50,自定义消息地址由用户设置,且与消息的执行先后无关,但要注意不能与其他的用户自定义消息地址相冲突。
自定义消息处理afx_msg函数,不同于VC6.0环境,在VS2010环境下afx_msg函数的类型为LRESULT。具体方法为右击对话框类打开类向导添加方法,函数名定义为OnWmpcomm,并添加两个参数,函数声明及函数体实现代码如下。
afx_msg LRESULT OnWmpcomm(WPARAM wParam, LPARAM lParam);
afx_msg LRESULT CSerialTestDlg::OnWmpcomm(WPARA M wParam, LPARAM lParam)
{
char revBuff[200];//声明字符串数组
int n=sio_read(n_port,revBuff,100);//读操作
if(n)
{
int len=MultiByteToWideChar(CP_ACP,0,revBuff,n,NULL,0);
TCHAR *buf=new TCHAR[len+1];
MultiByteToWideChar(CP_ACP,0,revBuff,n,buf,len);
buf[len]=’ ’;
CString pWideChar;
pWideChar.Append(buf);
SetDlgItemText(IDC_EDIT_REV,pWideChar);
delete []buf;
}
return 0;
}
考虑到发送框中可能输入汉字,而汉字为双字节,发送数据是转换为字符串数组被发送出去的,则接收时需要将串口收到并保存在缓冲区的数据字符串数组再转换为CString类型。第一个MultiByteToWideChar()函数按照字节获取多字节字符的大小,第二个MultiByteToWideChar()函数将多字节编码转换成宽字节TCHAR编码,最后Append()函数将宽字符数组转换为CString类型,显示在接收编辑框中。
收到数据后,回调函数发出用户自定义消息WM_PCOMM,若要用定义的OnWmpcomm()函数用来响应此消息,就需要在对话框cpp文件消息映射表中声明对应的消息映射,添加代码
ON_MESSAGE(WM_PCOMM, & CSerialTestDlg::OnWmpcomm)
至此编译无误,运行程序后选择正确的串口号,就可以通过串口收到并显示数据。
(4)发送过程
为“发送”按钮添加事件处理程序,或者直接双击按钮添加程序。添加代码如下:
UpdateData(TRUE);
int n=m_send.GetLength();//获取发送内容(包含空格)的长度
if (n)//加入发送内容不为空
{
int len=WideCharToMultiByte(CP_ACP,0,m_send,m_send.GetLength(),NULL,0,NULL,NULL);
char *sendBuff=new char[len+1];
WideCharToMultiByte(CP_ACP,0,m_send,m_send.GetL ength(),sendBuff,len,NULL,NULL);
sendBuff[len+1]=’ ’;
sio_write(n_port,(char*)sendBuff,len);
}
UpdateData(FALSE);
第一个WideCharToMultiByte()函数是按照字节获取宽字节字符的大小,第二个WideCharToMultiByte()函数将宽字节编码转换成多字节编码,以’ ’结束,将多字节编码再转换成单字节的字符串数组发送出去。
(5)清空操作
为“清空”按钮添加事件处理程序,或者直接双击按钮添加程序。实现点击“清空”按钮后,发送编辑框和接收编辑框同时清空。添加代码如下:
m_recv=_T(“”);
m_send=_T(“”);
SetDlgItemText(IDC_EDIT_SEND,m_send);
SetDlgItemText(IDC_EDIT_REV,m_recv);
基于VS2010平台进行PComm串口通信软件的设计与实现,利用适用于Win64位系统的动态链接库避免了无法识别函数名标识符的问题。通过转换发送和接收数据类型,使得串口能够传输汉字、数字及字母等多种类型的数据。但是下一步还应继续优化程序,解决软件传输混编数据的问题。文中详细的软件设计流程及代码能为其他PComm串口通信的学习者提供良好的借鉴作用。