多线程串口编程详解.docx
《多线程串口编程详解.docx》由会员分享,可在线阅读,更多相关《多线程串口编程详解.docx(28页珍藏版)》请在冰豆网上搜索。
多线程串口编程详解
深入浅出Win32多线程程序设计综合实例
本章我们将以工业控制和嵌入式系统中运用极为广泛的串口通信为例讲述多线程的典型应用。
而网络通信也是多线程应用最广泛的领域之一,所以本章的最后一节也将对多线程网络通信进行简短的描述。
1.串口通信在工业控制系统中,工控机(一般都基于PCWindows平
-
本章我们将以工业控制和嵌入式系统中运用极为广泛的串口通信为例讲述多线程的典型应用。
而网络通信也是多线程应用最广泛的领域之一,所以本章的最后一节也将对多线程网络通信进行简短的描述。
1.串口通信
在工业控制系统中,工控机(一般都基于PCWindows平台)经常需要与单片机通过串口进行通信。
因此,操作和使用PC的串口成为大多数单片机、嵌入式系统领域工程师必须具备的能力。
串口的使用需要通过三个步骤来完成的:
(1)打开通信端口;
(2)初始化串口,设置波特率、数据位、停止位、奇偶校验等参数。
为了给读者一个直观的印象,下图从Windows的"控制面板->系统->设备管理器->通信端口(COM1)"打开COM的设置窗口:
(3)读写串口。
在WIN32平台下,对通信端口进行操作跟基本的文件操作一样。
创建/打开COM资源
下列函数如果调用成功,则返回一个标识通信端口的句柄,否则返回-1:
HADLECreateFile(PCTSTRlpFileName,//通信端口名,如"COM1"
WORDdwDesiredAccess,//对资源的访问类型
WORDdwShareMode,//指定共享模式,COM不能共享,该参数为0
PSECURITY_ATTRIBUTESlpSecurityAttributes,
//安全描述符指针,可为NULL
WORDdwCreationDisposition,//创建方式
WORDdwFlagsAndAttributes,//文件属性,可为NULL
HANDLEhTemplateFile//模板文件句柄,置为NULL
);
获得/设置COM属性
下列函数可以获得COM口的设备控制块,从而获得相关参数:
BOOLWINAPIGetCommState(
HANDLEhFile,//标识通信端口的句柄
LPDCBlpDCB//指向一个设备控制块(DCB结构)的指针
);
如果要调整通信端口的参数,则需要重新配置设备控制块,再用WIN32APISetCommState()函数进行设置:
BOOLSetCommState(
HANDLEhFile,//标识通信端口的句柄
LPDCBlpDCB//指向一个设备控制块(DCB结构)的指针
);
DCB结构包含了串口的各项参数设置,如下:
typedefstruct_DCB
{
//dcb
DWORDDCBlength;//sizeof(DCB)
DWORDBaudRate;//currentbaudrate
DWORDfBinary:
1;//binarymode,noEOFcheck
DWORDfParity:
1;//enableparitychecking
DWORDfOutxCtsFlow:
1;//CTSoutputflowcontrol
DWORDfOutxDsrFlow:
1;//DSRoutputflowcontrol
DWORDfDtrControl:
2;//DTRflowcontroltype
DWORDfDsrSensitivity:
1;//DSRsensitivity
DWORDfTXContinueOnXoff:
1;//XOFFcontinuesTx
DWORDfOutX:
1;//XON/XOFFoutflowcontrol
DWORDfInX:
1;//XON/XOFFinflowcontrol
DWORDfErrorChar:
1;//enableerrorreplacement
DWORDfNull:
1;//enablenullstripping
DWORDfRtsControl:
2;//RTSflowcontrol
DWORDfAbortOnError:
1;//abortreads/writesonerror
DWORDfDummy2:
17;//reserved
WORDwReserved;//notcurrentlyused
WORDXonLim;//transmitXONthreshold
WORDXoffLim;//transmitXOFFthreshold
BYTEByteSize;//numberofbits/byte,4-8
BYTEParity;//0-4=no,odd,even,mark,space
BYTEStopBits;//0,1,2=1,1.5,2
charXonChar;//TxandRxXONcharacter
charXoffChar;//TxandRxXOFFcharacter
charErrorChar;//errorreplacementcharacter
charEofChar;//endofinputcharacter
charEvtChar;//receivedeventcharacter
WORDwReserved1;//reserved;donotuse
}DCB;
读写串口
在读写串口之前,还要用PurgeComm()函数清空缓冲区,并用SetCommMask()函数设置事件掩模来监视指定通信端口上的事件,其原型为:
BOOLSetCommMask(
HANDLEhFile,//标识通信端口的句柄
DWORDdwEvtMask//能够使能的通信事件
);
串口上可能发生的事件如下表所示:
值
事件描述
EV_BREAK
Abreakwasdetectedoninput.
EV_CTS
TheCTS(clear-to-send)signalchangedstate.
EV_DSR
TheDSR(data-set-ready)signalchangedstate.
EV_ERR
Aline-statuserroroccurred.Line-statuserrorsareCE_FRAME,CE_OVERRUN,andCE_RXPARITY.
EV_RING
Aringindicatorwasdetected.
EV_RLSD
TheRLSD(receive-line-signal-detect)signalchangedstate.
EV_RXCHAR
Acharacterwasreceivedandplacedintheinputbuffer.
EV_RXFLAG
Theeventcharacterwasreceivedandplacedintheinputbuffer.Theeventcharacterisspecifiedinthedevice'sDCBstructure,whichisappliedtoaserialportbyusingtheSetCommStatefunction.
EV_TXEMPTY
Thelastcharacterintheoutputbufferwassent.
在设置好事件掩模后,我们就可以利用WaitCommEvent()函数来等待串口上发生事件,其函数原型为:
BOOLWaitCommEvent(
HANDLEhFile,//标识通信端口的句柄
LPDWORDlpEvtMask,//指向存放事件标识变量的指针
LPOVERLAPPEDlpOverlapped,//指向overlapped结构
);
我们可以在发生事件后,根据相应的事件类型,进行串口的读写操作:
BOOLReadFile(HANDLEhFile,//标识通信端口的句柄
LPVOIDlpBuffer,//输入数据Buffer指针
DWORDnNumberOfBytesToRead,//需要读取的字节数
LPDWORDlpNumberOfBytesRead,//实际读取的字节数指针
LPOVERLAPPEDlpOverlapped//指向overlapped结构
);
BOOLWriteFile(HANDLEhFile,//标识通信端口的句柄
LPCVOIDlpBuffer,//输出数据Buffer指针
DWORDnNumberOfBytesToWrite,//需要写的字节数
LPDWORDlpNumberOfBytesWritten,//实际写入的字节数指针
LPOVERLAPPEDlpOverlapped//指向overlapped结构
);
2.工程实例
下面我们用第1节所述API实现一个多线程的串口通信程序。
这个例子工程(工程名为MultiThreadCom)的界面很简单,如下图所示:
它是一个多线程的应用程序,包括两个工作者线程,分别处理串口1和串口2。
为了简化问题,我们让连接两个串口的电缆只包含RX、TX两根连线(即不以硬件控制RS-232,串口上只会发生EV_TXEMPTY、EV_RXCHAR事件)。
在工程实例的BOOLCMultiThreadComApp:
:
InitInstance()函数中,启动并设置COM1和COM2,其源代码为:
BOOLCMultiThreadComApp:
:
InitInstance()
{
AfxEnableControlContainer();
//打开并设置COM1
hComm1=CreateFile("COM1",GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,0,NULL);
if(hComm1==(HANDLE)-1)
{
AfxMessageBox("打开COM1失败");
returnfalse;
}
else
{
DCBwdcb;
GetCommState(hComm1,&wdcb);
wdcb.BaudRate=9600;
SetCommState(hComm1,&wdcb);
PurgeComm(hComm1,PURGE_TXCLEAR);
}
//打开并设置COM2
hComm2=CreateFile("COM2",GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,0,NULL);
if(hComm2==(HANDLE)-1)
{
AfxMessageBox("打开COM2失败");
returnfalse;
}
else
{
DCBwdcb;
GetCommState(hComm2,&wdcb);
wdcb.BaudRate=9600;
SetCommState(hComm2,&wdcb);
PurgeComm(hComm2,PURGE_TXCLEAR);
}
CMultiThreadComDlgdlg;
m_pMainWnd=&dlg;
intnResponse=dlg.DoModal();
if(nResponse==IDOK)
{
//TODO:
Placecodeheretohandlewhenthedialogis
//dismissedwithOK
}
elseif(nResponse==IDCANCEL)
{
//TODO:
Placecodeheretohandlewhenthedialogis
//dismissedwithCancel
}
returnFALSE;
}
此后我们在对话框CMultiThreadComDlg的初始化函数OnInitDialog中启动两个分别处理COM1和COM2的线程:
BOOLCMultiThreadComDlg:
:
OnInitDialog()
{
CDialog:
:
OnInitDialog();
//Add"About..."menuitemtosystemmenu.
//IDM_ABOUTBOXmustbeinthesystemcommandrange.
ASSERT((IDM_ABOUTBOX&0xFFF0)==IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX<0xF000);
CMenu*pSysMenu=GetSystemMenu(FALSE);
if(pSysMenu!
=NULL)
{
CStringstrAboutMenu;
strAboutMenu.LoadString(IDS_ABOUTBOX);
if(!
strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING,IDM_ABOUTBOX,strAboutMenu);
}
}
//Settheiconforthisdialog.Theframeworkdoesthisautomatically
//whentheapplication'smainwindowisnotadialog
SetIcon(m_hIcon,TRUE);//Setbigicon
SetIcon(m_hIcon,FALSE);//Setsmallicon
//TODO:
Addextrainitializationhere
//启动串口1处理线程
DWORDnThreadId1;
hCommThread1=:
:
CreateThread((LPSECURITY_ATTRIBUTES)NULL,0,
(LPTHREAD_START_ROUTINE)Com1ThreadProcess,AfxGetMainWnd()->m_hWnd,0,&nThreadId1);
if(hCommThread1==NULL)
{
AfxMessageBox("创建串口1处理线程失败");
returnfalse;
}
//启动串口2处理线程
DWORDnThreadId2;
hCommThread2=:
:
CreateThread((LPSECURITY_ATTRIBUTES)NULL,0,
(LPTHREAD_START_ROUTINE)Com2ThreadProcess,AfxGetMainWnd()->m_hWnd,0,&nThreadId2);
if(hCommThread2==NULL)
{
AfxMessageBox("创建串口2处理线程失败");
returnfalse;
}
returnTRUE;//returnTRUEunlessyousetthefocustoacontrol
}
两个串口COM1和COM2对应的线程处理函数等待串口上发生事件,并根据事件类型和自身缓冲区是否有数据要发送进行相应的处理,其源代码为:
DWORDWINAPICom1ThreadProcess(HWNDhWnd//主窗口句柄)
{
DWORDwEven;
charstr[10];//读入数据
SetCommMask(hComm1,EV_RXCHAR|EV_TXEMPTY);
while(TRUE)
{
WaitCommEvent(hComm1,&wEven,NULL);
if(wEven=0)
{
CloseHandle(hCommThread1);
hCommThread1=NULL;
ExitThread(0);
}
else
{
switch(wEven)
{
caseEV_TXEMPTY:
if(wTxPos{
//在串口1写入数据
DWORDwCount;//写入的字节数
WriteFile(hComm1,com1Data.TxBuf[wTxPos],1,&wCount,NULL);
com1Data.wTxPos++;
}
break;
caseEV_RXCHAR:
if(com1Data.wRxPos{
//读取串口数据,处理收到的数据
DWORDwCount;//读取的字节数
ReadFile(hComm1,com1Data.RxBuf[wRxPos],1,&wCount,NULL);
com1Data.wRxPos++;
if(com1Data.wRxPos==com1Data.wRxLen);
:
:
PostMessage(hWnd,COM_SENDCHAR,0,1);
}
break;
}
}
}
}
returnTRUE;
}
DWORDWINAPICom2ThreadProcess(HWNDhWnd//主窗口句柄)
{
DWORDwEven;
charstr[10];//读入数据
SetCommMask(hComm2,EV_RXCHAR|EV_TXEMPTY);
while(TRUE)
{
WaitCommEvent(hComm2,&wEven,NULL);
if(wEven=0)
{
CloseHandle(hCommThread2);
hCommThread2=NULL;
ExitThread(0);
}
else
{
switch(wEven)
{
caseEV_TXEMPTY:
if(wTxPos{
//在串口2写入数据
DWORDwCount;//写入的字节数
WriteFile(hComm2,com2Data.TxBuf[wTxPos],1,&wCount,NULL);
com2Data.wTxPos++;
}
break;
caseEV_RXCHAR:
if(com2Data.wRxPos{
//读取串口数据,处理收到的数据
DWORDwCount;//读取的字节数
ReadFile(hComm2,com2Data.RxBuf[wRxPos],1,&wCount,NULL);
com2Data.wRxPos++;
if(com2Data.wRxPos==com2Data.wRxLen);
:
:
PostMessage(hWnd,COM_SENDCHAR,0,1);
}
break;
}
}
}
returnTRUE;
}
线程控制函数中所操作的com1Data和com2Data是与串口对应的数据结构structtagSerialPort的实例,这个数据结构是:
typedefstructtagSerialPort
{
BYTERxBuf[SPRX_BUFLEN];//接收Buffer
WORDwRxPos;//当前接收字节位置
WORDwRxLen;//要接收的字节数
BYTETxBuf[SPTX_BUFLEN];//发送Buffer
WORDwTxPos;//当前发送字节位置
WORDwTxLen;//要发送的字节数
}SerialPort,*LPSerialPort;
3.多线程串口类
使用多线程串口通信更方便的途径是编写一个多线程的串口类,例如RemonSpekreijse编写了一个CSerialPort串口类。
仔细分析这个类的源代码,将十分有助于我们对先前所学多线程及同步知识的理解。
3.1类的定义
#ifndef__SERIALPORT_H__
#define__SERIALPORT_H__
#defineWM_COMM_BREAK_DETECTEDWM_USER+1//Abreakwasdetectedoninput.
#defineWM_COMM_CTS_DETECTEDWM_USER+2//TheCTS(clear-to-send)signalchangedstate.
#defineWM_COMM_DSR_DETECTEDWM_USER+3//TheDSR(data-set-ready)signalchangedstate.
#defineWM_COMM_ERR_DETECTEDWM_USER+4//Aline-statuserroroccurred.Line-statuserrorsareCE_FRAME,CE_OVERRUN,andCE_RXPARITY.
#defineWM_COMM_RING_DETECTEDWM_USER+5//Aringindicatorwasdetected.
#defineWM_COMM_RLSD_DETECTEDWM_USER+6//TheRLSD(receive-line-signal-detect)signalchangedstate.
#defineWM_COMM_RXCHARWM_USER+7//Acharacterwasreceivedandplacedintheinputbuffer.
#defineWM_COMM_RXFLAG_DETECTEDWM_USER+8//Theeventcharacterwasreceivedandplacedintheinputbuffer.
#defineWM_COMM_TXEMPTY_DETECTEDWM_USER+9//Thelastcharacterintheoutputbufferwassent.
classCSerialPort
{
public:
//contructionanddestruction
CSerialPort();
virtual~CSerialPort();
//portinitialisation
BOOLInitPort(CWnd*pPortOwner,UINTportnr=1,UINTbaud=19200,charparity='N',UINTdatabits=8,UINTstopsbits=1,DWORDdwCommEvents=EV_RXC