串口通讯理解

article/2025/9/11 9:14:44

机器的通信方式有两种,分别是并行通信与串行通信

并行通信:并行通信是指多比特数据同时通过并行线进行传送,这样数据传送速度大大提高,但并行传送的线路长度受到限制,因为长度增加,干扰就会增加,数据也就容易出错。

串行通信:串行通信是指使用一条数据线,将数据一位一位地依次传输,每一位数据占据一个固定的时间长度。其只需要少数几条线就可以在系统间交换信息,特别适用于计算机与计算机、计算机与外设之间的远距离通信。

异步串行通信:异步串行通信是指通信双方以一个字符(包括特定附加位)作为数据传输单位且发送方传送字符的间隔时间不一定,具有不规则数据段传送特性的串行数据传输。

同步串行通信:同步串行通信是指在约定的通信速率下(即相同波特率),发送端和接收端的时钟信号频率和相位始终保持一致(同步),这就保证了通信双方在发送和接收数据时具有完全一致的定时关系。

比特率(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,//安全属性指针,默认值为NULLOPEN_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();//发送数据或写数据,成功返回发送数据长度,失败返回0int send(string dat);//接受数据或读数据,成功返回读取实际数据的长度,失败返回0string 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,//安全属性指针,默认值为NULLOPEN_EXISTING, //打开现有的串口文件0, //0:同步方式,FILE_FLAG_OVERLAPPED:异步方式NULL);//用于复制文件句柄,默认值为NULL,对串口而言该参数必须置为NULL}else{//异步方式hCom = CreateFileA(portname, //串口名GENERIC_READ | GENERIC_WRITE, //支持读写0, //独占方式,串口不支持共享NULL,//安全属性指针,默认值为NULLOPEN_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://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


http://chatgpt.dhexx.cn/article/FrlUSulX.shtml

相关文章

Linux-C语言-利用有名管道简单实现两个进程间的全双工通信

有名管道特点: 1.有名管道是对无名管道的改进&#xff0c;它可以使互不相关的两个进程互相通信,并且在文件系统中可见&#xff0c;可以通过文件名来找到。 2.半双工的通信方式,进程通过文件IO来操作有名管道。 3.有名管道遵循先进先出原则,不支持lseek()。 2.有名管道的创建 配…

E70_433半双工无线模组,伪全双工方案

E70_433半双工无线模组&#xff0c;伪全双工方案 前言架构设计外侧输入模块外侧输出模块内侧发帧模块内侧接受模块 实现代码通用宏定义代码解析 通用工具函数代码解析 串口输入输出函数代码解析 核心业务代码发送模块代码解析 接收模块代码解析代码流程 结语附带 前言 要搞一个…

计算机双工模式,100M 全双工、100M 半双工、10M全双工几种模式分别测试

1. 首先核实是否有多台电脑同时上网&#xff0c;或是当前电脑有没有在进行P2P下载或在线视频之类比较占用网络带宽的操作&#xff0c;此操作可能导致宽带数据量过大影响稳定性。若存在此情况&#xff0c;请关闭些软件断开些联网设备&#xff0c;减少带宽使用测试。 2. 若有其他…

Linux 查看网卡全双工 还是半双工 以及设置网卡为半双工

分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴! 请问各位兄弟,在Linux中怎么看网卡是全双工工作还是半双工工作?mii-tool Linux:~ # mii…

路由之静态路由配置

一、路由概念 二、路由表的形成 三、路由协议 静态路由 路由器之间的连接需要配置静态路由 首先创建一个局域网&#xff0c;给pc0和pc1分别配置ip地址。 创建三个不同网段的局域网分别给电脑配置ip地址&#xff0c;将三个连接到一个路由上。为路由器与局域网相连的端口&#…

路由器静态路由配置实验

简单配置: 1.连接设备 2.配置PC和服务器的IP地址和网关 3.在路由器配置接口的IP地址(作为网关) 4.实现PC之间的通信 5.分别命名交换机和路由器,并设置时间 6.实现PC1可以telnet和ssh管理SW2和R1 7.配置静态路由实现全网互通 8.开启web服务器,在4台PC上测试是否能访问 实验…

静态路由配置实验

题目&#xff1a; 目录 1、首先进行基础配置 2、创建环回接口并配置IP地址 3、配置静态路由 对r1&#xff0c;要配置除自身连接的3个网段以外的5个网段 对r2&#xff0c;要配置除自身连接的3个网段以外的5个网段,其中3.3.3.0/24网段已配过&#xff0c;则配剩余4个网段 对r…

华为简单静态路由配置

今天通过学习路由交换的知识&#xff0c;学会了配置简单的静态路由&#xff0c;所以我这里介绍一个简单的实 验&#xff0c;在开始之前&#xff0c;我们要先了解一下什么是路由&#xff1f; 路由 路由是指导报文转发的路径信息&#xff0c;通过路由可以确认转发IP报文的路径。…

vue 3.0 静态路由配置

今天研究了一下vue3.0的静态路由配置&#xff0c;分享一下。 首先我们现在项目中建立router文件夹&#xff0c;如下图所示&#xff1a; 404文件夹存放404页面&#xff0c;components文件夹存放首页界面&#xff0c;config为设置文件夹&#xff0c;diz存放具体的业务逻辑和界面…

计算机配置静态路由目标,静态路由的配置命令

用户需求: 某学校网络拓扑图如图所示,要求配置静态路由,实现计算机PC1、PC2和PC3互通。 直连路由直连路由出现在路由表的条件 (1)接口为“up/up”状态。 (2)接口已经完成了IP地址的配置。直连路由的检查 静态路由是指由网络管理员手工配置的路由信息,用于定义去往目的网络的…

路由基础(简单的静态路由配置)

目录 一.路由基础 1.路由信息获取方式 2.路由加表前的比较-优先级&#xff1b;度量值 3.路由转发的选择-最长掩码 4.静态路由应用场景及配置 1.路由信息获取方式 路由器依据路由表进行转发&#xff0c;要实现转发功能&#xff0c;路由器需要发现路由&#xff0c;三种常见…

四种静态路由配置方法

拓扑图如下&#xff1a; 接口IP地址如拓扑图所示&#xff0c;配置过程省略&#xff0c;ar1和ar3都开启了dhcp。过程省略。 ar1的路由表&#xff1a; 此时只有直连的1.0和2.0网段的IP。没有3.0网段的。 那怎么有3.0网段的呢&#xff1f;接下来介绍四种方法。 方法一&#xff1a…

Windows系统的静态路由配置

Windows电脑静态路由的配置&#xff0c;使电脑同时使用两个不同网段的IP 操作方法&#xff1a; 1.开始菜单&#xff0c;输入cmd&#xff0c;右击cmd.exe&#xff0c;选择以管理员身份运行 2.查看当前路由的配置 命令格式&#xff1a; ##查看静态路由 route print 输出结果…

华为静态路由配置

静态路由 静态路由(Static Router)是由管理员通过手动配置的方式创建的路由&#xff0c;可以让路由器便捷的获知到达目的网络的路由。在静态路由基础上也可使用负载均衡、路由备份等技术。 本文分别对静态路由、负载均衡、路由备份进行实验配置及配置完之后如何验证。 网络拓…

ENSP静态路由配置

拓扑图 实现结果&#xff1a; 1.pc1访问pc345走上面R1,R2&#xff0c;R2实现备份21.1.1.0/24&#xff0c;实现备份pc3456访问pc12走下面的R3,R4并实现等价路由&#xff0c;尽量减少路由条目&#xff0c;全网可达 2.R7代表运行商&#xff0c;所有pc均可访问 第一步&#xff1a…

静态路由配置全面详解,静态路由快速入门指南

操作步骤 配置各路由器接口的IP地址 以配置Router_1的GE0/0/0接口为例&#xff0c;其他接口配置与之类似&#xff0c;不再重复介绍。 a、如图2所示&#xff0c;依次选择“广域网互联 > 以太接口”&#xff0c;进入“以太接口”界面。 图2 以太接口界面 b、在“以太接口列…

12 路由器静态路由配置

借鉴网址&#xff1a;(28条消息) Packet Tracer 思科模拟器入门教程 之十一 路由器静态路由配置_柚子君.的博客-CSDN博客_思科模拟器新手详细教程 姓 名 彭彭头 实验日期 2022.04.26 学 号 实验序号 12 实验名称 路由器静态路由配置 一、实验目的及要求 掌握静态路由…

静态路由配置案例

静态路由配置案例 配置静态路由原理命令&#xff1a;案例&#xff1a;最后结果&#xff1a; 配置静态路由原理命令&#xff1a; [Huawei]ip route-static 来源ip 子网掩码 去向ip [Huawei]ip route-static 192.168.20.1 255.255.255.0 192.168.1.2 案例&#xff1a; pc1,pc2…

ipv6 静态路由配置

ipv6 静态路由配置 R1 配置 <Huawei>system-view Enter system view, return user view with CtrlZ. [Huawei]sysname R1 [R1]ipv6 # 全局使能 ipv6 [R1]interface GigabitEthernet 0/0/0 [R1-GigabitEthernet0/0/0]ipv6 enable [R1-GigabitEthernet0/0/0]ipv6 add…

路由器静态路由配置

实验名称 路由器静态路由配置 一、实验目标 1.掌握静态路由的配置方法和技巧&#xff1b; 2.掌握通过静态路由方式实现网络的连通性&#xff1b; 3.熟悉广域网线缆的链接方式&#xff1b; 二、实验背景 学校有新旧两个校区&#xff0c;每个校区是一个独立的局域网&…