VC串口通信实例_第1页
VC串口通信实例_第2页
VC串口通信实例_第3页
VC串口通信实例_第4页
VC串口通信实例_第5页
已阅读5页,还剩24页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

12. 4 一个通信演示程序为了使读者更好地掌握本章的概念,这里举一个具体实例来说明问题。如图12.1所示,例子程序名为Terminal,是一个简单的TTY终端仿真程序。读者可以用该程序打开一个串行口,该程序会把用户的键盘输入发送给串行口,并把从串口接收到的字符显示在视图中。用户通过选择File-Connect命令来打开串行口,选择File-Disconnect命令则关闭串行口。图12.1 Terminal终端仿真程序当用户选择File-Settings.命令时,会弹出一个Communication settings对话框,如图12.2所示。该对话框主要用来设置串行口,包括端口、波特率、每字节位数、校验、停止位数和流控制。图12.2 Communication settings对话框通过该对话框也可以设置TTY终端仿真的属性,如果选择New Line(自动换行),那么每当从串口读到回车符(r)时,视图中的正文就会换行,否则,只有在读到换行符(n)时才会换行。如果选择Local echo(本地回显),那么发送的字符会在视图中显示出来。终端仿真程序的特点是数据的传输没有规律。因为键盘输入速度有限,所以发送的数据量较小,但接收的数据源是不确定的,所以有可能会有大量数据高速涌入的情况发生。根据Terminal的这些特性,我们在程序中创建了一个辅助工作者线程专门来监视串行口的输入。由于写入串行口的数据量不大,不会太费时,所以在主线程中完成写端口的任务是可以的,不必另外创建线程。现在就让我们开始工作。请读者按下面几步进行:用AppWizard建立一个名为Terminal的MFC应用程序。在MFC AppWizard对话框的第1步选择Single document,在第4步去掉Docking toolbar的选择,在第6步把CTerminalView的基类改为CEditView。在Terminal工程的资源视图中打开IDR_MAINFRAME菜单资源。去掉Edit菜单和View菜单,并去掉File菜单中除Exit以外的所有菜单项。然后在File菜单中加入三个菜单项,如表12.5所示。表12.5 新菜单项标题 ID Settings. ID_FILE_SETTINGS Connect ID_FILE_CONNECT Disconnect ID_FILE_DISCONNECT 用ClassWizard为CTerminalDoc类创建三个与上表菜单消息对应的命令处理函数,使用缺省的函数名。为ID_FILE_CONNECT和ID_FILE_DISCONNECT命令创建命令更新处理函数。另外,用ClassWizard为该类加入CanCloseFrame成员函数。用ClassWizard为CTerminalView类创建OnChar函数,该函数用来把用户键入的字符向串行口输出。新建一个对话框模板资源,令其ID为IDD_COMSETTINGS。请按图12.2和表12.6设计对话框模板。表12.6 通信设置对话框中的主要控件控件 ID 属性设置 Base options组框 缺省 标题为Base options Port组合框 IDC_PORT Drop List,不选Sort,初始列表为COM1、COM2、COM3、COM4 Baud rate组合框 IDC_BAUD Drop List,不选Sort,初始列表为300、600、1200、2400、9600、14400、19200、38400、57600 Data bits组合框 IDC_DATABITS Drop List,不选Sort,初列表为5、6、7、8 Parity组合框 IDC_PARITY Drop List,不选Sort,初列表为None、Even、Odd Stop bits组合框 IDC_STOPBITS Drop List,不选Sort,初列表为1、1.5、2 Flow control组框 缺省 标题为Flow control None单选按钮 IDC_FLOWCTRL 标题为None,选择Group属性 RTS/CTS单选按钮 缺省 标题为RTS/CTS XON/XOFF单选按钮 缺省 标题为XON/XOFF TTY options组框 缺省 标题为TTY options New line检查框 IDC_NEWLINE 标题为New line Local echo检查框 IDC_ECHO 标题为Local echo 打开ClassWizard,为IDD_COMSETTINGS模板创建一个名为CSetupDlg的对话框类。为该类加入OnInitDialog成员函数,并按表12.7加入数据成员。表12.7 CSetupDlg类的数据成员控件ID 变量名 数据类型 IDC_BAND m_sBaud CString IDC_DATABITS m_sDataBits CString IDC_ECHO m_bEcho BOOL IDC_FLOWCTRL m_nFlowCtrl int IDC_NEWLINE m_bNewLine BOOL IDC_PARITY m_nParity int IDC_PORT m_sPort CString IDC_STOPBITS m_nStopBits int 按清单12.6、12.7和12.8修改程序。清单12.6列出了CTerminalDoc类的部分代码,清单12.7是CTerminalView的部分代码,清单12.8是CSetupDlg类的部分代码。在本例中使用了WM_COMMNOTIFY消息。虽然在Win32中,WM_COMMNOTIFY消息已经取消,系统自己不会产生该消息,但Visual C+对该消息的定义依然保留。考虑到使用习惯,Terminal程序辅助线程通过发送该消息来通知视图有通信事件发生。清单12.6 CTerminalDoc类的部分代码/ TerminalDoc.h : interface of the CTerminalDoc class/#define MAXBLOCK 2048#define XON 0x11#define XOFF 0x13UINT CommProc(LPVOID pParam);class CTerminalDoc : public CDocumentprotected: / create from serialization onlyCTerminalDoc();DECLARE_DYNCREATE(CTerminalDoc)/ Attributespublic:CWinThread* m_pThread; / 代表辅助线程volatile BOOL m_bConnected;volatile HWND m_hTermWnd;volatile HANDLE m_hPostMsgEvent; / 用于WM_COMMNOTIFY消息的事件对象OVERLAPPED m_osRead, m_osWrite; / 用于重叠读/写volatile HANDLE m_hCom; / 串行口句柄int m_nBaud;int m_nDataBits;BOOL m_bEcho;int m_nFlowCtrl;BOOL m_bNewLine;int m_nParity;CString m_sPort;int m_nStopBits;/ Operationspublic:BOOL ConfigConnection();BOOL OpenConnection();void CloseConnection();DWORD ReadComm(char *buf,DWORD dwLength);DWORD WriteComm(char *buf,DWORD dwLength);/ Overrides. . .;/ TerminalDoc.cpp : implementation of the CTerminalDoc class/#include SetupDlg.hCTerminalDoc:CTerminalDoc()/ TODO: add one-time construction code herem_bConnected=FALSE;m_pThread=NULL;m_nBaud = 9600;m_nDataBits = 8;m_bEcho = FALSE;m_nFlowCtrl = 0;m_bNewLine = FALSE;m_nParity = 0;m_sPort = COM2;m_nStopBits = 0;CTerminalDoc:CTerminalDoc()if(m_bConnected)CloseConnection();/ 删除事件句柄if(m_hPostMsgEvent)CloseHandle(m_hPostMsgEvent);if(m_osRead.hEvent)CloseHandle(m_osRead.hEvent);if(m_osWrite.hEvent)CloseHandle(m_osWrite.hEvent);BOOL CTerminalDoc:OnNewDocument()if (!CDocument:OnNewDocument()return FALSE;(CEditView*)m_viewList.GetHead()-SetWindowText(NULL);/ TODO: add reinitialization code here/ (SDI documents will reuse this document)/ 为WM_COMMNOTIFY消息创建事件对象,手工重置,初始化为有信号的if(m_hPostMsgEvent=CreateEvent(NULL, TRUE, TRUE, NULL)=NULL)return FALSE;memset(&m_osRead, 0, sizeof(OVERLAPPED);memset(&m_osWrite, 0, sizeof(OVERLAPPED);/ 为重叠读创建事件对象,手工重置,初始化为无信号的if(m_osRead.hEvent=CreateEvent(NULL, TRUE, FALSE, NULL)=NULL)return FALSE;/ 为重叠写创建事件对象,手工重置,初始化为无信号的if(m_osWrite.hEvent=CreateEvent(NULL, TRUE, FALSE, NULL)=NULL)return FALSE;return TRUE;void CTerminalDoc:OnFileConnect() / TODO: Add your command handler code hereif(!OpenConnection()AfxMessageBox(Cant open connection);void CTerminalDoc:OnFileDisconnect() / TODO: Add your command handler code hereCloseConnection(); void CTerminalDoc:OnUpdateFileConnect(CCmdUI* pCmdUI) / TODO: Add your command update UI handler code herepCmdUI-Enable(!m_bConnected);void CTerminalDoc:OnUpdateFileDisconnect(CCmdUI* pCmdUI) / TODO: Add your command update UI handler code herepCmdUI-Enable(m_bConnected);/ 打开并配置串行口,建立工作者线程BOOL CTerminalDoc:OpenConnection()COMMTIMEOUTS TimeOuts;POSITION firstViewPos;CView *pView;firstViewPos=GetFirstViewPosition();pView=GetNextView(firstViewPos);m_hTermWnd=pView-GetSafeHwnd();if(m_bConnected)return FALSE;m_hCom=CreateFile(m_sPort, GENERIC_READ | GENERIC_WRITE, 0, NULL,OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL); / 重叠方式if(m_hCom=INVALID_HANDLE_VALUE)return FALSE;SetupComm(m_hCom,MAXBLOCK,MAXBLOCK);SetCommMask(m_hCom, EV_RXCHAR);/ 把间隔超时设为最大,把总超时设为0将导致ReadFile立即返回并完成操作TimeOuts.ReadIntervalTimeout=MAXDWORD; TimeOuts.ReadTotalTimeoutMultiplier=0; TimeOuts.ReadTotalTimeoutConstant=0; /* 设置写超时以指定WriteComm成员函数中的GetOverlappedResult函数的等待时间*/TimeOuts.WriteTotalTimeoutMultiplier=50; TimeOuts.WriteTotalTimeoutConstant=2000;SetCommTimeouts(m_hCom, &TimeOuts);if(ConfigConnection()m_pThread=AfxBeginThread(CommProc, this, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED, NULL); / 创建并挂起线程if(m_pThread=NULL)CloseHandle(m_hCom);return FALSE;elsem_bConnected=TRUE;m_pThread-ResumeThread(); / 恢复线程运行elseCloseHandle(m_hCom);return FALSE;return TRUE;/ 结束工作者线程,关闭串行口void CTerminalDoc:CloseConnection()if(!m_bConnected) return;m_bConnected=FALSE;/结束CommProc线程中WaitSingleObject函数的等待SetEvent(m_hPostMsgEvent); /结束CommProc线程中WaitCommEvent的等待SetCommMask(m_hCom, 0); /等待辅助线程终止WaitForSingleObject(m_pThread-m_hThread, INFINITE);m_pThread=NULL;CloseHandle(m_hCom);/ 让用户设置串行口void CTerminalDoc:OnFileSettings() / TODO: Add your command handler code hereCSetupDlg dlg;CString str;dlg.m_bConnected=m_bConnected;dlg.m_sPort=m_sPort;str.Format(%d,m_nBaud);dlg.m_sBaud=str;str.Format(%d,m_nDataBits);dlg.m_sDataBits=str;dlg.m_nParity=m_nParity;dlg.m_nStopBits=m_nStopBits;dlg.m_nFlowCtrl=m_nFlowCtrl;dlg.m_bEcho=m_bEcho;dlg.m_bNewLine=m_bNewLine;if(dlg.DoModal()=IDOK)m_sPort=dlg.m_sPort;m_nBaud=atoi(dlg.m_sBaud);m_nDataBits=atoi(dlg.m_sDataBits);m_nParity=dlg.m_nParity;m_nStopBits=dlg.m_nStopBits;m_nFlowCtrl=dlg.m_nFlowCtrl;m_bEcho=dlg.m_bEcho;m_bNewLine=dlg.m_bNewLine;if(m_bConnected)if(!ConfigConnection()AfxMessageBox(Cant realize the settings!);/ 配置串行口BOOL CTerminalDoc:ConfigConnection()DCB dcb;if(!GetCommState(m_hCom, &dcb)return FALSE;dcb.fBinary=TRUE;dcb.BaudRate=m_nBaud; / 波特率dcb.ByteSize=m_nDataBits; / 每字节位数dcb.fParity=TRUE;switch(m_nParity) / 校验设置case 0: dcb.Parity=NOPARITY;break;case 1: dcb.Parity=EVENPARITY;break;case 2: dcb.Parity=ODDPARITY;break;default:;switch(m_nStopBits) / 停止位case 0: dcb.StopBits=ONESTOPBIT;break;case 1: dcb.StopBits=ONE5STOPBITS;break;case 2: dcb.StopBits=TWOSTOPBITS;break;default:;/ 硬件流控制设置dcb.fOutxCtsFlow=m_nFlowCtrl=1;dcb.fRtsControl=m_nFlowCtrl=1?RTS_CONTROL_HANDSHAKE:RTS_CONTROL_ENABLE;/ XON/XOFF流控制设置dcb.fInX=dcb.fOutX=m_nFlowCtrl=2;dcb.XonChar=XON;dcb.XoffChar=XOFF;dcb.XonLim=50;dcb.XoffLim=50;return SetCommState(m_hCom, &dcb);/ 从串行口输入缓冲区中读入指定数量的字符DWORD CTerminalDoc:ReadComm(char *buf,DWORD dwLength)DWORD length=0;COMSTAT ComStat;DWORD dwErrorFlags;ClearCommError(m_hCom,&dwErrorFlags,&ComStat);length=min(dwLength, ComStat.cbInQue);ReadFile(m_hCom,buf,length,&length,&m_osRead);return length;/ 将指定数量的字符从串行口输出DWORD CTerminalDoc:WriteComm(char *buf,DWORD dwLength)BOOL fState;DWORD length=dwLength;COMSTAT ComStat;DWORD dwErrorFlags;ClearCommError(m_hCom,&dwErrorFlags,&ComStat);fState=WriteFile(m_hCom,buf,length,&length,&m_osWrite);if(!fState)if(GetLastError()=ERROR_IO_PENDING)GetOverlappedResult(m_hCom,&m_osWrite,&length,TRUE);/ 等待elselength=0;return length;/ 工作者线程,负责监视串行口UINT CommProc(LPVOID pParam)OVERLAPPED os;DWORD dwMask, dwTrans;COMSTAT ComStat;DWORD dwErrorFlags;CTerminalDoc *pDoc=(CTerminalDoc*)pParam;memset(&os, 0, sizeof(OVERLAPPED);os.hEvent=CreateEvent(NULL, TRUE, FALSE, NULL);if(os.hEvent=NULL)AfxMessageBox(Cant create event object!);return (UINT)-1;while(pDoc-m_bConnected)ClearCommError(pDoc-m_hCom,&dwErrorFlags,&ComStat);if(ComStat.cbInQue)/ 无限等待WM_COMMNOTIFY消息被处理完WaitForSingleObject(pDoc-m_hPostMsgEvent, INFINITE);ResetEvent(pDoc-m_hPostMsgEvent);/ 通知视图PostMessage(pDoc-m_hTermWnd, WM_COMMNOTIFY, EV_RXCHAR, 0);continue;dwMask=0;if(!WaitCommEvent(pDoc-m_hCom, &dwMask, &os) / 重叠操作if(GetLastError()=ERROR_IO_PENDING)/ 无限等待重叠操作结果GetOverlappedResult(pDoc-m_hCom, &os, &dwTrans, TRUE);elseCloseHandle(os.hEvent);return (UINT)-1;CloseHandle(os.hEvent);return 0;BOOL CTerminalDoc:CanCloseFrame(CFrameWnd* pFrame) / TODO: Add your specialized code here and/or call the base classSetModifiedFlag(FALSE); / 将文档的修改标志设置成未修改return CDocument:CanCloseFrame(pFrame);毫无疑问,CTerminalDoc类是研究重点。该类负责Terminal的通信任务,主要包括设置通信参数、打开和关闭串行口、建立和终止辅助工作线程、用辅助线程监视串行口等等。在CTerminalDoc类的头文件中,有些变量是用volatile关键字声明的。当两个线程都要用到某一个变量且该变量的值会被改变时,应该用volatile声明,该关键字的作用是防止优化编译器把变量从内存装入CPU寄存器中。如果变量被装入寄存器,那么两个线程有可能一个使用内存中的变量,一个使用寄存器中的变量,这会造成程序的错误执行。成员m_bConnected用来表明当前是否存在一个通信连接。m_hTermWnd用来保存是视图的窗口句柄。m_hPostMsgEvent事件对象用于WM_COMMNOTIFY消息的允许和禁止。m_pThread用来指向AfxBeginThread创建的CWinThread对象,以便对线程进行控制。OVERLAPPED结构m_osRead和m_osWrite用于串行口的重叠读/写,程序应该为它们的hEvent成员创建事件句柄。CTerminalDoc类的构造函数主要完成一些通信参数的初始化工作。OnNewDocument成员函数创建了三个事件对象,CTerminalDoc的析构函数关闭串行口并删除事件对象句柄。OnFileSettings是File-Settings.的命令处理函数,该函数弹出一个CSetupDlg对话框来设置通信参数。实际的设置工作由ConfigConnection函数完成,在OpenConnection和OnFileSettings中都会调用该函数。OpenConnection负责打开串行口并建立辅助工作线程,当用户选择了File-Connect命令时,消息处理函数OnFileConnect将调用该函数。该函数调用CreateFile以重叠方式打开指定的串行口并把返回的句柄保存在m_hCom成员中。接着,函数对m_hCom通信设备进行各种设置。需要注意的是对超时的设定,将读间隔超时设置为MAXDWORD并使其它读超时参数为0会导致ReadFile函数立即完成操作并返回,而不管读入了多少字符。设置超时就规定了GetOverlappedResult函数的等待时间,因此有必要将写超时设置成适当的值,这样如果不能完成写串口的任务,GetOverlappedResult函数会在超过规定超时后结束等待并报告实际传输的字符数。如果对m_hCom设置成功,则函数会建立一个辅助线程并暂时将其挂起。在最后,调用CWinThread: ResumeThread使线程开始运行。OpenConnection调用成功后,线程函数CommProc就开始工作。该函数的主体是一个while循环,在该循环内,混合了两种方法监视串行口输入的方法。先是调用ClearCommError函数查询输入缓冲区中是否有字符,如果有,就向视图发送WM_COMMNOTIFY消息通知其接收字符。如果没有,则调用WaitCommEvent函数监视EV_RXCHAR通信事件,该函数执行重叠操作,紧接着调用的GetOverlappedResult函数无限等待通信事件,如果EV_RXCHAR事件发生(串口收到字符并放入输入缓冲区中),那么函数就结束等待。上述两种方法的混合使用兼顾了线程的效率和可靠性。如果只用ClearCommError函数,则辅助线程将不断耗费CPU时间来查询,效率较低。如果只用WaitCommEvent来监视,那么由于该函数对输入缓冲区中已有的字符不会产生EV_RXCHAR事件,因此在通信速率较高时,会造成数据的延误和丢失。注意到辅助线程用m_PostMsgEvent事件对象来同步WM_COMMNOTIFY消息的发送。在发送消息之前,WaitForSingleObject函数无限等待m_PostMsgEvent对象,WM_COMMNOTIFY的消息处理函数CTerminalView:OnCommNotify在返回时会把该对象置为有信号,因此,如果WaitForSingleObject函数返回,则说明上一个WM_COMMNOTIFY消息已被处理完,这时才能发下一个消息,在发消息前还要调用ResetEvent把m_PostMsgEvent对象置为无信号的,以供下次使用。由于PostMessage函数在消息队列中放入消息后会立即返回,所以如果不采取上述措施,那么辅助线程可能在主线程未处理之前重复发出WM_COMMNOTIFY消息,这会降低系统的效率。可能有读者会问,为什么不用SendMessage?该函数在发送的消息被处理完毕后才返回,这样不就不用考虑同步问题了吗?是的,本例中也可以使用SendMessage,但该函数会阻塞辅助线程的执行直到消息处理完毕,这会降低效率。如果用PostMessage,那么在函数立即返回后线程还可以干别的事情,因此,考虑到效率问题,这里使用了PostMessage而不是SendMessage。函数ReadComm和WriteComm分别用来从m_hCom通信设备中读/写指定数量的字符。ReadComm函数很简单,由于对读超时的特殊设定,ReadFile函数会立即返回并完成操作,并在length变量中报告实际读入的字符数。此时,没有必要调用等待函数或GetOverlappedResult。在WriteComm中,调用GerOverlappedResult来等待操作结果,直到超时发生。不管是否超时,该函数在结束等待后都会报告实际的传输字符数。CloseConnection函数的主要任务是终止辅助线程并关闭m_hCom通信设备。为了终止线程,该函数设置了一系列信号,以结束辅助线程中的等待和循环,然后调用WaitForSingleObject等待线程结束。清单12.7 CTerminalView类的部分代码/ TerminalView.h : interface of the CTerminalView class/class CTerminalView : public CEditView. . .afx_msg LRESULT OnCommNotify(WPARAM wParam, LPARAM lParam);DECLARE_MESSAGE_MAP();/ TerminalView.cpp : implementation of the CTerminalView class/BEGIN_MESSAGE_MAP(CTerminalView, CEditView). . .ON_MESSAGE(WM_COMMNOTIFY, OnCommNotify)END_MESSAGE_MAP()LRESULT CTerminalView:OnCommNotify(WPARAM wParam, LPARAM lParam)char bufMAXBLOCK/4;CString str;int nLength, nTextLength;CTerminalDoc* pDoc=GetDocument();CEdit& edit=GetEditCtrl();if(!pDoc-m_bConnected | (wParam & EV_RXCHAR)!=EV_RXCHAR) / 是否是EV_RXCHAR事件?SetEvent(pDoc-m_hPostMsgEvent); / 允许发送下一个WM_COMMNOTIFY消息return 0L;nLength=pDoc-ReadComm(buf,100);if(nLength)nTextLength=edit.GetWindowTextLength();edit.SetSel(nTextLength,nTextLength); /移动插入光标到正文末尾for(int i=0;im_bNewLine) break;case n: / 换行str+=rn;break;case b: / 退格edit.SetSel(-1, 0);edit.ReplaceSel(str);nTextLength=edit.GetWindowTextLength();edit.SetSel(nTextLength-1,nTextLength);edit.Replace

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论