文档库 最新最全的文档下载
当前位置:文档库 › 多线程读串口

多线程读串口

多线程读串口
多线程读串口

基于多线程技术实现多串口的实时通信

邓林涛

(江西赣粤高速公路股份有限公司江西南昌 330000)

摘要:介绍了采用一种通过基于多线程的多串口实时通信方式实现在现代加工制造业中对多台数控设备进行集中控制的方法。具体介绍了Visual C+中基于多线程的多串口实时通信的实现技术,并给出了核心代码。基于该方法的解决方案实际应用效果良好。

关键词:信息工程;多线程;多串口;实时通信;通信协议;数控设备

0 前言

随着电子仪器、测绘技术、计算机技术的快速发展, 计算机与电子、测绘仪器的通信问题是经常遇到的问题, 在现代制造工业控制中,现有电子、测绘仪器大多都采用串行通信口与计算机进行通信,串口通信是常用的计算机与外部串行设备之间的数据传输通道,串口通信以其灵活、实现方便易行、系统费用低、传输可靠、信号线数量少的优点已经在许多领域得到了广泛应用。而一台PC机本身自带的一两个串口根本无法满足现代工业控制的需要,因此多串口卡就应运而生,使一台PC机有甚至多达10多个串口与电子仪器进行通信控制,如何使这些各种仪器通过多串口跟PC机进行实时准确无误的通信,我们在此选择使用多线程技术,多线程技术可以使得各端口独立,准确地实现串行通信,使串行通信具有更广泛的灵活性与严格性,且充分利用CPU时间。在具体的实时监控系统中如何协调多个线程、线程之间以何种方式实现同步,这是多线程串行通信程序实现的关键点。本文探讨的就是在Windows 9X/NT下利用VC++的API 函数采用多线程技术对RS-232串口编程实行精准实时控制。

1 Visual C++中基于多线程的多串

口实时通信技术

在现代的各种实时监控系统和通信系统中,在Windows 9X/NT下利用VC++对RS-232串口编程是常用的手段。Windows 9X/NT是抢先式的多任务操作系统,程序对CPU的占用时间由系统决定。多任务指的是系统可以同时运行多个进程,每个进程又可以同时执行多个线程。进程是应用程序的运行实例,拥有自己的地址空间。每个进程拥有一个主线程,同时还可以建立其他的线程。线程是操作系统分配CPU时间的基本实体,每个线程占用的CPU时间由系统分配,系统不停的在线程之间切换。进程中的线程共享进程的虚拟地址空间,可以访问进程的资源,处于并行执行状态,这就是多线程的基本概念。

使用MFC开发是较普遍的VC++编程方法。在VC++6.0下,MFC应用程序的线程由CWinThread对象表示。VC++把线程分为两种:用户界面线程(User Interface Thread)和工作者线程(Work Thread)。用户界面线程能够提供界面和用户交互,通常用于处理用户输入并相应各种事件和消息;而工作者线程主要用来处理程序的后台任务。本文监视串口事件的线程即为工作线程。

1.1 多线程下的串行通信的操作方式

(1)同步方式

同步方式中,读串口的函数试图在串口的接收缓冲区中读取规定数目的数据,直到规定数目的数据全部被读出或设定的超时时间已到时才返回。使用同步通信要较好地防止线程阻塞

(2)异步方式

异步方式中,利用Windows的多线程结构,可以让串口的读写操作在后台进行,而应用程序的其他部分在前台执行。采用异步通信,可以提高系统的整体性能。

多线程程序的编写在端口的配置,连接部分与单线程的相同,在端口配置完毕后,最重要的是根据实际情况,建立多线程之间的同步对象,如信号灯、临界区和事件等,防止多线程同时访问同一块内存数据。

CSemaphore:信号灯对象,允许一定数目的线程访问某个共享资源,常用来控制访问共享资源的线程数量。

Cmutex:互斥量对象,一个时刻至多只允许一个线程访问某资源,未被占用时处于有信号状态,可以实现对共享资源的互斥访问。

CEvent:事件对象,用于使一个线程通知其他线

程某一事件的发生,所以也可以用来封锁对某一资源的访问,直到线程释放资源使其成为有信号状态。适用于某一线程等待某事件发生才能执行的场合。 CCriticalSection :临界区对象,将一段代码置入临界区,只允许最多一个线程进入执行这段代码。一个临界区仅在创建它的进程中有效。

1.2 程序中所使用的主要WIN32 API 函数

BOOL SetCommState(HANDLE hFile ,LPDCB lpDCB ); //串口配置函数

BOOL SetupComm(HANDLE hFile ,DWORD

dwInQueue ,DWORD dwOutQueue ); //设置缓冲区

HANDLE CreateFile(LPCTSTR lpFileName ,DWORD

dwDesiredAccess ,DWORD

dwShareMode ,LPSECURITY_ATTRIBUTES ,DWORD dwCreationDisposition ,

DWORD dwFlagsAndAttributes ,HANDLE

hTemplateFile );// 打开串口

BOOL PurgeComm(HANDLE hFile ,DWORD dwFlags ); //清空串口缓冲区

BOOL SetCommTimeouts(HANDLE

hFile ,LPCOMMTIMEOUTS lpCommTimeouts );//设置超

时时间 BOOL

ReadFile(HANDLE hFile ,LPVOID lpBuffer ,DWORD nNum

berOfBytesToRead ,LPDWORD lpNumberOfBytesRead ,LPOVERLAPPED lpOverlapped );//读串口数据

DWORD WaitForSingleObject(HANDLE hHandle ,DWORD

dwMilliseconds );//等待事件

BOOL GetOverlappedResult(HANDLE hFile , LPOVERLAPPED lpOverlapped , LPDWORD

lpNumberOfBytesTransferred , BOOL bWait );// 等

待读数据返回

BOOL WriteFile(HANDLE hFile ,LPCVOID

lpBuffer ,DWORD nNumberOfBytesToWrite ,

LPDWORD lpNumberOfBytesWritten ,LPOVERLAPPED

lpOverlapped );//写串口数据

BOOL PostMessage(HWND hWnd ,UINT Msg ,WPARAM

wParam ,LPARAM lParam );//发送消息

HANDLE CreateThread(LPSECURITY_ATTRIBUTES

lpThreadAttributes ,DWORD

dwStackSize ,LPTHREAD_START_ROUTINE lpStartAddress ,LPVOID lpParameter , DWORD dwCreationFlags ,LPDWORD lpThreadId );//创建辅助线程

DWORD SuspendThread(HANDLE hThread ); //暂停监视线程

DWORD ResumeThread(HANDLE hThread ); //恢复监视线程

BOOL TerminateThread(HANDLE hThread ,DWORD

dwExitCode );//终止线程

2 实例分析

我们实验基地拥有6台型号各不相同数控机床,大多数数控机床不具有网络功能,一台微机标准只有两个串口,并且一般通信软件也无法对多串口进行支持。经过一段时间的努力,我们利用安装台湾MOXA 公司串口扩展卡及自编通信软件对不同数控设备串口控制,实现了一台PC 对多种不同类型数控设备的集成管理;另外还通过对串口接口改造,使信号增强,实现计算机远程控制。编程环境:Visual C++ 6.0,在WIN9x/NT 操作系统中调试通过。

2.1 实现过程

(1)网络集成管理方案

图1 网络集成管理方案示意

(2)工控机的多串口卡的安装

一般来说一台PC 所配备的RS232串口只有两个,而集中管理六台数控机床则需要至少六个。C104P 是一款由台湾MOXA 公司出品的串口功能扩展卡,可插在PC 内的ISA 插槽内(现在很多PC 中已没有了这种插槽,可选用C104P 的PCI 总线式的)。此扩展卡的主要功能就是使得PC 的串口数目从两个可扩展最大至六个。此卡的使用非常简单,在把卡在PC 主板安装好后,使用默认的跳线,及安装好与操作系统对应的驱动程序后,用自带的测试程序即可测试此卡是否安装成功。在确认安装成功后,便可在逻辑上忽略此卡的存在而只需要将其扩展出来的串口与原PC 上的

串口等同看待,通过编程实现对各个串口的操作。 (3)软件组成及运行流程 软件由:用户登入、端口设置、发送数据、接收

数据四部分组成。其运行如图2所示。

图2 软件运行流程图

用户登录:出现欢迎画面后提示登录系统,实现代码略。

端口设置:界面如图

图3

此模块能够把各个端口的设置文件保存到配置文件中,下次使用时直接读取端口配置来打开串口进行数据的接受和发送。

2.2 程序关键代码的实现

//写串口配置函数,成功返回TRUE,失败返回FALSE

BOOL CCommDlg::WriteConfig ()

{

DCB dcb;

m_nBaud=m_combBaud.GetCurSel();

s witch(m_nBaud)//波特率(数据传输速率)

{

case 0:

dcb.BaudRate=300;

break;

default:

dcb.BaudRate=9600;

break;

}

WritePrivateProfileString(COM,"BaudRate", itoa(dcb.BaudRate),strPathNme);

…//写入配置文件

}

多线程:c++是优秀的面向对象编程语言,把线程写在类中使程序简明清晰,一个串口对应一个类对象,各个端口对象互不干扰。所以本程序专门写了一个串口通信类CCommPort类来创建线程进行通讯,下面给出关键成员函数的核心代码。

//当接受到数据送到窗口的消息

#define ON_COM_RECEIVE WM_USER + 618

//WPARAM 端口号

void CCommPort::Init() //初始化串口

{ DCB _dcb;

COMMTIMEOUTS _co; // 超时时间

memset(&_dcb, 0, sizeof(_dcb));

_dcb.DCBlength = sizeof(_dcb);

_com_handle = INVALID_HANDLE_VALUE;

_in_buf = _out_buf = buffer;

memset(&_co, 0, sizeof(_co));

//_co.ReadIntervalTimeout = 0xFFFFFFFF;

_co.ReadIntervalTimeout =100;

_co.ReadTotalTimeoutMultiplier = 50;

_co.ReadTotalTimeoutConstant

=MAXDWORD;

_co.WriteTotalTimeoutMultiplier = 0;

_co.WriteTotalTimeoutConstant = 0;

}

virtual bool CCommPort::OpenPort() //打开串口同时打开监视线程

{ …

com_handle=CreateFile(com_str,GENERIC

_READ|GENERIC_WRITE,0,NULL,OPEN_EXIS

TING,FILE_ATTRIBUTE_NORMAL|FILE_FLAG

_OVERLAPPED,NULL);

SetupComm(_com_handle, _in_buf, _out_buf);

//设置推荐缓冲区

SetCommState(_com_handle, &_dcb); //

设置串口参数:波特率,停止位,等SetCommTimeouts(_com_handle, &_co); //

设置超时时间

PurgeComm(_com_handle,PURGE_TXABORT|PURG E_RXABORT| PURGE_TXCLEAR | PURGE_RXCLEAR );

//清空串口缓冲区

_thread_handle = CreateThread(NULL, 0, ComThread, this, 0, &id); //创建辅助监视线程

assert(_thread_handle);

if(!_thread_handle)

{

CloseHandle(_com_handle);

_com_handle = INVALID_HANDLE_VALUE;

Return false;

}

return true;

}

//异步读串口数据

int CCommPort::ReadData(char *buf, int buf_len)

{

if(!is_open())

return 0;

buf[0] = '\0';

COMSTAT stat;

DWORD error;

if(ClearCommError(_com_handle, &error, &stat) && error > 0) //清除错误

{

PurgeComm(_com_handle,

PURGE_RXABORT | PURGE_RXCLEAR); /*清除输入

缓冲区*/

return 0;

}

if(!stat.cbInQue)// 缓冲区无数据

return 0;

unsigned long r_len = 0;

buf_len = min((int)(buf_len - 1), (int)stat.cbInQue);

if(!ReadFile(_com_handle, buf, buf_len, &r_len, &_ro)) //2000下ReadFile始终返回 True {

if(GetLastError() == ERROR_IO_PENDING) // 结束异步I/O

{

if(!GetOverlappedResult(_com_handle, &_ro, &r_len, false))

{

if(GetLastError() != ERROR_IO_INCOMPLETE)//其他错误

r_len = 0;

}

}

else

r_len = 0;

}

totallength=totallength+r_len;

buf[r_len] = '\0';

return r_len;

}

//异步写串口数据

int CCommPort::WriteData(char *buf, int buf_len)

{

if(!is_open())

return 0;

assert(buf);

DWORD error;

if(ClearCommError(_com_handle, &error, NULL) && error > 0) //清除错误

PurgeComm(_com_handle, PURGE_TXABORT | PURGE_TXCLEAR);

unsigned long w_len = 0, o_len = 0;

if(!WriteFile(_com_handle, buf, buf_len, &w_len, &_wo))

if(GetLastError() != ERROR_IO_PENDING)

w_len = 0;

return w_len;

}

//暂停监视线程

bool CCommPort::suspend()

{

return _thread_handle != NULL ? SuspendThread(_thread_handle) != 0xFFFFFFFF : false;

}

//恢复监视线程

bool CCommPort::resume()

{

return _thread_handle != NULL ? ResumeThread(_thread_handle) != 0xFFFFFFFF : false;

}

//串口事件监视线程

static DWORD WINAPI ComThread(LPVOID para) {

_thread_com *pcom = (_thread_com *)para;

if(!SetCommMask(pcom->_com_handle,

EV_RXCHAR | EV_ERR))

return 0;

COMSTAT stat;

DWORD error;

for(DWORD length, mask = 0; pcom->_run_flag && pcom->is_open(); mask = 0)

{ // 异步操作,等待串口事件,事件结果在mask中

if(!WaitCommEvent(pcom->_com_handle, &mask, &pcom->_wait_o))

{

if(GetLastError() == ERROR_IO_PENDING)

{

GetOverlappedResult(pcom->_com_handle ,&pcom->_wait_o, &length, true);

}

}

if(mask & EV_ERR) // == EV_ERR

ClearCommError(pcom->_com_handle, &error, &stat);//清除串口可能发生的错误

if(mask & EV_RXCHAR) // == EV_RXCHAR

{

ClearCommError(pcom->_com_handle, &error, &stat);

if((stat.cbInQue > pcom->_notify_num))

{

//向外部发送通知有数据到达消息

PostMessage(_notify_hwnd,ON_COM_RECEIVE,

WPARAM(_port),LPARAM(0)); }

}

}

return 0;

}

//外部消息处理函数

Void OnReceiveData(WPARAM wp, LPARAM lp)

{

byte temBuffer[1024];

memset(temBuffer,0,1024);

BufferLength=CommPort.read((char

*)temBuffer,1024);

if(BufferLength==0)

{

return;

}

if(…)//根据通信协议判断数据是否发送完,

{

memcpy(&Buffer[totallength],temBuffer,BufferL ength);

totallength+=BufferLength;

}

…//进行数据处理控制设备

}

3 结语

从实际运行情况看,采用串口异步通信及事件驱动方式和多线程相结合的方法,通过串口功能扩展卡对多台数控机床的远程集成控制,是一种性价比很高的解决方案。这样监控主程序就可以通过类对象来产生串口实时监控线程,它能实时监控串口事件的发生,发送和接受数据非常及时,互相独立通信,且资金用量不大,本地管理方便,信号质量能保证,不会发生线路争用现象,简化操作程序,减少程序输入失误率,信息误码率大大降低,信息传输速度大大提高,实现复杂零件的远程在线加工,提高生产效率,满足现代加工制造业实时准确通信监控的需要。

参考文献:

[1]美David J. Kruglinski 著. Visual C++技术内幕(第

四版)[M].潘爱民,王国印译.北京:清华大学出版社,1999.

[2]候俊杰.深入签出MFC(第二版)[M].武汉:华中科技

大学出版社,2001.

[3]MOXA 公司.C104P User’s Manual[M].台湾:MOXA ,

1999.

[4]王爱玲.现代数控机床[M].北京:国防工业出版社,

2003.

相关文档