c++串口小结

来源:互联网 发布:数据新闻的采编播优势 编辑:程序博客网 时间:2024/05/16 15:53

串口小结

一、            概念

串行接口简称串口,也称串行通信接口或串行通讯接口(通常指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)    读取超时时间设置

在用ReadFileWriteFile读写串行口时,需要考虑超时问题。如果在指定的时间

内没有读出或写入指定数量的字符,那么ReadFileWriteFile的操作就会结束。要查询当前的超时设置应调用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,并将ReadTotalTimeoutMultiplierReadTotalTimeoutConstant设置为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,那么就不使用写超时。如果ReadIntervalTimeout0,那么就不使用读间隔超时,如果

ReadTotalTimeoutMultiplierReadTotalTimeoutConstant都为0,则不使用读总超时。如果读间隔超时被设置成MAXDWORD并且两个读总超时为0,那么在读一次输入缓冲区中的内容后读操作就立即完成,而不管是否读入了要求的字符。在用重叠方式读写串行口时,虽然ReadFileWriteFile在完成操作以前就可能返回,但超时仍然是起作用的。在这种情况下,超时规定的是操作的完成时间,而不是ReadFileWriteFile的返回时间。

 

(2)    设置串口的波特率、校验、停止位、数据位

GetCommState读取串口设置(波特率,校验,停止位,数据位等).

函数声明:

BOOL GetCommState(

HANDLEhFile,

LPDCBlpDCB

);

GetCommState函数的第一个参数hFile是由CreateFile函数返回指向已打开串行口的句柄。第二个参数指向设备控制块DCB。如果函数调用成功,则返回值为非0;若函数调用失败,则返回值为0

当应用程序仅仅需要修改一部分串行口的配置值时,可以通过GetCommState函数获得当前的DCB结构,然后更改参数,再调用SetCommState函数设置修改过的DCB来配置串行口。

 

(3)    指定一组监视通信设备的事件

BOOL SetCommMask(HANDLE hFile, //标识通信端口的句柄

DWORD dwEvtMask //能够使能的通信事件

);

参数说明:-hFile:串口句柄

-dwEvtMask:准备监视的串口事件掩码

串口上可能发生的事件如下表所示:

 

EV_BREAK:收到BREAK信号。

EV_CTSCTS(clear to send)线路发生变化。

EV_DSRDST(Data Set Ready)线路发生变化。

EV_ERR:线路状态错误,包括了CE_FRAME / CE_OVERRUN / CE_RXPARITY 3种错误。

EV_RING:检测到振铃信号。

EV_RLSDCD(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_OVERLAPPEDlpOverlapped不能指向一个空OVERLAPPED结构体,而是与Readfile()和WriteFile()中的OVERLAPPED参数为同一个参数。如果hFile是用异步方式打开的,而lpOverlapped指向一个空的OVERLAPPED结构体,那么函数会错误地报告,等待的操作已经完成(而此时等待的操作可能还没有完成)。

如果hFile是用异步方式打开的,而lpOverlapped指向一个非空的OVERLAPPED结构体,那么函数WaitCommEvent被默认为异步操作,马上返回。这时,OVERLAPPED结构体必须包含一个由CreateEvent()函数返回的手动重置事件对象的句柄hEven

如果hFile是用同步方式打开的,那么函数WaitCommEvent不会返回,直到要等待的事件发生。

返回值:

如果函数成功,返回非零值,否则返回0。要得到错误信息,可以调用GetLastError函数。

备注:

WaitCommEvent函数为指定的通信资源监听一系列的Event,这些Event可以由SetcommMaskGetcommMask函数来设置和查询。

如果异步操作不能马上完成,那么该函数会返回一个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]);                    }                                                            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]);                    }                                        dwReadToal = 0;                    //解析数据包                    if(pThis->ParseMsg(recvBuf) == FALSE)                    {                        continue;                    }                }            }        }    }}


 

 

2 0
原创粉丝点击