文章目录
通信基础
通信的本质:发送端与接收端进行数据交互和信息传递。
- 按通信方式划分
- 串行通信
同一时间只能传输或发送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位停止位。
起始位 | 数据位 | 校验位 | 停止位 |
---|---|---|---|
0 | xxxxxxxx | x | 1 |
波特率
字符帧是按位依次传输,波特率即传输字符帧时的位速率,单位为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;
}