一、自定义通信协议
1、协议介绍
什么是协议??
协议,网络协议的简称,网络协议是通信计算机双方必须共同遵从的一组约定。如怎么样建立连接、怎么样互相识别等。只有遵守这个约定,计算机之间才能相互通信交流。它的三要素是:语法、语义、时序。为了使数据在网络上从源到达目的,网络通信的参与方必须遵循相同的规则,这套规则称为协议(protocol),它最终体现为在网络上传输的数据包的格式。
串口,STM32 跟电脑通信,双方的波特率必须是一样的才能够通信。----这也是一种协议。
2、网络协议
网络七层协议--TCP--
3、自定义的通信协议
自己定义的一种协议。随便定义 ----即使是自定义的协议,一般也遵循一般协议的格式。
协议有什么格式?
通信中的数据往往以数据包的形式进行传送,我们把这样的一个数据包称为一帧数据。类似于网络通信中的TCPIP协议一样,比较靠谱的通信协议往往都是含有一下几个组成部分:帧头、地址信息、数据类型、数据长度、数据块、校验码、帧尾。
RS485 发送hello . 如果以数据帧格式发送hello. 怎么样写???
帧头: 一帧数据开始,可以使用多个字节。 假如以一个字节:0XFF ----自己定义。
地址信息:跟哪个设备进行通信(类型于IIC器件地址) ---0X01 0X02 芯片ID
数据类型:如 0x01 –代表发的是字符。 0x02 –代表16进制 ---- 类似命令
数据长度: --- 如果发送的数据大于256个字节,至少用2个字节表示 0x00 0x05
数据块 :数据,发送的数据
校验码 :求和,CRC16, CRC8,
帧尾 : 一帧数据结束,可以使用多个字节。 假如以一个字节:0XFE ----自己定义。
二、MODBUS通信协议
参考资料
1、概述
串行链路上的 MODBUS
MODBUS 串行链路取决于 TIA/EIA 标准:232-F 和 485-A。
MODBUS 是 OSI 模型第 7 层上的应用层报文传输协议,它在连接至不同类型总线或网络的设备
之间提供客户机/服务器通信。
MODBUS 是一个请求/应答协议,并且提供功能码规定的服务。MODBUS 功能码是 MODBUS
请求/应答 PDU 的元素。
主机发起问答数据包,从机回复应答数据包,从机不能主动发起通信。
2、MODBUS帧结构
协议描述
MODBUS数据帧包含了:地址域、功能码、数据域、校验 四部分 。
功能码向服务器指示将执行哪种操作。
3、MODBUS数据模型
把数据模型看成功能码类型:Modbus一共有四种模式:
离散量输入:单个比特(一个位) ,只读。MCU用在读一个位的状态。如读取一个IO口的电平状态。
理解 STM32 GPIOX->IDR & 1<<0 ; 即如果我们想要读取STM32 一个IO口的电平状态,那么 我们就可以把这个功能码设置为离散量输入 。在智能楼宇节点板上哪些功能可以使用离散量输入??(读取按键状态、读取LED状态,读取继电器开关等待)。
线圈:单个比特(一个位) ,可读写。MCU用在读或写一个位的状态。如读取或者写一个IO口的电平状态。GPIOX->ODR |= 1<<0 ; GPIOX->ODR |= 1<<1;即如果我们想要写STM32 一个IO口的电平状态,那么 我们就可以把这个功能码设置为线圈 。在智能楼宇节点板上哪些功能可以使用线圈??(开或者关LED,开或开关继电器。)
输入寄存器:16比特字(16位),只读。MCU用在读十六(多)个位的状态。如读取十六个IO口的电平状态。 u16 temp = GPIOX->IDR ; 在智能楼宇节点板上哪些功能可以使用输入寄存器??(读取温湿度—
temp=get();读取光照强度、MP2.5 、空气质量、)。
保持寄存器:16比特字(16位),可读写。MCU用在读十六(多)个位的状态。如读取十六个IO口的电平状态。 u16 temp = GPIOX->IDR ;(OLED—写)
4、MODBUS事务处理的定义
5、MODBUS功能码
有三类 MODBUS 功能码:有效的码字范围是十进制 1-255(128-255 为
异常响应保留)
第一类:公共功能码
第二类:用户定义功能码
第三类:保留功能码
6、功能码定义
读输入离散量 – 0x02
功能 :读一个位的状态。
PDU: 协议数据单元,包含MODBUS数据帧的功能码和数据
功能码:0x02 占一字节。
起始地址:0x0000至0Xffff,占两字节,如写 0x1234
输入数量:需要读取的数量,最大可以读取2000个位。如写0x08 (相当于读8个位的状态。这8 个位可以是不连续)。
功能码:0x82 占一字节。
字节数:需要多少个字节来存放你读取的状态。如读取8个位,则字节数应为0x01 。如读取9个位,则字节数应为0x02 à算法 N=输出数量/8,如果余数不等于 0,那么N = N+1
输入状态 :u8 temp; temp = read() ; 如 0xab 表示读取的8个位的状态
将离散量输入状态 204-197 表示为十六进制字节值 AC,或二进制 1010 1100。输入 204 是这个字节的 MSB,输入 197 是这个字节的 LSB。
将离散量输入状态 218-213 表示为十六进制字节值 35,或二进制 0011 0101。输入 218 位于左侧第 3 比特,输入 213 是 LSB。
注:用零填充 2 个剩余比特(一直到高位端)。
读线圈 –0x01
读输入寄存器--0x04
7、MODBUS数据链路层
主节点以两种模式对子节点发出 Modbus 请求:
1. 在单播模式,主节点以特定地址访问某个子节点,子节点接到并处理完请求后,子节点向主节点返回一个报文(一个 '应答')。在这种模式, 一个 Modbus 事务处理包含 2 个报文: 一个来自主节点的请求, 一个来自子节点的应答。每个子节点必须有唯一的地址 (1 到 247),这样才能区别于其它节点被独立的寻址。
2. 在广播模式,主节点向所有的子节点发送请求。对于主节点广播的请求没有应答返回。 广播请求一般用于写命令。所有设备必须接受广播模式的写功能。地址 0 是专门用于表示广播数据的。
8、MODBUS地址规则
地址 0 保留为广播地址。 所有的子节点必须识别广播地址。
Modbus 主节点没有地址, 只有子节点必须有一个地址。 该地址必须在 Modbus 串行总线上唯一。
9、MODBUS帧描述
10、MODBUS两种串行传输模式
有两种串行传输模式被定义: RTU 模式 和 ASCII 模式。
它定义了报文域的位内容在线路上串行的传送。它确定了信息如何打包为报文和解码。
Modbus 串行链路上所有设备的传输模式 (和串行口参数) 必须相同。
尽管在特定的领域 ASCII 模式是要求的,但达到 Modbus 设备之间的互操作性只有每个设备都有相同的模式: 所有设备必须必须实现 RTU 模式。 ASCII 传输模式是选项。
设备应该由用户设成期望的模式, RTU 或 ASCII。 默认设置必须为 RTU 模式。
- RTU ---常用的。
Modbus RTU 帧最大为 256 字节。
没有帧头,也没有帧尾。没有帧尾,怎么知道一帧数据结束????
之前485怎么判断发送一次数据结束??使用定显示器。发送两次数据之间时间间隔不超过10ms.
由图可知,MODBUS RTU模式两帧数据之前使用 3.5个字符的时间来隔开。那么3.5个字符的时间为多长??
由波特率决定。如波特率为9600时。发送一个字符的时间 1/9600(s)=0.104ms. 3.5个字符时间 3.5/9600(s) =0.365ms .
可以使用定时器,定时时间可以设置为1ms .
- CRC校验
MODbus RTU模式使用的是CRC16校验。
CRC 包含由两个 8 位字节组成的一个 16 位值àCRC校验之后会得到一个16位的值。
CRC校验:
生成 CRC 的过程为:
1. 将一个 16 位寄存器装入十六进制 FFFF (全 1). 将之称作 CRC 寄存器.
2. 将报文的第一个 8 位字节与 16 位 CRC 寄存器的低字节异或,结果置于 CRC 寄存器.
3. 将 CRC 寄存器右移 1 位 (向 LSB 方向), MSB 充零. 提取并检测 LSB.
4. (如果 LSB 为 0): 重复步骤 3 (另一次移位).
(如果 LSB 为 1): 对 CRC 寄存器异或多项式值 0xA001 (1010 0000 0000 0001).
5. 重复步骤 3 和 4,直到完成 8 次移位。当做完此操作后,将完成对 8 位字节的完整操作。
6. 对报文中的下一个字节重复步骤 2 到 5,继续此操作直至所有报文被处理完毕。
7. CRC 寄存器中的最终内容为 CRC 值.
8. 当放置 CRC 值于报文时,如下面描述的那样,高低字节必须交换。
11、MODBUS主机实现
按MODBUS协议的数据帧的格式来发送数据 :
主机发送数据包括:
- 地址域 :自行定义 (0x01)
- 功能码:以0x01功能码为例
功能码发送过程: 功能码(1字节) + 起始地址(2字节) + 线圈数量(2字节) à先发高字节
如: 0x010x00 0x00 0x00 0x05
- 数据 à
- CRC
- 响应PDU à按照它的格式
void ModbusSend_Master(u8 slaveaddr ,u8 cmd,u16 startaddr,u16 dataornum) { u8 sendbuf [20]={0}; u16 crc; sendbuf[0]= slaveaddr; sendbuf[1]= cmd; sendbuf[2]=(u8) startaddr>>8; //地址高8位 sendbuf[3]=(u8) startaddr & 0X00FF; //地址低8位 sendbuf[4]=(u8) dataornum >>8; //读线圈数量高8位 sendbuf[5]=(u8) dataornum & 0X00FF; //读线圈数量低8位 crc = CRC_Compute(sendbuf,6) ; //得到CRC sendbuf[6]= (u8) crc >>8 ; //得到CRC 高8位 sendbuf[7]= (u8) crc&0x00ff ; //得到CRC 低8位 Modbus_SendData(sendbuf,8); } ModbusSend_Master(0x01,0x01,0x0000,0x0005);
- 写一个主机接收函数,参考从机接收。