各位大侠,小弟因要做毕业设计,需要一些有关于用VC++实现串口通信的资料,最好是源代码,不只那位大虾肯帮帮忙,送我一些关于此的源代码,或者告诉我哪里有可以下载,小弟在这里感激不尽!急呀!请各位帮忙!
可发邮件至:[email protected]
可发邮件至:[email protected]
解决方案 »
- 双向链表删除节点时,free该节点报错。高手帮忙!malloc申请的空间
- 面向对象编程需要操作系统支持吗?
- 类似Kodak Capture的小程序,一次同时显示8幅图像,效率问题。
- VC的DLL组件在ASP服务器上两三个小时后显示overflow错误,是怎么回事?
- 倾家荡产,露宿街头,乞讨要饭,只求一解!
- 请教一个替换字串2端的正则表达式
- SOCKET做同步的通信问题
- 如何得到窗体被覆盖或是移出屏幕的消息?
- 请大家帮我看看:我写的关于IE控件的程序总是出内存错误!
- 如何从16bits的位图得到灰度,并且显示?
- PeekMessage和GetMessage简单问题
- windows不是不在产生窗口时自动产生一个WM_SIZE消息???
[email protected]
2002-04-09· ·郎锐··YESKY
1 2 下一页
摘要:本文介绍了在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编译通过。
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;} 上述函数基本实现串口的打开,读写操作。本文章略去该串口类的说明和关闭函数。读者应该能将这些内容写完。接下来,你就可以在你的程序中调用该串口类了。
{
protected:
VARIANT InBuffer;
VARIANT OutBuffer;
CMSComm m_Com;
public:
......
}BOOL CMyDiaLog::OnInitDialog()
{
CDialog::OnInitDialog();
m_Com.SetCommPort(1);
if (!m_Com.GetPortOpen()) {
m_Com.SetSettings("57600,N,8,1");
m_Com.SetPortOpen(true);
m_Com.SetInBufferCount(0);
SetTimer(1,10,NULL);
InBuffer.bstrVal=new unsigned short[MESSAGELENGTH];
OutBuffer.bstrVal=new unsigned short[MESSAGELENGTH];
OutBuffer.vt=VT_BSTR;
}
return true;
}void CMyDiaLog::OnTimer(UINT nIDEvent)
{
if (m_Com.GetInBufferCount()>=MESSAGELENGTH) {
InBuffer=m_Com.GetInput();
// handle the InBuffer.
// Fill the OutBuffer.
m_Com.SetOutput(OutBuffer);
}
CDialog::OnTimer(nIDEvent);
} 用该控件传输的数据是UNICODE格式。关于UNICODE和ANSI的关系和转换请参看MSDN。 关于该控件的其他详细资料请查看MSDN关于COMM CONTROL部分。