MIMO技术变革下一代移动通信系统设计 饽饽 2005-05-30 11:26
引言
随着无线通信技术的快速发展,频谱资源的严重不足已经日益成为遏制无线通信事业的瓶颈。如何充分开发利用有限的频谱资源,提高频谱利用率,是当前通信界研究的热点课题之一。
多输入多输出(MIMO)无线通信技术的概念非常简单,任何一个无线通信系统,只要其发射端和接收端均采用了多个天线或者天线阵列,就构成了一个无线MIMO系统。该系统采用空时处理技术进行信号处理。在多经环境下,该技术能在不增加带宽的情况下成倍地提高通信系统的容量和频谱利用率,是新一代移动通信系统必须采用的关键技术。MIMO技术实质上是为系统提供空间复用增益和空间分集增益,目前针对MIMO信道所进行的研究也主要围绕这两个方面。空间复用技术可以大大提高信道容量,而空间分集则可以提高信道的可靠性,降低信道误码率。
空间复用就是在接收端和发射端使用多副天线,充分利用空间传播中的多径分量,在同一频带上使用多个数据通道(MIMO子信道)发射信号,从而使得容量随着天线数量的增加而线性增加。这种信道容量的增加不需要占用额外的带宽,也不需要消耗额外的发射功率,因此是提高信道和系统容量一种非常有效的手段。
空间分集是用来克服无线传输中的信道衰落的一种技术,以分为接收分集和发射分集两类,通常可以认为SIMO系统是接收分集,MISO系统是发射分集。无线信号在复杂的无线信道中传播产生Rayleigh衰落,在不同空间位置上其衰落特性不同。如果两个位置间距大于天线之间的相关距离(通常相隔十个信号波长以上),就认为两处的信号完全不相关,这样就可以实现信号空间分集接收。
MIMO技术的关键是能够将传统通信系统中存在的多径衰落影响因素变成对用户通信性能有利的增强因素。MIMO技术有效地利用了随机衰落和可能存在的多径传播来成倍地提高业务传输速率。MIMO技术成功之处主要是它能够在不增加所占用的信号带宽的前提下带来无线通信的性能上几个数量级的改善。因此在多天线相关的信道建模、信息论和编码理论、信号处理算法、天线设计以及固定和移动的蜂窝设计每一面都取得了进展。
MIMO技术带来无线通信系统的四大变革
无线通信系统从发送端开始,首先经过发送端编码和发送端调制,然后通过无线通信链路到接收端天线,在接收端解调译码恢复原始信号。MIMO技术的引入是无线通信的一场革命,它的影响是全方位的。从无线通信系统的结构来看,MIMO技术通过和无线通信系统的各个关键部分相结合以改善系统的整体性能,如和编码技术结合形成空时编码技术、与调制技术结合形成MIMO OFDM技术、与天线技术结合成为多天线的智能天线系统。MIMO技术的引入使先前的通信信号的时频域处理转变为空时频三维的信号处理,极大的增强了系统性能。MIMO无线通信系统在以下一些方面具有巨大的潜力,如频谱的高效使用、带宽的动态分配、安全的无线应用、更高的服务质量、高性能的信号调制传输技术。为此,下一代MIMO无线通信系统使用了许多新的关键技术,包括空时编码技术、MIMO OFDM技术、智能天线术以及空时频三维信号的自适应技术等等。

无线通信系统简图
1. 空时编码技术——MIMO与编码技术的结合
空时编码技术是在1998年由Vahid Tarokh等人提出的一项基于发射分集的技术。Tarokh等人认为:如果在发射端采用适合多天线传输的编码技术,同时在接收端进行相应地信号处理技术,能获得很大的性能增益,这样就能够实现数据的高速传输。
最近几年来空时编码技术在无线通信领域引起了广泛关注。朗讯实验室的Forchini和Gans,AT&T的Tarokh及其同事们在这方面作了关键性工作,率先提出了空时编码的概念。空时编码的有效工作需要在发射和接收端使用多个天线,因为空时编码同时利用时间和空间两维信号处理来构造码字,这样才能有效抵消衰落,提高功率效率。并且能够在传输信道中实现并行的多路传送,提高频谱效率。需要说明的是,空时编码技术因为属于空间分集的范畴,所以要求在多散射体的多径情况下应用,天线间距应适当拉开以保证发射、接收信号的相互独立性,以充分利用多散射体所造成的多径(也称之为充分多径)。
空时编码方案结合了信道编码和多发送天线,通过空时编码后的数据被串并转换成n个数据流,每一路数据流经脉冲形成、调制,然后通过n个天线同时发送到无线空间。在接收端,可以用单一天线,也可以用多个天线进行接收,每一个接收天线接收到的是n个发送信号与干扰噪声线性的叠加(衰落系数为权重),然后通过最大似然检测的方法,正确地识别出发送信号。空时译码算法和信道估计技术结合以获得分集增益和编码增益。
目前,空时编码基本上有空时分组码(STBC)、空时格状码(STTC)和空时分层码(BLAST)等多种。
2. MIMO OFDM技术——MIMO与调制技术的结合
OFDM是一种新的高效的多载波调制技术,它能够有效地对抗多径传播,使受到干扰的信号能够可靠地接收。到目前为止,对于OFDM系统的研究已经相当深入,同时对于MIMO系统研究也早在1998,1999年就有人提出空时码的概念,利用空时码和多个发射、接受天线来完成系统的发射分集和接受分集,可以说这就是MIMO系统的雏形。我们知道单纯的OFDM系统要对抗无线环境中的多经衰落是不够的,他必须和相应的分集技术结合起来,才能更好的发挥其功效。因此我们可以将MIMO系统和OFDM系统结合起来,即构成MIMO OFDM系统.这种系统利用的是空间分集。
在未来的宽带无线通信系统中,存在两个最严峻的挑战:多径衰落信道和带宽效率。OFDM通过将频率选择性多径衰落信道在频域内转换为平坦信道,从而减小了多径衰落的影响。而MIMO技术能够在空间中产生独立的并行信道同时传输多路数据流,这样就有效的增加了系统的传输速率,即由MIMO提供的空间复用技术能够在不增加系统带宽的情况下增加频谱效率。这样,如果我们将OFDM和MIMO两种技术相结合,就能达到两种效果:一种是系统很高的传输速率,另一种是通过分集达到的很强的可靠性。同时,在MIMO-OFDM系统中加入合适的数字信号处理的算法能更好的增强系统的稳定性。
3. 智能天线技术——MIMO与天线技术的结合
MIMO技术的核心是空时信号处理,也就是利用在空间中分布的多个天线将时间域和空间域结合起来进行信号处理。因此,MIMO技术可以看作是智能天线的扩展,
在多天线技术中,最受关注的是智能天线技术,国际电联已明确将智能天线技术作为三代以后移动通信技术发展的主要方向。智能天线通常也被称作自适应天线,主要用于完成空间滤波和定位。从本质上看,智能天线利用了天线阵列中各单元之间的位置关系,即利用了信号的相位关系,这是它与传统分集技术的本质区别。从一定意义上看,智能天线可看作是一种空分多址SDMA,在SDMA中,多个用户可共享一个信道,这将极大地增加系统容量。
智能天线技术可以定义为:具有波束成形能力的天线阵列,可以形成特定的天线波束,实现定向发送和接收。智能天线可以利用信号的空间特征分开用户信号、多址干扰以及多径干扰信号。智能天线分为两大类:多波束智能天线与自适应阵智能天线,简称多波束天线和自适应阵天线。
更高数据速率和越来越复杂的工作特性必然要求下一代无线通信的天线系统提供精确而且灵活的干扰控制。而智能天线提供了一种方案来解决困扰无线网络的根本问题。因此,智能天线一定会在移动通信系统中得到广泛的应用。
4.空时频自适应技术
正交频分复用技术由于能有效地抑制码间干扰(ISI)而成为多径衰落信道环境下传输高速数据的有效调制技术。但在传统OFDM传输系统(即没有采用空间分集和自适应调制等性能增强技术的基本OFDM系统)中,由于在所有的子载波中采用相同且固定的调制方式,因此它没有充分利用各子载波的频率选择性衰落信道信息。为了克服这个缺点,人们提出了充分利用子载波信道特性的自适应OFDM技术。在自适应OFDM中,子载波上的调制方式随信道质量而自适应变化,这样就充分利用了各子载波的信道信息,从而大大地提高了系统的误比特性能。
MIMO系统中不同信道相互独立,在高信噪比的情况下系统的容量几乎跟发射端和接收端天线的最小数目成正比。不同调制技术也会给系统带来不同的增益。根据一些已经发表的论文,MIMO技术跟自适应调制技术结合可以获得更大的系统增益,所以MIMO技术与先进自适应技术结合的研究也成为无线通信研究的一个重要方面。
MIMO与OFDM技术结合,可以将无线通信的信号处理从时频分集扩展为时空频分集,进一步分割信道为空时频正交的子信道。这样,我们就需要根据各个子信道的实际传输情况灵活的分配发送功率和信息比特。而且由于无线信道的频率选择性和时变性,也需要实时地对信道进行检测,更加有效的利用无线资源。
对于所有子载波都使用相同固定调制方案的通信系统来说,其误码率主要由经历衰落最严重的子载波决定。因此在频率选择性衰落信道中,随着平均信噪比的增加,系统的误码率下降是十分缓慢的。但我们可以对不同的子信道采用不同传输方案。所有的传输方案要适应每个子信道的信噪比。而且也可以对单个子信道的功率分配实现最优化。(图3为自适应方案的系统结构图)
结束语
最近十年来随着因特网和移动通信飞速发展,第三代移动通信也已经引入了无线因特网和多媒体业务。而在下一代移动通信系统(即所谓的Beyond 3G或4G)中,人们对传输速率提出了更高的要求,这就需要采用更先进的技术来实现更高的传输速率。然而频谱资源总是有限的,要支持高速率就要开发具有极高频谱利用率的无线通信技术。MIMO技术可以显著提高无线系统的频谱利用率,也非常适用于室内环境下的无线局域网系统使用,采用MIMO技术的无线局域网系统在室内传播环境下的频谱效率可以达到20~40 bit/s/Hz;而使用传统无线通信技术在移动蜂窝中的频谱效率仅为1~5 bit/s/Hz,在点到点的固定微波系统中也只有10~12 bit/s/Hz。MIMO技术作为提高数据传输速率的重要手段得到人们越来越多的关注,已经被认为是新一代无线宽带通信系统的革命技术。
MIMO技术将成为第三代和未来移动通信与个人通信系统实现数据速率,提高传输质量的重要途径。
re: Vc编程基础 饽饽 2005-05-24 22:45
对VC编程基础(1)的主程序进行分析后,就会知道,主函数的任务就是 注册(MyRegisterClass(hInstance)),创建(Create(hInstance,nCmdShow))并显示窗体(ShowWindow(hWnd,nCmdShow); UpdateWindow(hWnd); )而且 实现消息循环(Run())。如果用面向对象的思想来考虑,主函数的函数体可以看成一个对象-应用程序类对象,而其中的窗体应该是应该是嵌入在这个应用程序类对象中的另一个对象。。。。。因此,为了形成应用程序框架,应该声明两个类:应用程序类和窗体类。也就是说,应用程序类和窗体类 分别与 主函数和窗体函数 相对应。。。
一窗体类的封装
窗体类应该具有窗体类的 定义、注册、创建和显示 等功能。同时在类中应该有一个HWND类型的窗口句柄hWnd,作为类的数据成员。 因此,窗体类只要把窗口句柄与对窗口操作的API函数封装到一起就可以了..........
//窗体类的声明
class CFrameWnd
{
public:
HWND hWnd;
public:
int RegisterWindow(); //注册函数
void Create(LPCTSTR lpClassName, LPCTSTR lpWindowName); //创建窗体函数
void ShowWindow(int nCmdShow);
void UpdateWindow();
};
//窗体类的成员函数--------------------------
int CFrameWnd::RegisterWindow()
{
WNDCLASS wc; // 定义窗口类结构 对象wc
wc.style=0;
wc.lpfnWndProc=WndProc;
wc.cbClsExtra=0;
wc.cbWndExtra=0;
wc.hInstance=hInstance;
wc.hIcon=LoadIcon(NULL,IDI_APPLICATION);
wc.hCursor=LoadCursor(NULL,IDC_ARROW);
wc.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);
wc.lpszMenuName=NULL;
wc.lpszClassName=lpszClassname;
return RegisterClass(&wc); // 实现 窗口类的注册
}
void CFrameWnd::Create(LPCTSTR lpClassName, LPCTSTR lpWindowName)
{
RegisterWindow();
hInst=hInstance;
hWnd=CreateWindow(lpszClassname,lpWindowName,WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,0,CW_USEDEFAULT,0,
NULL,NULL,hInstance,NULL);
}
void CFrameWnd::ShowWindow(int nCmdShow)
{
::ShowWindow(hWnd,nCmdShow);
}
void CFrameWnd::UpdateWindow()
{
::UpdateWindow(hWnd);
}
//--------------------------------
通过上面 分析,然后再与 VC编程基础(1) 的程序中的前面部分比较,你就会理解MFC的类的封装过程。。
下一部分将讲述 应用程序类 的 封装。。。。。
envily edited on 2005-04-20 15:30
利用CSocket传送大型数据实现 饽饽 2005-03-25 16:49
利用CSocket传送大型数据实现
(dlutyuanhongl发表于2005-3-19 16:08:05)
传输大型文件实列
SOCKET API,98/NT/2000调试通过。
C/S均建立读/写线程,一但连接,C/S便都可发送/接收文件,发送文件方式使用字节流传送。将传送文件进行分割,每次传送1K,CLIENT或SERVER方收到后进行重组。接收文件方式使用异步SOCKET,根据实际读入数据写文件。在LAN上测试,C/S多次相互传输大型文件如:IIS,SQL SP,和几个上百M的文件效果良好。
Server.cpp
//---------------------------------------------------------------------------
// SERVER端
//
// 贾佳,jiasys@21cn.com
//---------------------------------------------------------------------------
#include <vcl.h>
#include <winsock.h>
#define BUFFSIZE 1024
#pragma hdrstop
#include "Server.h"
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
WSADATA wsaData;
SOCKET sck,sc;
SOCKADDR_IN to,client;
BOOL flag=TRUE;
int iAddrSize,ret,ret_no,i=0;
TCHAR szBuf[BUFFSIZE];
fd_set FdRead;
//---------------------------------------------------------------------------
// ReadClient
//
// 接收文件数据线程,判断建立文件关键字,每次将实际读到的数据写入文件。
//
//---------------------------------------------------------------------------
DWORD WINAPI ReadClient(LPVOID lPort)
{
HANDLE hFile=NULL;
DWORD dwWrite,dwFileSize;
TCHAR szFileName[MAX_PATH];
FD_ZERO(&FdRead);
FD_SET(sck,&FdRead);
while(TRUE)
{
ret_no=select(0,&FdRead,NULL,NULL,NULL);
if(ret_no==SOCKET_ERROR)
{
closesocket(sck);
return FALSE;
}
if(FD_ISSET(sck,&FdRead))
{
iAddrSize=sizeof(client);
sc=accept(sck,(SOCKADDR *)&client,&iAddrSize);
if(sc==INVALID_SOCKET)
{
MessageBox(NULL,"accept error",NULL,MB_OK);
closesocket(sc);
WSACleanup();
}
getpeername(sck,(SOCKADDR *)&client,&iAddrSize);
ShowMessage(inet_ntoa(client.sin_addr));
FD_SET(sc,&FdRead);
}
if(FD_ISSET(sc,&FdRead))
{
ZeroMemory(szBuf,sizeof(szBuf));
if((dwFileSize=recv(sc,szBuf,BUFFSIZE,0))==SOCKET_ERROR)
{
closesocket(sc);
CloseHandle(hFile);
return FALSE;
}
else if((strncmp(szBuf,"UPFILE_",lstrlen("UPFILE_")))==0)
{
wsprintf(szFileName,"c:\%s",szBuf);
hFile=CreateFile(szFileName,GENERIC_READ|GENERIC_WRITE,FILE_SHARE_READ,
NULL,CREATE_NEW,FILE_ATTRIBUTE_NORMAL|
FILE_ATTRIBUTE_ARCHIVE,(HANDLE)NULL);
if(hFile==INVALID_HANDLE_VALUE)
{
MessageBox(NULL,"Server Open File Error",NULL,MB_OK);
return FALSE;
}
ZeroMemory(szBuf,sizeof(szBuf));
}
else
WriteFile(hFile,szBuf,dwFileSize,&dwWrite,NULL);
}
}
CloseHandle(hFile);
return TRUE;
}
//---------------------------------------------------------------------------
// WriteClient
//
// 发送文件数据线程,每次预读1K数据,根据实际读取发送,直到读取数据小于1K
//
//---------------------------------------------------------------------------
DWORD WINAPI WriteClient(LPVOID szFileName)
{
HANDLE hFile;
DWORD dwRead,dwNdx;
BOOL bRet;
TCHAR szFileBuff[BUFFSIZE],szSend[MAX_PATH];
wsprintf(szSend,"DOWNFILE_%s",ExtractFileName((LPCTSTR)szFileName).c_str());
send(sc,szSend,lstrlen(szSend),0);
hFile=CreateFile((LPCTSTR)szFileName,GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL|
FILE_ATTRIBUTE_ARCHIVE,(HANDLE)NULL);
if(hFile==INVALID_HANDLE_VALUE)
{
MessageBox(NULL,"Open File Error",NULL,MB_OK);
ExitProcess(0);
}
do
{
bRet=ReadFile(hFile,szFileBuff,BUFFSIZE,&dwRead,NULL);
if(bRet==FALSE)
{
MessageBox(NULL,"Read Buf ERROR!",NULL,MB_OK);
break;
}
else if(dwRead==0)
{
MessageBox(NULL,"File EOF!",NULL,MB_OK);
break;
}
else
{
send(sc,szFileBuff,dwRead,0);
}
}while(dwRead==BUFFSIZE);
CloseHandle(hFile);
return TRUE;
}
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender)
{
HANDLE hThread;
DWORD dwTid;
if(WSAStartup(MAKEWORD(1,1),&wsaData)!=NULL)
{
ShowMessage("初始化WINSOCK错误");
WSACleanup();
}
if((sck=socket(AF_INET,SOCK_STREAM,0))==SOCKET_ERROR)
{
ShowMessage("SOCKET错误");
closesocket(sck);
WSACleanup();
}
to.sin_family=AF_INET;
to.sin_port=htons(926);
to.sin_addr.s_addr=htonl(INADDR_ANY);
if(setsockopt(sck,SOL_SOCKET,SO_REUSEADDR,(LPSTR)&flag,sizeof(flag))==SOCKET_ERROR)
{
ShowMessage("setsockopt error!");
closesocket(sck);
}
if(bind(sck,(struct sockaddr *)&to,sizeof(to))==SOCKET_ERROR)
{
ShowMessage("Could not bind");
closesocket(sck);
}
else
{
listen(sck,1);
hThread=CreateThread(NULL,0,ReadClient,(LPVOID)0,0,&dwTid);
CloseHandle(hThread);
}
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
HANDLE hThread;
DWORD dwTid;
if(OpenDialog1->Execute())
{
hThread=CreateThread(NULL,0,WriteClient,(LPVOID)OpenDialog1->FileName.c_str(),0,&dwTid);
CloseHandle(hThread);
}
}
//---------------------------------------------------------------------------
Client.cpp
//---------------------------------------------------------------------------
// CLIENT端
//
// 贾佳,jiasys@21cn.com
//---------------------------------------------------------------------------
#include <vcl.h>
#include <winsock.h>
#define BUFFSIZE 1024
#pragma hdrstop
#include "Client.h"
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
WSADATA wsa;
SOCKET sck;
SOCKADDR_IN tto;
hostent *host;
fd_set FdRead;
int port=926,ret,i;
DWORD dwRead;
//---------------------------------------------------------------------------
// ReadClient
//
// 接收文件数据线程,判断建立文件关键字,每次将实际读到的数据写入文件。
//
//---------------------------------------------------------------------------
DWORD WINAPI ReadClient(LPVOID lParam)
{
HANDLE hFile;
DWORD dwWrite,dwFileSize;
TCHAR szFileName[MAX_PATH];
TCHAR szBuff[BUFFSIZE];
FD_ZERO(&FdRead);
FD_SET(sck,&FdRead);
while(TRUE)
{
ret=select(0,&FdRead,NULL,NULL,NULL);
if(ret==SOCKET_ERROR)
{
closesocket(sck);
return FALSE;
}
else if(FD_ISSET(sck,&FdRead))
{
ZeroMemory(szBuff,sizeof(szBuff));
if((dwFileSize=recv(sck,szBuff,BUFFSIZE,0))==SOCKET_ERROR)
{
closesocket(sck);
CloseHandle(hFile);
return FALSE;
}
else if((strncmp(szBuff,"DOWNFILE_",lstrlen("DOWNFILE_")))==0)
{
wsprintf(szFileName,"c:\%s",szBuff);
hFile=CreateFile(szFileName,GENERIC_READ|GENERIC_WRITE,FILE_SHARE_READ,
NULL,CREATE_NEW,FILE_ATTRIBUTE_NORMAL|
FILE_ATTRIBUTE_ARCHIVE,(HANDLE)NULL);
if(hFile==INVALID_HANDLE_VALUE)
{
MessageBox(NULL,"Cli Open File Error",NULL,MB_OK);
return FALSE;
}
ZeroMemory(szBuff,sizeof(szBuff));
}
else
WriteFile(hFile,szBuff,dwFileSize,&dwWrite,NULL);
}
}
return TRUE;
}
//---------------------------------------------------------------------------
// WriteClient
//
// 发送文件数据线程,每次预读1K数据,根据实际读取发送,直到读取数据小于1K
//
//---------------------------------------------------------------------------
DWORD WINAPI WriteClient(LPVOID szFileName)
{
HANDLE hFile;
DWORD dwRead,dwNdx;
BOOL bRet;
TCHAR szFileBuff[BUFFSIZE],szSend[MAX_PATH];
wsprintf(szSend,"UPFILE_%s",ExtractFileName((LPCTSTR)szFileName).c_str());
send(sck,szSend,lstrlen(szSend),0);
hFile=CreateFile((LPCTSTR)szFileName,GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL|
FILE_ATTRIBUTE_ARCHIVE,(HANDLE)NULL);
if(hFile==INVALID_HANDLE_VALUE)
{
MessageBox(NULL,"Open File Error",NULL,MB_OK);
ExitProcess(0);
}
do
{
bRet=ReadFile(hFile,szFileBuff,BUFFSIZE,&dwRead,NULL);
if(bRet==FALSE)
{
MessageBox(NULL,"Read Buf ERROR!",NULL,MB_OK);
break;
}
else if(dwRead==0)
{
MessageBox(NULL,"File EOF!",NULL,MB_OK);
break;
}
else
{
send(sck,szFileBuff,dwRead,0);
}
}while(dwRead==BUFFSIZE);
CloseHandle(hFile);
return TRUE;
}
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
HANDLE hThread;
DWORD dwTid;
WSAStartup(MAKEWORD(1,1),&wsa);
sck=socket(AF_INET,SOCK_STREAM,0);
if(sck==INVALID_SOCKET)
{
ShowMessage("Could not create a sock");
ExitProcess(0);
}
else
{
host=gethostbyname(Edit1->Text.c_str());
tto.sin_family=AF_INET;
tto.sin_port=htons(port);
CopyMemory(&tto.sin_addr,host->h_addr,host->h_length);
if((connect(sck,(struct sockaddr FAR *)&tto,sizeof(tto))==SOCKET_ERROR))
{
ShowMessage("connect error!");
closesocket(sck);
}
else
{
hThread=CreateThread(NULL,0,ReadClient,(LPVOID)0,0,&dwTid);
CloseHandle(hThread);
}
}
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
HANDLE hThread;
DWORD dwTid;
if(OpenDialog1->Execute())
{
hThread=CreateThread(NULL,0,WriteClient,(LPVOID)OpenDialog1->FileName.c_str(),0,&dwTid);
CloseHandle(hThread);
}
}
//---------------------------------------------------------------------------
re: 怎样读串口 饽饽 2005-03-16 20:42
这是我的一个读串口的函数:
HANDLE m_hIDComDev;
int ReceiveComm(char* RecCommData)
{
DWORD dRead,dReadNum;
COMSTAT ComStat;
LPDWORD ComError;
char *Data;
ClearCommBreak(m_hIDComDev);
ClearCommError(m_hIDComDev,ComError,&ComStat);
dRead=ReadFile(m_hIDComDev, Data, ComStat.cbInQue, &dReadNum, NULL); //接收200个字符
//dReadNum为实际接收字节数
PurgeComm(m_hIDComDev,PURGE_RXCLEAR); //清空接收缓冲区
for(int i = 0 ;i < ComStat.cbInQue; i++)
{
*RecCommData = *Data;
RecCommData++;
Data++;
}
*RecCommData = '\0';
if(dRead)
return 1;
else
return 0;
}
re: 怎样读串口 饽饽 2005-03-16 20:41
方法三 多线程下实现串行通信
方法一,二适用于单线程通信。在很多工业控制系统中,常通过扩展串口连接多个外设,各外设发送数据的重复频率不同,要求后台实时无差错捕捉,采集,处理,记录各端口数据,这就需要在自定义的串行通信类中创建端口监视线程,以便在指定的事件发生时向相关的窗口发送通知消息。
线程的基本概念可详见VC++参考书目,Windows内部的抢先调度程序在活动的线程之间分配CPU时间,Win 32 区分两种不同类型的线程,一种是用户界面线程UI(User Interface Thread),它包含消息循环或消息泵,用于处理接收到的消息;另一种是工作线程(Work Thread),它没有消息循环,用于执行后台任务。用于监视串口事件的线程即为工作线程。
多线程通信类的编写在端口的配置,连接部分与单线程通信类相同,在端口配置完毕后,最重要的是根据实际情况,建立多线程之间的同步对象,如信号灯,临界区,事件等,相关细节可参考VC++ 中的同步类。
一切就绪后即可启动工作线程:
CWinThrea *CommThread = AfxBegin
Thread(CommWatchThread, // 线程函数名
(LPVOID) m_pTTYInfo, // 传递的参数
THREAD_PRIORITY_ABOVE_NORMAL, // 设置线程优先级
(UINT) 0, // 最大堆栈大小
(DWORD) CREATE_SUSPENDED , // 创建标志
(LPSECURITY_ATTRIBUTES) NULL); // 安全性标志
同时,在串口事件监视线程中:
if(WaitCommEvent(pTTYInfo->idComDev,&dwEvtMask,NULL))
{
if((dwEvtMask & pTTYInfo->dwEvtMask )== pTTYInfo->dwEvtMask)
{
WaitForSingleObject(pTTYInfo->hPostEvent,0xFFFFFFFF);
ResetEvent(pTTYInfo->hPostEvent); // 置同步事件对象为非信号态
::PostMessage(CSampleView,ID_COM1_DATA,0,0); // 发送通知消息
}
}
用PostMessage()向指定窗口的消息队列发送通知消息,相应地,需要在该窗口建立消息与成员函数间的映射,用ON_MESSAGE将消息与成员函数名关联。
BEGIN_MESSAGE_MAP(CSampleView, CView)
//{{AFX_MSG_MAP(CSampleView)
ON_MESSAGE(ID_COM1_DATA, OnProcessCom1Data)
ON_MESSAGE(ID_COM2_DATA, OnProcessCom2Data)
.....
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
然后在各成员函数中完成对各串口数据的接收处理,但必须保证在下一次监测到有数据到来之前,能够完成所有的中间处理工作。否则将造成数据的捕捉错误。
多线程的实现可以使得各端口独立,准确地实现串行通信,使串口通信具有更广泛的灵活性与严格性,且充分利用了CPU时间。但在具体的实时监控系统中如何协调多个线程,线程之间以何种方式实现同步也是在多线程串行通信程序实现的难点。
re: 怎样读串口 饽饽 2005-03-16 20:41
方法二:在单线程中实现自定义的串口通信类
控件简单易用,但由于必须拿到对话框中使用,在一些需要在线程中实现通信的应用场合,控件的使用显得捉襟见肘。此时,若能够按不同需要定制灵活的串口通信类将弥补控件的不足,以下将介绍如何在单线程中建立自定义的通信类。
该通信类CSimpleComm需手动加入头文件与源文件,其基类为CObject,大致建立步骤如下:
(1) 打开串口,获取串口资源句柄
通信程序从CreateFile处指定串口设备及相关的操作属性。再返回一个句柄,该句柄将被用于后续的通信操作,并贯穿整个通信过程。CreateFile()函数中有几个值得注意的参数设置:串口共享方式应设为0,串口为不可共享设备;创建方式必须为OPEN_EXISTING,即打开已有的串口。对于dwFlagAndAttribute参数,对串口有意义的值是FILE_FLAG_OVERLAPPED,该标志表明串口采用异步通信模式,可进行重叠操作;若值为NULL,则为同步通信方式,在同步方式下,应用程序将始终控制程序流,直到程序结束,若遭遇通信故障等因素,将导致应用程序的永久等待,所以一般多采用异步通信。
(2)串口设置
串口打开后,其属性被设置为默认值,根据具体需要,通过调用GetCommState(hComm,&dcb)读取当前串口设备控制块DCB(Device Control Block)设置,修改后通过SetCommState(hComm,&dcb)将其写入。再需注意异步读写的超时控制设置, 通过COMMTIMEOUTS结构设置超时,调用SetCommTimeouts(hComm,&timeouts)将结果写入。以下是温度监控程序中串口初始化成员函数:
BOOL CSimpleComm::Open( )
{
DCB dcb;
m_hIDComDev=CreateFile( "COM2",
GENERIC_READ | GENERIC_WRITE,
0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_
NORMAL|FILE_FLAG_OVE RLAPPED, NULL );
// 打开串口,异步操作
if( m_hIDComDev == NULL ) return( FALSE );
dcb.DCBlength = sizeof( DCB );
GetCommState( m_hIDComDev, &dcb ); // 获得端口默认设置
dcb.BaudRate=CBR_4800;
dcb.ByteSize=8;
dcb.Parity= NOPARITY;
dcb.StopBits=(BYTE) ONESTOPBIT;
...... }
(3)串口读写操作
主要运用ReadFile()与WriteFile()API函数,若为异步通信方式,两函数中最后一个参数为指向OVERLAPPED结构的非空指针,在读写函数返回值为FALSE的情况下,调用GetLastError()函数,返回值为ERROR_IO_PENDING,表明I/O操作悬挂,即操作转入后台继续执行。此时,可以用WaitForSingleObject()来等待结束信号并设置最长等待时间,举例如下:
BOOL bReadStatus;
bReadStatus = ReadFile( m_hIDComDev, buffer,
dwBytesRead, &dwBytesRead, &m_OverlappedRead );
if(!bReadStatus)
{
if(GetLastError()==ERROR_IO_PENDING)
{
WaitForSingleObject(m_OverlappedRead.hEvent,1000);
return ((int)dwBytesRead);
}
return(0);
}
return ((int)dwBytesRead);
定义全局变量m_Serial作为新建通信类CSimpleComm的对象,通过调用类的成员函数即可实现所需串行通信功能。与方法一相比,方法二赋予串行通信程序设计较大的灵活性,端口的读写可选择较简单的查询式,或通过设置与外设数据发送时间间隔TimeCycle相同的定时器:SetTimer(1,TimeCycle,NULL),进行定时读取或发送。
CSampleView:: OnTimer(UINT nIDEvent)
{
char InputData[30];
m_Serial.ReadData(InputData,30);
// 数据处理
}
若对端口数据的响应时间要求较严格,可采用事件驱动I/O读写,Windows定义了9种串口通信事件,较常用的有:
EV_RXCHAR: 接收到一个字节,并放入输入缓冲区。
EV_TXEMPTY: 输出缓冲区中的最后一个字符发送出去。
EV_RXFLAG: 接收到事件字符(DCB结构中EvtChar成员),放入输入缓冲区。
在用SetCommMask()指定了有用的事件后,应用程序可调用WaitCommEvent()来等待事件的发生。
SetCommMask(hComm,0)可使WaitCommEvent()中止。