如何用API函数实现WINDOWS下的串口写程

如何用API函数实现WINDOWS下的串口写程,第1张

用API函数实现Windows下的串行通讯

冯华亮 2002年4月 四川·电子科大

以往的DOS系统是通过DOS中断和BIOS中断向用户提供串行接口的通讯能力。在Windows环境下,C++的开发工具既没有提供象DOS和BIOS

中那样专门的串行通讯控制方法,也不允许用户直接控制串口的中断。

为了保证资源共享,Windows系统完全接管了各种硬件资源,使用中断来控制端口将破坏系统的多任务性,使系统的稳定性受到影响。

但Windows同时也提供了功能强大的API函数使用户能间接的控制串行通讯。

1、实现串行通讯的相关API函数

API函数不仅提供了打开和读写通讯端口的 *** 作方法,还提供了名目繁多的函数以支持对串行通讯的各种 *** 作。常用函数及作用如表厅塌5-1所示。

表5-1 常用串行通讯API函数及其作用

函数名 作用

CreateFile 打开串口

GetCommState 检测串口设置

SetCommState 设置串口

BuilderCommDCB 用字符串中的值来填充设备控制块

GetCommTimeouts 检测通信超时设置

SetCommTimeouts 设置通信超时参数

SetCommMask 设定被监控事件

WaitCommEvent 等待被监控事件发生

WaitForMultipleObjects 等待多个被监测对象的结果

WriteFile 发送数据

ReadFile 接收数据

GetOverlappedResult 返回最后重叠(异步) *** 作结果

PurgeComm 清空串口缓冲区,退出所有相关 *** 作

ClearCommError 更新串口状态结构体,并清除所有串口硬件错误

CloseHandle 关闭串行口

2、打开串口

函数CreateFile原本用于打开文件,但它同样可用于打开一个通信端口。与系统中其他对象一样,通信端口也是用句柄来标识的。

CreateFile函数返回被 *** 作的通信端口句柄,其调用方法如下:

HANDLE CreateFile (

LPCTSTR lpFileName, //指向文件名字符串的指针

DWORD dwDesireAccess, // *** 作模式

DWORD dwShareMode,  //共享方式

LPSECURITY_ATTRIBUTES lpSecurityAttributes, //指向安全属性的指针

DWORD dwCreationDistribution, //文件建立方式

DWORD dwFlagsAndAttributes //文件属性

HANDLE hTemplateFile //模板文件句柄

)

lpFileName:指向一个以NULL结束的字符串,该串指定了要创建、打开或截断的文件、管道、通信源、磁盘设备或控制台的名字。

当用CreateFile打开串口时,这个参数可用“COM1”指定串口1,用“COM2”指定串口2,依此类推。

dwDesireAccess: 指定对文件访问的类型,该参数可以为GENERIC_READ(指定竖并对该文件的读访问权)

或ENERIC_WRITE(指定该文件的写访问权)两个值之一或同时为为这两个值。用ENERIC_READ|GENERIC_WRITE则指定可对串口进行读写

dwShareMode:指定此文件可以怎样被共享。因为串行口不支持任何共享模式,所以dwShareMode必须设为0

lpSecurityAttributes定义安全属性,一般不用,可设为NULL。Win 9x下该参数被忽略;

dwCreationDistribution定义文件创建方式, 对串口必须设为OPEN_EXISTING,表示打开已经存在的文件;

dwFlagsAndAttributes为该文件指定定义文件属性和标志,这个程序中设为FILE_FLAG_OVERLAPPED,表示异步通信方式;

hTemplateFile 指向一个模板文件的句柄,串口无模板可言,设为NULL。在 Windows 9x下该参数必须为NULL。

用异步读写扮纤圆方式打开串口1的函数调用如下:

m_hComm = CreateFile(“COM1”,//打开串口1

GENERIC_READ | GENERIC_WRITE, //读写方式

0, //不能共享

NULL, //不用安全结构

OPEN_EXISTING, //打开已存在的设备

FILE_FLAG_OVERLAPPED,//异步方式

0) //无模板

串口被成功打开时,返回其句柄,否则返回INVALID_HANDLE_VALUE(0XFFFFFFFF)。

3、串口设置

第一次打开串口时,串口设置为系统默认值,函数GetCommState和SetCommState可用于检索和设定端口设置的DCB(设备控制块)结构,

该结构中BaudRate、ByteSize、StopBits和Parity字段含有串口波特率、数据位数、停止位和奇偶校验控制等信息。

程序中可先用GetCommState检索端口的当前设置,修改其中的部分字段后再用SetCommState进行端口设定。这样可不必构造一个完整的DCB结构。

下面介绍几个主要的函数和结构体:

(1)GetCommState

BOOL GetCommState( hCommDev, lpdcb);

参数hCommDev标识通信设备,应使用CreateFile返回的句柄。Lpdcb是指向DCB结构的指针,

函数调用后当前串口配置信息将被保存在这个结构内。如果函数成功返回值为TRUE;否则返回值为FALSE。

SetCommState用法与GetCommState相似,在此不再重复。DCB结构定义如下(只介绍主要的几项):

typedef struct _ DCB{

……

DWORD BardRate //波特率的设置

BYTE ByteSize; //数据位的个数

BYTE Parity//是否有奇偶校验位

BYTE StopBits //停止位的个数

……

}DCB;

(2)SetCommTimeouts

BOOL SetCommTimeouts( hCommDev, lpctmo );

Lpctmo指向包含新的超时参数的COMMTIMEOUTS结构。COMMTIMEOUTS结构定义如下:

typedef struct _ COMMTIMEOUTS{

DWORD ReadIntervalTimeout

DWORD ReadTotalTimeoutMultiplier

DWORD ReadTotalTimeoutconstant

DWORD WriteTotalTimeoutMultiplier

DWORD WriteTotalTimeoutconstant

}COMMTIMEOUTS, LPCOMMTIMEOUTS

ReadIntervalTimeout: 以毫秒为单位指定通信线上两个字符到达之间的最大时间。在ReadFile *** 作其间,

收到第一个字符时开始计算时间。若任意两个字符到达之间的间隔超过这个最大值,ReadFile *** 作完成,

返回缓冲数据。0值表示不用间隔限时。若该成员为MAXDWORD,且ReadTotalTimeoutconstant和

ReadTotalTimeoutMultiplier成员为零,则指出读 *** 作要立即返回已接收到的字符,即使未收到字符,

读 *** 作也要返回。

ReadTotalTimeoutMultiplier:以毫秒为单位指定一个乘数,该乘数用来计算读 *** 作的总限时时间。每个读 *** 作的总限时时间等于读 *** 作所需的字节数与该值的乘积。

ReadTotalTimeoutConstant:以毫秒为单位指定一个常数,用于计算读 *** 作的总限时时间。每个 *** 作的总限时时间等于ReadTotalTimeoutMultiplier成员乘以读 *** 作所需字节数再加上该值的和。ReadTotalTimeoutMultiplier和ReadTotalTimeoutConstant成员的值为0表示读 *** 作不使用限时时间。

WriteTotalTimeoutMultiplier和WriteTotalTimeoutconstant的意义和作用分别与ReadTotalTimeoutMultiplier和ReadTotalTimeoutConstant相似,不再重复。

(3)BuilderCommDCB

BOOL BuilderCommDCB(lpszDef,lpdcb)

这个函数按lpszDef字符串所指定的格式来配置串口的DCB。

LpszDef:指向一个以NULL结束的字符串,该字符串指定串口的控制信息。比如,“1200,N,8,1”指定波特率为1200,无奇偶校验位,有8个数据位和1个停止位。

lpdcb:指向被填充的DCB结构。

(4)SetCommMask

BOOL SetCommMask(hCommDev,fdwEvtMask)

fdwEvtMask指向一个32位的屏蔽码,如果指定为EV_RXCHAR | EV_CTS,表示程序监控串口的收、发事件。

下面以简单的例子说明串口设置的步骤:

m_CommTimeouts.ReadIntervalTimeout = 1000

m_CommTimeouts.ReadTotalTimeoutMultiplier = 1000

m_CommTimeouts.ReadTotalTimeoutConstant = 1000

m_CommTimeouts.WriteTotalTimeoutMultiplier = 1000

m_CommTimeouts.WriteTotalTimeoutConstant = 1000

if (SetCommTimeouts(m_hComm, &m_CommTimeouts))

// 串口超时参数设置

if (SetCommMask(m_hComm, dwCommEvents))

// 设置串口事件掩码

if (GetCommState(m_hComm, &m_dcb))

// 获取串口当前状态

if (BuildCommDCB(“1200,N,8,1”, &m_dcb))

// 建立串口设备控制块

if (SetCommState(m_hComm, &m_dcb))

// 设置串口参数

……

以上任何一个if语句的判断条件为假时都将调用GetLastError函数获取错误信息,进行错误处理。

4、读写串口数据

SerialPort_DataReceived()事件是.net提供好的很完善的实时接收串口响应的方法.

正常情况用它就可以了.

mSerialPort.DataReceived

+=

new

SerialDataReceivedEventHandler(SerialPort_DataReceived)

这行代亩伍哪码执行后就意味着程序已经另开一线程了.它不会影响主进程的 *** 作,也就是说不会卡死主程序.

当你不开线程直接serialPort_jmt.Read的时候如果没有串口响应来的数据那么该方法会一直等待,也就是说回卡住主程序.

这不是死循环,而是主线程等待.

如果不用SerialPort_DataReceived()事迅码件橘皮,可以自己写线程,用线程去serialPort_jmt.Read,这样就让线程一直等待,而不会卡死主程序.

有帮助请采纳,如有疑问请追问.

初始化:

//串行设备句柄;

HANDLE hComDev=0

//串口打开标志;

BOOL bOpen=FALSE

//线程同步事件句柄;

HANDLE hEvent=0

DCB dcb

COMMTIMEOUTS timeouts

//设备已打开

if(bOpen) return FALSE

//打开COM1

if((hComDev=CreateFile(“COM1”,GENERIC�READ|GENERIC�WRITE,0,NULL,OPEN�EXISTING,FILE�ATTRIBUTE�NORMAL,NULL))==INVALID�HANDLE�VALUE)

return FALSE

//设置超时控制

SetCommTimeouts(hComDev,&timeouts)

//设置接收缓冲区和输出缓冲区的大小

SetupComm(hComDev,1024,512)

//获取缺省的DCB结构的值

GetCommState(hComDev,&dcb)

//设定波特率为9600 bps

dcb.BaudRate=CBR�9600

//设定无奇偶校验

dcb.fParity=NOPARITY

//设定数据位为8

dcb.ByteSize=8

//设定一个停止位

dcb.StopBits=ONESTOPBIT

//监视串口的错误和接收到字符两种事件

SetCommMask(hComDev,EV�ERR|EV�RXCHAR)

//设置串行设备控制参数

SetCommState(hComDev,&dcb)

//设备已打开

bOpen=TRUE

//创建人工重设、未发信号的事件

hEvent=CreateEvent(NULL,FALSE,FALSE,

“WatchEvent”)

//创建一个事件监视线程来监视串口事件

AfxBeginThread(CommWatchProc,pParam)

}

数据发送

数据发送利用WriteFile(山歼)函数实现。对于同步I/O *** 作,它的最后一个参数可为NULL;而对异步I/O *** 作,它的最后一个参数必需是一个指向OVERLAPPED结构的指针,通过OVERLAPPED结构来获得当前的 *** 作状态。

BOOL WriteComm(LPCVOID lpSndBuffer,DWORD dwBytesToWrite)

{

//lpSndBuffer为发送数据缓冲区指针,

dwBytesToWrite为将要发送的字节长度

//设备已打开

BOOL bWriteState

//实际发送的字节数

DWORD dwBytesWritten

//设备未打开

if(!bOpen) return FALSE

bWriteState=WriteFile(hComDev,lpSndBuffer,dwBytesToWrite,&dwBytesWritten,NULL)

if(!bWriteState || dwBytesToWrite!=dwBytesWritten)

//发送失败

return FALSE

else

//发送成功

return TRUE

}

数据接收

接收数据的任务由ReadFile函数完成。该函数从串口接收缓冲区中读取数据,读取数据前,先用ClearCommError函数获得接收缓冲区中的字节数。接收数据时,同步和异步读取的差别同发送数据是一样的返做。

DWORD ReadComm(LPVOID lpInBuffer,DWORD dwBytesToRead)

{

//lpInBuffer为接收数据的缓冲区指针, dwBytesToRead为准备读取的数据长度(字节数)

//串行设备状态结构

COMSTAT ComStat

DWORD dwBytesRead,dwErrorFlags

//设备未打开

if(!bOpen) return 0

//读取串行设备的当前状态

ClearCommError(hComDev,&dwErrorFlags,&ComStat)

/漏唯衡/应该读取的数据长度

dwBytesRead=min(dwBytesToRead,ComStat.cbInQue)

if(dwBytesRead>0)

//读取数据

if(!ReadFile(hComDev,lpInBuffer,dwBytesRead,&dwBytesRead,NULL))

dwBytesRead=0

return dwBytesRead

}

事件监视线程

事件监视线程对串口事件进行监视,当监视的事件发生时,监视线程可将这个事件发送(SendMessage)或登记(PostMessage)到对事件进行处理的窗口类(由pParam指定)中。

UINT CommWatchProc(LPVOID pParam)

{

DWORD dwEventMask=0//发生的事件;

while(bOpen)

{

//等待监视的事件发生

WaitCommEvent(hComDev, &dwEventMask,NULL)

if ((dwEventMask & EV�RXCHAR)==EV�RXCHAR)

……//接收到字符事件后,可以将此消息登记到由pParam有指定的窗口类中进行处理

if(dwEventMask & EV�ERR)==EV�ERROR)

……//发生错误时的处理

}

SetEvent(hEvent)

//发信号,指示监视线程结束

return 0

}

关闭串行设备

在整个应用程序结束或不再使用串行设备时,应将串行设备关闭,包括取消事件监视,将设备打开标志bOpen置为FALSE以使事件监视线程结束,清除发送/接收缓冲区和关闭设备句柄。

void CloseSynComm()

{

if(!bOpen) return

//结束事件监视线程

bOpen=FALSE

SetCommMask(hComDev,0)

//取消事件监视,此时监视线程中的WaitCommEvent将返回

WaitForSingleObject(hEvent,INFINITE)

//等待监视线程结束

CloseHandle(hEvent)//关闭事件句柄

//停止发送和接收数据,并清除发送和接收缓冲区

PurgeComm(hComDev,PURGE�TXABORT| PURGE�RXABORT|PURGE�TXCLEAR|PURGE�RXCLEAR)

//关闭设备句柄

CloseHandle(hComDev)

}


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/yw/12400588.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-25
下一篇 2023-05-25

发表评论

登录后才能评论

评论列表(0条)

保存