﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-Thinking in C++-文章分类-串口</title><link>http://www.cppblog.com/yishanhante/category/4697.html</link><description /><language>zh-cn</language><lastBuildDate>Sat, 24 May 2008 14:04:19 GMT</lastBuildDate><pubDate>Sat, 24 May 2008 14:04:19 GMT</pubDate><ttl>60</ttl><item><title>Win32串口编程</title><link>http://www.cppblog.com/yishanhante/articles/28069.html</link><dc:creator>jay</dc:creator><author>jay</author><pubDate>Sun, 15 Jul 2007 07:23:00 GMT</pubDate><guid>http://www.cppblog.com/yishanhante/articles/28069.html</guid><wfw:comment>http://www.cppblog.com/yishanhante/comments/28069.html</wfw:comment><comments>http://www.cppblog.com/yishanhante/articles/28069.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yishanhante/comments/commentRss/28069.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yishanhante/services/trackbacks/28069.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 在工业控制中，工控机（一般都基于Windows平台）经常需要与智能仪表通过串口进行通信。串口通信方便易行，应用广泛。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 一般情况下，工控机和各智能仪表通过RS485总线进行通信。RS485的通信方式是半双工的，只能由作为主节点的工控PC机依次轮询网络上的各智能控制单元子节点。每次通信都是由PC机通过串口向智能控制单元发布命令，智能控制单元在接收到正确的命令后作出应答。<br>　　在Win32下，可以使用两种编程方式实现串口通信，其一是使用ActiveX控件，这种方法程序简单，但欠灵活。其二是调用Windows的API函数，这种方法可以清楚地掌握串口通信的机制，并且自由灵活。本文我们只介绍API串口通信部分。<br>　　串口的操作可以有两种操作方式：同步操作方式和重叠操作方式（又称为异步操作方式）。同步操作时，API函数会阻塞直到操作完成以后才能返回（在多线程方式中，虽然不会阻塞主线程，但是仍然会阻塞监听线程）；而重叠操作方式，API函数会立即返回，操作在后台进行，避免线程的阻塞。
<p>无论那种操作方式，一般都通过四个步骤来完成：</p>
<p>（1） <a name=打开串口>打开串口</a>
<p>　　Win32系统把文件的概念进行了扩展。无论是文件、通信设备、命名管道、邮件槽、磁盘、还是控制台，都是用API函数CreateFile来打开或创建的。该函数的原型为： </p>
<pre>HANDLE CreateFile( LPCTSTR lpFileName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDistribution,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile);
</pre>
<ul>
    <li>lpFileName：将要打开的串口逻辑名，如&#8220;COM1&#8221;；
    <li>dwDesiredAccess：指定串口访问的类型，可以是读取、写入或二者并列；
    <li>dwShareMode：指定共享属性，由于串口不能共享，该参数必须置为0；
    <li>lpSecurityAttributes：引用安全性属性结构，缺省值为NULL；
    <li>dwCreationDistribution：创建标志，对串口操作该参数必须置为OPEN_EXISTING；
    <li>dwFlagsAndAttributes：属性描述，用于指定该串口是否进行异步操作，该值为FILE_FLAG_OVERLAPPED，表示使用异步的I/O；该值为0，表示同步I/O操作；
    <li>hTemplateFile：对串口而言该参数必须置为NULL； </li>
</ul>
<p>同步I/O方式打开串口的示例代码： </p>
<pre>	HANDLE hCom;  //全局变量，串口句柄
hCom=CreateFile("COM1",//COM1口
GENERIC_READ|GENERIC_WRITE, //允许读和写
0, //独占方式
NULL,
OPEN_EXISTING, //打开而不是创建
0, //同步方式
NULL);
if(hCom==(HANDLE)-1)
{
AfxMessageBox("打开COM失败!");
return FALSE;
}
return TRUE;
</pre>
重叠I/O打开串口的示例代码：
<pre>	HANDLE hCom;  //全局变量，串口句柄
hCom =CreateFile("COM1",  //COM1口
GENERIC_READ|GENERIC_WRITE, //允许读和写
0,  //独占方式
NULL,
OPEN_EXISTING,  //打开而不是创建
FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED, //重叠方式
NULL);
if(hCom ==INVALID_HANDLE_VALUE)
{
AfxMessageBox("打开COM失败!");
return FALSE;
}
return TRUE;
</pre>
（2）、<a name=配置串口>配置串口</a>
<p>　　在打开通讯设备句柄后，常常需要对串口进行一些初始化配置工作。这需要通过一个DCB结构来进行。DCB结构包含了诸如波特率、数据位数、奇偶校验和停止位数等信息。在查询或配置串口的属性时，都要用DCB结构来作为缓冲区。<br>　　一般用CreateFile打开串口后，可以调用GetCommState函数来获取串口的初始配置。要修改串口的配置，应该先修改DCB结构，然后再调用SetCommState函数设置串口。<br>　　DCB结构包含了串口的各项参数设置，下面仅介绍几个该结构常用的变量： </p>
<pre>typedef struct _DCB{
&#8230;&#8230;&#8230;
//波特率，指定通信设备的传输速率。这个成员可以是实际波特率值或者下面的常量值之一：
DWORD BaudRate;
CBR_110，CBR_300，CBR_600，CBR_1200，CBR_2400，CBR_4800，CBR_9600，CBR_19200， CBR_38400，
CBR_56000， CBR_57600， CBR_115200， CBR_128000， CBR_256000， CBR_14400
DWORD fParity; // 指定奇偶校验使能。若此成员为1，允许奇偶校验检查
&#8230;
BYTE ByteSize; // 通信字节位数，4—8
BYTE Parity; //指定奇偶校验方法。此成员可以有下列值：
EVENPARITY 偶校验     NOPARITY 无校验
MARKPARITY 标记校验   ODDPARITY 奇校验
BYTE StopBits; //指定停止位的位数。此成员可以有下列值：
ONESTOPBIT 1位停止位   TWOSTOPBITS 2位停止位
ONE5STOPBITS   1.5位停止位
&#8230;&#8230;&#8230;
} DCB;
winbase.h文件中定义了以上用到的常量。如下：
#define NOPARITY            0
#define ODDPARITY           1
#define EVENPARITY          2
#define ONESTOPBIT          0
#define ONE5STOPBITS        1
#define TWOSTOPBITS         2
#define CBR_110             110
#define CBR_300             300
#define CBR_600             600
#define CBR_1200            1200
#define CBR_2400            2400
#define CBR_4800            4800
#define CBR_9600            9600
#define CBR_14400           14400
#define CBR_19200           19200
#define CBR_38400           38400
#define CBR_56000           56000
#define CBR_57600           57600
#define CBR_115200          115200
#define CBR_128000          128000
#define CBR_256000          256000
</pre>
GetCommState函数可以获得COM口的设备控制块，从而获得相关参数：
<pre>BOOL GetCommState(
HANDLE hFile, //标识通讯端口的句柄
LPDCB lpDCB //指向一个设备控制块（DCB结构）的指针
);
SetCommState函数设置COM口的设备控制块：
BOOL SetCommState(
HANDLE hFile,
LPDCB lpDCB
);
</pre>
　　除了在BCD中的设置外，程序一般还需要设置I/O缓冲区的大小和超时。Windows用I/O缓冲区来暂存串口输入和输出的数据。如果通信的速率较高，则应该设置较大的缓冲区。调用SetupComm函数可以设置串行口的输入和输出缓冲区的大小。
<pre>BOOL SetupComm(
HANDLE hFile,	// 通信设备的句柄
DWORD dwInQueue,	// 输入缓冲区的大小（字节数）
DWORD dwOutQueue	// 输出缓冲区的大小（字节数）
);
</pre>
　　在用ReadFile和WriteFile读写串行口时，需要考虑超时问题。超时的作用是在指定的时间内没有读入或发送指定数量的字符，ReadFile或WriteFile的操作仍然会结束。<br>　　要查询当前的超时设置应调用GetCommTimeouts函数，该函数会填充一个COMMTIMEOUTS结构。调用SetCommTimeouts可以用某一个COMMTIMEOUTS结构的内容来设置超时。<br>　　读写串口的超时有两种：间隔超时和总超时。间隔超时是指在接收时两个字符之间的最大时延。总超时是指读写操作总共花费的最大时间。写操作只支持总超时，而读操作两种超时均支持。用COMMTIMEOUTS结构可以规定读写操作的超时。<br>COMMTIMEOUTS结构的定义为：
<pre>typedef struct _COMMTIMEOUTS {
DWORD ReadIntervalTimeout; //读间隔超时
DWORD ReadTotalTimeoutMultiplier; //读时间系数
DWORD ReadTotalTimeoutConstant; //读时间常量
DWORD WriteTotalTimeoutMultiplier; // 写时间系数
DWORD WriteTotalTimeoutConstant; //写时间常量
} COMMTIMEOUTS,*LPCOMMTIMEOUTS;
</pre>
COMMTIMEOUTS结构的成员都以毫秒为单位。总超时的计算公式是：<br>总超时＝时间系数&#215;要求读/写的字符数＋时间常量 <br>例如，要读入10个字符，那么读操作的总超时的计算公式为：<br>读总超时＝ReadTotalTimeoutMultiplier&#215;10＋ReadTotalTimeoutConstant <br>可以看出：间隔超时和总超时的设置是不相关的，这可以方便通信程序灵活地设置各种超时。 <br><br>如果所有写超时参数均为0，那么就不使用写超时。如果ReadIntervalTimeout为0，那么就不使用读间隔超时。如果ReadTotalTimeoutMultiplier 和 ReadTotalTimeoutConstant 都为0，则不使用读总超时。如果读间隔超时被设置成MAXDWORD并且读时间系数和读时间常量都为0，那么在读一次输入缓冲区的内容后读操作就立即返回，而不管是否读入了要求的字符。<br>　　在用重叠方式读写串口时，虽然ReadFile和WriteFile在完成操作以前就可能返回，但超时仍然是起作用的。在这种情况下，超时规定的是操作的完成时间，而不是ReadFile和WriteFile的返回时间。<br>配置串口的示例代码：
<pre>	SetupComm(hCom,1024,1024); //输入缓冲区和输出缓冲区的大小都是1024
COMMTIMEOUTS TimeOuts;
//设定读超时
TimeOuts.ReadIntervalTimeout=1000;
TimeOuts.ReadTotalTimeoutMultiplier=500;
TimeOuts.ReadTotalTimeoutConstant=5000;
//设定写超时
TimeOuts.WriteTotalTimeoutMultiplier=500;
TimeOuts.WriteTotalTimeoutConstant=2000;
SetCommTimeouts(hCom,&amp;TimeOuts); //设置超时
DCB dcb;
GetCommState(hCom,&amp;dcb);
dcb.BaudRate=9600; //波特率为9600
dcb.ByteSize=8; //每个字节有8位
dcb.Parity=NOPARITY; //无奇偶校验位
dcb.StopBits=TWOSTOPBITS; //两个停止位
SetCommState(hCom,&amp;dcb);
PurgeComm(hCom,PURGE_TXCLEAR|PURGE_RXCLEAR);
</pre>
在读写串口之前，还要用PurgeComm()函数清空缓冲区，该函数原型：
<pre>BOOL PurgeComm(
HANDLE hFile,	//串口句柄
DWORD dwFlags	// 需要完成的操作
);
</pre>
参数dwFlags指定要完成的操作，可以是下列值的组合：
<pre>PURGE_TXABORT	  中断所有写操作并立即返回，即使写操作还没有完成。
PURGE_RXABORT	  中断所有读操作并立即返回，即使读操作还没有完成。
PURGE_TXCLEAR	  清除输出缓冲区
PURGE_RXCLEAR	  清除输入缓冲区
</pre>
（3）、<a name=读写串口>读写串口</a>
<p>我们使用ReadFile和WriteFile读写串口，下面是两个函数的声明： </p>
<pre>BOOL ReadFile(
HANDLE hFile,	//串口的句柄
// 读入的数据存储的地址，
// 即读入的数据将存储在以该指针的值为首地址的一片内存区
LPVOID lpBuffer,
DWORD nNumberOfBytesToRead,	// 要读入的数据的字节数
// 指向一个DWORD数值，该数值返回读操作实际读入的字节数
LPDWORD lpNumberOfBytesRead,
// 重叠操作时，该参数指向一个OVERLAPPED结构，同步操作时，该参数为NULL。
LPOVERLAPPED lpOverlapped
);
BOOL WriteFile(
HANDLE hFile,	//串口的句柄
// 写入的数据存储的地址，
// 即以该指针的值为首地址的nNumberOfBytesToWrite
// 个字节的数据将要写入串口的发送数据缓冲区。
LPCVOID lpBuffer,
DWORD nNumberOfBytesToWrite,	//要写入的数据的字节数
// 指向指向一个DWORD数值，该数值返回实际写入的字节数
LPDWORD lpNumberOfBytesWritten,
// 重叠操作时，该参数指向一个OVERLAPPED结构，
// 同步操作时，该参数为NULL。
LPOVERLAPPED lpOverlapped
);
</pre>
　　在用ReadFile和WriteFile读写串口时，既可以同步执行，也可以重叠执行。在同步执行时，函数直到操作完成后才返回。这意味着同步执行时线程会被阻塞，从而导致效率下降。在重叠执行时，即使操作还未完成，这两个函数也会立即返回，费时的I/O操作在后台进行。<br>　　ReadFile和WriteFile函数是同步还是异步由CreateFile函数决定，如果在调用CreateFile创建句柄时指定了FILE_FLAG_OVERLAPPED标志，那么调用ReadFile和WriteFile对该句柄进行的操作就应该是重叠的；如果未指定重叠标志，则读写操作应该是同步的。ReadFile和WriteFile函数的同步或者异步应该和CreateFile函数相一致。<br>　　ReadFile函数只要在串口输入缓冲区中读入指定数量的字符，就算完成操作。而WriteFile函数不但要把指定数量的字符拷入到输出缓冲区，而且要等这些字符从串行口送出去后才算完成操作。<br>　　如果操作成功，这两个函数都返回TRUE。需要注意的是，当ReadFile和WriteFile返回FALSE时，不一定就是操作失败，线程应该调用GetLastError函数分析返回的结果。例如，在重叠操作时如果操作还未完成函数就返回，那么函数就返回FALSE，而且GetLastError函数返回ERROR_IO_PENDING。这说明重叠操作还未完成。<br><br>同步方式读写串口比较简单，下面先例举同步方式读写串口的代码：
<pre>//同步读串口
char str[100];
DWORD wCount;//读取的字节数
BOOL bReadStat;
bReadStat=ReadFile(hCom,str,100,&amp;wCount,NULL);
if(!bReadStat)
{
AfxMessageBox("读串口失败!");
return FALSE;
}
return TRUE;
//同步写串口
char lpOutBuffer[100];
DWORD dwBytesWrite=100;
COMSTAT ComStat;
DWORD dwErrorFlags;
BOOL bWriteStat;
ClearCommError(hCom,&amp;dwErrorFlags,&amp;ComStat);
bWriteStat=WriteFile(hCom,lpOutBuffer,dwBytesWrite,&amp; dwBytesWrite,NULL);
if(!bWriteStat)
{
AfxMessageBox("写串口失败!");
}
PurgeComm(hCom, PURGE_TXABORT|
PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR);
</pre>
在重叠操作时,操作还未完成函数就返回。 <br><br>　　重叠I/O非常灵活，它也可以实现阻塞（例如我们可以设置一定要读取到一个数据才能进行到下一步操作）。有两种方法可以等待操作完成：一种方法是用象WaitForSingleObject这样的等待函数来等待OVERLAPPED结构的hEvent成员；另一种方法是调用GetOverlappedResult函数等待，后面将演示说明。<br>下面我们先简单说一下OVERLAPPED结构和GetOverlappedResult函数：<br>OVERLAPPED结构<br>OVERLAPPED结构包含了重叠I/O的一些信息，定义如下：
<pre>typedef struct _OVERLAPPED { // o
DWORD  Internal;
DWORD  InternalHigh;
DWORD  Offset;
DWORD  OffsetHigh;
HANDLE hEvent;
} OVERLAPPED;
</pre>
　　在使用ReadFile和WriteFile重叠操作时，线程需要创建OVERLAPPED结构以供这两个函数使用。线程通过OVERLAPPED结构获得当前的操作状态，该结构最重要的成员是hEvent。hEvent是读写事件。当串口使用异步通讯时，函数返回时操作可能还没有完成，程序可以通过检查该事件得知是否读写完毕。<br>　　当调用ReadFile, WriteFile 函数的时候，该成员会自动被置为无信号状态；当重叠操作完成后，该成员变量会自动被置为有信号状态。
<pre>GetOverlappedResult函数
BOOL GetOverlappedResult(
HANDLE hFile,	// 串口的句柄
// 指向重叠操作开始时指定的OVERLAPPED结构
LPOVERLAPPED lpOverlapped,
// 指向一个32位变量，该变量的值返回实际读写操作传输的字节数。
LPDWORD lpNumberOfBytesTransferred,
// 该参数用于指定函数是否一直等到重叠操作结束。
// 如果该参数为TRUE，函数直到操作结束才返回。
// 如果该参数为FALSE，函数直接返回，这时如果操作没有完成，
// 通过调用GetLastError()函数会返回ERROR_IO_INCOMPLETE。
BOOL bWait
);
</pre>
该函数返回重叠操作的结果，用来判断异步操作是否完成，它是通过判断OVERLAPPED结构中的hEvent是否被置位来实现的。<br><br>异步读串口的示例代码：
<pre>char lpInBuffer[1024];
DWORD dwBytesRead=1024;
COMSTAT ComStat;
DWORD dwErrorFlags;
OVERLAPPED m_osRead;
memset(&amp;m_osRead,0,sizeof(OVERLAPPED));
m_osRead.hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);
ClearCommError(hCom,&amp;dwErrorFlags,&amp;ComStat);
dwBytesRead=min(dwBytesRead,(DWORD)ComStat.cbInQue);
if(!dwBytesRead)
return FALSE;
BOOL bReadStatus;
bReadStatus=ReadFile(hCom,lpInBuffer,
dwBytesRead,&amp;dwBytesRead,&amp;m_osRead);
if(!bReadStatus) //如果ReadFile函数返回FALSE
{
if(GetLastError()==ERROR_IO_PENDING)
//GetLastError()函数返回ERROR_IO_PENDING,表明串口正在进行读操作
{
WaitForSingleObject(m_osRead.hEvent,2000);
//使用WaitForSingleObject函数等待，直到读操作完成或延时已达到2秒钟
//当串口读操作进行完毕后，m_osRead的hEvent事件会变为有信号
PurgeComm(hCom, PURGE_TXABORT|
PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR);
return dwBytesRead;
}
return 0;
}
PurgeComm(hCom, PURGE_TXABORT|
PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR);
return dwBytesRead;
</pre>
　　对以上代码再作简要说明：在使用ReadFile 函数进行读操作前，应先使用ClearCommError函数清除错误。ClearCommError函数的原型如下：
<pre>BOOL ClearCommError(
HANDLE hFile,	// 串口句柄
LPDWORD lpErrors,	// 指向接收错误码的变量
LPCOMSTAT lpStat	// 指向通讯状态缓冲区
);
</pre>
该函数获得通信错误并报告串口的当前状态，同时，该函数清除串口的错误标志以便继续输入、输出操作。<br>参数lpStat指向一个COMSTAT结构，该结构返回串口状态信息。 COMSTAT结构 COMSTAT结构包含串口的信息，结构定义如下：
<pre>typedef struct _COMSTAT { // cst
DWORD fCtsHold : 1;   // Tx waiting for CTS signal
DWORD fDsrHold : 1;   // Tx waiting for DSR signal
DWORD fRlsdHold : 1;  // Tx waiting for RLSD signal
DWORD fXoffHold : 1;  // Tx waiting, XOFF char rec''d
DWORD fXoffSent : 1;  // Tx waiting, XOFF char sent
DWORD fEof : 1;       // EOF character sent
DWORD fTxim : 1;      // character waiting for Tx
DWORD fReserved : 25; // reserved
DWORD cbInQue;        // bytes in input buffer
DWORD cbOutQue;       // bytes in output buffer
} COMSTAT, *LPCOMSTAT;
</pre>
本文只用到了cbInQue成员变量，该成员变量的值代表输入缓冲区的字节数。<br><br>　　最后用PurgeComm函数清空串口的输入输出缓冲区。
<p>　　这段代码用WaitForSingleObject函数来等待OVERLAPPED结构的hEvent成员，下面我们再演示一段调用GetOverlappedResult函数等待的异步读串口示例代码： </p>
<pre>char lpInBuffer[1024];
DWORD dwBytesRead=1024;
BOOL bReadStatus;
DWORD dwErrorFlags;
COMSTAT ComStat;
OVERLAPPED m_osRead;
ClearCommError(hCom,&amp;dwErrorFlags,&amp;ComStat);
if(!ComStat.cbInQue)
return 0;
dwBytesRead=min(dwBytesRead,(DWORD)ComStat.cbInQue);
bReadStatus=ReadFile(hCom, lpInBuffer,dwBytesRead,
&amp;dwBytesRead,&amp;m_osRead);
if(!bReadStatus) //如果ReadFile函数返回FALSE
{
if(GetLastError()==ERROR_IO_PENDING)
{
GetOverlappedResult(hCom,
&amp;m_osRead,&amp;dwBytesRead,TRUE);
// GetOverlappedResult函数的最后一个参数设为TRUE，
//函数会一直等待，直到读操作完成或由于错误而返回。
return dwBytesRead;
}
return 0;
}
return dwBytesRead;
</pre>
异步写串口的示例代码：
<pre>char buffer[1024];
DWORD dwBytesWritten=1024;
DWORD dwErrorFlags;
COMSTAT ComStat;
OVERLAPPED m_osWrite;
BOOL bWriteStat;
bWriteStat=WriteFile(hCom,buffer,dwBytesWritten,
&amp;dwBytesWritten,&amp;m_OsWrite);
if(!bWriteStat)
{
if(GetLastError()==ERROR_IO_PENDING)
{
WaitForSingleObject(m_osWrite.hEvent,1000);
return dwBytesWritten;
}
return 0;
}
return dwBytesWritten;
</pre>
（4）、<a name=关闭串口>关闭串口</a>
<p>　　利用API函数关闭串口非常简单，只需使用CreateFile函数返回的句柄作为参数调用CloseHandle即可： </p>
<pre>BOOL CloseHandle(
HANDLE hObject; //handle to object to close
);
</pre>
串口编程的一个实例
<p>　　为了让您更好地理解串口编程,下面我们分别编写两个例程（见附带的源码部分）,这两个例程都实现了工控机与百特显示仪表通过RS485接口进行的串口通信。其中第一个例程采用同步串口操作,第二个例程采用异步串口操作。<br>　　我们只介绍软件部分，RS485接口接线方法不作介绍，感兴趣的读者可以查阅相关资料。</p>
<p>例程1</p>
<p>　　打开VC++6.0，新建基于对话框的工程RS485Comm，在主对话框窗口IDD_RS485COMM_DIALOG上添加两个按钮，ID分别为IDC_SEND和IDC_RECEIVE，标题分别为&#8220;发送&#8221;和&#8220;接收&#8221;；添加一个静态文本框IDC_DISP，用于显示串口接收到的内容。<br><br>在RS485CommDlg.cpp文件中添加全局变量： </p>
<pre>HANDLE hCom;  //全局变量，串口句柄
</pre>
在RS485CommDlg.cpp文件中的OnInitDialog()函数添加如下代码：
<pre>	// TODO: Add extra initialization here
hCom=CreateFile("COM1",//COM1口
GENERIC_READ|GENERIC_WRITE, //允许读和写
0, //独占方式
NULL,
OPEN_EXISTING, //打开而不是创建
0, //同步方式
NULL);
if(hCom==(HANDLE)-1)
{
AfxMessageBox("打开COM失败!");
return FALSE;
}
SetupComm(hCom,100,100); //输入缓冲区和输出缓冲区的大小都是1024
COMMTIMEOUTS TimeOuts;
//设定读超时
TimeOuts.ReadIntervalTimeout=MAXDWORD;
TimeOuts.ReadTotalTimeoutMultiplier=0;
TimeOuts.ReadTotalTimeoutConstant=0;
//在读一次输入缓冲区的内容后读操作就立即返回，
//而不管是否读入了要求的字符。
//设定写超时
TimeOuts.WriteTotalTimeoutMultiplier=100;
TimeOuts.WriteTotalTimeoutConstant=500;
SetCommTimeouts(hCom,&amp;TimeOuts); //设置超时
DCB dcb;
GetCommState(hCom,&amp;dcb);
dcb.BaudRate=9600; //波特率为9600
dcb.ByteSize=8; //每个字节有8位
dcb.Parity=NOPARITY; //无奇偶校验位
dcb.StopBits=TWOSTOPBITS; //两个停止位
SetCommState(hCom,&amp;dcb);
PurgeComm(hCom,PURGE_TXCLEAR|PURGE_RXCLEAR);
</pre>
分别双击IDC_SEND按钮和IDC_RECEIVE按钮，添加两个按钮的响应函数：
<pre>void CRS485CommDlg::OnSend()
{
// TODO: Add your control notification handler code here
// 在此需要简单介绍百特公司XMA5000的通讯协议：
//该仪表RS485通讯采用主机广播方式通讯。
//串行半双工，帧11位，1个起始位(0)，8个数据位，2个停止位(1)
//如：读仪表显示的瞬时值，主机发送：DC1 AAA BB ETX
//其中：DC1是标准ASCII码的一个控制符号，码值为11H(十进制的17)
//在XMA5000的通讯协议中，DC1表示读瞬时值
//AAA是从机地址码，也就是XMA5000显示仪表的通讯地址
//BB为通道号，读瞬时值时该值为01
//ETX也是标准ASCII码的一个控制符号，码值为03H
//在XMA5000的通讯协议中，ETX表示主机结束符
char lpOutBuffer[7];
memset(lpOutBuffer,''\0'',7); //前7个字节先清零
lpOutBuffer[0]=''\x11'';  //发送缓冲区的第1个字节为DC1
lpOutBuffer[1]=''0'';  //第2个字节为字符0(30H)
lpOutBuffer[2]=''0''; //第3个字节为字符0(30H)
lpOutBuffer[3]=''1''; // 第4个字节为字符1(31H)
lpOutBuffer[4]=''0''; //第5个字节为字符0(30H)
lpOutBuffer[5]=''1''; //第6个字节为字符1(31H)
lpOutBuffer[6]=''\x03''; //第7个字节为字符ETX
//从该段代码可以看出，仪表的通讯地址为001
DWORD dwBytesWrite=7;
COMSTAT ComStat;
DWORD dwErrorFlags;
BOOL bWriteStat;
ClearCommError(hCom,&amp;dwErrorFlags,&amp;ComStat);
bWriteStat=WriteFile(hCom,lpOutBuffer,dwBytesWrite,&amp; dwBytesWrite,NULL);
if(!bWriteStat)
{
AfxMessageBox("写串口失败!");
}
}
void CRS485CommDlg::OnReceive()
{
// TODO: Add your control notification handler code here
char str[100];
memset(str,''\0'',100);
DWORD wCount=100;//读取的字节数
BOOL bReadStat;
bReadStat=ReadFile(hCom,str,wCount,&amp;wCount,NULL);
if(!bReadStat)
AfxMessageBox("读串口失败!");
PurgeComm(hCom, PURGE_TXABORT|
PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR);
m_disp=str;
UpdateData(FALSE);
}
</pre>
您可以观察返回的字符串，其中有和仪表显示值相同的部分，您可以进行相应的字符串操作取出仪表的显示值。<br>打开ClassWizard,为静态文本框IDC_DISP添加CString类型变量m_disp，同时添加WM_CLOSE的相应函数：
<pre>void CRS485CommDlg::OnClose()
{
// TODO: Add your message handler code here and/or call default
CloseHandle(hCom);	//程序退出时关闭串口
CDialog::OnClose();
}
</pre>
程序的相应部分已经在代码内部作了详细介绍。连接好硬件部分，编译运行程序，细心体会串口同步操作部分。
<p>例程2</p>
<p>　　打开VC++6.0，新建基于对话框的工程RS485Comm，在主对话框窗口IDD_RS485COMM_DIALOG上添加两个按钮，ID分别为IDC_SEND和IDC_RECEIVE，标题分别为&#8220;发送&#8221;和&#8220;接收&#8221;；添加一个静态文本框IDC_DISP，用于显示串口接收到的内容。在RS485CommDlg.cpp文件中添加全局变量： </p>
<pre>HANDLE hCom; //全局变量，</pre>
<p>串口句柄在RS485CommDlg.cpp文件中的OnInitDialog()函数添加如下代码： </p>
<pre>	hCom=CreateFile("COM1",//COM1口
GENERIC_READ|GENERIC_WRITE, //允许读和写
0, //独占方式
NULL,
OPEN_EXISTING, //打开而不是创建
FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED, //重叠方式
NULL);
if(hCom==(HANDLE)-1)
{
AfxMessageBox("打开COM失败!");
return FALSE;
}
SetupComm(hCom,100,100); //输入缓冲区和输出缓冲区的大小都是100
COMMTIMEOUTS TimeOuts;
//设定读超时
TimeOuts.ReadIntervalTimeout=MAXDWORD;
TimeOuts.ReadTotalTimeoutMultiplier=0;
TimeOuts.ReadTotalTimeoutConstant=0;
//在读一次输入缓冲区的内容后读操作就立即返回，
//而不管是否读入了要求的字符。
//设定写超时
TimeOuts.WriteTotalTimeoutMultiplier=100;
TimeOuts.WriteTotalTimeoutConstant=500;
SetCommTimeouts(hCom,&amp;TimeOuts); //设置超时
DCB dcb;
GetCommState(hCom,&amp;dcb);
dcb.BaudRate=9600; //波特率为9600
dcb.ByteSize=8; //每个字节有8位
dcb.Parity=NOPARITY; //无奇偶校验位
dcb.StopBits=TWOSTOPBITS; //两个停止位
SetCommState(hCom,&amp;dcb);
PurgeComm(hCom,PURGE_TXCLEAR|PURGE_RXCLEAR);
</pre>
分别双击IDC_SEND按钮和IDC_RECEIVE按钮，添加两个按钮的响应函数：
<pre>void CRS485CommDlg::OnSend()
{
// TODO: Add your control notification handler code here
OVERLAPPED m_osWrite;
memset(&amp;m_osWrite,0,sizeof(OVERLAPPED));
m_osWrite.hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);
char lpOutBuffer[7];
memset(lpOutBuffer,''\0'',7);
lpOutBuffer[0]=''\x11'';
lpOutBuffer[1]=''0'';
lpOutBuffer[2]=''0'';
lpOutBuffer[3]=''1'';
lpOutBuffer[4]=''0'';
lpOutBuffer[5]=''1'';
lpOutBuffer[6]=''\x03'';
DWORD dwBytesWrite=7;
COMSTAT ComStat;
DWORD dwErrorFlags;
BOOL bWriteStat;
ClearCommError(hCom,&amp;dwErrorFlags,&amp;ComStat);
bWriteStat=WriteFile(hCom,lpOutBuffer,
dwBytesWrite,&amp; dwBytesWrite,&amp;m_osWrite);
if(!bWriteStat)
{
if(GetLastError()==ERROR_IO_PENDING)
{
WaitForSingleObject(m_osWrite.hEvent,1000);
}
}
}
void CRS485CommDlg::OnReceive()
{
// TODO: Add your control notification handler code here
OVERLAPPED m_osRead;
memset(&amp;m_osRead,0,sizeof(OVERLAPPED));
m_osRead.hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);
COMSTAT ComStat;
DWORD dwErrorFlags;
char str[100];
memset(str,''\0'',100);
DWORD dwBytesRead=100;//读取的字节数
BOOL bReadStat;
ClearCommError(hCom,&amp;dwErrorFlags,&amp;ComStat);
dwBytesRead=min(dwBytesRead, (DWORD)ComStat.cbInQue);
bReadStat=ReadFile(hCom,str,
dwBytesRead,&amp;dwBytesRead,&amp;m_osRead);
if(!bReadStat)
{
if(GetLastError()==ERROR_IO_PENDING)
//GetLastError()函数返回ERROR_IO_PENDING,表明串口正在进行读操作
{
WaitForSingleObject(m_osRead.hEvent,2000);
//使用WaitForSingleObject函数等待，直到读操作完成或延时已达到2秒钟
//当串口读操作进行完毕后，m_osRead的hEvent事件会变为有信号
}
}
PurgeComm(hCom, PURGE_TXABORT|
PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR);
m_disp=str;
UpdateData(FALSE);
}
</pre>
打开ClassWizard,为静态文本框IDC_DISP添加CString类型变量m_disp，同时添加WM_CLOSE的相应函数：
<pre>void CRS485CommDlg::OnClose()
{
// TODO: Add your message handler code here and/or call default
CloseHandle(hCom);	//程序退出时关闭串口
CDialog::OnClose();
}
</pre>
<img src ="http://www.cppblog.com/yishanhante/aggbug/28069.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yishanhante/" target="_blank">jay</a> 2007-07-15 15:23 <a href="http://www.cppblog.com/yishanhante/articles/28069.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>深入浅出VC++串口编程之基于Win32 API</title><link>http://www.cppblog.com/yishanhante/articles/27881.html</link><dc:creator>jay</dc:creator><author>jay</author><pubDate>Wed, 11 Jul 2007 14:27:00 GMT</pubDate><guid>http://www.cppblog.com/yishanhante/articles/27881.html</guid><wfw:comment>http://www.cppblog.com/yishanhante/comments/27881.html</wfw:comment><comments>http://www.cppblog.com/yishanhante/articles/27881.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yishanhante/comments/commentRss/27881.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yishanhante/services/trackbacks/27881.html</trackback:ping><description><![CDATA[1、API描述<br><br>　　在WIN32 API中，串口使用文件方式进行访问，其操作的API基本上与文件操作的API一致。<br><br>　　打开串口<br><br>　　Win32 中用于打开串口的API 函数为CreateFile，其原型为：<br><br>
<p>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>HANDLE CreateFile (<br>　LPCTSTR lpFileName, //将要打开的串口逻辑名，如COM1 或COM2<br>　DWORD dwAccess, //指定串口访问的类型，可以是读取、写入或两者并列<br>　DWORD dwShareMode, //指定共享属性，由于串口不能共享,该参数必须置为0<br>　LPSECURITY_ATTRIBUTES lpsa, //引用安全性属性结构，缺省值为NULL<br>　DWORD dwCreate, //创建标志，对串口操作该参数必须置为OPEN EXISTING<br>　DWORD dwAttrsAndFlags, //属性描述，用于指定该串口是否可进行异步操作，<br>　//FILE_FLAG_OVERLAPPED：可使用异步的I/O<br>　HANDLE hTemplateFile //指向模板文件的句柄，对串口而言该参数必须置为NULL<br>);</td>
        </tr>
    </tbody>
</table>
<br>　　例如，以下程序用于以同步读写方式打开串口COM1：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>HANDLE hCom;<br>DWORD dwError;<br>hCon = CreateFile("COM1", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);<br>if (hCom == (HANDLE)0xFFFFFFFF)<br>{<br>　dwError = GetLastError();<br>　MessageBox(dwError);<br>}</td>
        </tr>
    </tbody>
</table>
<br>　　对于dwAttrsAndFlags参数及FILE_FLAG_OVERLAPPED标志的由来，可解释如下：Windows文件操作分为同步I/O和重叠I/O(Overlapped I/ O)两种方式，在同步I/O方式中，API会阻塞直到操作完成以后才能返回（在多线程方式中，虽然不会阻塞主线程，但是仍然会阻塞监听线程）；而在重叠I/O方式中，API会立即返回，操作在后台进行，避免线程的阻塞。重叠I/O非常灵活，它也可以实现阻塞（例如我们可以设置一定要读取到一个数据才能进行到下一步操作)。如果进行I/O操作的API 在没有完成操作的情况下返回，我们可以通过调用GetOverLappedResult()函数阻塞到I/O操作完成后返回。<br><br>　　配置串口<br><br>　　配置串口是通过改变设备控制块DCB(Device Control Block) 的成员变量值来实现的，接收缓冲区和发送缓冲区的大小可通过SetupComm函数来设置。<br><br>　　DCB结构体定义为：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>typedef struct _DCB { // dcb <br>　DWORD DCBlength; // sizeof(DCB) <br>　DWORD BaudRate; // current baud rate <br>　DWORD fBinary: 1; // binary mode, no EOF check <br>　DWORD fParity: 1; // enable parity checking <br>　DWORD fOutxCtsFlow:1; // CTS output flow control <br>　DWORD fOutxDsrFlow:1; // DSR output flow control <br>　DWORD fDtrControl:2; // DTR flow control type <br>　DWORD fDsrSensitivity:1; // DSR sensitivity <br>　DWORD fTXContinueOnXoff:1; // XOFF continues Tx <br>　DWORD fOutX: 1; // XON/XOFF out flow control <br>　DWORD fInX: 1; // XON/XOFF in flow control <br>　DWORD fErrorChar: 1; // enable error replacement <br>　DWORD fNull: 1; // enable null stripping <br>　DWORD fRtsControl:2; // RTS flow control <br>　DWORD fAbortOnError:1; // abort reads/writes on error <br>　DWORD fDummy2:17; // reserved <br>　WORD wReserved; // not currently used <br>　WORD XonLim; // transmit XON threshold <br>　WORD XoffLim; // transmit XOFF threshold <br>　BYTE ByteSize; // number of bits/byte, 4-8 <br>　BYTE Parity; // 0-4=no,odd,even,mark,space <br>　BYTE StopBits; // 0,1,2 = 1, 1.5, 2 <br>　char XonChar; // Tx and Rx XON character <br>　char XoffChar; // Tx and Rx XOFF character <br>　char ErrorChar; // error replacement character <br>　char EofChar; // end of input character <br>　char EvtChar; // received event character <br>　WORD wReserved1; // reserved; do not use <br>} DCB; <br>而SetupComm函数的原型则为：<br>BOOL SetupComm(<br>　HANDLE hFile, // handle to communications device<br>　DWORD dwInQueue, // size of input buffer<br>　DWORD dwOutQueue // size of output buffer<br>);</td>
        </tr>
    </tbody>
</table>
<br>　　以下程序将串口设置为：波特率为9600，数据位数为7位，停止位为2 位，偶校验，接收缓冲区和发送缓冲区大小均为1024个字节，最后用PurgeComm函数终止所有的后台读写操作并清空接收缓冲区和发送缓冲区：</p>
<br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>DCB dcb;<br>dcb.BaudRate = 9600; //波特率为9600<br>dcb.ByteSize = 7; //数据位数为7位<br>dcb.Parity = EVENPARITY; //偶校验<br>dcb.StopBits = 2; //两个停止位<br>dcb.fBinary = TRUE;<br>dcb.fParity = TRUE;<br>if (!SetCommState(hCom, &amp;dcb))<br>{<br>　MessageBox("串口设置出错!");<br>} <br>SetupComm(hCom, 1024, 1024);<br>PurgeComm(hCom, PURCE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR);</td>
        </tr>
    </tbody>
</table>
<br>　　超时设置<br><br>　　超时设置是通过改变COMMTIMEOUTS结构体的成员变量值来实现的，COMMTIMEOUTS的原型为：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>typedef struct _COMMTIMEOUTS<br>{<br>　DWORD ReadIntervalTimeout; //定义两个字符到达的最大时间间隔，单位：毫秒<br>　//当读取完一个字符后，超过了ReadIntervalTimeout，仍未读取到下一个字符，就会<br>　//发生超时<br>　DWORD ReadTotalTimeoutMultiplier; <br>　DWORD ReadTotalTimeoutConstant;<br>　//其中各时间所满足的关系如下：<br>　//ReadTotalTimeout = ReadTotalTimeOutMultiplier* BytesToRead + ReadTotalTimeoutConstant<br>　DWORD WriteTotalTimeoutMultiplier;<br>　DWORD WriteTotalTimeoutConstant;<br>} COMMTIMEOUTS, *LPCOMMTIMEOUTS;<br></td>
        </tr>
    </tbody>
</table>
<br>　　设置超时的函数为SetCommTimeouts，其原型中接收COMMTIMEOUTS的指针为参数：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>BOOL SetCommTimeouts(<br>　HANDLE hFile, // handle to communications device<br>　LPCOMMTIMEOUTS lpCommTimeouts // pointer to comm time-out structure<br>);</td>
        </tr>
    </tbody>
</table>
<br>　　以下程序将串口读操作的超时设定为10 毫秒：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>COMMTIMEOUTS to;<br>memset(&amp;to, 0, sizeof(to));<br>to.ReadIntervalTimeout = 10;<br>SetCommTimeouts(hCom, &amp;to);</td>
        </tr>
    </tbody>
</table>
<br>　　与SetCommTimeouts对应的GetCommTimeouts()函数的原型为：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>BOOL GetCommTimeouts(<br>　HANDLE hFile, // handle of communications device<br>　LPCOMMTIMEOUTS lpCommTimeouts // pointer to comm time-out structure<br>);</td>
        </tr>
    </tbody>
</table>
<br>　　事件设置<br><br>　　在读写串口之前，需要用SetCommMask ()函数设置事件掩模来监视指定通信端口上的事件，其原型为：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>BOOL SetCommMask(<br>　HANDLE hFile, //标识通信端口的句柄<br>　DWORD dwEvtMask //能够使能的通信事件<br>);</td>
        </tr>
    </tbody>
</table>
<br>　　有了Set当然还会有Get，与SetCommMask对应的GetCommMask()函数的原型为：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>BOOL GetCommMask(<br>　HANDLE hFile, //标识通信端口的句柄<br>　LPDWORD lpEvtMask // address of variable to get event mask<br>);</td>
        </tr>
    </tbody>
</table>
<br>　　串口上可以发生的事件可以是如下事件列表中的一个或任意组合：EV_BREAK、EV_CTS、EV_DSR、EV_ERR、EV_RING、EV_RLSD、EV_RXCHAR、EV_RXFLAG、EV_TXEMPTY。<br><br>　　我们可以用WaitCommEvent()函数来等待串口上我们利用SetCommMask ()函数设置的事件：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>BOOL WaitCommEvent(<br>　HANDLE hFile, //标识通信端口的句柄<br>　LPDWORD lpEvtMask, // address of variable for event that occurred<br>　LPOVERLAPPED lpOverlapped, // address of overlapped structure<br>);</td>
        </tr>
    </tbody>
</table>
<br>　　WaitCommEvent()函数一直阻塞，直到串口上发生我们用所SetCommMask ()函数设置的通信事件为止。一般而言，当WaitCommEvent()返回时，程序员可以由分析*lpEvtMask而获得发生事件的类别，再进行相应的处理。<br><br>　　读串口<br><br>　　对串口进行读取所用的函数和对文件进行读取所用的函数相同，读函数原型如下：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>BOOL ReadFile(<br>　HANDLE hFile, // handle of file to read<br>　LPVOID lpBuffer, // pointer to buffer that receives data<br>　DWORD nNumberOfBytesToRead, // number of bytes to read<br>　LPDWORD lpNumberOfBytesRead, // pointer to number of bytes read<br>　LPOVERLAPPED lpOverlapped // pointer to structure for overlapped I/O<br>);</td>
        </tr>
    </tbody>
</table>
<br>　　写串口<br><br>　　对串口进行写入所用的函数和对文件进行写入所用的函数相同，写函数原型如下：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>BOOL WriteFile(<br>　HANDLE hFile, // handle to file to write to<br>　LPCVOID lpBuffer, // pointer to data to write to file<br>　DWORD nNumberOfBytesToWrite, // number of bytes to write<br>　LPDWORD lpNumberOfBytesWritten, // pointer to number of bytes written<br>　LPOVERLAPPED lpOverlapped // pointer to structure for overlapped I/O<br>);</td>
        </tr>
    </tbody>
</table>
<br>　　关闭串口<br><br>　　利用API 函数实现串口通信时关闭串口非常简单，只需使用CreateFile 函数返回的句柄作为参数调用CloseHandle 即可：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>BOOL CloseHandle(<br>　HANDLE hObject // handle to object to close<br>);</td>
        </tr>
    </tbody>
</table>
<p>&#160;</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp; 2.例程<br><br>　　在笔者的《深入浅出Win32多线程程序设计之综合实例》中我们已经给出一个利用WIN API进行串口通信的例子，这里再给出一个类似的例子，以进一步加深理解。<br><br>
<table width="90%" align=center border=0>
    <tbody>
        <tr>
            <td>
            <div align=center><img style="WIDTH: 485px; HEIGHT: 322px" alt=深入浅出VC++串口编程之基于Win32API(2) hspace=0 src="http://news.newhua.com/Newhua_Files/Net_pic/2006-2/20/25046378740591923.jpg" border=1></div>
            <div align=center>&nbsp;</div>
            <div align=center><font style="FONT-SIZE: 12px">利用WIN API进行串口通信</font></div>
            </td>
        </tr>
    </tbody>
</table>
<br>　　对话框上控件对应的资源文件(.RC)中的内容如下：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>BEGIN<br>　EDITTEXT IDC_RECV_EDIT,28,119,256,46,ES_AUTOHSCROLL<br>　GROUPBOX "发送数据",IDC_STATIC,19,15,282,70<br>　GROUPBOX "接收数据",IDC_STATIC,19,100,282,80<br>　EDITTEXT IDC_SEND_EDIT,29,33,214,39,ES_AUTOHSCROLL<br>　PUSHBUTTON "清除",IDC_CLEAR_BUTTON,248,33,50,14<br>　PUSHBUTTON "发送",IDC_SEND_BUTTON,248,55,50,14<br>END</td>
        </tr>
    </tbody>
</table>
<br>　　而整个对话框的消息映射（描述了消息及其对应的行为）如下：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>BEGIN_MESSAGE_MAP(CSerialPortAPIDlg, CDialog)<br>//{{AFX_MSG_MAP(CSerialPortAPIDlg)<br>　ON_WM_SYSCOMMAND()<br>　ON_WM_PAINT()<br>　ON_WM_QUERYDRAGICON()<br>　ON_BN_CLICKED(IDC_CLEAR_BUTTON, OnClearButton)<br>　ON_BN_CLICKED(IDC_SEND_BUTTON, OnSendButton)<br>　ON_MESSAGE(COM_RECVDATA, OnRecvData)<br>//}}AFX_MSG_MAP<br>END_MESSAGE_MAP()</td>
        </tr>
    </tbody>
</table>
<br>　　我们为IDC_SEND_EDIT和IDC_RECV_EDIT编辑框控件分别添加了一个CString变量m_recv和m_send，下面的代码描述了这一行为：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>class CSerialPortAPIDlg : public CDialog<br>{<br>　// Construction<br>　public:<br>　　CSerialPortAPIDlg(CWnd* pParent = NULL); // standard constructor<br><br>　　// Dialog Data<br>　　//{{AFX_DATA(CSerialPortAPIDlg)<br>　　　enum { IDD = IDD_SERIALPORTAPI_DIALOG };<br>　　　CString m_recv; //IDC_RECV_EDIT控件对应的变量<br>　　　CString m_send; //IDC_SEND_EDIT控件对应的变量<br>　　//}}AFX_DATA<br><br>　　// ClassWizard generated virtual function overrides<br>　　//{{AFX_VIRTUAL(CSerialPortAPIDlg)<br>　protected:<br>　　virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support<br>　//}}AFX_VIRTUAL<br><br>　// Implementation<br>　protected:<br>　　BOOL OpenSerialPort1();<br>　　HICON m_hIcon;<br><br>　　// Generated message map functions<br>　　//{{AFX_MSG(CSerialPortAPIDlg)<br>　　　virtual BOOL OnInitDialog();<br>　　　afx_msg void OnSysCommand(UINT nID, LPARAM lParam);<br>　　　afx_msg void OnPaint();<br>　　　afx_msg HCURSOR OnQueryDragIcon();<br>　　　afx_msg void OnClearButton();<br>　　　afx_msg void OnSendButton();<br>　　　afx_msg void OnRecvData(WPARAM wParam, LPARAM lParam);<br>　　//}}AFX_MSG<br>　　DECLARE_MESSAGE_MAP()<br>};<br><br>CSerialPortAPIDlg::CSerialPortAPIDlg(CWnd* pParent /*=NULL*/)<br>: CDialog(CSerialPortAPIDlg::IDD, pParent)<br>{<br>　//{{AFX_DATA_INIT(CSerialPortAPIDlg)<br>　　//在构造函数中初始化变量<br>　　m_recv = _T(""); //在构造函数中初始化变量<br>　　m_send = _T("");<br>　//}}AFX_DATA_INIT<br>　// Note that LoadIcon does not require a subsequent DestroyIcon in Win32<br>　m_hIcon = AfxGetApp()-&gt;LoadIcon(IDR_MAINFRAME);<br>}<br><br>//建立编辑框控件和变量之间的映射<br>void CSerialPortAPIDlg::DoDataExchange(CDataExchange* pDX)<br>{<br>　CDialog::DoDataExchange(pDX);<br>　//{{AFX_DATA_MAP(CSerialPortAPIDlg)<br>　　DDX_Text(pDX, IDC_RECV_EDIT, m_recv);<br>　　DDX_Text(pDX, IDC_SEND_EDIT, m_send);<br>　//}}AFX_DATA_MAP<br>}</td>
        </tr>
    </tbody>
</table>
<br>　　在对话框的OnInitDialog()函数中，我们启动窗口监听线程并将主窗口句柄传递给线程控制函数：</p>
<br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>BOOL CSerialPortAPIDlg::OnInitDialog()<br>{<br>　CDialog::OnInitDialog();<br><br>　// Add "About..." menu item to system menu.<br><br>　// IDM_ABOUTBOX must be in the system command range.<br>　ASSERT((IDM_ABOUTBOX &amp; 0xFFF0) == IDM_ABOUTBOX);<br>　ASSERT(IDM_ABOUTBOX &lt; 0xF000);<br><br>　CMenu* pSysMenu = GetSystemMenu(FALSE);<br>　if (pSysMenu != NULL)<br>　{<br>　　CString strAboutMenu;<br>　　strAboutMenu.LoadString(IDS_ABOUTBOX);<br>　　if (!strAboutMenu.IsEmpty())<br>　　{<br>　　　pSysMenu-&gt;AppendMenu(MF_SEPARATOR);<br>　　　pSysMenu-&gt;AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);<br>　　}<br>　}<br><br>　// Set the icon for this dialog. The framework does this automatically<br>　// when the application's main window is not a dialog<br>　SetIcon(m_hIcon, TRUE); // Set big icon<br>　SetIcon(m_hIcon, FALSE); // Set small icon<br><br>　// TODO: Add extra initialization here<br>　//启动串口监视线程<br>　DWORD threadID;<br>　hCommThread = ::CreateThread((LPSECURITY_ATTRIBUTES)NULL, 0,<br>　　　　　(LPTHREAD_START_ROUTINE)SerialPort1ThreadProcess, <br>　AfxGetMainWnd()-&gt;m_hWnd, 0, &amp;threadID);<br>　if (hCommThread == NULL)<br>　{<br>　　::AfxMessageBox("创建串口1处理线程失败");<br>　　::PostQuitMessage(0);<br>　}<br>　return TRUE; // return TRUE unless you set the focus to a control<br>}<br><br>//"清除"按钮函数<br>void CSerialPortAPIDlg::OnClearButton() <br>{<br>　// TODO: Add your control notification handler code here<br>　m_send = "";<br>　UpdateData(false);<br>}<br><br>//发送数据函数（"发送"按钮函数）<br>void CSerialPortAPIDlg::OnSendButton() <br>{<br>　// TODO: Add your control notification handler code here<br>　UpdateData(true);<br>　DWORD wCount = 0;<br>　WriteFile(hCom, m_send, m_send.GetLength(), &amp;wCount, NULL);//发送数据<br>}<br><br>//接收数据后（通过监听线程发来的用户自定义消息）显示<br>void CSerialPortAPIDlg::OnRecvData(WPARAM wParam, LPARAM lParam)<br>{<br>　CString recvStr((char *)wParam);<br>　m_recv += recvStr;<br>　UpdateData(false);<br>}</td>
        </tr>
    </tbody>
</table>
<br><br>　　在工程中添加SerialPortControl.h和SerialPortControl.cpp两个文件，前者声明串口控制的接口函数及外部全局变量，后者实现串口接口函数及串口监听线程控制函数。<br><br>　　SerialPortControl.h文件<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>#ifndef _SERIAL_PORT_CONTROL_H<br>#define _SERIAL_PORT_CONTROL_H<br><br>#define COM_RECVDATA WM_USER+1000//自定义消息<br><br>extern HANDLE hCom; //全局变量，串口句柄<br>extern HANDLE hCommThread; //全局变量，串口线程<br>//串口监视线程控制函数<br>extern DWORD WINAPI SerialPort1ThreadProcess(HWND hWnd);<br>//打开并设置PC串口1(COM1)<br>extern BOOL OpenSerialPort1();<br><br>#endif<br>SerialPortControl.cpp文件<br>#include "StdAfx.h"<br>#include "SerialPortControl.h"<br><br>HANDLE hCom; //全局变量，串口句柄<br>HANDLE hCommThread; //全局变量，串口线程<br><br>BOOL OpenSerialPort1()<br>{<br>　//打开并设置COM1<br>　hCom=CreateFile("COM1", GENERIC_READ|GENERIC_WRITE, 0,NULL , OPEN_EXISTING, 0, NULL);<br>　if (hCom==(HANDLE)-1)<br>　{<br>　　AfxMessageBox("打开COM1失败");<br>　　return false;<br>　}<br>　else<br>　{<br>　　DCB wdcb;<br>　　GetCommState (hCom, &amp;wdcb);<br>　　wdcb.BaudRate=9600;//波特率：9600，其他：不变<br>　　SetCommState (hCom, &amp;wdcb);<br>　　PurgeComm(hCom, PURGE_TXCLEAR);<br>　}<br>　return true;<br>}<br><br>//以一个线程不同监控串口行接收的数据<br>DWORD WINAPI SerialPort1ThreadProcess( HWND hWnd//主窗口句柄)<br>{<br>　char str[101];<br>　DWORD wCount; //读取的字节数<br>　while(1)<br>　{<br>　　ReadFile(hCom,str, 100, &amp;wCount, NULL);<br>　　if(wCount &gt; 0) //收到数据<br>　　{<br>　　　str[wCount] = '\0';<br>　　　::PostMessage(hWnd, COM_RECVDATA, (unsigned int) str, wCount); <br>　　　//发送消息给对话框主窗口，以进行接收内容的显示<br>　　}<br>　}<br>　return TRUE;<br>}</td>
        </tr>
    </tbody>
</table>
<br><br>　　为了验证程序的正确性，我们使用串口调试助手与本程序协同工作，互相进行收发。下面的抓图显示本程序工作正确，发送和接收字符准确无误。<br><br>
<p>&#160;</p>
<p align=center><img style="WIDTH: 554px; HEIGHT: 328px" alt=深入浅出VC++串口编程之基于Win32API(2) hspace=0 src="http://news.newhua.com/Newhua_Files/Net_pic/2006-2/20/6568682389739084.jpg" border=1></p>
<p align=center><font style="FONT-SIZE: 12px">显示本程序工作正确，发送和接收字符准确无误</font></p>
<img src ="http://www.cppblog.com/yishanhante/aggbug/27881.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yishanhante/" target="_blank">jay</a> 2007-07-11 22:27 <a href="http://www.cppblog.com/yishanhante/articles/27881.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>