《VC串口编程了解.doc》由会员分享,可在线阅读,更多相关《VC串口编程了解.doc(14页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、用 VC 6.0 实现串行通信的三种方法在实验室和工业应用中,串口是常用的计算机与外部串行设备之间的数据传输通道,由于串行通信方便易行,所以应用广泛。依据不同的条件实现对串口的灵活编程控制是我们所需要的。 - 在光学镜片镀膜工艺中,用单片机进行多路温度数据采集控制,采集结果以串行方式进入主机,每隔10秒向主机发送一次采样数据,主机向单片机发送相关的控制命令,实现串行数据接收、处理、记录、显示,实时绘制曲线。串行通信程序开发环境为 VC 6.0。 Windows下串行通信 - 与以往 DOS 下串行通信程序不同的是, 窗口 不提倡应用程序直接控制硬件,而是通过 窗口 操作系统提供的设备驱动程序来
2、进行数据传递。串行口在WIN 32 中是作为文件来进行处理的,而不是直接对端口进行操作,对于串行通信, WIN 32 提供了相应的文件 I/O 函数与通信函数,通过了解这些函数的使用,可以编制出符合不同需要的通信程序。 -与通信设备相关的结构有COMMCONFIG、 COMMPROP、COMMTIMEOUTS、COMSTAT、DCB、MODEMDEVCAPS、 MODEMSETTINGS共7个,与通信有关的Windows API函数共有26个,详细说明可参考 MSDN帮助文件。以下将结合实例,给出实现串行通信的三种方法。 实现串行通信的三种方法 - 方法一:使用 VC 提供的串行通信控件 MS
3、Comm -首先,在对话框中创建通信控件,若Control工具栏中缺少该控件,可通过菜单ProjectAdd to ProjectComponents and Control插入即可,再将该控件从工具箱中拉到对话框中。此时,你只需要关心控件提供的对 Windows 通信驱动程序的 API 函数的接口。换句话说,只需要设置和监视MSComm控件的属性和事件。 -在ClassWizard中为新创建的通信控件定义成员对象(CMSComm m_Serial),通过该对象便可以对串口属性进行设置,MSComm 控件共有27 个属性,这里只介绍其中几个常用属性: CommPort 设置并返回通信端口号,缺
4、省为 COM1。 Settings 以字符串的形式设置并返回波特 率、奇偶校验、数据位、停止位。 PortOpen 设置并返回通信端口的状态,也可 以打开和关闭端口。 Input 从接收缓冲区返回和删除字符。 Output 向发送缓冲区写一个字符串。 InputLen 设置每次Input读入的字符个数,缺 省值为0,表明读取接收缓冲区中的全 部内容。 InBufferCount 返回接收缓冲区中已接收到的字符 数,将其置0可以清除接收缓冲区。 InputMode 定义Input属性获取数据的方式(为 0:文本方式;为1:二进制方式)。 -RThreshold 和 SThreshold 属性,表
5、示在 OnComm 事件发生之前,接收缓冲区或发送缓冲区中可以接收的字符数。 - 以下是通过设置控件属性对串口进行初始化的实例: BOOLCSampleDlg: PortOpen () BOOLm_Opened; m_Serial.SetCommPort(2);/指定串口号 m_Serial.SetSettings(“4800,N,8,1); /通信参数设置 m_Serial.SetInBufferSize(1024);/指定接收缓冲区大小 m_Serial.SetInBufferCount(0);/清空接收缓冲区 m_Serial.InputMode(1);/设置数据获取方式 m_Seria
6、l.SetInputLen(0);/设置读取方式 & nbsp ;& nbsp ; m_Opened=m_Serail.SetPortOpen(1); /打开指定的串口 回来m_Opened; - 空的CSampleDlg:OnComm () & nbsp ; switch(m_Serial.GetCommEvent() 盒子2: /串行口数据接收,处理; - 方法二:在单线程中实现自定义的串口通信类 -控件简单易用,但由于必须拿到对话框中使用,在一些需要在线程中实现通信的应用场合,控件的使用显得捉襟见肘。此时,若能够按不同需要定制灵活的串口通信类将弥补控件的不足,以下将介绍如何在单线程中建立
7、自定义的通信类。 -该通信类CSimpleComm需手动加入头文件与源文件,其基类为CObject,大致建立步骤如下: -(1) 打开串口,获取串口资源句柄 -通信程序从CreateFile处指定串口设备及相关的操作属性,再返回一个句柄,该句柄将被用于后续的通信操作,并贯穿整个通信过程。CreateFile() 函数中有几个值得注意的参数设置:串口共享方式应设为0,串口为不可共享设备;创建方式必须为OPEN_EXISTING,即打开已有的串口。对于dwFlagAndAttribute参数,对串口有意义的值是FILE_FLAG_OVERLAPPED,该标志表明串口采用异步通信模式,可进行重叠操作
8、;若值为NULL,则为同步通信方式,在同步方式下,应用程序将始终控制程序流,直到程序结束,若遭遇通信故障等因素,将导致应用程序的永久等待,所以一般多采用异步通信。 -(2)串口设置 - BOOL & nbsp ; CSimpleComm:打开 () DCB dcb; m_hIDComDev=CreateFile ( “COM2, GENERIC_READ | GENERIC_WRITE,0, 空,打开_现有 ,文件_ATTRIBUTE_NORMAL | 文件_FLAG_OVE & nbsp ;& nbsp ;& nbsp ; RLAPPED ,空);& nbsp ;& nbsp ;& nbsp
9、 ;& nbsp ;& nbsp ;& nbsp ; / 打开串口,异步操作 如果( m_hIDComDev =NULL )回来(假); & nbsp ;& nbsp ;& nbsp ; dcb.DCBlength = sizeof ( DCB ); GetCommState( m_hIDComDev, dcb ); /获得端口默认设置 & nbsp ;& nbsp ; dcb.BaudRate=CBR_4800; & nbsp ;& nbsp ; dcb.ByteSize=8; & nbsp ;& nbsp ; dcb.Parity= NOPARITY ; & nbsp ;& nbsp ;
10、dcb.StopBits=(BYTE) ONESTOPBIT ; -(3)串口读写操作 -主要运用ReadFile()与WriteFile()API函数,若为异步通信方式,两函数中最后一个参数为指向OVERLAPPED结构的非空指针,在读写函数返回值为FALSE的情况下,调用GetLastError()函数,返回值为ERROR_IO_PENDING,表明I/O操作悬挂,即操作转入后台继续执行。此时,可以用WaitForSingleObject()来等待结束信号并设置最长等待时间。举例如下: BOOLbReadStatus; bReadStatus = ReadFile ( m_hIDComDe
11、v ,缓冲区, dwBytesRead, dwBytesRead,m_OverlappedRead ); 如果(! bReadStatus ) 如果( GetLastError () =ERROR_IO_PENDING ) & nbsp ;& nbsp ; WaitForSingleObject(m_OverlappedRead.hEvent,1000); 回来( int ) dwBytesRead ); 回来(0); 回来( int ) dwBytesRead ); -定义全局变量m_Serial作为新建通信类CSimpleComm的对象,通过调用类的成员函数即可实现所需串行通信功能。与方法
12、一相比,方法二赋予串行通信程序设计较大的灵活性,端口的读写可选择较简单的查询式,或通过设置与外设数据发送时间间隔TimeCycle相同的定时器:SetTimer(1,TimeCycle,NULL),进行定时读取或发送。 CSampleView: OnTimer ( UINT nIDEvent ) charInputData 30; & nbsp ;& nbsp ;& nbsp ;& nbsp ;& nbsp ;& nbsp ; m_Serial.ReadData(InputData,30); / 数据处理 -若对端口数据的响应时间要求较严格,可采用事件驱动 I/O读写,Windows定义了9种
13、串口通信事件,较常用的有: EV_RXCHAR: 接收到一个字节,并放入输入 缓冲区。 EV_TXEMPTY: 输出缓冲区中的最后一个字 符发送出去。 EV_RXFLAG: 接收到事件字符(DCB结构中 EvtChar成员),放入输入缓冲区。 -在用SetCommMask()指定了有用的事件后,应用程序可调用WaitCommEvent()来等待事件的发生。SetCommMask(hComm,0)可使WaitCommEvent() 中止。 - 方法三:多线程下实现串行通信 - -线程的基本概念可详见VC参考书目,Windows内部的抢先调度程序在活动的线程之间分配CPU时间,Win 32 区分两
14、种不同类型的线程,一种是用户界面线程UI(User Interface Thread),它包含消息循环或消息泵,用于处理接收到的消息;另一种是工作线程(Work Thread),它没有消息循环,用于执行后台任务。用于监视串口事件的线程即为工作线程。 -多线程通信类的编写在端口的配置,连接部分与单线程通信类相同,在端口配置完毕后,最重要的是根据实际情况,建立多线程之间的同步对象,如信号灯、临界区、事件等,相关细节可参考VC 中的同步类。 -一切就绪后即可启动工作线程: CWinThrea CommThread = AfxBeginThread(CommWatchThread, / 线程函数名 (
15、LPVOID) m_pTTYInfo, / 传递的参数 THREAD_优先权_ABOVE_NORMAL, / 设置线程优先级 (UINT) 0,/最大堆栈大小 (DWORD) CREATE_SUSPENDED, /创建标志 (LPSECURITY_ATTRIBUTES) NULL);/安全性标志 - 同时,在串口事件监视线程中: if(WaitCommEvent(pTTYInfoidComDev, dwEvtMask,NULL) if(dwEvtMask pTTYInfodwEvtMask ) = pTTYInfodwEvtMask) WaitForSingleObject(pTTYInfoh
16、PostEvent,0xFFFFFFFF); ResetEvent(pTTYInfohPostEvent); / 置同步事件对象为非信号态 : PostMessage ( CSampleView , ID_COM1_DATA ,0,0); / 发送通知消息 -用PostMessage()向指定窗口的消息队列发送通知消息,相应地,需要在该窗口建立消息与成员函数间的映射,用ON_MESSAGE将消息与成员函数名关联。 BEGIN_MESSAGE_MAP ( CSampleView , CView ) / AFX_MSG_MAP ( CSampleView ) ON_MESSAGE ( ID_COM
17、1_DATA , OnProcessCom1Data ) ON_MESSAGE ( ID_COM2_DATA , OnProcessCom2Data ) / AFX_MSG_MAP END_MESSAGE_MAP () -然后在各成员函数中完成对各串口数据的接收处理,但必须保证在下一次监测到有数据到来之前,能够完成所有的中间处理工作,否则将造成数据的捕捉错误。 -多线程的实现可以使得各端口独立,准确地实现串行通信,使串口通信具有更广泛的灵活性与严格性,且充分利用了CPU时间。但在具体的实时监控系统中如何协调多个线程,线程之间以何种方式实现同步也是在多线程串行通信程序实现的难点。 - 以 VC
18、6.0 为工具,实现串行通信的三种方法各有利弊VC+ 简单而强大 的多线程串口编程工具 CserialPort类一个串口类, 已作了一个基于对话框的同时检测4个串口示例的程序,在这儿主要介绍如何将这个类应用到VC中基于文档的程序中。为了加深对串口数据处理的了解,我们利用这个类解决如下问题: 问题: 串口2(COM2)每隔1秒向串口1(COM1)发送的NEMA格式的报文:串头为$,串尾为,中间为一个xxxx的整数( 比如2345,不足4位则前面以0代替代),最后是hh校验,规定hh为xxxx四个数的半BYTE校验和,最后加上回车与换行。整个数据包为$xxxx*hh。 串口1收到上述报文后,校验正
19、确后,将发来的数据显示在视窗中,并记下发来的正确帧数和错误帧数,若正确,还向串口2发送Y,串口2收到Y后将收到的Y的计数显示在视窗中。 测试方法: 将三线制串口线联接上同一台计算机的两个串口,编好程序后就可测试。如果没有两个串口的微机,自己改改程序。 好了,你可以先下载源程序: scporttest.zip(大小:49KB,VC6,WIN9X/2000,SerialPort.h SerialPort.cpp是两个类文件) 编程步骤: 1. 建立程序: 建立一个基于单文档的MFC应用程序SCPortTest,所有步骤保持缺省状态。 2. 添加类文件: 将SerialPort.h SerialPo
20、rt.cpp两个类文件复制到工程文件夹中,用Project-Add to Project-Files命令将上述两个文件加入工程。并在SCPortTestView.h中将头文件SerialPort.h说明:#include SerialPort.h。 3. 人工增加串口消息响应函数:OnCommunication(WPARAM ch, LPARAM port) 首先在SCPortTestView.h中添加串口字符接收消息WM_COMM_RXCHAR(串口接收缓冲区内有一个字符)的响应函数声明: / AFX_MSG ( CSCPortTestView ) afx_msg长的 OnCommunica
21、tion ( WPARAM ch , LPARAM端口 ); / AFX_MSG 然后在SCPortTestView.cpp文件中进行WM_COMM_RXCHAR消息映射: BEGIN_MESSAGE_MAP ( CSCPortTestView , CView ) / AFX_MSG_MAP ( CSCPortTestView ) ON_MESSAGE ( WM_COMM_RXCHAR , OnCommunication ) / AFX_MSG_MAP END_MESSAGE_MAP () 接着在SCPortTestView.cpp中加入函数的实现: 长的 CSCPortTestView:On
22、Communication ( WPARAM ch , LPARAM端口 ) . 注意:由于这个串口类加入工程后,没有自动的消息映射机制,因此上述步骤均需要手工添加。 4 初始化串口 在视创建时初始化串口,首先利用ClassWizardr按下图生成OnInitialUpdate()函数。 接着在SerialPort.h文件中说明我们在程序中要用到的全局变量: 保存两个串口接收数据: char m_chChecksum;/用于COM1的校验和计算 CString m_strRXhhCOM1;/用于存放COM1接收的半BYTE校验字节hh CString m_strRXDataCOM1;/COM1
23、接收数据 CString m_strRXDataCOM2;/COM2接收数据 UINT m_nRXErrorCOM1;/COM1接收数据错误帧数 UINT m_nRXErrorCOM2;/COM2接收数据错误帧数 UINT m_nRXCounterCOM1;/COM1接收数据错误帧数 UINT m_nRXCounterCOM2;/COM2接收数据错误帧数CString 再在SerialPort.h文件中说明串口类对象:CSerailPort m_ComPort2;(public)。 因为要初始化2个串口,所以这里用了数组。 下面是初始化串口1和串口2: 空的 CSCPortTestView:O
24、nInitialUpdate () CView:OnInitialUpdate (); / TODO :添加你的这里的专业化的代码并且/或呼叫底班 m_chChecksum=0;/校验和置0 m_nRXErrorCOM1=0;/COM1接收数据错误帧数置0 m_nRXErrorCOM2=0;/COM2接收数据错误帧数置0 m_nRXCounterCOM1=0;/COM1接收数据错误帧数置0 m_nRXCounterCOM2=0;/COM2接收数据错误帧数置0 m_strRXhhCOM1.Empty();/清空半BYTE校验hh存储变量 适合于 ( int i=0;我2;i+) 如果 (m_Co
25、mPorti.InitPort(this,i+1,9600,N,8,1,EV_RXFLAG | EV_RXCHAR ,512) /portnr=1(2),baud=960,parity=N,databits=8,stopsbits=1, / dwCommEvents=EV_RXCHAR | EV_RXFLAG ,nBufferSize=512 m_ComPorti.StartMonitoring();/启动串口监视线程 if(i=1) SetTimer(1,1000,NULL);/设置定时器,1秒后发送数据 另外 CString str; str.Format(COM%d 没有发现,或被其它设
26、备占用,i+1); AfxMessageBox ( str ); 5 利用ClassWizard按下图生成CSCPortTestView 的时间消息WM_TIMER响应函数: 空的 CSCPortTestView:OnTimer ( UINT nIDEvent ) / TODO :添加你的这里的消息处理器代码并且/或呼叫默认 int randdata=rand()%9000;/产生9000以内的随机数 CString strSendData; strSendData.Format(%04d,randdata); SendString(strSendData, 2);/串口2发送数据; CVie
27、w:OnTimer ( nIDEvent ); 上面用到的SendString()需按如下方式生成: 在ClassView中单击鼠标右键,在环境菜单中选择Add Member Function: 空的 CSCPortTestView:SendString ( CString & str , int端口 ) char checksum=0, cr=CR , lf=LF; char c1,c2; 适合于 ( int i=0;i4)&0x0f ); 如果(c110)c1+=“0;另外的c1+=一-10; 如果(c2TextOut(10,50,strtemp);/显示接收到的数据 ReleaseDC
28、( pDC ); if(port=1) /COM1接收到数据 m_strRXDataCOM1+=( char ) ch; 切换( ch ) 大小写“$”: m_chChecksum=0;/开始计算CheckSum flag=0; 打破; 大小写“*”: flag=2; c2=m_chChecksum&0x0f;c1=( m_chChecksum4)&0x0f ); 如果(c110)c1+=“0;另外的c1+=一-10; 如果(c20) m_strRXhhCOM1 += ch;/得到收到的校验值hh 如果(flag=1) strCheck = strCheck+c1+c2;/计算得到的校验值hh
29、 if(strCheck!=m_strRXhhCOM1) /如果校验有错 m_strRXDataCOM1.Empty(); m_nRXErrorCOM1+;/串口1错误帧数加1 另外 m_nRXCounterCOM1+; if(m_strRXDataCOM1.Left(1)=$) /接收数据的第一个字符是$吗? char tbuf 6; char *temp=( char *)( LPCTSTR )m_strRXDataCOM1); tbuf 0 =temp 1;tbuf 1 =temp 2; tbuf 2 =temp 3;tbuf 3 =temp 4; tbuf4=0;/0表示字符串的结束,
30、必要 int data=atoi ( tbuf ); CString strDisplay1,strDisplay2; strDisplay1.Format(NO. % 06d: reseived数据是% 04d & quot ;,m_nRXCounterCOM1,数据); strDisplay2.Format(Error Counter=%04d & quot ;,m_nRXErrorCOM1); CDC* pDC=GetDC();/准备数据显示 / int nColor=pDC-SetTextColor ( RGB (255,255,0); pDC-TextOut(10,10,strDisplay1);/显示接收到的数据 pDC-TextOut(30,30,strDisplay2);/显示错误帧数 /pDC-SetTextColor ( nColor ); ReleaseDC ( pDC ); CString str1=Y; m_ComPort0.WriteToPort(LPCTSTR)str1);/发送应答信号Y m_strRXhhCOM1.Empty(); 旗帜-; 另外 m_chChecksum = ch; 打破; 回来0;