1、多线程串口编程详解深入浅出Win32多线程程序设计综合实例本章我们将以工业控制和嵌入式系统中运用极为广泛的串口通信为例讲述多线程的典型应用。 而网络通信也是多线程应用最广泛的领域之一,所以本章的最后一节也将对多线程网络通信进行简短的描述。 1.串口通信 在工业控制系统中,工控机(一般都基于PC Windows平-本章我们将以工业控制和嵌入式系统中运用极为广泛的串口通信为例讲述多线程的典型应用。而网络通信也是多线程应用最广泛的领域之一,所以本章的最后一节也将对多线程网络通信进行简短的描述。1.串口通信在工业控制系统中,工控机(一般都基于PC Windows平台)经常需要与单片机通过串口进行通信。
2、因此,操作和使用PC的串口成为大多数单片机、嵌入式系统领域工程师必须具备的能力。串口的使用需要通过三个步骤来完成的:(1) 打开通信端口;(2) 初始化串口,设置波特率、数据位、停止位、奇偶校验等参数。为了给读者一个直观的印象,下图从Windows的控制面板系统设备管理器通信端口(COM1)打开COM的设置窗口:(3) 读写串口。在WIN32平台下,对通信端口进行操作跟基本的文件操作一样。创建/打开COM资源下列函数如果调用成功,则返回一个标识通信端口的句柄,否则返回-1:HADLE CreateFile(PCTSTR lpFileName, /通信端口名,如COM1WORD dwDesire
3、dAccess, /对资源的访问类型WORD dwShareMode, /指定共享模式,COM不能共享,该参数为0PSECURITY_ATTRIBUTES lpSecurityAttributes,/安全描述符指针,可为NULLWORD dwCreationDisposition, /创建方式WORD dwFlagsAndAttributes, /文件属性,可为NULLHANDLE hTemplateFile /模板文件句柄,置为NULL); 获得/设置COM属性下列函数可以获得COM口的设备控制块,从而获得相关参数:BOOL WINAPI GetCommState(HANDLE hFile,
4、 /标识通信端口的句柄LPDCB lpDCB /指向一个设备控制块(DCB结构)的指针); 如果要调整通信端口的参数,则需要重新配置设备控制块,再用WIN32 API SetCommState()函数进行设置:BOOL SetCommState(HANDLE hFile, /标识通信端口的句柄LPDCB lpDCB /指向一个设备控制块(DCB结构)的指针); DCB结构包含了串口的各项参数设置,如下:typedef struct _DCB/ dcbDWORD DCBlength; / sizeof(DCB)DWORD BaudRate; / current baud rateDWORD fB
5、inary: 1; / binary mode, no EOF checkDWORD fParity: 1; / enable parity checkingDWORD fOutxCtsFlow: 1; / CTS output flow controlDWORD fOutxDsrFlow: 1; / DSR output flow controlDWORD fDtrControl: 2; / DTR flow control typeDWORD fDsrSensitivity: 1; / DSR sensitivityDWORD fTXContinueOnXoff: 1; / XOFF co
6、ntinues TxDWORD fOutX: 1; / XON/XOFF out flow controlDWORD fInX: 1; / XON/XOFF in flow controlDWORD fErrorChar: 1; / enable error replacementDWORD fNull: 1; / enable null strippingDWORD fRtsControl: 2; / RTS flow controlDWORD fAbortOnError: 1; / abort reads/writes on errorDWORD fDummy2: 17; / reserv
7、edWORD wReserved; / not currently usedWORD XonLim; / transmit XON thresholdWORD XoffLim; / transmit XOFF thresholdBYTE ByteSize; / number of bits/byte, 4-8BYTE Parity; / 0-4=no,odd,even,mark,spaceBYTE StopBits; / 0,1,2 = 1, 1.5, 2char XonChar; / Tx and Rx XON characterchar XoffChar; / Tx and Rx XOFF
8、 characterchar ErrorChar; / error replacement characterchar EofChar; / end of input characterchar EvtChar; / received event characterWORD wReserved1; / reserved; do not use DCB; 读写串口在读写串口之前,还要用PurgeComm()函数清空缓冲区,并用SetCommMask ()函数设置事件掩模来监视指定通信端口上的事件,其原型为:BOOL SetCommMask(HANDLE hFile, /标识通信端口的句柄DWOR
9、D dwEvtMask /能够使能的通信事件); 串口上可能发生的事件如下表所示:值事件描述EV_BREAKA break was detected on input.EV_CTSThe CTS (clear-to-send) signal changed state.EV_DSRThe DSR(data-set-ready) signal changed state.EV_ERRA line-status error occurred. Line-status errors are CE_FRAME, CE_OVERRUN, and CE_RXPARITY.EV_RINGA ring ind
10、icator was detected.EV_RLSDThe RLSD (receive-line-signal-detect) signal changed state.EV_RXCHARA character was received and placed in the input buffer.EV_RXFLAGThe event character was received and placed in the input buffer. The event character is specified in the devices DCB structure, which is app
11、lied to a serial port by using the SetCommState function.EV_TXEMPTYThe last character in the output buffer was sent.在设置好事件掩模后,我们就可以利用WaitCommEvent()函数来等待串口上发生事件,其函数原型为:BOOL WaitCommEvent(HANDLE hFile, /标识通信端口的句柄LPDWORD lpEvtMask, /指向存放事件标识变量的指针LPOVERLAPPED lpOverlapped, / 指向overlapped结构); 我们可以在发生事件后
12、,根据相应的事件类型,进行串口的读写操作:BOOL ReadFile(HANDLE hFile, /标识通信端口的句柄LPVOID lpBuffer, /输入数据Buffer指针DWORD nNumberOfBytesToRead, / 需要读取的字节数LPDWORD lpNumberOfBytesRead, /实际读取的字节数指针LPOVERLAPPED lpOverlapped /指向overlapped结构);BOOL WriteFile(HANDLE hFile, /标识通信端口的句柄LPCVOID lpBuffer, /输出数据Buffer指针DWORD nNumberOfBytes
13、ToWrite, /需要写的字节数LPDWORD lpNumberOfBytesWritten, /实际写入的字节数指针LPOVERLAPPED lpOverlapped /指向overlapped结构); 2.工程实例下面我们用第1节所述API实现一个多线程的串口通信程序。这个例子工程(工程名为MultiThreadCom)的界面很简单,如下图所示:它是一个多线程的应用程序,包括两个工作者线程,分别处理串口1和串口2。为了简化问题,我们让连接两个串口的电缆只包含RX、TX两根连线(即不以硬件控制RS-232,串口上只会发生EV_TXEMPTY、EV_RXCHAR事件)。在工程实例的BOOL
14、CMultiThreadComApp:InitInstance()函数中,启动并设置COM1和COM2,其源代码为:BOOL CMultiThreadComApp:InitInstance()AfxEnableControlContainer();/打开并设置COM1hComm1=CreateFile(COM1, GENERIC_READ|GENERIC_WRITE, 0, NULL ,OPEN_EXISTING, 0,NULL);if (hComm1=(HANDLE)-1)AfxMessageBox(打开COM1失败);return false;elseDCB wdcb;GetCommSta
15、te (hComm1,&wdcb);wdcb.BaudRate=9600;SetCommState (hComm1,&wdcb);PurgeComm(hComm1,PURGE_TXCLEAR);/打开并设置COM2hComm2=CreateFile(COM2, GENERIC_READ|GENERIC_WRITE, 0, NULL ,OPEN_EXISTING, 0,NULL);if (hComm2=(HANDLE)-1)AfxMessageBox(打开COM2失败);return false;elseDCB wdcb;GetCommState (hComm2,&wdcb);wdcb.Baud
16、Rate=9600;SetCommState (hComm2,&wdcb);PurgeComm(hComm2,PURGE_TXCLEAR);CMultiThreadComDlg dlg;m_pMainWnd = &dlg;int nResponse = dlg.DoModal();if (nResponse = IDOK)/ TODO: Place code here to handle when the dialog is/ dismissed with OKelse if (nResponse = IDCANCEL)/ TODO: Place code here to handle whe
17、n the dialog is/ dismissed with Cancelreturn FALSE; 此后我们在对话框CMultiThreadComDlg的初始化函数OnInitDialog中启动两个分别处理COM1和COM2的线程:BOOL CMultiThreadComDlg:OnInitDialog()CDialog:OnInitDialog();/ Add About. menu item to system menu./ IDM_ABOUTBOX must be in the system command range.ASSERT(IDM_ABOUTBOX & 0xFFF0) =
18、IDM_ABOUTBOX);ASSERT(IDM_ABOUTBOX AppendMenu(MF_SEPARATOR);pSysMenu-AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);/ Set the icon for this dialog. The framework does this automatically/ when the applications main window is not a dialogSetIcon(m_hIcon, TRUE); / Set big iconSetIcon(m_hIcon, FALSE);
19、 / Set small icon/ TODO: Add extra initialization here/启动串口1处理线程DWORD nThreadId1;hCommThread1 = :CreateThread(LPSECURITY_ATTRIBUTES)NULL, 0,(LPTHREAD_START_ROUTINE)Com1ThreadProcess, AfxGetMainWnd()-m_hWnd, 0, &nThreadId1);if (hCommThread1 = NULL)AfxMessageBox(创建串口1处理线程失败);return false;/启动串口2处理线程DWO
20、RD nThreadId2;hCommThread2 = :CreateThread(LPSECURITY_ATTRIBUTES)NULL, 0,(LPTHREAD_START_ROUTINE)Com2ThreadProcess, AfxGetMainWnd()-m_hWnd, 0, &nThreadId2);if (hCommThread2 = NULL)AfxMessageBox(创建串口2处理线程失败);return false;return TRUE; / return TRUE unless you set the focus to a control 两个串口COM1和COM2对应
21、的线程处理函数等待串口上发生事件,并根据事件类型和自身缓冲区是否有数据要发送进行相应的处理,其源代码为:DWORD WINAPI Com1ThreadProcess(HWND hWnd/主窗口句柄)DWORD wEven;char str10; /读入数据SetCommMask(hComm1, EV_RXCHAR | EV_TXEMPTY);while (TRUE)WaitCommEvent(hComm1, &wEven, NULL);if(wEven = 0)CloseHandle(hCommThread1);hCommThread1 = NULL;ExitThread(0);elseswi
22、tch (wEven)case EV_TXEMPTY:if (wTxPos wTxLen)/在串口1写入数据DWORD wCount; /写入的字节数WriteFile(hComm1, com1Data.TxBufwTxPos, 1, &wCount, NULL);com1Data.wTxPos+;break;case EV_RXCHAR:if (com1Data.wRxPos com1Data.wRxLen)/读取串口数据, 处理收到的数据DWORD wCount; /读取的字节数ReadFile(hComm1, com1Data.RxBufwRxPos, 1, &wCount, NULL)
23、;com1Data.wRxPos+;if(com1Data.wRxPos= com1Data.wRxLen);:PostMessage(hWnd, COM_SENDCHAR, 0, 1);break;return TRUE;DWORD WINAPI Com2ThreadProcess(HWND hWnd /主窗口句柄)DWORD wEven;char str10; /读入数据SetCommMask(hComm2, EV_RXCHAR | EV_TXEMPTY);while (TRUE)WaitCommEvent(hComm2, &wEven, NULL);if (wEven = 0)Close
24、Handle(hCommThread2);hCommThread2 = NULL;ExitThread(0);elseswitch (wEven)case EV_TXEMPTY:if (wTxPos wTxLen)/在串口2写入数据DWORD wCount; /写入的字节数WriteFile(hComm2, com2Data.TxBufwTxPos, 1, &wCount, NULL);com2Data.wTxPos+;break;case EV_RXCHAR:if (com2Data.wRxPos com2Data.wRxLen)/读取串口数据, 处理收到的数据DWORD wCount; /
25、读取的字节数ReadFile(hComm2, com2Data.RxBufwRxPos, 1, &wCount, NULL);com2Data.wRxPos+;if(com2Data.wRxPos= com2Data.wRxLen);:PostMessage(hWnd, COM_SENDCHAR, 0, 1);break;return TRUE; 线程控制函数中所操作的com1Data和com2Data是与串口对应的数据结构struct tagSerialPort的实例,这个数据结构是:typedef struct tagSerialPortBYTE RxBufSPRX_BUFLEN;/接收B
26、ufferWORD wRxPos; /当前接收字节位置WORD wRxLen; /要接收的字节数BYTE TxBufSPTX_BUFLEN;/发送BufferWORD wTxPos; /当前发送字节位置WORD wTxLen; /要发送的字节数SerialPort, * LPSerialPort; 3.多线程串口类使用多线程串口通信更方便的途径是编写一个多线程的串口类,例如Remon Spekreijse编写了一个CSerialPort串口类。仔细分析这个类的源代码,将十分有助于我们对先前所学多线程及同步知识的理解。3.1类的定义#ifndef _SERIALPORT_H_#define _S
27、ERIALPORT_H_#define WM_COMM_BREAK_DETECTED WM_USER+1 / A break was detected on input.#define WM_COMM_CTS_DETECTED WM_USER+2 / The CTS (clear-to-send) signal changed state.#define WM_COMM_DSR_DETECTED WM_USER+3 / The DSR (data-set-ready) signal changed state.#define WM_COMM_ERR_DETECTED WM_USER+4 / A
28、 line-status error occurred. Line-status errors are CE_FRAME, CE_OVERRUN, and CE_RXPARITY.#define WM_COMM_RING_DETECTED WM_USER+5 / A ring indicator was detected.#define WM_COMM_RLSD_DETECTED WM_USER+6 / The RLSD (receive-line-signal-detect) signal changed state.#define WM_COMM_RXCHAR WM_USER+7 / A
29、character was received and placed in the input buffer.#define WM_COMM_RXFLAG_DETECTED WM_USER+8 / The event character was received and placed in the input buffer.#define WM_COMM_TXEMPTY_DETECTED WM_USER+9 / The last character in the output buffer was sent.class CSerialPortpublic:/ contruction and destructionCSerialPort();virtual CSerialPort();/ port initialisationBOOL InitPort(CWnd* pPortOwner, UINT portnr = 1, UINT baud = 19200, char parity = N, UINT databits = 8, UINT stopsbits = 1, DWORD dwCommEvents = EV_RXC
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1