串口小结
一、 概念
串行接口简称串口,也称串行通信接口或串行通讯接口(通常指COM接口),是采用
串行通信方式的扩展接口。
串行接口 (Serial Interface) 是指数据一位一位地顺序传送,其特点是通信线路简单,只要一对传输线就可以实现双向通信(可以直接利用电话线作为传输线),从而大大降低了成本,特别适用于远距离通信,但传送速度较慢。一条信息的各位数据被逐位按顺序传送的通讯方式称为串行通讯。串行通讯的特点是:数据位的传送,按位顺序进行,最少只需一根传输线即可完成;成本低但传送速度慢。串行通讯的距离可以从几米到几千米;根据信息的传送方向,串行通讯可以进一步分为单工、半双工和全双工三种。
二、 使用
在Windows中串口被当做一种文件来看待,因此对它的创建也就和对文件的创建方式
是一致的。
m_hComDev = CreateFile((LPCTSTR)strComName.c_str(),
GENERIC_READ | GENERIC_WRITE, //能对设备进行读写
0, NULL, OPEN_EXISTING, //设备不存在则创建失败
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
NULL);
需要注意其中参数FILE_FLAG_OVERLAPPED通知操作系统对该文件的操作采取异步I/O的方式。
在创建设备句柄时通常需要对其设置许多属性,比如说串口读取超时时间、初始化通信缓冲区、设置串口的波特率、校验、停止位、数据位等等。
(1) 读取超时时间设置
在用ReadFile和WriteFile读写串行口时,需要考虑超时问题。如果在指定的时间
内没有读出或写入指定数量的字符,那么ReadFile或WriteFile的操作就会结束。要查询当前的超时设置应调用GetCommTimeouts函数,该函数会填充一个COMMTIMEOUTS结构。调用SetCommTimeouts可以用某一个COMMTIMEOUTS结构的内容来设置超时。有两种超时:间隔超时和总超时。间隔超时是指在接收时两个字符之间的最大时延,总超时是指读写操作总共花费的最大时间。写操作只支持总超时,而读操作两种超时均支持。
用COMMTIMEOUTS结构可以规定读/写操作的超时,该结构的定义为:
typedef struct _COMMTIMEOUTS {
DWORD ReadIntervalTimeout; // 读间隔超时
DWORD ReadTotalTimeoutMultiplier; // 读时间系数
DWORD ReadTotalTimeoutConstant; // 读时间常量
DWORD WriteTotalTimeoutMultiplier; // 写时间系数
DWORD WriteTotalTimeoutConstant; // 写时间常量
} COMMTIMEOUTS,*LPCOMMTIMEOUTS;
COMMTIMEOUTS结构的成员都以毫秒为单位。
ReadIntervalTimeout:两字符之间最大的延时,当读取串口数据时,一旦两个字符传输的时间差超过该时间,读取函数将返回现有的数据。设置为0表示该参数不起作用。指定时间最大值(毫秒),允许接收的2个字节间有时间差。也就是说,刚接收了一个字节后,等了ReadIntervalTimeout时间后还没有新的字节到达,就认为本次读串口操作结束(后面的字节等下一次读取操作来处理)。即使你想读8个字节,但读第2个字节后,过了ReadIntervalTimeout时间后,第3个字节还没到。实际上就只读了2个字节。
ReadTotalTimeoutMultiplier:指定比例因子(毫秒),实际上是设置读取一个字节和等待下一个字节所需的时间,这样总的超时时间为读取的字节数乘以该值,同样一次读取操作到达这个时间后,也认为本次读操作己经结束。
ReadTotalTimeoutConstant:一次读取串口数据的固定超时。所以在一次读取串口的操作中,其超时为ReadTotalTimeoutMultiplier乘以读取的字节数再加上 ReadTotalTimeoutConstant。将ReadIntervalTimeout设置为MAXDWORD,并将ReadTotalTimeoutMultiplier 和ReadTotalTimeoutConstant设置为0,表示读取操作将立即返回存放在输入缓冲区的字符。可以理解为一个修正时间,实际上就是按ReadTotalTimeoutMultiplier计算出的超时时间再加上该时间才作为整个超时时间。
WriteTotalTimeoutMultiplier:写入每字符间的超时。
WriteTotalTimeoutConstant:一次写入串口数据的固定超时。所以在一次写入串口的操作中,其超时为WriteTotalTimeoutMultiplier乘以写入的字节数再加上 WriteTotalTimeoutConstant
一般都会做以下设置:
TimeOuts.ReadIntervalTimeout=MAXDWORD;
// 把间隔超时设为最大,把总超时设为0将导致ReadFile立即返回并完成操作
TimeOuts.ReadTotalTimeoutMultiplier=0;
//读时间系数
TimeOuts.ReadTotalTimeoutConstant=0;
//读时间常量
TimeOuts.WriteTotalTimeoutMultiplier=50;
//总超时=时间系数*要求读/写的字符数+时间常量
TimeOuts.WriteTotalTimeoutConstant=2000;
//设置写超时以指定WriteComm成员函数中的
总超时的计算公式是:
总超时=时间系数×要求读/写的字符数 + 时间常量
例如,如果要读入10个字符,那么读操作的总超时的计算公式为:
读总超时=ReadTotalTimeoutMultiplier×10 + ReadTotalTimeoutConstant
可以看出,间隔超时和总超时的设置是不相关的,这可以方便通信程序灵活地设置各种超时。如果所有写超时参数均为0,那么就不使用写超时。如果ReadIntervalTimeout为0,那么就不使用读间隔超时,如果
ReadTotalTimeoutMultiplier和ReadTotalTimeoutConstant都为0,则不使用读总超时。如果读间隔超时被设置成MAXDWORD并且两个读总超时为0,那么在读一次输入缓冲区中的内容后读操作就立即完成,而不管是否读入了要求的字符。在用重叠方式读写串行口时,虽然ReadFile和WriteFile在完成操作以前就可能返回,但超时仍然是起作用的。在这种情况下,超时规定的是操作的完成时间,而不是ReadFile和WriteFile的返回时间。
(2) 设置串口的波特率、校验、停止位、数据位
GetCommState 读取串口设置(波特率,校验,停止位,数据位等).
函数声明:
BOOL GetCommState(
HANDLE hFile,
LPDCB lpDCB
);
GetCommState函数的第一个参数hFile是由CreateFile函数返回指向已打开串行口的句柄。第二个参数指向设备控制块DCB。如果函数调用成功,则返回值为非0;若函数调用失败,则返回值为0。
当应用程序仅仅需要修改一部分串行口的配置值时,可以通过GetCommState函数获得当前的DCB结构,然后更改参数,再调用SetCommState函数设置修改过的DCB来配置串行口。
(3) 指定一组监视通信设备的事件
BOOL SetCommMask(HANDLE hFile, //标识通信端口的句柄
DWORD dwEvtMask //能够使能的通信事件
);
参数说明:-hFile:串口句柄
-dwEvtMask:准备监视的串口事件掩码
串口上可能发生的事件如下表所示:
EV_CTS:CTS(clear to send)线路发生变化。
EV_DSR:DST(Data Set Ready)线路发生变化。
EV_ERR:线路状态错误,包括了CE_FRAME / CE_OVERRUN / CE_RXPARITY 3种错误。
EV_RING:检测到振铃信号。
EV_RLSD:CD(Carrier Detect)线路信号发生变化。
EV_RXCHAR:输入缓冲区中已收到数据,即接收到一个字节并放入输入缓冲区。
EV_RXFLAG:使用SetCommState()函数设置的DCB结构中的等待字符已被传入输入缓冲区中。
EV_TXEMPTY:输出缓冲区中的数据已被完全送出。
另外,可以通过SetCommMask(hFile,0)来清除该通讯设备的所有事件
可以通过WaitCommEvent(__in HANDLEhFile,
__out LPDWORDlpEvtMask,
__in LPOVERLAPPEDlpOverlapped
);
等待串口上事件的发生以作相应的处理。
(4) 初始化指定通信设备的通信参数
SetupComm
该函数初始化一个指定的通信设备的通信参数。
BOOL SetupComm(
HANDLE hFile,
DWORD dwInQueue,
DWORD dwOutQueue
);
参数
hFile
[IN]通讯设备句柄。
CreateFile函数返回此句柄。
dwInQueue
[in]指定推荐的大小,以字节为单位,对设备的内部输入缓冲区。
dwOutQueue
[in]指定推荐的大小,以字节为单位,对设备的内部输出缓冲区。
返回值
非零表示成功。零表示失败。要获得更多错误信息,调用GetLastError函数
通常实际编程的时候,首先需要清空缓冲区,然后在设置缓冲区大小。清空缓冲区
可以用PurgeComm()函数进行。
PurgeComm()函数--清空缓冲区
该函数原型:
BOOL PurgeComm(HANDLE hFile,DWORD dwFlags )
HANDLE hFile //串口句柄
DWORD dwFlags // 需要完成的操作
参数dwFlags指定要完成的操作,可以是下列值的组合:
PURGE_TXABORT 终止所有正在进行的字符输出操作,完成一个正处于等待状态的重叠i/o操作,他将产生一个事件,指明完成了写操作
PURGE_RXABORT 终止所有正在进行的字符输入操作,完成一个正在进行中的重叠i/o操作,并带有已设置得适当事件
PURGE_TXCLEAR 这个命令指导设备驱动程序清除输出缓冲区,经常与PURGE_TXABORT 命令标志一起使用
PURGE_RXCLEAR 这个命令用于设备驱动程序清除输入缓冲区,经常与PURGE_RXABORT 命令标志一起使用
(5) 等待串口事件发生
BOOL WINAPI WaitCommEvent(
__in HANDLEhFile,
__out LPDWORDlpEvtMask,
__in LPOVERLAPPEDlpOverlapped
);
hFile:指向通信设备的一个句柄,该句柄应该是由 CreateFile函数返回的。
lpEvtMask:一个指向DWORD的指针。如果发生错误,pEvtMask指向0,否则指向以下的某一事件
EV_DSR 0x0010 |
The DSR (data-set-ready) signal changed state. DSR(数据装置就绪)信号改变状态。 |
EV_ERR 0x0080 |
A line-status error occurred. Line-status errors are CE_FRAME, CE_OVERRUN, and CE_RXPARITY. |
EV_RING 0x0100 |
A ring indicator was detected. |
EV_RLSD 0x0020 |
The RLSD (receive-line-signal-detect) signal changed state. |
EV_RXCHAR 0x0001 |
A character was received and placed in the input buffer.(在输入缓冲区中接收并放置一个字符) |
EV_RXFLAG 0x0002 |
The event character was received and placed in the input buffer. The event character is specified in the device‘sDCBstructure, which is applied to a serial port by using theSetCommStatefunction. |
EV_TXEMPTY 0x0004 |
The last character in the output buffer was sent.(输出缓冲区的数据全部发送出去) |
lpOverlapped:指向OVERLAPPED结构体的一个指针。如果hFile是用异步方式打开的(在CreateFile()函数中,第三个参数设置为FILE_FLAG_OVERLAPPED),lpOverlapped不能指向一个空OVERLAPPED结构体,而是与Readfile()和WriteFile()中的OVERLAPPED参数为同一个参数。如果hFile是用异步方式打开的,而lpOverlapped指向一个空的OVERLAPPED结构体,那么函数会错误地报告,等待的操作已经完成(而此时等待的操作可能还没有完成)。
如果hFile是用异步方式打开的,而lpOverlapped指向一个非空的OVERLAPPED结构体,那么函数WaitCommEvent被默认为异步操作,马上返回。这时,OVERLAPPED结构体必须包含一个由CreateEvent()函数返回的手动重置事件对象的句柄hEven。
如果hFile是用同步方式打开的,那么函数WaitCommEvent不会返回,直到要等待的事件发生。
返回值:
如果函数成功,返回非零值,否则返回0。要得到错误信息,可以调用GetLastError函数。
备注:
WaitCommEvent函数为指定的通信资源监听一系列的Event,这些Event可以由SetcommMask和GetcommMask函数来设置和查询。
如果异步操作不能马上完成,那么该函数会返回一个FALSE,同时GetLastError函数可以截获错误码ERROR_IO_PENDING(#define ERROR_IO_PENDING 997),表示操作转到后台运行。在WaitCommEvent函数返回之前,系统将OVERLAPPED结构中的hEven句柄设置为无信号状态;当WaitCommEvent函数所等待的任何一个Event发生后,系统将OVERLAPPED结构中的hEven句柄设置为有信号状态,同时将所发生事件赋给lpEvtMask。
父进程可以根据lpEvtMask来做出相应的事件处理,然后也可以调用GetOverlappedResult函数来判断WaitCommEvent的操作是否成功。
如果WaitCommEvent函数在后台运行的时候,进程企图想通过SetcommMask函数来改变当前设备的Event,那么WaitCommEvent函数马上返回,lpEvtMask指向0。
Windows系统利用此函数清除硬件的通讯错误以及获取通讯设备的当前状态,ClearCommError函数声明如下:
BOOL ClearCommError(
HANDLE hFile,
LPDWORD lpErrors,
LPCOMSTAT lpStat
);
ClearCommError函数的第一个参数hFile是由CreateFile函数返回指向已打开串行口的句柄。第二个参数指向定义了错误类型的32位变量。第三个参数指向一个返回设备状态的控制块COMSTAT。如果函数调用成功,则返回值为非0;若函数调用失败,则返回值为0
下面附上一小段用重叠I/O操作的串口代码
int CDevImp::Connect(char * lpszURL) { SD_TRACE("opencom URL=%s", lpszURL); string strComName; int nBaudRate = 0; //波特率 int nDataBits = 0; //数据位 int nStopBits = 0; //停止位 ParseUrlParam(lpszURL, strComName, nBaudRate, nDataBits, nStopBits); SD_INFO("串口名称: %s, 波特率: %d, 数据位: %d, 停止位: %d", strComName.c_str(), nBaudRate, nDataBits, nStopBits); //创建设备 m_hComDev = CreateFile((LPCTSTR)strComName.c_str(), GENERIC_READ | GENERIC_WRITE, //能对设备进行读写 0, NULL, OPEN_EXISTING, //设备不存在则创建失败 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, //通知操作系统对该文件的操作采取一步I/O的方式 NULL); if(m_hComDev == INVALID_HANDLE_VALUE) { SD_ERROR("创建设备句柄失败"); return PE_FAIL; } memset(&m_Overlapped, 0, sizeof(m_Overlapped)); //串口读取超时时间设置 //总的超时时间为比例因子*读取到的字节数+固定超时时间 COMMTIMEOUTS stComTimeOuts; //单位都是毫秒 stComTimeOuts.ReadIntervalTimeout = 1000; //读取等待超时时间 stComTimeOuts.ReadTotalTimeoutMultiplier = 1000; //比例因子 stComTimeOuts.ReadTotalTimeoutConstant = 1000; //一次读取串口数据的固定超时时间 stComTimeOuts.WriteTotalTimeoutConstant = 1000; stComTimeOuts.WriteTotalTimeoutMultiplier = 1000; SetCommTimeouts(m_hComDev, &stComTimeOuts); //创建操作完成时的通知事件 m_Overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); //设置串口基本参数,波特率,数据位,停止位等 DCB dcb; dcb.DCBlength = sizeof(dcb); GetCommState(m_hComDev, &dcb); dcb.BaudRate = nBaudRate; dcb.ByteSize = nDataBits; dcb.StopBits = nStopBits; dcb.Parity = 0; //指定一组监视通信设备的事件,EV_RXCHAR缓冲区收到数据哪怕只收到一个字节的数据 SetCommMask(m_hComDev, EV_RXCHAR); //清空收发缓冲区 PurgeComm(m_hComDev, PURGE_TXABORT| PURGE_RXABORT | PURGE_TXCLEAR| PURGE_RXCLEAR); //初始化通信参数,输入输出缓冲区都为1024 if(!SetCommState(m_hComDev, &dcb) || !SetupComm(m_hComDev, 1024, 1024) || m_Overlapped.hEvent == NULL) { CloseHandle(m_hComDev); SD_INFO("COM set faild"); return PE_FAIL; } //开启接收线程 CloseHandle((HANDLE)_beginthreadex(NULL, 0, RecvMsg, (void*)this, 0, NULL)); m_bConnected = TRUE; return PE_NOERROR; } unsigned int WINAPI CDevImp::RecvMsg(void* pParam) { CDevImp* pThis = (CDevImp*)pParam; DWORD dwEvMask; //触发的事件类型 DWORD dwRet; unsigned char recvBuf[MAX_MSG_LEN] = {0}; unsigned char recvTmp[MAX_MSG_LEN] = {0}; COMSTAT stRet; //设备状态 DWORD dwErr; DWORD dwReadToal = 0; //总共读取的字节数 while(1) { //对重叠I/O操作函数总会返回FALSE,操作转到后台, //当事件发生时系统会将m_Overlapped.hEvent直为有信号 WaitCommEvent(pThis->m_hComDev, &dwEvMask, &pThis->m_Overlapped); dwRet = WaitForSingleObject(pThis->m_Overlapped.hEvent, 1600); if(dwRet == WAIT_TIMEOUT) { continue;//超时继续等待 } //操作完成 if(dwRet == WAIT_OBJECT_0) { memset(recvBuf, 0, sizeof(recvBuf)); memset(recvTmp, 0, sizeof(recvTmp)); memset(&stRet, 0, sizeof(stRet)); //清除硬件通讯错误,获取通讯设备当前状态 ClearCommError(pThis->m_hComDev, &dwErr, &stRet); //读取出错 if(!ReadFile(pThis->m_hComDev, recvTmp, MAX_MSG_LEN, &stRet.cbInQue, &pThis->m_Overlapped)) { continue; } else { memcpy_s(recvBuf + dwReadToal, MAX_MSG_LEN, recvTmp, stRet.cbInQue); dwReadToal += stRet.cbInQue; if(recvBuf[2] == POWER_ALARM && dwReadToal == POWER_ALARM_LEN)//电网数据包 { char sShow[MAX_MSG_LEN] = {0}; for(int i = 0; i < POWER_ALARM_LEN; i ++) { sprintf_s(sShow + i * sizeof(char), sizeof(sShow) - 1, "%02x ", recvBuf[i]); } SD_INFO("收到电网数据包: %s", sShow); //清空以进行下一轮接收 dwReadToal = 0; //解析数据包 if(pThis->ParseMsg(recvBuf) == FALSE)//校验失败,数据有误 { continue; } } else if(recvBuf[2] == SENSOR_ALARM && dwReadToal == SENSOR_ALARM_LEN)//入侵防范传感器数据包 { char sShow[MAX_MSG_LEN] = {0}; for(int i = 0; i < POWER_ALARM_LEN; i ++) { sprintf_s(sShow + i * sizeof(char), sizeof(sShow) - 1, "%02x ", recvBuf[i]); } SD_INFO("收到入侵防范传感器数据包: %s", sShow); dwReadToal = 0; //解析数据包 if(pThis->ParseMsg(recvBuf) == FALSE) { continue; } } } } } }
版权声明:本文为博主原创文章,未经博主允许不得转载。
原文地址:http://blog.csdn.net/lqlblog/article/details/48087309