串口的概念

串口称为串口,现在的PC电脑一般都有COM 1和COM 2两个串口。串口与并口不同,它的数据和控制信息是逐位传输的。虽然速度会慢一些,但是传输距离比并口长,所以远距离通信要用串口。COM 1通常使用9针D型连接器,也称为RS-232接口,而COM 2使用老式的DB25针连接器,也称为RS-422接口,目前很少使用。

串行通信简介

一、串行通信的基本原理

串口的基本功能是充当CPU和串行设备之间的编解码器。当CPU通过串口发送数据时,字节数据被转换成串行位。接收数据时,串行位被转换为字节数据。

在Windows环境下(Windows NT、Win98、Windows2000),串口是系统资源的一部分。

如果一个应用程序要使用串口进行通信,必须在使用之前向操作系统申请资源(打开串口),通信完成后释放资源(关闭串口)。

串行通信程序的流程如下:

2.串行信号线的连接

一个完整的RS-232C接口有22根线,采用标准的25芯插头插座(或9芯插头插座)。25芯和9芯主信号线是一样的。下面介绍以25核RS-232C为例。

①主信号线的定义:

引脚2:发送数据TXD;;引脚3:接收数据RXD;;Pin 4:请求发送RTS;引脚5:清除并发送CT;;

6针:数据设备就绪data;20针:数据终端就绪data;8针:数据载体检测data;

1英尺:保护地;7英尺:信号地。

②电气特性:

最大数据传输速率可达20K bps,最大距离仅为15m。

注:看了微软的MSDN 6.0,其Windows API中的串口通信设备(不一定是串口RS-232C或者RS-422或者RS-449)的设置可以支持RS_256000,也就是最多256K bps!不知道是什么串口通讯设备。但无论如何,通用主机与单片机的串行通信大多在9600 bps,可以满足通信需求。

③界面的典型应用:

大多数计算机应用系统和智能单元只需要3到5条信号线就能工作。此时,除了TXD和RXD,还需要RTS、CTS、DCD、DTR和DSR等信号线。(当然,相应的信号线需要在程序中设置。)

在上面的连接中,设计程序时,直接收发数据就够了,不需要判断或设置信号线的状态。(如果应用中需要握手信号,则需要监控或设置相应信号线的状态。)

三。16位串行应用简述

16位串行端口应用程序中使用的16位Windows API通信函数:

① OpenComm()打开串口资源,指定输入输出缓冲区的大小(以字节为单位);

CloseComm()关闭串口;

例如:int idComDev

idComDev = OpenComm("COM1 ",1024,128);

CloseComm(idComDev);

② BuildCommDCB()和setCommState()填充设备控制块DCB,然后配置所开串口的参数;

例子:DCB dcb

BuildCommDCB("COM1:2400,n,8,1 ",& ampdcb);

SetCommState(& amp;dcb);

③ ReadComm和WriteComm()读写串口,即接收和发送数据。

例如:char * m _ pRecieveint计数;

ReadComm(idComDev,m_pRecieve,count);

char wr[30];int count2

WriteComm(idComDev,wr,count 2);

16 bit下的串口通信程序最大的特点是对串口等外部设备的操作有自己独特的API函数;32位程序统一了串口操作(和并口等。)和文件操作,并采用类似的操作。

4.MFC下的32位串口应用

32位下的串行通信程序有两种实现方式:使用ActiveX控件;使用API通信函数。

使用ActiveX控件,程序实现非常简单,结构清晰,缺点是不灵活;使用API通信函数的优缺点基本相反。

以下是为单文档(SDI)应用程序添加串行通信功能的所有程序。

使用ActiveX控件:

VC++ 6.0提供的MSComm控件通过串口发送和接收数据,为应用程序提供串行通信功能。使用起来非常方便,但遗憾的是,关于MSComm控件的资料很少。

(1).在当前工作区插入MSComm控件。

项目菜单->添加到项目-& gt;组件和控件-& gt;注册的

ActiveX控件-& gt;选择组件:Microsoft通信控件,

6.0版将插入当前工作空间。

结果添加了CMSComm(以及相应的文件:mscomm.h和mscomm.cpp)。

⑵.在MainFrm.h中添加MSComm控件

受保护:

CMSComm m _ ComPort

在Mainfrm.cpp::OnCreare()中:

DWORD style = WS _ VISIBLE | WS _ CHILD;

如果(!m_ComPort。Create(NULL,style,CRect(0,0,0,0),this,ID_COMMCTRL)){

TRACE0("未能创建OLE通信控件");

return-1;//创建失败

}

(3).初始化串行端口

m_ComPort。SetCommPort(1);//选择COM?

m_ComPort。SetInBufferSize(1024);//设置输入缓冲区的大小,字节。

m_ComPort。SetOutBufferSize(512);//设置输入缓冲区的大小,Bytes//

如果(!M _组件。getportopen())//打开串口。

m_ComPort。SetPortOpen(TRUE);

m_ComPort。SetInputMode(1);//将输入模式设置为二进制模式。

m_ComPort。setsetsets(" 9600,n,8,1 ");//设置波特率等参数。

m_ComPort。SetRThreshold(1);//1表示某个角色触发了一个事件。

m_ComPort。SetInputLen(0);

(4).捕捉连续事件。MSComm控件可以使用轮询或事件驱动的方法从端口获取数据。我们介绍一种比较常用的事件驱动方法:当有事件发生时(比如接收数据)通知程序。这些通信事件需要在程序中被捕获和处理。

在MainFrm.h中:

受保护:

afx _ msg void oncommscomm();

DECLARE_EVENTSINK_MAP()

在MainFrm.cpp中:

BEGIN_EVENTSINK_MAP(CMainFrame,CFrameWnd)

ON_EVENT(CMainFrame,ID_COMMCTRL,1,OnCommMscomm,VTS_NONE)

//映射ActiveX控件事件

END_EVENTSINK_MAP()

5].串口读写。读写的功能真的很简单,GetInput()和SetOutput()就可以了。这两个函数的原型是:

VARIANT GetInput();和void SetOutput(常量变量& ampnew value);使用VARIANT类型(Idispatch::Invoke的所有参数和返回值在内部都被视为VARIANT对象)。

无论是PC读取上传数据还是发送下行命令,我们都习惯使用字符串(或数组)的形式。查找VARIANT文档,我们知道可以用BSTR来表示字符串,但遗憾的是所有BSTR都包含宽字符,即使我们没有定义_UNICODE_UNICODE!WinNT支持宽字符,但Win95不支持。为了解决上述问题,我们在实际工作中使用了CbyteArray,并给出了一些相应的程序如下:

void CMainFrame::oncommscomm(){

变体vResponseint k;

if(m_commCtrl。GetCommEvent()==2) {

k=m_commCtrl。GetInBufferCount();//接收的字符数

if(k & gt;0) {

vResponse=m_commCtrl。GetInput();//读取

SaveData(k,(unsigned char *)v response . parray-& gt;pvData);

}//当接收到字符时,MSComm控件发送一个事件}

。。。。。//处理其他MSComm控件

}

void CMainFrame::OnCommSend() {

。。。。。。。。//准备好要发送的命令,放入TxData[]中。

CByteArray数组;

数组。remove all();

数组。SetSize(计数);

for(I = 0;我

数组。SetAt(i,tx data[I]);

m_ComPort。set output(cole variant(array));//发送数据

}

请密切注意(4)和(5)中的内容,这是实际工作中的重点和难点。

2.使用32位API通信函数:

可能很多朋友会觉得奇怪:用32位API函数写串口通信程序,不就是把16位API换成32位API吗?16位串行通信程序很多年前就被很多人讨论过...

主要介绍如何在API串行通信中结合非阻塞通信、多线程等手段,编写高质量的通信程序。尤其是当CPU处理任务繁重,与外围设备的通信数据较多时,更有实际意义。

(1).在MainFrm.cpp中定义全局变量

处理hCom//要打开的串口句柄

处理hCommWatchThread//工作线程的全局函数

2.打开串口,设置串口。

Hcom = createfile ("com2 ",generic _ read | generic _ write,//允许读写。

0,//此项必须为0。

NULL,//没有安全属性

OPEN_EXISTING,//设置生成方式。

FILE_FLAG_OVERLAPPED,//我们要用异步通信。

NULL);

请注意,我们使用FILE_FLAG_OVERLAPPED结构。这是使用API进行非阻塞通信的关键。

断言(hCom!=无效句柄值);//检测串口打开操作是否成功。

SetCommMask(hCom,EV _ rx char | EV _ tx empty);//设置事件驱动类型。

SetupComm( hCom,1024,512);//设置输入和输出缓冲区的大小。

PurgeComm( hCom,PURGE _ tx abort | PURGE _ rx abort | PURGE _ tx clear

| PURGE _ rx clear);//清除输入和输出缓冲区。

comm time out comm time out;//定义超时结构并填写。

…………

SetCommTimeouts(hCom & amp;CommTimeOuts//设置读写操作允许的超时。

DCB dcb//定义数据控制块结构

GetCommState(hCom & amp;dcb);//读取串口原始参数设置。

dcb。波德拉特= 9600;dcb。ByteSize = 8;dcb。奇偶性= NOPARITY

dcb。StopBits = ONESTOPBITdcb.fBinary = TRUEdcb.fParity = FALSE

SetCommState(hCom & amp;dcb);//串口参数配置

上面提到的COMMTIMEOUTS结构和DCB都很重要,实际工作中需要仔细选择参数。

⑶启动一个处理串行事件的辅助线程。

Windows提供了两种线程,工作线程和用户界面线程。区别在于工作线程没有窗口,所以它没有自己的消息循环。但是工作线程很容易编程,而且通常很有用。

第二次,我们使用工作线程。主要用于监控串口状态,看是否有数据到达和通信错误;主线程可以专注于数据处理、提供友好的用户界面等重要工作。

hCommWatchThread =

CreateThread((LP security _ attributes)null,//安全属性。

0,//初始化线程堆栈的大小,默认与主线程大小相同。

(LP thread _ start _ routine)commwatchproc,//线程的全局函数。

GetSafeHwnd(),//这里传入主框架的句柄。

0,& ampdwThreadID);

ASSERT(hCommWatchThread!= NULL);

(4)为辅助线程编写一个全局函数,主要完成数据接收的工作。请注意重叠结构的使用,以及如何实现无阻塞通信。

UINT CommWatchProc(HWND hSendWnd){

DWORD dwEvtMask = 0;

SetCommMask( hCom,EV _ rx char | EV _ tx empty);//需要监控哪些连环事件?

WaitCommEvent(hCom & amp;dwEvtMask,OS);//等待串行通信事件发生。

检测返回的dwEvtMask并了解发生了什么串行事件:

if((dwEvtMask & amp;EV_RXCHAR) == EV_RXCHAR){ //有数据到达缓冲区。

COMSTAT ComStatDWORD dwLength

ClearCommError(hCom & amp;dwErrorFlags。ComStat);

dwLength = ComStat.cbInQue//输入缓冲区中有多少数据?

if(dwLength & gt;0) {

BOOL fReadStat

fReadStat = ReadFile( hCom,lpBuffer,dwLength,& ampdwBytesRead

& ampREAD _ OS(npTTYInfo));//读取数据

注意:我们在创建FILE()时使用了FILE_FLAG_OVERLAPPED,现在CreareFile()也必须使用。

LP重叠结构。否则,该函数将错误地报告读取操作已完成。

利用LPOVERLAPPED结构,ReadFile()不等待读操作完成就立即返回,实现了非阻塞。

交流。此时,ReadFile()返回FALSE,GetLastError()返回ERROR_IO_PENDING。

如果(!fReadStat){

if(GetLastError()= = ERROR _ IO _ PENDING){

而(!GetOverlappedResult(hCom,

& ampREAD_OS( npTTYInfo),& ampdwBytesRead,TRUE )){

dwError = GetLastError();

if(dw ERROR = = ERROR _ IO _ INCOMPLETE)继续;

//缓冲区数据尚未完全读取。继续。

…… ……

* PostMessage((HWND)hSendWnd,WM_NOTIFYPROCESS,0,0);//通知主线程串口已经收到数据}

所谓无阻塞通信就是异步通信。意味着在读写需要大量时间的数据时(不仅仅是串口通信操作),一旦调用ReadFile()和WriteFile(),就可以立即返回,让实际的读写操作在后台运行;相反,如果使用阻塞通信,则必须在所有读取或写入操作完成后返回。出现问题是因为操作可能需要很长时间才能完成。

非常阻塞的操作还允许读写操作同时进行(也就是重叠操作?),在实际工作中非常有用。

要使用非阻塞通信,在创建file()时必须先使用file _ flag _ overlapped那么ReadFile()时lpOverlapped参数一定不能为NULL,然后检查函数调用的返回值,调用GetLastError()看是否返回ERROR_IO_PENDING。如果是,最后调用GetOverlappedResult()返回重叠运算的结果;WriteFile()的用法类似。

5.在主线程中发送下行命令。

BOOL fWriteStatchar SZ buffer[count];

................//准备发送数据,放入szBuffer[]。

fWriteStat = WriteFile(hCom,szBuffer,dwBytesToWrite,

& ampdwbytes written & amp;WRITE _ OS(npTTYInfo));//写入数据

注意:我们在创建FILE()时使用了FILE_FLAG_OVERLAPPED,现在CreareFile()也必须使用LPOVERLAPPED结构。否则,该函数将错误地报告写操作已经完成。

使用LPOVERLAPPED结构,WriteFile()不等待写操作完成就立即返回,实现了无阻塞通信。此时,WriteFile()返回FALSE,GetLastError()返回ERROR_IO_PENDING。

int err = GetLastError();

如果(!fWriteStat) {

if(GetLastError()= = ERROR _ IO _ PENDING){

而(!GetOverlappedResult(hCom & amp;WRITE_OS( npTTYInfo),

& ampdwBytesWritten,TRUE )) {

dwError = GetLastError();

if(dw ERROR = = ERROR _ IO _ INCOMPLETE){

//未完成时的正常结果

dwBytesSent+= dwbytes written;继续;}

......................

综上所述,我们在辅助线程中使用多线程技术监听串口,当数据到达时,由事件驱动,读取数据并上报给主线程(在主线程中发送数据,相对来说,下行命令的数据总是少很多);而且WaitCommEvent()、ReadFile()和WriteFile()都使用了非阻塞通信技术,依靠重叠读写操作使串行读写操作在后台运行。

依托vc6.0丰富的功能,结合我们提到的技术,我们编写了一个控制能力很强的串口通信应用程序。我个人比较喜欢API技术,因为控制手段灵活很多,功能也强很多。