Windows平台下C++串口通信


通信基础

通信的本质:发送端与接收端进行数据交互和信息传递。

  • 通信方式划分
    • 串行通信
      同一时间只能传输或发送1bit的数据信息,因此只用一根信号线即可。数据一位一位串起来,逐个传输,数据按位顺序传输。优点是占用引脚资源少,缺点是速度相对较慢。
    • 并行通信
      同一个时间可以接受或发送多个bit位的数据信息,因此需要多根信号线。使用多根线同时传输一个字的多个位,如八根线一次性传输8个位。优点是数据传输速度快,缺点是占用引脚资源多。
  • 按时钟信号划分
    • 同步通信
      通信双方根据同步信号通信,比如双方有一个共同的时钟信号(SPI全双工、I2C半双工),发送数据的时候会出现阻塞状态,需要等待数据传输完毕,程序才会执行下一行语句。
    • 异步通信
      通信双方有自己独立的系统时钟,双方或者多方需要确定好通信速度,异步通信不需要同步信号,但是并不是通信过程不同步(UART),发送数据的时候没有阻塞状态,执行发送数据语句后直接执行下一行语句。
  • 通信方向划分
    • 单工
      单方向传输信息,要么接收信息,要么发送信息,只能做接收设备或者发送设备,例如大喇叭、遥控器、收音机等。
      一根信号线只能单向发送或接收。
    • 半双工
      可以发送和接收,但是通信双方不能同时发送、接收,例如对讲机。
      一根信号线既可以发送数据也可以接收数据,但是两者不能同时进行
    • 全双工
      可以在同一时刻既接收又发送数据,例如电话、智能手机。
      两根信号线,一根发送数据,一根接收数据,实现同时接收发送数据,速度快。

串口通信

串口通信(Serial Communication)一般采用串行通信+异步通信。

串口

串行接口(Serial Interface)即COM口(DB9接口)。

UART

UART是通用异步收发器(Universal Asynchronous Receiver/Transmitter)的简称,它是设备实现串口通信的核心部件。
在这里插入图片描述

  • RS232
    RS (Recommeded Standard) 代表推荐标准,232是标识号。RS232标准主要规定了指电平定义、信号引脚功能的定义及应答协议等。UART-RS232就是采用232电平的全双工UART接口。

  • RS485
    UART-RS485属于总线类型,最小只需要两根数据线(差分信号:A线与B线),差分线之间的压差反映传输信号值,因此可以抵制共模干扰,抗干扰能力强,理论传输距离可以达到1200米。
    单组差分信号线的RS485设备只能半双工通信,也就是同一时刻要么接收数据,要么发送数据(设备的输入/输出模式的切换有专门的引脚控制)。
    由于RS485没有总线仲裁机制,只能有上层软件来保证通信可靠性。
    因此,用户编程时必须遵循二个原则:
    (a)仅在要发送数据时才强占总线(将设备切换为发送模式),一旦发送完数据则立即释放总线(切换为输入模式);
    (b)强占总线前最好先检测下总线是否空闲(比如,用低电平或高电平保持一定的时间作为空闲状态)。
    RS485与PC通信时,通常将RS485转换为RS232。

  • RS422
    RS422和RS485都是串行总线类型,电气特性完全一样。唯一的区别是RS422有两对差分线,可以实现全双工通信;而RS485只有一对差分线,只能半双工通信。

RS-485支持的协议

  • Modbus RTU:Modbus是一种广泛使用的工业通信协议,Modbus协议简单、易于实现,多用于工业设备间进行数据采集和控制。
  • Profibus-DP:是一种用于工业自动化的总线系统,比较适合高速、实时控制的应用。支持多种设备连接,常用在复杂的工业自动化环境。
  • CANopen:是一种基于CAN(Controller Area Network)总线的应用层协议,某些情况下也可以通过RS485实现。支持分布式实时控制应用,汽车领域使用较多。
  • DeviceNet:是一种用在自动化领域的现场总线标准,Allen-Bradley公司开发,在美国市场占用率较高。DeviceNet协议支持多种设备类型,包括传感器、执行器和控制器。
  • Ethernet/IP:工业以太网协议,名称中的IP是Industrial Protocol(工业协议)的简称,不是网际协议。提供了高速的数据传输能力,并且支持多种工业设备之间的通信。
  • DNP3:专为电力系统设计的通信协议,DNP3协议支持多种通信介质,包括RS485,适用于安全和高可靠性的电力自动化和远程监控系统。
  • DLT645:DLT645协议主要用于电能表的数据采集,也是电表采集最常用最简单的通信协议,可参考DLT645协议-光伏电表数据采集
  • CC-Link:一种开放的现场总线网络协议,支持多种工业设备,可以使用RS485接口。

字符帧

字符帧由四个部分构成,分别是起始位数据位校验位以及停止位

  • 起始位占1位,为逻辑0。
  • 数据位占5 ~ 8位,可配置。
  • 校验位占1位,可配置为奇校验(odd)、偶校验(even)、0 校验(space)、1 校验(mark) 、无校验(noparity)、和校验、异或校验、CRC校验等。
    • 配置为无校验时字符帧不包含校验位;
    • 配置为奇校验时,数据位中逻辑1的个数为奇数时,校验位的值为逻辑0,否则为逻辑1;
    • 配置为偶校验时,数据位中逻辑1的个数为偶数时,校验位的值为逻辑0,否则为逻辑1。
  • 停止位占1/1.5/2位,可配置,停止位的值为逻辑1。

常用的字符帧格式如下,1位起始位、8位数据位、1位校验位、1位停止位。

起始位数据位校验位停止位
0xxxxxxxxx1

波特率

字符帧是按位依次传输,波特率即传输字符帧时的位速率,单位为bit/s。串口最常用的是异步通讯,没有时钟信号同步数据,所以通讯双方需要约定好数据的传输速率。通信双方要使用相同的波特率,常用的波特率如9600、115200。

Windows API串口通信

  • CreateFile - 打开串口;
  • SetupComm - 初始化一个指定的通信设备的通信参数
  • ReadFile - 读数据;
  • WriteFile - 写数据;
  • CloseHandle - 关闭串口;
  • GetCommState - 取得串口当前状态;
  • BuildCommDCB;
  • SetCommState - 设置串口状态;
  • SetCommTimeouts 。
  • ClearCommError - 清除串口错误或者读取串口现在的状态;
  • PurgeComm - 清除串口缓冲区 ;
  • SetCommMask - 设置串口通信事件;
  • WaitCommEvent - 用来判断用SetCommMask()函数设置的串口通信事件是否已发生;
  • WaitForSingleObject;
  • GetOverlappedResult;
  • GetCommModemStatus;
  • EscapeCommFunction;

CreateFile

打开串口。
文件打开成功,串口即可以使用,该函数返回串口的句柄,使用该句柄对串口操作。

//原型定义在fileapi.h
HANDLE CreateFile(	
	LPCTSTR lpFileName,  //要打开的文件名称。对串口通信来说就是COM1或COM2
	DWORD dwDesiredAccess, //读写模式设置
	DWORD dwShareMode,  //串口共享模式。此处不允许其他应用程序共享,应为0。
	LPSECURITY_ATTRIBUTES lpSecurityAttributes, //串口的安全属性,应为0,表示该串口不可被子程序继承。
	DWORD dwCreationDistribution, //创建文件的性质,此处为OPEN_EXISTING
	DWORD dwFlagsAndAttributes, //属性及相关标志,这里使用异步方式应该用FILE_FLAG_OVERLAPPED。
	HANDLE hTemplateFile //0。
); 

std::string comPort = "COM5";
HANDLE mComHandle = CreateFile(comPort.c_str(), GENERIC_WRITE | GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);

SetupComm

初始化一个指定的通信设备的通信参数。
返回非零表示成功。零表示失败。 要获得更多错误信息,调用GetLastError函数

BOOL SetupComm(
	HANDLE hFile,	//串口句柄, CreateFile函数返回此句柄。
	DWORD dwInQueue, //指定推荐的大小,以字节为单位,对设备的内部输入缓冲区。
	DWORD dwOutQueue //指定推荐的大小,以字节为单位,对设备的内部输出缓冲区。
);

ReadFile

读数据

//原型定义在fileapi.h
BOOL ReadFile(
	HANDLE hFile, 	// 串口句柄
	LPVOID lpBuffer, //存储被读出数据的首地址,输入缓冲区地址 
	DWORD nNumberOfBytesToRead, //准备读出的字节个数
	lpNumberOfBytesRead, //实际读出的字节数的变量指针 
	lpOverlapped  //异步I/O结构
); 

ReadFile(mComHandle, oBuffer, OBUFFER_LEN, &rCount, NULL);

WriteFile

写数据

//原型定义在fileapi.h
BOOL WriteFile(
	HANDLE hFile, 	//串口句柄
	LPCVOID lpBuffer, 	//待写入数据的首地址
	DWORD nNumberOfBytesToWrite, //待写入数据的字节数长度
	LPDWORD lpNumberOfBytesWritten, //函数返回的实际写入串口的数据个数的地址,利用此变量可判断实际写入的字节数和准备写入的字节数是否相同。
	LPOVERLAPPED lpOverlapped //重叠I/O结构的指针
); 

unsigned long wCount = 0;
WriteFile(mComHandle, iBuffer, IBUFFER_LEN, &wCount, NULL);

CloseHandle

关闭串口

BOOL CloseHandle(HANDLE hObjedt);

if(mComHandle != INVALID_HANDLE_VALUE) {
	CloseHandle(mComHandle);
	mComHandle = INVALID_HANDLE_VALUE;
}

GetCommState

取得串口当前状态。

BOOL GetCommState(
	HANDLE hFile, 
	LPDCB lpDCB
); 

DCB dcb;
GetCommState(mComHandle, &dcb);

DCB(Device Control Block)设备控制块结构,LPDCB即设备控制块结构地址。
此结构中含有和设备相关的参数。此处是与串口相关的参数。由于参数非常多,当需要设置串口参数时,通常是先取得串口的参数结构,修改部分参数后再将参数结构写入。仅介绍少数的几个常用的参数:

  • DWORD BaudRate:串口波特率
  • DWORD fParity:为1的话激活奇偶校验检查
  • DWORD Parity:校验方式,值0~4分别对应无校验、奇校验、偶校验、校验置位、校验清零
  • DWORD ByteSize:一个字节的数据位个数,范围是5~8
  • DWORD StopBits:停止位个数,0~2分别对应1位、1.5位、2位停止位

BuildCommDCB

BuildCommDCB(
    LPCTSTR lpDef,
    LPDCB lpDCB
);

DCB dcb;
GetCommState(mComHandle, &dcb);
std::string dcbStr = "baud=9600 parity=N data=8 stop=1 to=off dtr=off rts=off";
BuildCommDCB(dcbStr.c_str(), &dcb);
SetCommState(mComHandle, &dcb);

SetCommState

BOOL SetCommState(
	HANDLE hFile, 
	LPDCB lpDCB
); 

Status = GetCommState(mComHandle, &dcbSerialParams);//取得当前串口状态
if (Status == FALSE)
	return FALSE;
dcbSerialParams.BaudRate = rate;//更波特率 
Status = SetCommState(mComHandle, &dcbSerialParams);//将更改后的参数写入串口

SetCommTimeouts

typedef struct _COMMTIMEOUTS {
     DWORD ReadIntervalTimeout;          /* Maximum time between read chars. */
     DWORD ReadTotalTimeoutMultiplier;   /* Multiplier of characters.        */
     DWORD ReadTotalTimeoutConstant;     /* Constant in milliseconds.        */
     DWORD WriteTotalTimeoutMultiplier;  /* Multiplier of characters.        */
     DWORD WriteTotalTimeoutConstant;    /* Constant in milliseconds.        */ 
} COMMTIMEOUTS,*LPCOMMTIMEOUTS;

COMMTIMEOUTS timeouts;
timeouts.ReadIntervalTimeout = MAXDWORD;
timeouts.ReadTotalTimeoutMultiplier = 0;
timeouts.ReadTotalTimeoutConstant = 0;
timeouts.WriteTotalTimeoutMultiplier = 500;
timeouts.WriteTotalTimeoutConstant = 100;
SetCommTimeouts(mComHandle, &timeouts);

ClearCommError

清除串口错误或者读取串口现在的状态

BOOL ClearCommError(
	HANDLE hFile, 
	LPDWORD lpErrors,  //错误标志数值的地址
	LPCOMSTAT lpStat //通信端口状态的地址
); 

COMSTAT comStat;
unsigned long errFlags = 0;
ClearCommError(mComHandle, &errFlags, &comStat);

errFlags错误常数:
1-CE_BREAK:检测到中断信号。意思是说检测到某个字节数据缺少合法的停止位。
2-CE_FRAME:硬件检测到帧错误。
3-CE_IOE:通信设备发生输入/输出错误。
4-CE_MODE:设置模式错误,或是hFile值错误。
5-CE_OVERRUN:溢出错误,缓冲区容量不足,数据将丢失。
6-CE_RXOVER:溢出错误。
7-CE_RXPARITY:硬件检查到校验位错误。
8-CE_TXFULL:发送缓冲区已满。

typedef struct _COMSTAT{


DWORD cbInQue; //输入缓冲区中的字节数
DWORD cbOutQue;//输出缓冲区中的字节数
}COMSTAT,*LPCOMSTAT;
该结构中对我们很重要的只有上面两个参数。

PurgeComm

清除串口缓冲区

BOOL PurgeComm(
	HANDLE hFile, 
	DWORD dwFlags 
); 

PurgeComm(mComHandle, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR);

dwFlags:指定串口执行的动作,由以下参数组成:

  • PURGE_TXABORT:停止目前所有的传输工作立即返回不管是否完成传输动作。
  • PURGE_RXABORT:停止目前所有的读取工作立即返回不管是否完成读取动作。
  • PURGE_TXCLEAR:清除发送缓冲区的所有数据。
  • PURGE_RXCLEAR:清除接收缓冲区的所有数据。

SetCommMask

设置串口通信事件

WaitCommEvent

用来判断用SetCommMask()函数设置的串口通信事件是否已发生

WaitForSingleObject

等待一个内核对象变为已通知状态

DWORD WaitForSingleObject(
	HANDLE hObject, 	//指明一个内核对象的句柄
	DWORD dwMilliseconds	//等待时间
); 

该函数需要传递一个内核对象句柄,该句柄标识一个内核对象,如果该内核对象处于未通知状态,则该函数导致线程进入阻塞状态;如果该内核对象处于已通知状态,则该函数立即返回WAIT_OBJECT_0。第二个参数指明了需要等待的时间(毫秒),可以传递INFINITE指明要无限期等待下去,如果第二个参数为0,那么函数就测试同步对象的状态并立即返回。如果等待超时,该函数返回WAIT_TIMEOUT。如果该函数失败,返回WAIT_FAILED。

DWORD dw = WaitForSingleObject(hProcess, 5000); //等待一个进程结束
switch (dw)
{
case WAIT_OBJECT_0:
	// hProcess所代表的进程在5秒内结束
	break;

case WAIT_TIMEOUT:
	// 等待时间超过5秒
	break;

case WAIT_FAILED:
	// 函数调用失败,比如传递了一个无效的句柄
	break;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值