串口的通信.docx
《串口的通信.docx》由会员分享,可在线阅读,更多相关《串口的通信.docx(10页珍藏版)》请在冰豆网上搜索。
![串口的通信.docx](https://file1.bdocx.com/fileroot1/2023-2/2/726ab89f-c424-48bb-9a6c-27d9a71f5148/726ab89f-c424-48bb-9a6c-27d9a71f51481.gif)
串口的通信
摘要:
本文介绍了在Microsoft Visual C++ 6.0环境下通过对Active X控件的编程来实现串口的通信的一般方法。
一、 引言
当我们在Windows操作系统下开发串行通信程序时通常不得不面对许多复杂的API函数,因为在Windows操作系
统下不能直接对设备端口进行操作,也不能在系统级(Ring 3级别)使用任何DOS或BIOS中断,如要对端口进行编
程则只能以文件的形式来对端口进行操作,这就使开发人员不得不面对非常烦琐的API函数编程。
本文对此提出了
另外一种封装性很好的使用Microsoft Visual C++ 6.0自带的"Microsoft Communications Control,version
6.0"Active X控件的编程方法,通过对该控件的正确使用,我们可以比较轻松地编写出所需的串行通信程序。
下面,我们将结合一个实际的程序示例来对此方法进行说明。
本程序的编程环境是Windows 98和Microsoft
Visual C++ 6.0。
在本程序示例中对为避免阻塞而对线程的使用以及在使用中遇到的一些问题也做了详细的介绍。
二、 程序的设计实现
在开始进行代码编程前,首先以在工程中插入组件或控件的方式将Active X控件"Microsoft Communications
Control,version 6.0"加入到工程中来,此时将会在工程中添加一个关于此控件的新类。
使用该控件的一些方法
和属性时不能象使用类一样简单的声明一个实例对象,而要通ClassWizard为该控件和一个成员变量建立起绑定关
系,在此我们将该控件同变量m_Comm相绑定后就可以通过该控件提供的方法来对串口的各种通讯参数进行设置了。
为了编程方便起见,也可以在资源视图中直接对该控件的属性进行设置,如无特别要求,对下表所列属性进行设置
就基本可以满足编程要求了。
现将常用的属性列表如下:
属性 设定值 属性说明
CommPort 1 串口号,一般从1到4
InBufferSize 30720 接收缓冲区大小,为保持程序的稳定,建议设得值足够大
InputMode 0-Text 接收数据的类型,0表示文本类型,1表示二进制类型
InputLen 0 从接收缓冲区读取的字节数,0表示全部读取
OutBufferSize 512 发送缓冲区大小
Settings 4800,n,8,1 串口的参数设置,依次为波特率、奇偶校验(n-无校验,e-偶校验,o-奇校验)、数据位数、停
止位数
RThreshold 1 设定当接收几个字符时触发OnComm事件,0表示不产生事件,
1表示每接收一个字符就产生一个事件
SThreshold 0 设定在触发OnComm事件前,发送缓冲区内所允许的最少的字符数,
0表示发送数据时不产生事件,1表示当发送缓冲区空时产生OnComm事件
我们要求能在程序启动的同时就打开串口以便即时对从串口到达的数据进行接收、处理。
一般来说可以将下面
的打开端口的代码写在OnCreate()、OnInitialUpdate()、InitInstance ()等程序入口函数中:
……
if(!
m_Comm.GetPortOpen()) //检测是否已经打开过端口
m_Comm.SetPortOpen(TRUE); //如没有打开则将端口打开
……
接下来的工作就是对数据的发送与接收了,这也是本文所要介绍的重点所在。
发送数据的代码原则上是可以写
到一个成员函数中被直接调用的,但这并不是一个良好的编程习惯:
我们应当把比较耗时的操作,如文件拷贝、打
印、端口传输等工作放到一个单独的线程当中,以避免其在工作时会引起整个进程的阻塞,以提高整个系统对CPU
的利用率。
例如我们可以在视类中菜单或按钮的响应函数中用AfxBeginThread(WriteProc,this)函数来开启一个名
为"WriteProc"的线程,由于在线程中还需要使用视类的函数和变量,为了不产生新的视类的实例对象,我们通过
该函数的第二个参数将指向当前的视类的指针this作为参数传递给线程。
在线程中可以用如下两种方法之中的一种
调用视类的成员函数:
((COLECommView*) pParam)->DoSendProc();
或是:
COLECommView* view=(COLECommView*) pParam;
View->DoSendProc();
其中从pParam传来的变量就是指向视类的指针。
在线程中通过调用视类中的DoSendProc函数来完成对数据的发
送,正是由于该函数是被全局的线程所调用的,我们就不可以使用取编辑框上的数据时通常所用的UpdateData()函
数了,取而带之的是API 函数GetDlgItemText(),取到输入的数据后通过控件的SetOutput() 方法就把数据从串口
发出去了,其中发送数据必须经ColeVariant类将其转换为通用的VARIANT型变量。
实现
主要代码如下:
……
char a[255];
HWND hwnd=GetSafeHwnd();
:
:
GetDlgItemText(hwnd,IDC_EDIT1,a,255);
int i=0;
CString str;
while(a[i]!
='\0')
{
str.Format("%c",a[i]);
m_SendData+=str;
i++;
}
str.Format("%c",10);
m_SendData+=str;
m_Comm.SetOutput(COleVariant(m_SendData));
……
至于数据的接收,我们可以通过让MS Comm控件响应其OnComm事件来完成,通过ClassWizard加入其对事件的响
应后,通过下面的事件映射,当有字符到达时便会通知 OnComm()函数去处理,从而实现数据的异步接收:
……
BEGIN_EVENTSINK_MAP(COLECommView, CFormView)
//{{AFX_EVENTSINK_MAP(COLECommView)
ON_EVENT(COLECommView, IDC_MSCOMM1, 1 /* OnComm */, OnComm, VTS_NONE)
//}}AFX_EVENTSINK_MAP
END_EVENTSINK_MAP()
……
void COLECommView:
:
OnComm()
{
VARIANT Input;
if(m_Comm.GetCommEvent()==2)//接收缓冲区内有字符
{
Input=m_Comm.GetInput();//读取缓冲区内的数据
CString msg=Input.bstrVal;
CString str;
str.Format("%c",10);
if(msg.Right
(1)==str)
{
m_RecvData+=msg;
m_History.AddString(m_RecvData);
m_RecvData="";
}
else
m_RecvData+=msg;
}
}
当数据被接收到接收缓冲区后,对于字符可以从VARIANT型结构变量的bstrVal成员变量中获取,VARIANT数据
结构相当复杂,并牵扯到COM(Component Object Model,组件对象模型)中的一些概念,具体详情请参阅Microsoft
Corpration发布的MSDN中的有关论述。
三、 测试与实验
编译运行程序之前有必要对机器的端口做一番检查,以确保端口的完好,可以用常见的DOS程序Comdebug来检
查。
在确认串口工作正常后,可用串口线将两台机器的串口相连,同时在两台机子上运行该程序,如果没有条件也
可只用一台微机,将其串口的2脚和3脚短接,使其处于自发自收状态。
经过数据的传输实验证明该程序是可靠、正
确的。
小结:
利用通讯控件可以很容易的编写出串行通信程序。
但相对来说通讯控件在VC中的使用要比在VB、Delphi
中复杂的多,要想对串口通讯开发出更多更灵活的使用方法还需要不断的实践中摸索。
本程序在
Windows 98下,由Microsoft Visual C++ 6.0编译通过。
二、直接用VC++访问串口。
在VC++中,串口和磁盘文件可以统一的方式来简单读写。
这两者几乎没有什么不同,只是在WINDOWS 9X下磁盘文件只能做同步访问,而串口只能做异步访问。
CreateFile:
用指定的方式打开指定的串口。
通常的方式为
m_hCom = CreateFile( "COM1", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL );
m_hCom为文件句柄。
GENERIC_READ | GENERIC_WRITE指定可以对串口进行读写操作。
第三个参数0表示串口为独占打开。
OPEN_EXISTING表示当指定串口不存在时,程序将返回失败。
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED则表示文件属性。
当打开串口时,必须指定 FILE_FLAG_OVERLAPPED,它表示文件或设备不会维护访问指针,则在读写时,必须使用OVERLAPPED 结构指定访问的文件偏移量。
ReadFile:
读取串口数据。
WriteFile:
向串口写数据。
CloseHandle:
关闭串口。
COMMTIMEOUTS:
COMMTIMEOUTS主要用于串口超时参数设置。
COMMTIMEOUTS结构如下:
typedef struct _COMMTIMEOUTS {
DWORD ReadIntervalTimeout;
DWORD ReadTotalTimeoutMultiplier;
DWORD ReadTotalTimeoutConstant;
DWORD WriteTotalTimeoutMultiplier;
DWORD WriteTotalTimeoutConstant;
} COMMTIMEOUTS,*LPCOMMTIMEOUTS;
ReadIntervalTimeout:
两字符之间最大的延时,当读取串口数据时,一旦两个字符传输的时间差超过该时间,读取函数将返回现有的数据。
设置为0表示该参数不起作用。
ReadTotalTimeoutMultiplier:
读取每字符间的超时。
ReadTotalTimeoutConstant:
一次读取串口数据的固定超时。
所以在一次读取串口的操作中,其超时为ReadTotalTimeoutMultiplier乘以读取的字节数再加上 ReadTotalTimeoutConstant。
将ReadIntervalTimeout设置为MAXDWORD,并将ReadTotalTimeoutMultiplier 和ReadTotalTimeoutConstant设置为0,表示读取操作将立即返回存放在输入缓冲区的字符。
WriteTotalTimeoutMultiplier:
写入每字符间的超时。
WriteTotalTimeoutConstant:
一次写入串口数据的固定超时。
所以在一次写入串口的操作中,其超时为WriteTotalTimeoutMultiplier乘以写入的字节数再加上 WriteTotalTimeoutConstant。
SetCommTimeouts函数可以设置某设备句柄的超时参数,要得到某设备句柄的超时参数可以用GetCommTimeouts函数。
DCB:
DCB结构主要用于串口参数设置。
该结构太庞大,这里就不一一讲述了,有兴趣者可查看MSDN关于DCB的描述。
其中下面两个是比较重要的属性。
BaudRate:
串口的通讯速度。
一般设置为9600。
ByteSize:
字节位数。
一般设置为8。
DCB结构可以用SetCommState函数来设置,并可以用GetCommState来得到现有串口的属性。
SetupComm:
设置串口输入、输出缓冲区。
OVERLAPPED:
保存串口异步通讯的信息。
具体结构如下:
typedef struct _OVERLAPPED {
DWORD Internal;
DWORD InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
HANDLE hEvent;
} OVERLAPPED;
Internal,InternalHigh是保留给系统使用的,用户不需要设置。
Offset,OffsetHigh是读写串口的偏移量,一般设置OffsetHigh为NULL,可以支持2GB数据。
hEvent读写事件,因为串口是异步通讯,操作可能被其他进程堵塞,程序可以通过检查该时间来得知是否读写完毕。
事件将在读写完成后,自动设置为有效。
通过以上这些函数和结构,我们就可以通过串口进行通讯了,现在我们具体看下面的实例:
BOOL CSerial:
:
Open( int nPort, int nBaud )
{
if( m_bOpened ) return( TRUE );
char szPort[15];
DCB dcb;
wsprintf( szPort, "COM%d", nPort );
m_hComDev = CreateFile( szPort, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL );
if( m_hComDev == NULL ) return( FALSE );
memset( &m_OverlappedRead, 0, sizeof( OVERLAPPED ) );
memset( &m_OverlappedWrite, 0, sizeof( OVERLAPPED ) );
COMMTIMEOUTS CommTimeOuts;
CommTimeOuts.ReadIntervalTimeout = 0xFFFFFFFF;
CommTimeOuts.ReadTotalTimeoutMultiplier = 0;
CommTimeOuts.ReadTotalTimeoutConstant = 0;
CommTimeOuts.WriteTotalTimeoutMultiplier = 0;
CommTimeOuts.WriteTotalTimeoutConstant = 5000;
SetCommTimeouts( m_hComDev, &CommTimeOuts );
m_OverlappedRead.hEvent = CreateEvent( NULL, TRUE, FALSE, NULL );
m_OverlappedWrite.hEvent = CreateEvent( NULL, TRUE, FALSE, NULL );
dcb.DCBlength = sizeof( DCB );
GetCommState( m_hComDev, &dcb );
dcb.BaudRate = nBaud;
dcb.ByteSize = 8;
if( !
SetCommState( m_hComDev, &dcb ) ||
!
SetupComm( m_hComDev, 10000, 10000 ) ||
m_OverlappedRead.hEvent == NULL ||
m_OverlappedWrite.hEvent == NULL ){
DWORD dwError = GetLastError();
if( m_OverlappedRead.hEvent !
= NULL ) CloseHandle( m_OverlappedRead.hEvent );
if( m_OverlappedWrite.hEvent !
= NULL ) CloseHandle( m_OverlappedWrite.hEvent );
CloseHandle( m_hComDev );
return FALSE;
}
m_bOpened = TRUE;
return m_bOpened;
}
int CSerial:
:
InBufferCount( void )
{
if( !
m_bOpened || m_hComDev == NULL ) return( 0 );
DWORD dwErrorFlags;
COMSTAT ComStat;
ClearCommError( m_hIDComDev, &dwErrorFlags, &ComStat );
return (int)ComStat.cbInQue;
}
DWORD CSerial:
:
ReadData( void *buffer, DWORD dwBytesRead)
{
if( !
m_bOpened || m_hComDev == NULL ) return 0;
BOOL bReadStatus;
DWORD dwErrorFlags;
COMSTAT ComStat;
ClearCommError( m_hComDev, &dwErrorFlags, &ComStat );
if( !
ComStat.cbInQue ) return 0;
dwBytesRead = min(dwBytesRead,(DWORD) ComStat.cbInQue);
bReadStatus = ReadFile( m_hComDev, buffer, dwBytesRead, &dwBytesRead, &m_OverlappedRead );
if( !
bReadStatus ){
if( GetLastError() == ERROR_IO_PENDING ){
WaitForSingleObject( m_OverlappedRead.hEvent, 2000 );
return dwBytesRead;
}
return 0;
}
return dwBytesRead;
}
DWORD CSerial:
:
SendData( const char *buffer, DWORD dwBytesWritten)
{
if( !
m_bOpened || m_hComDev == NULL ) return( 0 );
BOOL bWriteStat;
bWriteStat = WriteFile( m_hComDev, buffer, dwBytesWritten, &dwBytesWritten, &m_OverlappedWrite );
if( !
bWriteStat){
if ( GetLastError() == ERROR_IO_PENDING ) {
WaitForSingleObject( m_OverlappedWrite.hEvent, 1000 );
return dwBytesWritten;
}
return 0;
}
return dwBytesWritten;
}
上述函数基本实现串口的打开,读写操作。
本文章略去该串口类的说明和关闭函数。
读者应该能将这些内容写完。
接下来,你就可以在你的程序中调用该串口类了。
一、Microsoft Communications Control
Microsoft公司在WINDOWS中提供了一个串口通讯控件,用它,我们可以很简单的利用串口进行通讯。
在使用它之前,应将控件加在应用程序的对话框上。
然后再用ClassWizard 生成相应的对象。
现在我们可以使用它了。
该控件有很多自己的属性,你可以通过它的属性窗口来设置,也可以用程序设置。
我推荐用程序设置,这样更灵活。
SetCommPort:
指定使用的串口。
GetCommPort:
得到当前使用的串口。
SetSettings:
指定串口的参数。
一般设为默认参数"9600,N,8,1"。
这样方便与其他串口进行通讯。
GetSettings:
取得串口参数。
SetPortOpen:
打开或关闭串口,当一个程序打开串口时,另外的程序将无法使用该串口。
GetPo