机器的通信方式有两种,分别是并行通信与串行通信
并行通信:并行通信是指多比特数据同时通过并行线进行传送,这样数据传送速度大大提高,但并行传送的线路长度受到限制,因为长度增加,干扰就会增加,数据也就容易出错。
串行通信:串行通信是指使用一条数据线,将数据一位一位地依次传输,每一位数据占据一个固定的时间长度。其只需要少数几条线就可以在系统间交换信息,特别适用于计算机与计算机、计算机与外设之间的远距离通信。
异步串行通信:异步串行通信是指通信双方以一个字符(包括特定附加位)作为数据传输单位且发送方传送字符的间隔时间不一定,具有不规则数据段传送特性的串行数据传输。
同步串行通信:同步串行通信是指在约定的通信速率下(即相同波特率),发送端和接收端的时钟信号频率和相位始终保持一致(同步),这就保证了通信双方在发送和接收数据时具有完全一致的定时关系。
比特率(Bitrate) 来表示,即每秒钟传输的二进制位数,单位为比特每秒(bit/s)。
“波特率”(Baudrate),它表示每秒钟传输了多少个码元。而码元是通讯信号调制的概念,通讯中常用时间间隔相同的符号来表示一个二进制数字,这样的信号称为码元。
波特率与比特率的关系为:比特率=波特率X单个调制状态对应的二进制位数。
因为很多常见的通讯中一个码元都是表示两种状态,人们常常直接以波特率来表示比特率
串口通讯的数据包由发送设备通过自身的TXD 接口传输到接收设备的RXD 接口。在串口通讯协议中,规定了数据包的内容,它由起始位、主体数据、校验位以及停止位组成,通讯双方的数据包格式要约定一致才能正常收发数据。
起始位占1位,为逻辑0。数据位占5 ~ 8位,可配置。校验位占1位,可配置为奇校验、偶校验、无校验,停止位的值为逻辑1。
串口通信即可以实现半双工,也可以实现全双工
单工:数据传输只支持数据在一个方向上传输
半双工:允许数据在两个方向上传输。但是,在某一时刻,只允许数据在一个方向上传输
全双工:允许数据同时在两个方向上传输
TTL 标准
理想状态下,使用5V 表示二进制逻辑“1”,使用0V 表示逻辑“0”.
UART
UART(Universal Asynchronous Receiver Transmitter:通用异步收发器),是电脑硬件的一部分,它把将要传输的资料在串行通信与并行通信之间加以转换,UART通常被集成于其他通讯接口的连接上。UART即我们通常说的“串口”。该总线有两条数据线,可以实现全双工的发送和接收,在嵌入式系统中常用于主机与辅助设备之间的通信。
UART进行串口通信使用TTL电平。5V工作电压的MCU,使用0 ~ 0.5V表示逻辑0,2.5V ~ 5V表示逻辑1;3.3V工作电压的MCU,使用0 ~ 0.5V表示逻辑0,2.5V ~ 3.3V表示逻辑1。5V的MUC不能与3.3V的MCU直接连接。
空闲位:不进行传输数据时,默认为逻辑1,为高电平;
起始位:先发出一个逻辑“0”,表示消息帧的开始;
数据位:紧接着起始位之后,可由5~8位组成,通常传输8位即一个字节。先发送数据的低位,后发送数据的高位;
奇偶校验位:紧接着数据位后面(可有可无),使得“1”的位数应为偶数(偶校验)或奇数(奇校验),校验数据传输是否正确;
停止位:它是消息传输结束的标志,它可以是1位、1.5位、2位的高电平, 由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会。
波特率:是衡量数据传输速率的指标,表示每秒钟传输的位数。例如设置串口的波特率为9600,则表示是1s传输9600个bit的数据,则传送每个位的时间为 1s / 9600 ≈ 104us,从而区分消息帧中每个位传输的数据;
缺点:UART一般直接使用TTL信号来表示0和1,但TTL信号抗干扰能力较差,数据在传输过程中很容易出错;且TTL信号的通信距离也很短;
RS232
在目前的其它工业控制使用的串口通信中,一般只使用RXD、TXD 以及GND 三条信号线,直接传输数据信号,而RTS、CTS、DSR、DTR 及DCD 信号都被裁剪掉了,这主要是考虑到近程通信与远程通信问题。
特性:
工作方式:单端(非平衡)
节点数:点对点通讯(1收1发)
最大传输距离:50ft ( 50 * 0.3048 = 15.24m)
最大传输速率:20kbit/s
连接方式:点对点(全双工)
电气特性:-3V ~ -15V表示逻辑1,3V ~ 15V表示逻辑0
常用芯片有max232、SP232等
缺点:通信距离短,速率低,而且只能点对点通信,无法组建多机通信系统,且容易受外界电气干扰导致信息传输错误。
RS-485
标准运行连接多个收发器,即具有多站能力,增加了多点、双向的通信能力
RS-485采用平衡发送和差分接收,因此具有抑制共模干扰的能力。
RS485有两线制和四线制两种接线,四线制只能实现点对点的通信方式,现很少采用,多采用的是两线制接线方式,这种接线方式为总线拓扑结构,在同一总线上最多可以挂接32个节点。
采用两线半双工传输,最大速率10Mb/s,电平逻辑是两线的电平差来决定的,提高抗干扰能力,传输距离长(几十米到上千米)。
特性:
工作方式:差分(平衡);
节点数:点对多通讯(1发32收);
最大传输距离:4000ft ( 4000 * 0.3048 = 1219.2m);
最大传输速率:10Mbit/s;
连接方式:多点对多点(两线制,半双工);
电气特性:2V ~ 6V表示逻辑1,-2V ~ -6V表示逻辑0;
具体编程
在32位的Windows系统中,串口和其它通信设备是作为文件处理的。串口的打开、关闭、读取和写入所用的函数与操作文件的函数完全一致。
打开串口
CreateFile()为读访问、写访问或读写访问“打开”串口。返回一个句柄、
HANDLE CreateFile
(
LPCTSTR lpszName,
DWORD fdwAccess,
DWORD fdwShareMode,
LPSECURITY_ATTRIBUTES lpsa,
DWORD fdwCreate,
DWORD fdwAttrsAndFlags,
HANDLE hTemplateFile
)
lpszName:指定要打开的串口逻辑名,用字符串表示,如“COM1”和“COM2”分别表示串口1和串口2。
·fdwAccess:用来指定串口访问的类型。与文件一样,串口也是可以被打开以供读取、写入或者两者兼有。因为大部分串口通信都是双向的,因此常常在设置中将两个标识符连接起来使用。如:
fdwAccess = GENERIC_READ | GENERIC_WRITE;
·fdwShareMode:指定该端口的共享属性。对于不能共享的串口,它必须设置为0。如果在当前的应用程序调用CreateFile()时,另一个应用程序已经打开了串口,该函数就会返回错误代码,原因是两个应用程序不能共享一个端口。
·Ipsa:引用安全性属性结构(SECURITY_ARRTIBUTES)。将该参数设置为NULL将为该端口分配缺省的安全性属性。
·fdwCreate:指定如果CreateFile()正在被已有的文件调用时应采取的动作。因为串口总是存在,fdwCreate必须设置成OPEN_EXISTING。该标志告诉Windows不用企图创建新端口,而是打开已经存在的端口
fdwAttrsAndFlags:描述了端口的各种属性。对于文件来说,有可能具有很多属性,但对于串口,异步设置为FILE_FLAG_OVERLAPPED。同步设置为0.
·hTemplateFile:指向模板文件的句柄,当端口处于打开状态时,不使用该参数,因而必须置成0。
//同步方式
hCom = CreateFileA(portname, //串口名
GENERIC_READ | GENERIC_WRITE, //支持读写
0, //独占方式,串口不支持共享
NULL,//安全属性指针,默认值为NULL
OPEN_EXISTING, //打开现有的串口文件
0, //0:同步方式,FILE_FLAG_OVERLAPPED:异步方式
NULL);//用于复制文件句柄,默认值为NULL,对串口而言该参数必须置为NULL
关闭串口
只需要调用CloseHandle()函数关闭由CreateHandle()函数返回得句柄即可。
设置缓冲区
通过调用SetupComm()实现其它初始化工作。也可以不调用SetupComm()函数,Windows系统也会分配缺省的发送和接收缓冲区。
BOOL SetupComm
(
HANDLE hFile, // 通信设备句柄
DWORD dwInQueue, // 输入缓冲区大小
DWORD dwOutQueue // 输出缓冲区大小
);
BOOL PurgeComm
(
HANDLE hFile, // 返回的句柄
DWORD dwFlags // 执行的动作
);
参数hFile指向由CreateFile函数返回的句柄,dwFlags表示执行的动作,这个参数可以是表表5中的任一个。参数hFile指向由CreateFile函数返回的句柄,可以调用GetLastError()函数获得进一步的错误信息。
值 |
描述 |
PURGE_TXABORT |
即使发送操作没有完成,也终止所有的重叠发送操作,立即返回 |
PURGE_RXABORT |
即使接收操作没有完成,也终止所有的重叠接收操作,立即返回 |
PURGE_TXCLEAR |
清除发送缓冲区 |
PURGE_RXCLEAR |
清除接收缓冲区 |
BOOL FlushFileBuffers
(
HANDLE hFile // 函数打开的句柄
);
如果要保证缓冲区的所有字符都被发送,应该调用FlushFileBuffer()函数。该函数只受流量控制的支配,不受超时控制的支配,它在所有的写操作完成后才返回。
获取配置
使用GetCommState()函数获取串口的当前配置。
BOOL GetCommState
(
HANDLE hFile, // 通信设备句柄
LPDCB lpDCB // 指向device-control block structure的指针
);
如果GetCommState()函数调用成功,则返回值不为零。若函数调用失败,则返回值为零,如果想得到进一步的错误信息,可以调用GetLastError()函数来获取。
使用GetCommState()函数获取串口的当前配置
设置配置
调用SetCommState()函数配置修改过的DCB来配置端口。
BOOL SetCommState
(
HANDLE hFile, // 已打开的串口的句柄
LPDCB lpDCB // 指向DCB结构的指针
);
DCB结构的主要参数说明如下:
·DCBLength: 一字节为单位指定的DCB结构的大小。
·Baudrate: 用于指定串口设备通信的数据传输速率,它可以是实际的数据传输速率数值,也可以是下列数据之一:CBR_110, CBR_19200, CBR_300, CBR_38400, CBR_600, CBR_56000, CBR_1200, CBR_57600, CBR_2400, CBR_115200, CBR_4800, CBR_12800, CBR_9600, CBR_25600, CBR_14400。
·fBinary: 指定是否允许二进制。Win32API不支持非二进制传输,因此这个参数必须设置为TRUE,如果设置为FALSE则不能正常工作。
·fParity: 指定是否允许奇偶校验,如果这个参数设置为TRUE,则执行奇偶校验并报告错误信息。
·fOutxCtsFlow: 指定CTS是否用于检测发送流控制。当该成员为TRUE,而CTS为OFF时,发送将被挂起,直到CTS置ON。
·fOutxDsrFlow: 指定DSR是否用于检测发送流控制,当该成员为TRUE,而DSR为OFF时,发送将被挂起,直到DSR置ON。
·fDtrControl: 指定DTR流量控制,可以是表1中的任一值。
值 |
功能描述 |
DTR_CONTROL_DISABLE |
禁止DTR线,并保持禁止状态 |
DTR_CONTROL_ENABLE |
允许DTR线,并保持允许状态 |
DTR_CONTROL_HANDSHAKE |
允许DTR握手,如果允许握手,则不允许应用程序使用EscapeCommFunction函数调整线路 |
·fDsrSensitivity: 指定通信驱动程序对DTR信号线是否敏感,如果该位置设为TRUE时,DSR信号为OFF,接收的任何字节将被忽略。
·fTXContinueOnXoff: 指定当接收缓冲区已满,并且驱动程序已经发送出XoffChar字符时发送是否停止。当该成员为TRUE时,在接收缓冲区内接收到了缓冲区已满的字节XoffLim,并且驱动程序已经发送出XoffChar字符终止接收字节之后,发送继续进行。该成员为FALSE时,接收缓冲区接收到代表缓冲区已空的字节XonLim,并且驱动程序已经发送出恢复发送的XonChar字符后,发送可以继续进行。
·fOutX: 该成员为TRUE时,接收到XoffChar之后停止发送,接收到XonChar之后发送将重新开始。
·fInX: 该成员为TRUE时,接收缓冲区内接收到代表缓冲区满的字节XoffLim之后,XoffChar发送出去,接收缓冲区接收到代表缓冲区已空的字节XonLim之后,XonChar发送出去。
·fErrorChar: 当该成员为TRUE,并且fParity为TRUE时,就会用ErrorChar成员指定的字符来代替奇偶校验错误的接收字符。
·fNull: 指明是否丢弃接收到的NULL( ASCII 0 )字符,该成员为TRUE时,接收时去掉空(零值)字节;反之则不丢弃。
表2 RTS 流量控制
值 |
功能描述 |
RTS_CONTROL_DISABLE |
打开设备时禁止RTS线,并保持禁止状态 |
RTS_CONTROL_ENABLE |
打开设备时允许RTS线,并保持允许状态 |
DTR_CONTROL_HANDSHAKE |
允许握手。在接收缓冲区小于半满时将RTS 置为ON,在接收缓冲区超过3/4时将RTS置为OFF。如果允许握手,则不允许应用程序使用EscapeCommFunction函数调整线路 |
DTR_CONTROL_TOGGLE |
当发送的字节有效,将RTS置为 ON,发送完缓冲区的所有字节后, RTS置为OFF |
·fRtsControl: 指定 RTS 流量控制,可以取表2中的值。0值和DTR_CONTROL_HANDSHAKE等价。
·fAbortOnError: 如果发送错误,指定是否可以终止读、写操作。如果该位为TRUE,当发生错误时,驱动程序以出错状态终止所有的读写操作。只有当应用程序调用ClearCommError()函数处理后,串口才能接收随后的通信操作。
·fDummy2: 保留的位,没有使用。
·wReserved:没有使用,必须为零。
·XonLim: 指定在XOFF字符发送之前接收到缓冲区中可允许的最小字节数。
·XoffLim: 指定在XOFF字符发送之前缓冲区中可允许的最小可用字节数
·ByteSize: 指定端口当前使用的数据位数。
·Parity: 指定端口当前使用的奇偶校验方法。它的可能值如表3所示。
·StopBits: 指定串口当前使用的停止位数,可能值如表4所示。
表3 奇偶校验方法
值 |
功能描述 |
EVENPARITY |
偶校验 |
MARKPARITY |
标号校验 |
NOPARITY |
无校验 |
ODDPARITY |
奇校验 |
SPACEPARITY |
空格效益 |
表4 停止位数描述
值 |
功能描述 |
ONESTOPBIT |
1位停止位 |
ONE5STOPBITS |
1.5位停止位 |
TWOSTOPBITS |
2位停止位 |
·XonChar: 指明发送和接收的XON字符值,它表明允许继续传输。
·XoffChar: 指明发送和接收的XOFF字符值,它表示暂停数据传输。
·ErrorChar: 本字符用来代替接收到的奇偶校验发生错误的字符。
·EofChar: 用来表示数据的结束。
·EvtChar: 事件字符。当接收到此字符的时候,会产生一个事件。
·wReserved1: 保留的位,没有使用。
// 配置参数
DCB p;
memset(&p, 0, sizeof(p));
p.DCBlength = sizeof(p);
p.BaudRate = baudrate; // 波特率
p.ByteSize = databit; // 数据位
switch (parity) //校验位
{
case 0:
p.Parity = NOPARITY; //无校验
break;
case 1:
p.Parity = ODDPARITY; //奇校验
break;
case 2:
p.Parity = EVENPARITY; //偶校验
break;
case 3:
p.Parity = MARKPARITY; //标记校验
break;
}
switch (stopbit) //停止位
{
case 1:
p.StopBits = ONESTOPBIT; //1位停止位
break;
case 2:
p.StopBits = TWOSTOPBITS; //2位停止位
break;
case 3:
p.StopBits = ONE5STOPBITS; //1.5位停止位
break;
}
if (!SetCommState(hCom, &p))
{
// 设置参数失败
return false;
}
超时设置
超时结构直接影响读和写的操作行为。当事先设定的超时间隔消逝时,ReadFile() 、ReadFileEx()、 WriteFile()和 WriteFileEx()操作仍未结束,那么超时设置将无条件结束读写操作,而不管是否已读出或已写入指定数量的字符。
在读或写操作期间发生的超时将不按错误处理,即读或写操作返回指定成功的值。对于同步读或写操作,实际传输的字节数由ReadFile()和Write()函数报告。对于异步操作,则有OVERLAPPED结构来获取。
如果欲获得当前超时参数,应用程序可以调用GetCommTimeouts()函数
BOOL GetCommTimeouts
(
HANDLE hFile,
LPCOMMTIMEOUTS lpCommTimeouts
);
如果要设置或改变原来的超时参数,应用程序可以调用SetCommTimeouts()函数
BOOL SetCommTimeouts
(
HANDLE hFile,
LPCOMMTIMEOUTS lpCommTimeouts
);
typedef struct_COMMTIMEOUTS
{
DWORD ReadIntervalTimeout;
DWORD ReadTotalTimeoutMultiplier;
DWORD ReadTotalTimeoutConstant;
DWORD WriteTotalTimeoutMultiplier;
DWORD WriteTotalTimeoutConstant;
} COMMTIMEOUTS,*LPCOMMTIMEOUTS;
ReadIntervalTimeout:以ms为单位指定通信线路上两个字符到达之间的最大时间间隔。在ReadFile()操作期间,从接收到第一个字符时开始计时。如果任意两个字符到达之间的时间间隔超过这个最大值,则ReadFile()操作完成,并返回缓冲数据。如果被置为0,则表示不使用间隔超时。
·ReadTotalTimeoutMultiplier:以ms为单位指定一个系数,该系数用来计算读操作的总超时时间。
·ReadTotalTimeoutConstant:以ms为单位指定一个常数,该常数也用来计算读操作的总超时时间。
·WriteTotalTimeoutMultiplier:以ms为单位指定一个系数,该系数用来计算写操作的总超时时间。
·WriteTotalTimeoutConstant:以ms为单位指定一个常数,该常数也用来计算写操作的总超时时间。
Windows使用下面的式子计算总超时时间:
ReadTotalTimeout=( ReadTotalTimeoutMultiplier*bytes_to_read )+ ReadTotalTimeoutConstant;
WriteTotalTimeout=( WriteTotalTimeoutMultiplier*bytes_to_write )+ WriteTotalTimeoutConstant;
//超时处理,单位:毫秒
//总超时=时间系数×读或写的字符数+时间常量
COMMTIMEOUTS TimeOuts;
TimeOuts.ReadIntervalTimeout = 1000; //读间隔超时
TimeOuts.ReadTotalTimeoutMultiplier = 500; //读时间系数
TimeOuts.ReadTotalTimeoutConstant = 5000; //读时间常量
TimeOuts.WriteTotalTimeoutMultiplier = 500; // 写时间系数
TimeOuts.WriteTotalTimeoutConstant = 2000; //写时间常量
SetCommTimeouts(hCom, &TimeOuts);
读串口
程序可以使用Win32API ReadFile()函数或者ReadFileEx()函数从串口中读取数据。ReadFile()函数对同步或异步操作都支持,而ReadFileEx()只支持异步操作。这两个函数都受到函数是否异步操作、超时操作等有关参数的影响和限定。
BOOL ReadFile
(
HANDLE hFile, // 指向标识的句柄
LPVOID lpBuffer, // 指向一个缓冲区
DWORD nNumberOfBytesToRead, // 读取的字节数
LPDWORD lpNumberOfBytesRead, // 指向调用该函数读出的字节数
LPOVERLAPPED lpOverlapped // 一个OVERLAPPED的结构
);
·hFile:指向标识的句柄。对串口来说,就是由CreateFile函数返回的句柄。该句柄必须拥有GENERIC_READ的权限。
·lpBuffer:指向一个缓冲区,该缓冲区主要用来存放从串口设备中读取的数据。
·nNumberOfBytesToRead:指定要从串口设备读取的字节数。
·lpNumberOfBytesRead:指向调用该函数读出的字节数。ReadFile()在读操作前,首先将其设置为0。Windows NT/2000中当lpOverlapped没有设置时,lpNumberOfBytesRead必须设置。当lpOverlapped设置时,lpNumberOfBytesRead可以不设置。这是可以调用GetOverlappedResult()函数获取实际的读取数值。Windows 9x中这个参数一定要设置。
·lpOverlapped:是一个OVERLAPPED的结构,该结构将在后面介绍。如果hFile以FILE_FLAG_OVERLAPPED方式常见,则需要此结构;否则,不需要此结构。
需要注意的是如果该函数因为超时而返回,那么返回值是TRUE。参数lpOverlapped 在操作时应该指向一个OVERLAPPED的结构,如果该参数为NULL ,那么函数将进行同步操作,而不管句柄是否是由 FILE_FLAG_OVERLAPPED 标志建立的。当ReadFile返回FALSE时,不一定就是操作失败,线程应该调用GetLastError函数分析返回的结果。例如,在重叠操作时如果操作还未完成函数返回,那么函数就返回FALSE,而且GetLastError函数返回ERROR_IO_PENDING。
写串口操作
可以使用Win32API函数WriteFile() 或者WriteFileEx()向串口中写数据。WriteFile()函数对同步或异步操作都支持,而WriteFileEx()只支持异步操作。这两个函数都受到函数是否异步操作、超时操作等有关参数的影响和限定。
BOOL WriteFile
(
HANDLE hFile, // 指向标识的句柄
LPCVOID lpBuffer, // 指向一个缓冲区
DWORD nNumberOfBytesToWrite, // 指定要向串口设备写入的字节数
LPDWORD lpNumberOfBytesWritten, // 指向调用该函数已写入的字节数
LPOVERLAPPED lpOverlapped // 一个OVERLAPPED的结构
);
·hFile:指向标识的句柄。对串口来说,就是由CreateFile函数返回的句柄。该句柄必须拥有GENERIC_WRITE的权限。
·lpBuffer:指向一个缓冲区,该缓冲区主要用来存放待写入串口设备的数据。
·nNumberOfBytesToWrite:指定要向串口设备写入的字节数。
·lpNumberOfBytesWritten:指向调用该函数已写入的字节数。WriteFile()在写操作前,首先将其设置为0。Windows NT/2000中当lpOverlapped没有设置时,lpNumberOfBytesWritten必须设置。当lpOverlapped设置时,lpNumberOfBytesWritten可以不设置。这是可以调用GetOverlappedResult()函数获取实际的读取数值。Windows 9x中这个参数一定要设置。
·lpOverlapped:是一个OVERLAPPED的结构,该结构将在后面介绍。如果hFile以FILE_FLAG_OVERLAPPED方式常见,则需要此结构;否则,不需要此结构。
如果函数调用成功,则返回值不为零;若函数调用失败,则返回值为零。调用GetLastError()函数可以获得进一步的出错信息。
通信状态和通信错误
如果在串口通信中发生错误,如发生中断,奇偶错误等,I/O操作将会终止。如果程序要进一步执行I/O操作,必须调用ClearCommError()函数。ClearCommError()函数有两个作用:第一个作用是清除错误条件;第二个作用是确定串口通信状态。ClearCommError()函数的声明如下:
BOOL ClearCommError
(
HANDLE hFile,
LPDWORD lpErrors,
LPCOMSTAT lpStat
);
其中主要参数介绍如下:
·hFile :标识通信设备,CreateFile()函数返回该句柄。
·lpErrors:指向用一个指明错误类型的掩码填充的32位变量。该参数可以是表6中各值的组合。
·lpStat:指向一个COMSTAT结构,该结构接收设备的状态信息。如果lpStat参数不设置,则没有设备状态信息被返回。
表6 通信错误列表
值 |
描述 |
CE_BREAK |
硬件检测到一个中断条件 |
CE_FRAME |
硬件检测到一个帧出错 |
CE_IOE |
发生I/O错误 |
CE_MODE |
模式出错,或者是句柄无效 |
CE_OVERRUN |
超速错误 |
CE_RXOVER |
接收缓冲区超限,或者是输入缓冲区中没有空间,或者实在文件结束符(EOF)接收后接收到一个字符 |
CE_RXPARITY |
奇偶校验错误 |
CE_TXFULL |
发送缓冲区满 |
CE_DNS |
没有检测到并行设备 |
CE_OOP |
并行设备缺纸 |
CE_PTO |
并行设备发生超时错误 |
如果该函数调用成功,则返回值不为零;若函数调用失败,则返回值为零。调用GetLastError()函数可以获得进一步的出错信息。在同步操作时,可以调用ClearCommError()函数来确定串口的接收缓冲区处于等待状态的字节数,而后可以使用ReadFile()或者WriteFile()函数一次读写完。
COMSTAT结构存放有关通信设备的当前信息。该结构内容由ClearCommError()函数填写。COMSTAT结构声明如下:
typedef struct_COMSTAT
(
DWORD fCtsHold: 1;
DWORD fDsrHold: 1;
DWORD fRlsdHold: 1;
DWORD fXoffSent: 1;
DWORD fEof: 1;
DWORD fTxim: 1;
DWORD fReserved: 25;
DWORD cbInQue;
DWORD cbOutQue;
} COMSTAT,*LPCOMSTAT;
其中主要参数介绍如下:
·fCtsHold:指明是否等待CRS信号,如果为1,则发送等待。
·fDsrHold:指明是否等到DRS信号,如果为1,则发送等待。
·fRlsdHold:指明是否等待RLSD信号,如果为1,则发送等待。
·fXoffSent:指明收到XOFF字符后发送是否等待。如果为1,则发送等待。如果把XOFF字符发送给一系统时,该系统就把下一个字符当成XON,而不管实际字符是什么,此时发送将停止。
·fEof:EOF字符送出。
·fTxim:指明字符是否正等待被发送,如果为1,则字符正等待被发送。
·fReserved:系统保留。
·cbInQue:指明串行设备接收到的字节数。并不是指ReadFile操作要求读的字节数。
·cbOutQue:指明发送缓冲区尚未发送的字节数。如果进行不重叠写操作时值为0。
代码示例
#ifndef _WZSERIALPORT_H
#define _WZSERIALPORT_H
#include<iostream>
#include <string>
#include <vector>
using namespace std;
class WZSerialPort
{
public:
WZSerialPort();
~WZSerialPort();
// 打开串口,成功返回true,失败返回false
// portname(串口名): 在Windows下是"COM1""COM2"等,在Linux下是"/dev/ttyS1"等
// baudrate(波特率): 9600、19200、38400、43000、56000、57600、115200
// parity(校验位): 0为无校验,1为奇校验,2为偶校验,3为标记校验
// databit(数据位): 4-8,通常为8位
// stopbit(停止位): 1为1位停止位,2为2位停止位,3为1.5位停止位
// synchronizable(同步、异步): 0为异步,1为同步
//在这里已经对配置进行了初始化,你们可以自己改配置使用
bool open(const char* portname, int baudrate = 115200, char parity = 0, char databit = 8, char stopbit = 1, char synchronizeflag = 1);
//关闭串口,参数待定
void close();
//发送数据或写数据,成功返回发送数据长度,失败返回0
int send(string dat);
//接受数据或读数据,成功返回读取实际数据的长度,失败返回0
string receive();
//vector<unsigned char> revcmsg;
private:
int pHandle[16];
char synchronizeflag;
};
#endif
#include "client.h"
#include <stdio.h>
#include <string.h>
#include <WinSock2.h>
#include <windows.h>
WZSerialPort::WZSerialPort()
{
}
WZSerialPort::~WZSerialPort()
{
}
bool WZSerialPort::open(const char* portname,
int baudrate,
char parity,
char databit,
char stopbit,
char synchronizeflag)
{
this->synchronizeflag = synchronizeflag;
HANDLE hCom = NULL;
if (this->synchronizeflag)
{
//同步方式
hCom = CreateFileA(portname, //串口名
GENERIC_READ | GENERIC_WRITE, //支持读写
0, //独占方式,串口不支持共享
NULL,//安全属性指针,默认值为NULL
OPEN_EXISTING, //打开现有的串口文件
0, //0:同步方式,FILE_FLAG_OVERLAPPED:异步方式
NULL);//用于复制文件句柄,默认值为NULL,对串口而言该参数必须置为NULL
}
else
{
//异步方式
hCom = CreateFileA(portname, //串口名
GENERIC_READ | GENERIC_WRITE, //支持读写
0, //独占方式,串口不支持共享
NULL,//安全属性指针,默认值为NULL
OPEN_EXISTING, //打开现有的串口文件
FILE_FLAG_OVERLAPPED, //0:同步方式,FILE_FLAG_OVERLAPPED:异步方式
NULL);//用于复制文件句柄,默认值为NULL,对串口而言该参数必须置为NULL
}
if (hCom == (HANDLE)-1)
{
return false;
}
//配置缓冲区大小
if (!SetupComm(hCom, 1024, 1024))
{
return false;
}
// 配置参数
DCB p;
memset(&p, 0, sizeof(p));
p.DCBlength = sizeof(p);
p.BaudRate = baudrate; // 波特率
p.ByteSize = databit; // 数据位
switch (parity) //校验位
{
case 0:
p.Parity = NOPARITY; //无校验
break;
case 1:
p.Parity = ODDPARITY; //奇校验
break;
case 2:
p.Parity = EVENPARITY; //偶校验
break;
case 3:
p.Parity = MARKPARITY; //标记校验
break;
}
switch (stopbit) //停止位
{
case 1:
p.StopBits = ONESTOPBIT; //1位停止位
break;
case 2:
p.StopBits = TWOSTOPBITS; //2位停止位
break;
case 3:
p.StopBits = ONE5STOPBITS; //1.5位停止位
break;
}
if (!SetCommState(hCom, &p))
{
// 设置参数失败
return false;
}
//超时处理,单位:毫秒
//总超时=时间系数×读或写的字符数+时间常量
COMMTIMEOUTS TimeOuts;
TimeOuts.ReadIntervalTimeout = 1000; //读间隔超时
TimeOuts.ReadTotalTimeoutMultiplier = 500; //读时间系数
TimeOuts.ReadTotalTimeoutConstant = 5000; //读时间常量
TimeOuts.WriteTotalTimeoutMultiplier = 500; // 写时间系数
TimeOuts.WriteTotalTimeoutConstant = 2000; //写时间常量
SetCommTimeouts(hCom, &TimeOuts);
PurgeComm(hCom, PURGE_TXCLEAR | PURGE_RXCLEAR);//清空串口缓冲区
memcpy(pHandle, &hCom, sizeof(hCom));// 保存句柄
return true;
}
void WZSerialPort::close()
{
HANDLE hCom = *(HANDLE*)pHandle;
CloseHandle(hCom);
}
int WZSerialPort::send(string dat)
{
HANDLE hCom = *(HANDLE*)pHandle;
if (this->synchronizeflag)
{
// 同步方式
DWORD dwBytesWrite = dat.length(); //成功写入的数据字节数
BOOL bWriteStat = WriteFile(hCom, //串口句柄
(char*)dat.c_str(), //数据首地址
dwBytesWrite, //要发送的数据字节数
&dwBytesWrite, //DWORD*,用来接收返回成功发送的数据字节数
NULL); //NULL为同步发送,OVERLAPPED*为异步发送
if (!bWriteStat)
{
return 0;
}
return dwBytesWrite;
}
else
{
//异步方式
DWORD dwBytesWrite = dat.length(); //成功写入的数据字节数
DWORD dwErrorFlags; //错误标志
COMSTAT comStat; //通讯状态
OVERLAPPED m_osWrite; //异步输入输出结构体
//创建一个用于OVERLAPPED的事件处理,不会真正用到,但系统要求这么做
memset(&m_osWrite, 0, sizeof(m_osWrite));
m_osWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, L"WriteEvent");
ClearCommError(hCom, &dwErrorFlags, &comStat); //清除通讯错误,获得设备当前状态
BOOL bWriteStat = WriteFile(hCom, //串口句柄
(char*)dat.c_str(), //数据首地址
dwBytesWrite, //要发送的数据字节数
&dwBytesWrite, //DWORD*,用来接收返回成功发送的数据字节数
&m_osWrite); //NULL为同步发送,OVERLAPPED*为异步发送
if (!bWriteStat)
{
if (GetLastError() == ERROR_IO_PENDING) //如果串口正在写入
{
WaitForSingleObject(m_osWrite.hEvent, 1000); //等待写入事件1秒钟
}
else
{
ClearCommError(hCom, &dwErrorFlags, &comStat); //清除通讯错误
CloseHandle(m_osWrite.hEvent); //关闭并释放hEvent内存
return 0;
}
}
return dwBytesWrite;
}
}
string WZSerialPort::receive()
{
HANDLE hCom = *(HANDLE*)pHandle;
string rec_str = "";
char buf[1024];
if (this->synchronizeflag)
{
//同步方式
DWORD wCount = 1024; //成功读取的数据字节数
BOOL bReadStat = ReadFile(hCom, //串口句柄
buf, //数据首地址
wCount, //要读取的数据最大字节数
&wCount, //DWORD*,用来接收返回成功读取的数据字节数
NULL); //NULL为同步发送,OVERLAPPED*为异步发送
for (int i = 0; i < strlen(buf); i++)
{
if (buf[i] != -52)
{
//cout << buf[i];
rec_str += buf[i];
//revcmsg.push_back(buf[i]);
}
else
{
break;
}
}
return rec_str;
}
else
{
//异步方式
DWORD wCount = 1024; //成功读取的数据字节数
DWORD dwErrorFlags; //错误标志
COMSTAT comStat; //通讯状态
OVERLAPPED m_osRead; //异步输入输出结构体
//创建一个用于OVERLAPPED的事件处理,不会真正用到,但系统要求这么做
memset(&m_osRead, 0, sizeof(m_osRead));
m_osRead.hEvent = CreateEvent(NULL, TRUE, FALSE, L"ReadEvent");
ClearCommError(hCom, &dwErrorFlags, &comStat); //清除通讯错误,获得设备当前状态
if (!comStat.cbInQue)
return ""; //如果输入缓冲区字节数为0,则返回false
//std::cout << comStat.cbInQue << std::endl;
BOOL bReadStat = ReadFile(hCom, //串口句柄
buf, //数据首地址
wCount, //要读取的数据最大字节数
&wCount, //DWORD*,用来接收返回成功读取的数据字节数
&m_osRead); //NULL为同步发送,OVERLAPPED*为异步发送
if (!bReadStat)
{
if (GetLastError() == ERROR_IO_PENDING) //如果串口正在读取中
{
//GetOverlappedResult函数的最后一个参数设为TRUE
//函数会一直等待,直到读操作完成或由于错误而返回
GetOverlappedResult(hCom, &m_osRead, &wCount, TRUE);
}
else
{
ClearCommError(hCom, &dwErrorFlags, &comStat); //清除通讯错误
CloseHandle(m_osRead.hEvent); //关闭并释放hEvent的内存
return "";
}
}
for (int i = 0; i < strlen(buf); i++)
{
if (buf[i] != -52)
{
rec_str += buf[i];
//revcmsg.push_back(buf[i]);
}
else
{
break;
}
}
return rec_str;
}
}
// test.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include "client.h"
using namespace std;
int main()
{
std::cout << "Hello World!\n";
WZSerialPort w;
//这里是选择端口号,其他波特率信息可在头文件修改,或者在下面重新赋值。
if (w.open("COM3"))
{
cout << "打开成功" << endl;
cout << "在这里我发送:恭喜发财" << endl;
w.send("恭喜发财");
//w.close();
}
else
{
cout << "打开失败" << endl;
}
while (true)
{
//w.receive();
cout << "receive: " << w.receive() << endl;
//w.revcmsg.clear();
}
}
// 运行程序: Ctrl + F5 或调试 >“开始执行(不调试)”菜单
// 调试程序: F5 或调试 >“开始调试”菜单
// 入门使用技巧:
// 1. 使用解决方案资源管理器窗口添加/管理文件
// 2. 使用团队资源管理器窗口连接到源代码管理
// 3. 使用输出窗口查看生成输出和其他消息
// 4. 使用错误列表窗口查看错误
// 5. 转到“项目”>“添加新项”以创建新的代码文件,或转到“项目”>“添加现有项”以将现有代码文件添加到项目
// 6. 将来,若要再次打开此项目,请转到“文件”>“打开”>“项目”并选择 .sln 文件
虚拟串口工具和串口工具见:
链接:https://pan.baidu.com/s/15BfvpeWpIPRauCico3Y44Q
提取码:zd4y文章来源:https://www.toymoban.com/news/detail-428567.html
另外可以借鉴:https://blog.csdn.net/qq_41480046/article/details/82220155?spm=1001.2101.3001.6650.13&utm_medium=distribute.pc_relevant.none-task-blog-2~default~BlogCommendFromBaidu~Rate-13-82220155-blog-104156394.pc_relevant_aa2&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2~default~BlogCommendFromBaidu~Rate-13-82220155-blog-104156394.pc_relevant_aa2&utm_relevant_index=21文章来源地址https://www.toymoban.com/news/detail-428567.html
到了这里,关于串口通讯理解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!