串行通信是核算机之间及核算机及数字化仪器和设备的一种重要通讯手法,是完结工业监控的一种首要办法。Windows下的串行通讯首要有两种办法:运用VB的MSCOMM控件和运用Windows API。MSCOMM控件简略易用,但由于其对串口设备的封装及调用办法的局限性,不能灵敏方便地对串口设备进行操控。而经过Windows API则能够完结对串口设备的彻底操控,而且能够供给多线程的通讯机制。
在杂乱运用中,通讯通常在后台完结,需求选用多线程技能。一个多线程的运用程序实际上是在其内部完结了多使命扩展,为代码赋予了并行履行的特性,适于履行一些实时性或随机性很强的操作,也有利于进步CPU的运用率,加速通讯程序的信息处理速度。
本文以一台工业操控PC机与多台依据单片机的智能操控单片进行串行通讯为实例。PC机和各智能操控单元经过RS485总线互联。由于RS485的通讯办法是半双工的,只能由作为主节点的PC机顺次轮询网络上的各智能操控单元子节点。每次通讯都是由PC机经过串口向智能操控单元发布指令,智能操控单元在接纳到正确的指令后做出应对。
体系的主节点运用程序是用VB6.0编写的,为了既能供给多线程的串行通讯机制,又可使运用程序易于完结串行通讯功用,运用VC++6.0开发依据Window API的多线程串行通讯ActiveX控件。主节点的运用程序经过对串行通讯ActiveX控件的调用完结与各子节点的通讯。
1 创立ActiveX控件JinRiComm.OCX
VGC++6.0和MFC是健建ActiveX控件的强壮而又灵敏的东西。JinRiComm控件创立过程简略概述如下:
(1)用MFC ActiveX ControlWizard生成ActiveX控件工程,命名为JinRiComm。
(2)翻开ClassWizard窗口,挑选Automation标签,单击“Add Property”按钮,命名新的特点。单击“AddMethod”按钮,命名新的办法。挑选ActiveX Event标签,单击“Add Event”按钮,命名新的事情。
(3)向控件工程中增加类CserialPort,为该类增加成员变量和成员函数,该类将完结串行通讯作业。
2 串口通讯的根本编程
用Windows API函数完结串行通讯,其特点是对串口的操作如对文件操作相同,翻开和封闭串行设备与翻开和封闭文件运用相同的函数。
(1)翻开串口
m_hComm=CreateFile(szPort,GENERIC_READ |GENER%&&&&&%_WRITE,0,NULL, OPEN_EXISTING,FILE_FLAG_OVERLAPPED,0);
(2)获取当时通讯信息,设备通讯设备
GetCommState(m_hComm,&m_dcb);
SetCommState(m_hComm,&m_dcb);
(3)读、写串口
bResult=ReadFile(port->m_hComm,&RXBuff,1,&BytesRead,Port->m_ov);
bResult=WriteFile(port->m_hComm,&(port-m_Byte)[i],1,&BytesSent,&port->m_ov);
(4)封闭串口
CloseHandle (m_hComm);
3 规划程序中的线程
MFC履行两种类型的线程:用户界面线程和作业线程。前者用来处理用户输入,呼应由用户产生的事情和音讯。后者不处理窗口音讯,用于完结后台核算、打印和其它一些没有必要逼迫用户来等候的使命。在本程序中,用户界面线程便是程序的主线程,别的再增加两个作业线程:通讯线程和延时线程。它们的功用介绍如表1所示。
表1
线程称号 | 首要功用 | 线程函数 |
主线程 | 响运用户对控件的调用(设置控件特点和调用操控办法);初始化串口;处理通讯线程接纳到的数据,并告诉用户(触发控件音讯);告诉通讯线程向串口写数据。 | |
通讯线程 | 在主线程初始化串口后被创立。CommThread函数进入死循环,线程一向监督串口事情,当读串口事情产生,读取串口接纳到的数据,向主线程发自界说音讯WM_COMM_RXCHAR,告诉主线程处理数据;收到主线程的写串口指令时,将缓存中的数据写到串口。 | CommThread(LPVOID pParam) |
延时线程 | 在主线程向串口写数据之后被创立。假如主线置为真,延时线程在检查到其为真后,线程函数回来;否则会延时10ms再判别一次。假如超越八规则时刻仍没检查到其为真,则向主线程发自界说音讯WM_DELAY_TIMEOUT,告诉主线程重发方才的指令,然后线程函数回来。 | DelayThread(LPVOID pParam) |
运用AfxBeginThread函数来发动一个作业线程,用法如下:
CWinThread* AfxBeginThread(AFX_THREADPROC pfnThreadProc,LPVOID pParam,int nPriority=THREAD_PRIORITY_NORMAL,UINT nStackSize=0,DWORD dwCreateFlage=0,LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL)
在发动一个作业线程之前,有必要为线程编写一个大局的线程函数。这个线程函数承受一个32位的LPVOID作为参数,回来一个UINT,线程函数的结构为:
UINT ThreadUFunction(LPVOID pParam)
{
//线程处理代码
return 0;
}
停止线程有两种途径:当线程函数回来时,线程停止;线程函数也能够在内部调用AfxEndThread函数来停止自己。
程序流程图如图1所示。
4 线程间的通讯
(1)经过大局变量
主线程能够选用多种办法与作业线程进行通讯,最简略的办法是经过大局变量,由于进程中的一切线程都能够拜访一切的大局变量。如:界说大局变量bReceiveSuccess,它表明是否收到了正确的呼应。在主线程向串口写数据之后它被置为FALSE,然后延时线程发动。当体系收到正确的呼应后,bReceiveSuccess被主线程改为TRUE。延时线程依据bReceiveSuccess的值来决定是完毕该线程仍是给主线程发音讯。
(2)经过参数
主线程能够向作业线程传递一个4字节的参数,一种运用该参数的常见办法是传递一个指针,它指向这个线程的父类。如:
UINT CserialPort::CommThread(LPVOID pParam)
{
CserialPort port=(CSerialPort)pParam; //取得串口类指针
//线程处理代码
}
(3)经过音讯
作业线程取得主线程的窗口句柄,则能够给主线程发送音讯。如:
通讯线程告诉主线程,串口接纳到了数据
::PostMessage ((port ->m_pOwner) ->m_hWnd,WM_COMM_RXCHAR,(WPARAM) RXBuff,(LPARAM)port->m_nPortNr);
5 线程的同步
多线程的长处之一是一切线程都能够拜访相同的大局目标和共享资源,它供给了程序规划的简捷性和便利性,进步了对信息处理的并发度。但假如不当善处理好线程的并发问题,也会带来数据的过错或是资源的死锁。为了防止这些问题产生,线程在运用共享资源或目标前有必要取得一个束缚拜访同步目标的权利,也便是经过同步的机制来操控这种权利的运用。线程间的同步多种办法。
(1)临界区
临界区是经过多个线程的串行化来拜访公共资源或一段代码。如:
InitializeCriticalSection(&(port->Lm_csCommunication Sync));
//初始化临界区目标
EnterCriticalSection(&prot->m_csCommunicationSyne);
//使调用线程等候得临界区目标并在取得具有权时回来
Do
{
if(!bReceiveSuccess) //拜访大局变量
{
LeaveCriticalSection(&port->m_csCommunicationSync);
//释放对临界区目标的具有权
//其它处理代码
}
}
(2)事情
事情用来告诉线程有一些事情现已产生,比较适合于信号操控。事情有手动复位和主动复位两种。手动复位事情是在运用程序或体系后台操控不改动它的信号状况。当手动复位事情处于有信号状况时,一切等候该事情的线程都被激活,事情保存有信号状况直到被一个运用程序复位停止。当一个主动复位事情处于有信号状况时,只要一个等候线程被激活,而且事情将复位成无信号,其它一切等候着的线程仍将坚持挂起状况。
界说3个事情:
m_hEventArray[0]=m_hShutdownEvent;
//完毕经过线程事情
m_hEventArray[1]=m_ov.hEvent; //读事情
m_hEventArray[2]=m_hWriteEvent;//写事情
在通讯线程的线程函数CommThread中等候3个事情的产生
Event=WaitForMultipleObjects (3,port ->m_hEventArray,FALSE,INFINITE);
switch (Event)
{
case 0: //完毕通讯线程事情
{
port->m_bThreadAlive=FALSE;
AfxEndThread(100);//完毕通讯线程
Bread;
}
case 1://读事情
{
GetCommMask(port->m_hComm,&CommEvent);
If (CommEvent & EV_RECHAR)
ReceiveChar(port,comstat); //从串口读数
Break;
}
case 2: //写事情
{
WriteChar(port); //向窗口写数
break;
}
}//end switch