串口的操作一般分为打开串口、配置串口、读写串口和关闭串口
一、打开串口 CreateFile
函数原型:
HANDLE CreateFile(LPCTSTR lpFileName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDistribution,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile);
参数说明:
lpFileName:指向一个以NULL结束的字符串,该串指定了要创建、打开或截断的文件。
当用createFile打开串口时,这个参数可用”COM1“指定串口1,用”COM2“指定串口2。
dwDesiredAccess:指定对文件访问的类型,可以是读取、写入或二者并列(GENERIC_READ、GENERIC_WRITE)
dwShareMode:指定此文件可以怎样被共享,由于串口不能共享,因此是独占方式,即该参数必须置为0。
共享属性(FILE_SHARE_READ、FILE_SHARE_WRITE)
lpSecurityAttributes:定义安全性属性,一般不用,可设置为NULL;
dwCreationDistribution:定义文件创建方式,对串口操作必须置为OPEN_EXISTING(打开而不是创建);
dwFlagsAndAttributes:定义文件的属性和标志,用于指定该串口是否进行异步操作,
该值为FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,表示使用异步的I/O;
该值为0(FILE_ATTRIBUTE_NORMAL),表示同步I/O操作;
hTemplateFile:指向一个模板文件的句柄。串口无模板可言,因此对串口而言该参数必须置为NULL;
返回值:串口被成功打开时,返回其句柄,否则返回INVALID_HANDLE_VALUE(0XFFFFFFFF)
二、配置串口函数
1、_DCB说明
在打开通讯设备句柄后,常常需要对串口进行一些初始化配置工作,这需要通过一个DCB结构来进行。
DCB结构包含了诸如波特率、数据位数、奇偶校验和停止位数等信息。在查询或配置串口的属性时,都要用DCB结构来作为缓冲区。
一般用CreateFile打开串口后,可以调用GetCommState函数来获取串口的初始配置。要修改串口的配置,应该先修改DCB结构,然后再调用SetCommState函数设置串口。
DCB结构包含了串口的各项参数设置:
typedef struct _DCB{
DWORD DCBlength;
//DCB结构体大小,即sizeof(_DCB),在调用SetCommState来更新DCB前必须作设置
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
DWORD fBinary:1;
//指定是否允许二进制模式。Win32 API不支持非二进制模式传输,应设置为true
DWORD fParity:1;
//指定是否允许奇偶校验。若此成员为1,允许奇偶校验检查,具体采用何种校验根据Parity成员的设置
DWORD fOutxCtsFlow:1;
//是否监控CTS(clear-to-send)信号来做输出流控(指定cts是否用于检测发送控制)。
//当设置为true时:若CTS为低电平/off,则数据发送将被挂起,直至CTS变为高。
DWORD fOutxDsrFlow:1;
//是否监控DSR (data-set-ready) 信号来做输出流控。
//当设置为true时:若DSR为低电平,则数据发送将被挂起,直至DSR变为高。DSR的信号一般由DCE来控制
DWORD fDtrControl:2;
//DTR (data-terminal-ready)流控,可取值如下:
//DTR_CONTROL_DISABLE 打开设备时置DTR信号为低电平,应用程序可通过调用EscapeCommFunction函数来改变DTR线电平状态
//DTR_CONTROL_ENABLE 打开设备时置DTR信号为高电平,应用程序可通过调用 EscapeCommFunction 函数来改变DTR线电平状态
//DTR_CONTROL_HANDSHAKE 允许DTR信号握手,此时应用程序不能调用EscapeCommFunction函数
DWORD fDsrSensitivity:1;
// 当该值为TRUE时DSR为OFF时接收的字节被忽略
DWORD fTXContinueOnXoff:1;
//TRUE时,不管接收端是否Xoff, 本方发送端持续发送。
//(也就是本方的发送端, 与本方接收端Xon/Xoff是相互独立的)。
//若为False 时,则当接收端buffer 达到XoffLim时,发送端发送完Xoff字符后,就停止发送。
DWORD fOutX:1;
//Xon和Xoff流量控制在发送时是否可用。
//如果为true,当XOFF 值被收到的时候,发送停止;当 XON 值被收到的时候,发送继续。
DWORD fInX:1;
//Xon和Xoff流量控制在接收时是否可用。
//如果为TRUE, 当输入缓冲区已接收满XoffLim 字节时,发送XOFF字符;
//当输入缓冲区已经有XonLim 字节的空余容量时,发送XON字符
DWORD fErrorChar:1;
//该值为TRUE,则用ErrorChar指定的字符代替奇偶校验错误的接收字符。
DWORD fNull:1;
//true时,接收时去掉空(0值)字节
DWORD fRtsControl:2;
//设置RTS (request-to-send)流控,若为0则缺省取 RTS_CONTROL_HANDSHAKE。可取值及其意义:
//RTS_CONTROL_DISABLE 打开设备时置RTS信号为低电平,应用程序可通过调用EscapeCommFunction函数来改变RTS线电平状态
//RTS_CONTROL_ENABLE 打开设备时置RTS信号为高电平,应用程序可通过调用EscapeCommFunction函数来改变RTS线电平状态
//RTS_CONTROL_HANDSHAKE 允许RTS信号握手,此时应用程序不能调用EscapeCommFunction函数。
//当输入缓冲区已经有足够空间接收数据时,驱动程序置RTS为高以允许DCE来发送;反之置RTS为低以阻止DCE发送数据。
//RTS_CONTROL_TOGGLE 有字节要发送时RTS变高,当所有缓冲字节已被发送完毕后,RTS变低。
DWORD fAbortOnError:1;
//true时,当发生错误时停止读写
DWORD fDummy2:17;
//保留,未启用
WORD wReserved;
//未启动,必须设置为0
WORD XonLim;
//在XON字符发送前接收缓冲区内可允许的最小字节数。
//当接收Buffer中的字符减少到XonLim规定的字符数, 就发送Xon字符,让对方继续发送。
WORD XoffLim;
//在XOFF字符发送前接收缓冲区内可允许的最大字节数。
//当接收Buffer达到XoffLim规定的字符数, 就发送Xoff字符, 让对方停止发送
BYTE ByteSize;
//通信数据位数,4-8
BYTE Parity;
//指定奇偶校验方法。此成员可以有下列值:
//EVENPARITY 偶校验
//NOPARITY 无校验
//MARKPARITY 标记校验
//ODDPARITY 奇校验
BYTE StopBits;
//指定停止位的位数。此成员可以有下列值:
//ONESTOPBIT 1位停止位
//TWOSTOPBITS 2位停止位
//ONE5STOPBITS 1.5位停止位
char XonChar;
//指定Xon字符
char XoffChar;
//指定Xoff字符
char ErrorChar;
//指定ErrorChar字符,奇偶校验发生错误时使用的字节
//(本字符用来代替接收到的奇偶校验发生错误时的值)
char EofChar;
//EOF替代字符(当没有使用二进制模式时,用来标识数据结束的字符)
char EvtChar;
//事件触发字符,即接收到此字符时会产生一个事件
WORD wReserved1;
//保留,未启用
}DCB;
注:
1)流控分为硬件流控和软件流控。其中:硬件流控分为rts/cts和dtr/dsr两种,软件流控分为Xon/Xoff。
2)软件流控Xon/Xoff相关的参数:fOutX、fInX、XonLim、XoffLim、XonChar、XoffChar
fOutX:发送端支持Xon/Xoff。收到Xoff停止发送,收到Xon重新开始
fInX:接收端支持Xon/Xoff。当FIFO中字节超过XoffLim时发送Xoff, 当FIFO中少于XonLim时发送Xon
XonLim:当接收Buffer中的字符减少到XonLim规定的字符数, 就发送Xon字符,让对方继续发送
XoffLim:接收Buffer达到XoffLim规定的字符数, 就发送Xoff字符, 让对方停止发送
XonChar:Xon 字符
charXoffChar:Xoff 字符
fTXContinueOnXoff:;TRUE时,不管接收端是否Xoff, 本方发送端持续发送。 (也就是本方的发送端, 与本方接收端Xon/Xoff是相互独立的)。
若为False 时,则当接收端buffer 达到XoffLim时,发送端发送完Xoff字符后,就停止发送
3)DTR/DSR硬件流控:
3.1)fOutxDsrFlow:true时,支持DSR流控制。当DSR为off时,停止发送
3.2)fDtrControl:DTR设置,有如下值:
fDTRControlDTR_CONTROL_Disable:使DTR为off
DTR_CONTROL_Enable:使DTR为on
DTR_CONTROL_HANDSHAKE:DTR硬件流控
3.3)fDsrSensitivity:true时,当DSR为OFF,则接收端忽略所有字符。
4)RTS/CTS硬件流控:
4.1)fOutxCtsFlow:true时,支持ctsl流控制,当cts为off时,停止发送
4.2)fRtsControl:rts设置,有如下值:
fRTSControlRTS_CONTROL_DISABLE:使RTS保持 off
RTS_CONTROL_ENABLE:使RTS保持on
RTS_CONTROL_HANDSHAKE:RTS硬件流控
RTS_CONTROL_TOGGLE:485通讯RTS自动流控
2、GetCommState 和 SetCommState函数
函数原型:
BOOL GetCommState(HANDLE hFile, LPDCB lpDCB);
BOOL SetCommState(HANDLE hFile, LPDCB lpDCB);
获得/设置COM口的设备控制块,从而获得相关参数。
参数说明:
hFile:标识通讯端口的句柄
lpDCB:指向一个设备控制块(DCB结构)的指针
3、SetupComm函数
函数原型:
BOOL SetupComm(HANDLE hFile, DWORD dwInQueue, DWORD dwOutQueue);
设置串行口的输入和输出缓冲区的大小
hFile:通信设备的句柄
dwInQueue:输入缓冲区的大小(字节数)
dwOutQueue:输出缓冲区的大小(字节数)
注:
除了在BCD中的设置外,程序一般还需要设置I/O缓冲区的大小和超时。Windows用I/O缓冲区来暂存串口输入和输出的数据。
如果通信的速率较高,则应该设置较大的缓冲区。调用SetupComm函数可以设置串行口的输入和输出缓冲区的大小。
4、GetCommTimeouts和SetCommTimeouts函数
函数原型:
BOOL GetCommTimeouts(HANDLE hFile, LPCOMMTIMEOUTS lpCommTimeouts);
BOOL SetCommTimeouts(HANDLE hFile, LPCOMMTIMEOUTS lpCommTimeouts);
参数说明:
hFile:通信设备的句柄
lpCommTimeouts:设置的超时时间
查询/设置当前的超时设置,该函数会填充一个COMMTIMEOUTS结构。
超时的作用是在指定的时间内没有读入或发送指定数量的字符,ReadFile或WriteFile的操作仍然会结束。
读写串口的超时有两种:间隔超时和总超时。
间隔超时是指在接收时两个字符之间的最大时延。总超时是指读写操作总共花费的最大时间。写操作只支持总超时,而读操作两种超时均支持。用COMMTIMEOUTS结构可以规定读写操作的超时。
COMMTIMEOUTS结构的成员都以毫秒为单位。
COMMTIMEOUTS结构定义为
typedef struct _COMMTIMEOUTS {
DWORD ReadIntervalTimeout; //读间隔超时
DWORD ReadTotalTimeoutMultiplier; //读时间系数
DWORD ReadTotalTimeoutConstant; //读时间常量
DWORD WriteTotalTimeoutMultiplier; // 写时间系数
DWORD WriteTotalTimeoutConstant; //写时间常量
} COMMTIMEOUTS,*LPCOMMTIMEOUTS;
注:
总超时的计算公式:
总超时=时间系数×要求读/写的字符数+时间常量
例如,要读入10个字符,那么读操作的总超时的计算公式为:
读总超时=ReadTotalTimeoutMultiplier×10+ReadTotalTimeoutConstant
注:
1)间隔超时和总超时的设置是不相关的
2)写操作只支持总超时,而读操作两种超时均支持
5、PurgeComm函数
函数原型:
BOOL PurgeComm(HANDLE hFile, DWORD dwFlags);
清空缓冲区
参数说明:
hFile:通信设备的句柄
dwFlags:需要完成的操作,可以是下列值的组合:
PURGE_TXABORT 中断所有写操作并立即返回,即使写操作还没有完成。
PURGE_RXABORT 中断所有读操作并立即返回,即使读操作还没有完成。
PURGE_TXCLEAR 清除输出缓冲区
PURGE_RXCLEAR 清除输入缓冲区
6、EscapeCommFunction函数
函数原型:
BOOL EscapeCommFunction( HANDLE hFile, DWORD dwFunc);
可将硬件信号置ON或OFF,模拟XON或XOFF
参数说明:
hFile:通信设备的句柄
dwFunc 事件掩码:
CLRDTR DTR置OFF
CLRRTS RTS置OFF
SETDTR STR置ON
SETRTS TRS置ON
SETXOFF 模拟XOFF字符的接收
SETXON 模拟XON字符的接收
SETBREAK 在发送中产生一个中止
CLRBREAK 在发送中清除中止
7、ClearCommError函数
函数原型:
BOOL ClearCommError(HANDLE hFile, LPDWORD lpErrors,LPCOMSTAT lpStat);
清除串行端口错误、读取串行端口现在的状态(清除串口的错误标志以便继续输入、输出操作),Windows系统利用此函数清除硬件的通讯错误以及获取通讯设备的当前状态。
参数说明:
hFile:通信设备的句柄
lpErrors:接收错误代码变量的类型,错误常数如下:
CE_BREAK:检测到中断信号
CE_DNS:Windows95专用,未被选择的并行端口
CE_FRAME:帧错误
CW_IOE:一般I/O错误,常伴有更为详细的错误标志
CE_MODE:不支持请求的模式
CE_OVERRUN:缓冲区超限下一个字符将丢失
CE_RXOVER:接收缓冲区超限
CE_RXPARITY:奇偶校验错误
CE_TXFULL:发送缓冲区满
CE_DNS:没有选择并行设备
CE_PTO:并行设备发生超时
CE_OOP:并行设备缺纸
lpStat:通信状态缓冲区的指针,指向一个COMSTAT结构,该结构返回串口状态信息。
COMSTAT结构包含串口的信息,结构定义如下:
typedef struct _COMSTAT { // cst
DWORD fCtsHold : 1;
// Tx waiting for CTS signal(为真时,等待cts信号开启传输)
DWORD fDsrHold : 1;
// Tx waiting for DSR signal (为真时,等待dsr信号时开启传输)
DWORD fRlsdHold : 1;
// Tx waiting for RLSD signal
DWORD fXoffHold : 1;
// Tx waiting, XOFF char rec’’d (为真时,等待接收到xoff时开始传输)
DWORD fXoffSent : 1;
// Tx waiting, XOFF char sent
DWORD fEof : 1;
// EOF character sent (为真时,eof字符可以被收到)
DWORD fTxim : 1;
// character waiting for Tx
DWORD fReserved : 25;
// reserved (保留)
DWORD cbInQue;
// bytes in input buffer //串口接收缓冲区的字节数,它们还没有来得及被ReadFile操作读走
DWORD cbOutQue;
// bytes in output buffer //串口发送缓冲区的字节数
}
8、GetCommMask和SetCommMask函数
函数原型:
BOOL GetCommMask(HANDLE hFile, LPDWORD lpEvtMask);
BOOL SetCommMask(HANDLE hFile, DWORD dwEvtMask);
设置/获取监听事件
参数说明:
hFile:通信设备的句柄
EvtMask:事件掩码,标识将被监视的通信事件。如果该参数设置为0,则表示禁止所有事件。如果不为0,则掩码可为如下:
EV_BREAK 检测到输入为止
EV_CTS CTS(清除发送)信号改变状态
EV_DSR DSR(数据设置就绪)信号改变状态
EV_ERR 发生了线路状态错误.
CE_FRAME(帧错误)
CE_OVERRUN(接收缓冲区超限)
CE_RXPARITY(奇偶校验错误)
EV_RING 检测到振铃
EV_RLSD RLSD(接收线路信号检测)信号改变状态
EV_EXCHAR 接收到一个字符,并放入输入缓冲区
EV_RXFLAG 接收到事件字符(DCB成员的EvtChar成员),度放入输入缓冲区
EV_TXEMPTY 输出缓冲区中最后一个字符发送出去
//在用SetCommMask指定了有用的事件后,应用程序可调用WaitCommEvent()来等待事件发生.
9、WaitCommEvent函数
函数原型:
BOOL WaitCommEvent(HANDLE hFile, LPDWORD lpEvtMask, LPOVERLAPPED lpOverlapped);
等待被监控事件发生
参数说明:
hFile:通信设备的句柄
lpEvtMask:指向一个32位变量,接收事件掩模,标识所发生的通信事件属于何种类型
lpOverlapped:指向一个OVERLAPPED结构,如果打开hFile表示的通信设备时,指定FILE_FLAG_OVERLAPPED标志,则该参数被忽略。如果不需要异步操作,则这个参数不用设置。
三、串口读写函数
1、读函数
函数原型:
BOOL ReadFile(HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped);
读串口数据
参数说明:
hFile:串口的句柄
lpBuffer:读入的数据存储的地址,即读入的数据将存储在以该指针的值为首地址的一片内存区
nNumberOfBytesToRead:要读入的数据的字节数
lpNumberOfBytesRead:指向一个DWORD数值,该数值返回读操作实际读入的字节数
lpOverlapped:重叠(异步)操作时,该参数指向一个OVERLAPPED结构;同步操作时,该参数为NULL。
2、写函数
函数原型:
BOOL WriteFile(HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten,LPOVERLAPPED lpOverlapped);
写串口函数
函数说明:
hFile:串口的句柄
lpBuffer:即以该指针的值为首地址的nNumberOfBytesToWrite个字节的数据将要写入串口的发送数据缓冲区。
nNumberOfBytesToWrite:将要写入的数据的字节数
lpNumberOfBytesWritten:指向一个DWORD数值,该数值返回实际写入的字节数
lpOverlapped:重叠(异步)操作时,该参数指向一个OVERLAPPED结构,同步操作时,该参数为NULL。
OVERLAPPED用于记录了当前正在操作的文件一些相关信息,OVERLAPPED结构说明:
typedef struct _OVERLAPPED {
DWORD Internal;
//保存已处理的I/O请求的错误码/状态码
DWORD InternalHigh;
//当异步I/O请求完成的时候,这个成员用来保存已传输的字节数。
DWORD Offset;
//该文件的位置是从文件起始处的字节偏移量。调用进程设置这个成员之前调用ReadFile或WriteFile函数。当读取或写入命名管道和通信设备时这个成员被忽略设为零。
DWORD OffsetHigh;
//指定文件传送的 字节 偏移量的高位字。当读取或写入命名管道和通信设备时这个成员被忽略设为零。
HANDLE hEvent;
//在转移完成时处理一个事件设置为有信号状态。
//调用进程集这个成员在调用ReadFile、 WriteFile、TransactNamedPipe、 ConnectNamedPipe函数之前。
} OVERLAPPED;
3、GetOverlappedResult函数
函数原型:
BOOL WINAPI GetOverlappedResult(HANDLE hFile, LPOVERLAPPED lpOverlapped, LPDWORD lpNumberOfBytesTransferred, BOOL bWait);
根据OVERLAPPED结构来获取重叠操作的状态/结果,用来判断异步操作是否完成,它是通过判断OVERLAPPED结构中的hEvent是否被置位来
参数说明:
hFile:串口的句柄
lpOverlapped:指向重叠操作开始时指定的OVERLAPPED结构
lpNumberOfBytesTransferred:指向一个32位变量,该变量的值返回实际读写操作传输的字节数
bWait:该参数用于指定函数是否一直等到重叠操作结束。如果该参数为TRUE,函数直到操作结束才返回;
如果该参数为FALSE,函数直接返回,这时如果操作没有完成,通过调用GetLastError()函数会返回ERROR_IO_INCOMPLETE。
注:
1)ReadFile函数只要在串口输入缓冲区中读入指定数量的字符,就算完成操作;而WriteFile函数不但要把指定数量的字符拷入到输出缓冲区,而且要等这些字符从串行口送出去后才算完成操作。
2)如果成功,两个函数都返回TRUE。当ReadFile和WriteFile返回FALSE时,不一定就是操作失败,线程应该调用GetLastError函数分析返回的结果。
例如,在重叠操作时如果操作还未完成函数就返回,那么函数就返回FALSE,而且 GetLastError函数返回ERROR_IO_PENDING,这说明重叠操作还未完成。
3)在使用ReadFile和WriteFile重叠操作时,线程需要创建OVERLAPPED结构以供这两个函数使用。线程通过OVERLAPPED结构获得当前的操作状态,该结构最重要的成员是hEvent。
hEvent是读写事件。当串口使用异步通讯时,函数返回时操作可能还没有完成,程序可以通过检查该事件得知是否读写完毕。
当调用ReadFile, WriteFile 函数的时候,该成员会自动被置为无信号状态;当重叠操作完成后,该成员变量会自动被置为有信号状态。