我是靠谱客的博主 壮观薯片,最近开发中收集的这篇文章主要介绍RS485通讯---Modbus数据链路层与应用层(二)前言,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

前言

RS485通讯–Modbus物理层:https://blog.csdn.net/qq_36883460/article/details/105630712

Modbus RTU通讯协议中OSI模型,数据链路层和应用层是通讯关键部分,下面是对Modbus RTU的介绍。
在这里插入图片描述
总所周知,RS485只是一种远距离传输手段,最根本的也就是串口消息发送,也就是串口TLL电平转出RS485特定电平而已。

一、连接通讯材料

如果想实践一下这个Modbus RTU通讯协议,老衲有几个方案。

A方案:
一路USART/UART串口通讯(单片机)
一个Modbus主机仿真软件
一个USB转串口
一台电脑

这个比较好对于学习很有帮助,而且比较经济实惠,还可以学习编写底层代码实现。

B方案:
两路USART/UART串口通讯(单片机)

自己发出去自己收回来,有点像电脑本地网络回环测试,这个老衲突发奇想应该行不通。。。

C方案:
一个主机仿真软件,比如 Modbus Poll
一个从机仿真软件,比如 Modbus Slave

不过是基于TCP本地回环网络进行测试,也就是(127.0.0.1:端口号)自己发送自己接收,这种办法最省钱上网找两软件,适合玩玩仿真。

D方案:
一路USART/UART串口通讯(单片机)
一个USART/UART转485电平电路
一个RS485转USB
一个Modbus主机仿真软件
一台电脑
一条自制简易STP双绞线(或者自己买条双绞线)

自制简易STP双绞线,找两个线(杜邦线)每米30转,自买铝箔纸或是锡纸包住两根线。材料怎么用就不用多说了吧,这一个比较切实际,但是自己话学习的成本挺高。

二、应用层

长度:功能码(1个字节)+ 数据值(N个字节)

总的长度不能超过253个字节,也就是说数据部分最多是252个字节。

一般四个都够用了0x1、0x3、0x5、0x10,下面是常用的功能码:

功能码说明
0x01读一个或多个线圈
0x03读一个或多个寄存器
0x05写单个线圈
0x06写单个寄存器
0x0F写多个线圈
0x10写一个或多个寄存器

线圈就是读取一个位(Bit)寄存器就是读取两个字节(Byte)

功能码:0x01

主机发送:

功能码数据起始地址线圈个数
0100 0000 02
1个字节2个字节2个字节

从机响应:

功能码字节数线圈值
010102
1个字节1个字节1个字节

三、数据链路层

数据链路层把是把上层进行封装,也就是将应用层的东西进行一个组装,有点像是TCP/IP协议。
数据链路层中单位为,每帧之间隔发送消息的间隔至少3.5个字符,一个字符就是8位,总共就是28位。由于单片机使用串口发送,不像TCP、UDP那样发送数据那么快,所以
【这里应该补一个图.....】
发送每个字节的数据之间,时间限制在小于1.5个字符之内,这个就自己算一下大概多少个吧…
在这里插入图片描述
正因如此,这个机制需要定时器来计时,计算数据有无超时。

总的来说就是在应用层基础上,头部加一个从机地址,尾部增加一个CRC校验,这个应该不难理解。

一帧的数据结构如下

从机地址功能码数据值CRC校验
1个字节2个字节N个字节2个字节

下面是来自Modbus中文手册的彩图,图中单位为位(Bit)
在这里插入图片描述

总长度为:256个字节

从机地址的范围在 0 ~ 248
广播地址范围 0
广播就会让所有的从机节点接收到数据,一般用来完成总体设备控制

单播地址范围 1 ~ 47
主机跟从机进行单独通讯

保留地址范围 55 ~ 248

CRC校验码
这个代码不用担心写不出来,已经有前辈写了好了。上网搜索一下CRC16位校验,大部分都是Modbus RTU有关,freemodbus中也有相应的函数。

又或者这里我给出一段计算CRC-16校验代码,直接调用crc16_updata计算就好

/* CRC校验码高位 */
const unsigned char auchCRCHi_updata[] = {
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01,
0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81,
0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01,
0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01,
0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01,
0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
0x40
};

/* CRC校验码低位 */
const unsigned char auchCRCLo_updata[]  = {
0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4,
0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,
0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD,
0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7,
0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,
0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE,
0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2,
0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,
0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB,
0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91,
0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88,
0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80,
0x40
};

/*-------------------------------------------------------------------------------------- 
函数功能:CRC-16校验码计算
入口参数:m_u8hMsg		计算校验数据缓冲区
		 m_u8DataLen	缓冲区长度
		 m_Crc			存放计算校验码缓冲区
---------------------------------------------------------------------------------------*/
void crc16_updata(uint8_t * m_u8hMsg,uint16_t m_u8DataLen,uint8_t *m_Crc)
{
	uint8_t mid_u8CRCHi = 0xFF;
	uint8_t mid_u8CRCLo = 0xFF;
	uint16_t uIndex;					
	while(m_u8DataLen--)
	{
		uIndex = mid_u8CRCHi^ *m_u8hMsg++;
		mid_u8CRCHi = mid_u8CRCLo^ auchCRCHi_updata[uIndex];
		mid_u8CRCLo = auchCRCLo_updata[uIndex];
	}
	m_Crc[0]=mid_u8CRCHi;  
	m_Crc[1]=mid_u8CRCLo;  
}

四、功能码实例

1、功能码 0x01
读多个线圈,一个线圈代表一个位。
主机发送

从机地址功能码数据起始地址线圈个数CRC校验
010100 0000 02BD CB
1个字节1个字节2个字节2个字节2个字节

从机响应

从机地址功能码字节数线圈值CRC校验
01010102D0 49
1个字节1个字节1个字节1个字节2个字节

2、功能码 0x03
读多个寄存器,每个寄存器16位。
主机发送

从机地址功能码数据起始地址寄存器个数CRC校验
010300 0000 02BD CB
1个字节1个字节2个字节2个字节2个字节

从机响应

从机地址功能码字节个数寄存器值CRC校验
01030402 01 02 22G1 49
1个字节1个字节1个字节4个字节2个字节

3、功能码 0x10
写多个寄存器,即发送多个数据,写每个寄存器16位,可以写入1至120个寄存器。
主机发送

从机地址功能码数据起始地址写入寄存器个数数据长度数据CRC校验
011000 0000 xxxx00 xx
1个字节1个字节2个字节2个字节1个字节多个字节2个字节

从机响应

从机地址功能码已经写入的寄存器个数CRC校验
0110xx xxxx xx
1个字节1个字节2个字节2个字节

以上对于使用串口来说已经够用了。

五、freemodbus库

软件开发平台:KEIL 5

STM32软件库 :HAL函数库

使用freemodbus-v1.5.0,这个版本已经很久没有更新了,freemodbus应用于嵌入式的通讯协议,一个奥地利的大佬写的,在此处感谢这个大佬,如果是linux的话有libmodbus库

freemodbus文件结构

在这里插入图片描述

文件名说明
demo存放实际的使用案例,底层驱动已经编写好,供参考
doc这个用浏览器打不开。。。我不知道这个干吗的。。
modbus实际Modbus通讯协议代码,没有底层驱动代码
tools测试工具,作用不大。。

不过这样套用他们代码,显然不够灵活,我更推荐自己编写代码逻辑。

六、组织Modbus RTU报文

如果只想使用Modbus RTU协议的访问寄存器,可以通过C语言组织modbus rtu协议报文,即向发送缓冲区一个值一个值的写入。

void SendModbusRTU(void)
{
	unsigned char buffer[1024]; //发送缓冲区
	
	buffer[0]=0xFF; //从机地址
	buffer[1]=0x03;//访问寄存器
	buffer[2]=0x03;//寄存器地址(高位)
	buffer[3]=0xe8;//寄存器地址(地位)
	
	//访问寄存器3个数(高位) 
	buffer[4]=0x00;
	buffer[5]=0x03
	
	//调用crc16函数校验代码,并写入buffer[6]和buffer[7]
	
	//调用自己编写的发送缓冲区函数
	
}

一般使用中断的方式,将接收数据放入缓冲区,然后使用定时器判定一帧数据有没有完成接收。

一帧数据完成接收机制,按照实际设定波特率,能否完成一帧数据来计算,并不需要循规蹈矩。

最后

以上就是壮观薯片为你收集整理的RS485通讯---Modbus数据链路层与应用层(二)前言的全部内容,希望文章能够帮你解决RS485通讯---Modbus数据链路层与应用层(二)前言所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(75)

评论列表共有 0 条评论

立即
投稿
返回
顶部