VC串口通信实例 MSComm计算机与外界的信息交换称为通信。
基本的通信方式有并行通信和串行通信两种。
串行通信是指一条信息额各位数据被逐位按顺序传送的通信方式。
随着计算机技术的发展和推广,利用串口进行数据通讯在通讯领域中占有着重要的地位。
串行通信的特点是:数据位传送,按位顺序进行,最少只需要一根传输线即可完成,成本低但传送速度慢。
串行通信的距离可以从几米到几千米。
利用串口进行数据通讯在通讯领域中占有着重要的地位,串口通讯在通讯软件中有着十分广泛的应用。
如电话、传真、视频和各种控制等。
串口通讯目前流行的方法大概有三种:一是利用Microsoft提供的CMSCOMM控件进行通讯,不过现在很多程序员都觉应该放弃这种方式。
二是利用WINAPI函数进行编程,这种编程的难度高,要求掌握很多的API 函数。
三是利用现在网络上面提供的一些串口通讯控件进行编写。
这三种方法都没有同Windows服务联系起来。
串行接口输入输出过程描述串行接口包括4个主要寄存器,即控制寄存器、状态寄存器、数据输入寄存器及数据输出寄存器。
控制寄存器用来接收CPU送给此接口的各种控制信息,而控制信息决定接口的工作方式。
状态寄存器的各位叫状态位,每一个状态位都可以用来指示传输过程中的某一种错误或当前传输状态。
数据输入寄存器总是和串行输入/并行输出移位寄存器配对使用的。
在输入过程中,数据一位一位从外部设备进入接口的寄存器,当接收完一个数据后,数据就从移位寄存器送到输入寄存器,再等待CPU来取走。
输出的情况与输入过程类似,在输出过程中,数据输出寄存器与并行输入/串行输出移位寄存器配对使用。
当CPU往数据输出寄存器中输出一个数据后,数据便传输到移位寄存器,然后一位一位地通过输出线送到外设。
串行通信数据的收发方式分为异步通信方式与同步通信方式。
本文档中详细描述了这次实现的应用程序中的串口通信服务的原理和工作流程,还列举出了相关的核心代码。
用简洁的语言来描述了各个模块的逻辑实现。
文档中简洁概要的说明了本程序的开发过程和一些开发过程中遇到的问题及相关的解决办法。
串口通信服务中采用安全队列的机制来控制多线程访问多串口。
这个程序具有图形界面,是一个典型的windows应用程序,操作方便且简洁,功能比较单一能通过网络连接到服务器完成串口的通信。
如图1所示,本程序名为Mywork,是一个简单的用VC的MFC框架结构的程序。
用户可以用该程序打开一个串行口,该程序会把用户的键盘输入发送给串行口,并把从串口接收到的字符显示在视图中。
用户通过选择文件->Connect命令来打开串行口,选择文件->Disconnect命令则关闭串行口。
图1 Mywork终端程序当用户选择File->Settings...命令时,会弹出一个Communication settings 对话框,如图2所示。
该对话框主要用来设置串行口,包括端口、波特率、每字节位数、校验、停止位数和流控制。
图2 Communication settings对话框通过该对话框也可以设置程序的一些属性,如果选择New Line(自动换行),那么每当从串口读到回车符(‘\r’)时,视图中的正文就会换行,否则,只有在读到换行符(‘\n’)时才会换行。
如果选择Local echo(本地回显),那么发送的字符会在视图中显示出来。
终端仿真程序的特点是数据的传输没有规律。
因为键盘输入速度有限,所以发送的数据量较小,但接收的数据源是不确定的,所以有可能会有大量数据高速涌入的情况发生。
根据Mywork的这些特性,我们在程序中创建了一个辅助工作者线程专门来监视串行口的输入。
由于写入串行口的数据量不大,不会太费时,所以在主线程中完成写端口的任务是可以的,不必另外创建线程。
现在开始介绍开发本程序的大致的步骤流程和应用的原理用AppWizard建立一个名为Mywork的MFC应用程序。
在MFC AppWizard对话框的第1步选择Single document,在第4步去掉Docking toolbar的选择,在第6步把CMyworkView的基类改为CEditView。
在Mywork工程的资源视图中打开IDR_MAINFRAME菜单资源。
去掉Edit菜单和View菜单,并去掉File菜单中除Exit以外的所有菜单项。
然后在File菜单中加入三个菜单项,如表1所示。
表1 新菜单项标题 IDSettings... ID_FILE_SETTINGSConnect ID_FILE_CONNECTDisconnect ID_FILE_DISCONNECT用ClassWizard为CMyworkDoc类创建三个与上表菜单消息对应的命令处理函数,使用缺省的函数名。
为ID_FILE_CONNECT和ID_FILE_DISCONNECT命令创建命令更新处理函数。
另外,用ClassWizard为该类加入CanCloseFrame成员函数。
用ClassWizard为CMyworkView类创建OnChar函数,该函数用来把用户键入的字符向串行口输出。
新建一个对话框模板资源,令其ID为IDD_COMSETTINGS。
请按图2和表2设计对话框模板。
表2 通信设置对话框中的主要控件控件 ID 属性设置Base options组框缺省标题为Base optionsDrop List,不选Sort,初始列表为Port组合框 IDC_PORT COM1、COM2、COM3、COM4Drop List,不选Sort,初始列表为Baud rate组合框 IDC_BAUD 300、600、1200、2400、9600、14400、19200、38400、57600Data bits组合框 IDC_DATABITS Drop List,不选Sort,初列表为5、6、7、8Drop List,不选Sort,初列表为None、Parity组合框 IDC_PARITY Even、OddDrop List,不选Sort,初列表为1、Stop bits组合框 IDC_STOPBITS 1.5、2Flow 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成员函数,并按表3加入数据成员。
表3 CSetupDlg类的数据成员控件ID 变量名数据类型 IDC_BAND m_sBaud CString IDC_DATABITS m_sDataBits CString IDC_ECHO m_bEcho BOOLIDC_FLOWCTRL m_nFlowCtrl int IDC_NEWLINE m_bNewLine BOOL IDC_PARITYm_nParity int IDC_PORT m_sPort CString IDC_STOPBITS m_nStopBits int 按清单6、7和8修改程序。
清单6列出了CMyworkDoc类的部分代码,清单7是CMyworkView的部分代码,清单8是CSetupDlg类的部分代码。
在本例中使用了WM_COMMNOTIFY消息。
虽然在Win32中,WM_COMMNOTIFY消息已经取消,系统自己不会产生该消息,但Visual C++对该消息的定义依然保留。
考虑到使用习惯,Mywork程序辅助线程通过发送该消息来通知视图有通信事件发生。
清单6 CMyworkDoc类的部分代码#define MAXBLOCK 2048#define XON 0x11#define XOFF 0x13UINT CommProc(LPVOID pParam);class CMyworkDoc : public CDocument {protected: // create from serialization only CMyworkDoc();DECLARE_DYNCREATE(CMyworkDoc)public:CWinThread* m_pThread; // 代表辅助线程 volatile BOOL m_bConnected;volatile HWND m_hTermWnd;volatile HANDLE m_hPostMsgEvent; // 用于WM_COMMNOTIFY消息的事件对象OVERLAPPED m_osRead, m_osWrite; // 用于重叠读/写 volatile HANDLEm_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;public:BOOL ConfigConnection();BOOL OpenConnection();void CloseConnection();DWORD ReadComm(char *buf,DWORD dwLength); DWORD WriteComm(char*buf,DWORD dwLength); };//////////////////////////////////////////////////////////////////// ////////// MyworkDoc.cpp : implementation of the CMyworkDoc class#include "SetupDlg.h"CMyworkDoc::CMyworkDoc(){// 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;}BOOL CMyworkDoc::OnNewDocument(){if (!CDocument::OnNewDocument())return FALSE;((CEditView*)m_viewList.GetHead())->SetWindowText(NULL);// TODO: add reinitialization code here// (SDI dcuments 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 CMyworkDoc::OnFileConnect() {if(!OpenConnection())AfxMessageBox("Can't open connection"); }void CMyworkDoc::OnFileDisconnect() {CloseConnection();}void CMyworkDoc::OnUpdateFileConnect(CCmdUI* pCmdUI){pCmdUI->Enable(!m_bConnected); }void CMyworkDoc::OnUpdateFileDisconnect(CCmdUI* pCmdUI){pCmdUI->Enable(m_bConnected); }// 打开并配置串行口,建立工作者线程 BOOL CMyworkDoc::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;}else{m_bConnected=TRUE;m_pThread->ResumeThread(); // 恢复线程运行}}else{CloseHandle(m_hCom);return FALSE;}return TRUE;}// 结束工作者线程,关闭串行口 void CMyworkDoc::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 CMyworkDoc::OnFileSettings() { CSetupDlg 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("Can't realize the settings!");}}// 配置串行口BOOL CMyworkDoc::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);}// 从串行口输入缓冲区中读入指定数量的字符 DWORDCMyworkDoc::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 CMyworkDoc::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;CMyworkDoc *pDoc=(CMyworkDoc*)pParam;memset(&os, 0, sizeof(OVERLAPPED)); os.hEvent=CreateEvent(NULL, TRUE, FALSE, NULL); if(os.hEvent==NULL){AfxMessageBox("Can't create event object!"); return (UINT)-1;}while(pDoc->m_bConnected){ClearCommError(pDoc->m_hCom,&dwErrorFlags,&ComStat);if(ComStat.cbInQue){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); else{CloseHandle(os.hEvent);return (UINT)-1;}}}CloseHandle(os.hEvent);return 0;}BOOL CMyworkDoc::CanCloseFrame(CFrameWnd* pFrame){SetModifiedFlag(FALSE); // 将文档的修改标志设置成未修改return CDocument::CanCloseFrame(pFrame);}毫无疑问,CMyworkDoc类是研究重点。