RS485与MUDBOS通信

RS485

TTL电平信号转为差分信号

物理层

                                RE控制接收模式(低电平有限)           DE 控制发送模式

在实际中,通过一个GPIO线控制     DE          RE

  1. 主机的GPIO会控制RS-485收发器的DE管脚,设置发送模式
  2. 从TXD线向RS-485收发器的数据(D或DI)线发送一个字节,收发器将在A和B线上将单端UART位流转换为差分位流,数据离开收发器后。
  3. 主机立即将收发器的模式切换为接收模式。
  1. 从机控制RS-485收发器的/RE管脚,设置为接收模式
  2. 接收主机发送的比特流,将其转换为单端信号,通过从机的UART RXD线接收,当从机准备好响应时,它按主机原来的方式进行发送,而主机变为接收。

电平状态: 

                         A > B 逻辑‘1’ 电平差+200mv

                        A < B 逻辑‘0’ 电平差-200mv

如何实现RS-485/422多点通讯

RS-485总线上任何时候只能有一发送器发送。半双工方式,主从只能一个发。全双工方式,主站总可发送,从站只能有一个发送。

#include "sys.h"		    
#include "rs485.h"	 
#include "delay.h"

#ifdef EN_USART2_RX   	//如果使能了接收


//接收缓存区 	
u8 RS485_RX_BUF[64];  	//接收缓冲,最大64个字节.
//接收到的数据长度
u8 RS485_RX_CNT=0;   		  
  
void USART2_IRQHandler(void)
{
	u8 res;	    
 
 	if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) //接收到数据
	{	 
	 			 
		res =USART_ReceiveData(USART2); 	//读取接收到的数据
		if(RS485_RX_CNT<64)
		{
			RS485_RX_BUF[RS485_RX_CNT]=res;		//记录接收到的值
			RS485_RX_CNT++;						//接收数据增加1 
		} 
	}  											 
} 
#endif										 
//初始化IO 串口2
//pclk1:PCLK1时钟频率(Mhz)
//bound:波特率	  
void RS485_Init(u32 bound)
{  
  GPIO_InitTypeDef GPIO_InitStructure;
  USART_InitTypeDef USART_InitStructure;
 	NVIC_InitTypeDef NVIC_InitStructure;
 
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOD, ENABLE);//使能GPIOA,D时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);//使能USART2时钟
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;				 //PD7端口配置
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
 	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 	GPIO_Init(GPIOD, &GPIO_InitStructure);
 
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;	//PA2
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽
  GPIO_Init(GPIOA, &GPIO_InitStructure);
   
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;//PA3
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
  GPIO_Init(GPIOA, &GPIO_InitStructure);  

	RCC_APB1PeriphResetCmd(RCC_APB1Periph_USART2,ENABLE);//复位串口2
	RCC_APB1PeriphResetCmd(RCC_APB1Periph_USART2,DISABLE);//停止复位
 
	
 #ifdef EN_USART2_RX		  	//如果使能了接收
	USART_InitStructure.USART_BaudRate = bound;//波特率设置
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;//8位数据长度
	USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
	USART_InitStructure.USART_Parity = USART_Parity_No;///奇偶校验位
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;//收发模式

  USART_Init(USART2, &USART_InitStructure); ; //初始化串口
  
	NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn; //使能串口2中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3; //先占优先级2级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //从优先级2级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
	NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
 
  USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//开启中断
   
  USART_Cmd(USART2, ENABLE);                    //使能串口 

 #endif

  RS485_TX_EN=0;			//默认为接收模式
 
}

//RS485发送len个字节.
//buf:发送区首地址
//len:发送的字节数(为了和本代码的接收匹配,这里建议不要超过64个字节)
void RS485_Send_Data(u8 *buf,u8 len)
{
	u8 t;
	RS485_TX_EN=1;			//设置为发送模式
  	for(t=0;t<len;t++)		//循环发送数据
	{		   
		while(USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET);	  
		USART_SendData(USART2,buf[t]);
	}	 
 
	while(USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET);		
	RS485_RX_CNT=0;	  
	RS485_TX_EN=0;				//设置为接收模式	
}
//RS485查询接收到的数据
//buf:接收缓存首地址
//len:读到的数据长度
void RS485_Receive_Data(u8 *buf,u8 *len)
{
	u8 rxlen=RS485_RX_CNT;
	u8 i=0;
	*len=0;				//默认为0
	delay_ms(10);		//等待10ms,连续超过10ms没有接收到一个数据,则认为接收结束
	if(rxlen==RS485_RX_CNT&&rxlen)//接收到了数据,且接收完成了
	{
		for(i=0;i<rxlen;i++)
		{
			buf[i]=RS485_RX_BUF[i];	
		}		
		*len=RS485_RX_CNT;	//记录本次数据长度
		RS485_RX_CNT=0;		//清零
	}
}
  • RS485_Init 函数为 485 通信初始化函数,其实基本上就是在配置串口 2,只是把 PD7 也顺带配置了,用于控制 SP3485 的收发。同时如果使能中断接收的话,会执行串口 2 的中断接收配置。
  • USART2_IRQHandler 函数用于中断接收来自 485 总线的数据,将其存放在 RS485_RX_BUF 里面。
  • 最后 RS485_Send_Data 和RS485_Receive_Data 这两个函数用来发送数据到 485 总线和读取从 485 总线收到的数据。

MUDBOS通信

应用层协议           请求/应答      主/从        类似IIC             高位先发

PDU:与基础通信层无关的简单协议数据单元

ADU:特定总线或网络上 的 MODBUS 协议映射能够在应用数据单元

设备地址:1个字节,范围是0~255之间。0是广播地址,1~247是从站设备地址。

功能码:1个字节,包括公共(1~65),自定义码         

数据域:0~252字节,其长度和内容由功能码决定。功能码不同,数据部分的定义不尽相同。

校验:2个字节,由发送设备计算,将校验码之前的数据按照CRC16算法进行计算,生成2个字节的数据,放置于发送信息的尾部。接受信息的设备再重新计算接收到的信息的CRC码,比较计算得到的CRC码是否与接收到的相符,如果不相符,则表明数据在传输过程中出错。通过数据校验增加了系统的安全与效率。

  常用的功能码

应用1:主机要    控制(写)      从机        灯/ 蜂鸣器/继电器(布尔值【单比特就能处理】)

       ------------- 就可以使用05/15功能码

应用 2: 主机要    读取 (读)     从机       传感器     的值(这个就需要16比特数据)

       --------------选择功能码:03/2

应用 3: 主机要     读取(读)     从机       设备状态    灯/蜂鸣器/继电器(单比特就能处理)
       --------------选择功能码:01 

当读线圈(01)时

请求PDU格式为   

01(读线圈)     +    2字节的起始地址(从哪个地方开始读)      +     2字节线圈数量(读多少个)

主机发送给从机,从机进行处理数据(具体流程在文章下面)。

响应PDU格式为 

正常时

01(主机发的功能码)    +        1字节数(要返回数据的字节数)       +       n线圈值

错误时

01+0x80+异常码

举例

从机处理过程(文章下文有类似的解释)

协议原理:主机发送请求到从机。

                    从机响应     无差错返回响应

                                        有错误回应差错码

MUDBOS有三种PDU:        

请求PUUD

响应PDU

差错PDU

离散输入:一个地址一个数据位,用户只能读取它的状态,不能修改。比如面板上的按键、开关状态,电机的故障状态。

线圈(输出)一个地址一个数据位,用户可以置位、复位,可以回读状态,比如继电器输出,电机的启停控制信号。

输入寄存器:一个地址16位数据,用户只能读,不能修改,比如一个电压值的读数。

保持寄存器(输出):一个地址16位数据,用户可以写,也可以回读,比如一个控制变频器的电流值。

从机如何处理主机数据

对PDU数据处理

  1. 判断功能码:无效功能码,返回异常码 1;

  2. 判断数据域下的数据地址: 无效,返回异常码 2;

  3. 判断请求是数据量,请求的值是否存在:无效,返回异常码3

  4. 执行回传响应操作,过程出错,返回异常码 4;

  5. 发送响应;

Modbus 主站/从站协议原理

Modbus 串行链路协议是一个主-从协议。 在同一时刻,只有一个主节点连接于总线,一个或多个 子节点 (最大编号为 247 ) 连接于同一个串行总线。Modbus 通信总是由主节点发起。子节点在没 有收到来自主节点的请求时,从不会发送数据。子节点之间从不会互相通信。

  • 单播模式:主机访问从机,从机处理完毕需要向主机返回一个报文(应答)

注意:每个子节点必须有唯一的地址 (1 到 247),这样才能区别于其它节点被独立的寻址。

  • 广播模式:主节点向所有的子节点发送请求。子节点不需要返回应答。

注意:广播请求一般用于写命令。所有设备必须接受广播模式的写功能。地址 0 是专门表示广播数据的。

Modbus 地址规则

Modbus 寻址空间有 256 个不同地址。

01-4755~248
广播子节点唯一地址保留地址

主机状态图

从站状态图

两种串行传输模式

RTU 模式 :16进制数据       速率高

ASCII 模式:字符串

注 : 由于一个子节需要两个字符,此模式比 RTU 效率低。 

例 : 子节 0X5B 会被编码为两个字符 : 0x35 和 0x42 ( ASCII 编码 0x35 ="5", 0x42 ="B" )。

RTU 模式每个字节 ( 10 位 ) 的格式为 :

1 起始位

8 数据位,

1 位作为奇偶校验

1 停止

是借助串口通信的,与串口一致!

字符是如何串行传送的:

每个字符或字节均由此顺序发送(从左到右): 低位先发

CRC 校验(循环冗余校验)

CRC 域检验整个报文的内容。不管报文有无奇偶校验,均执行此检 验。

CRC 包含由两个 8 位字节组成的一个 16 位值。 CRC 域作为报文的最后的域附加在报文之后。计算后,首先附加低字节,然后是高字节。CRC 高字节为报文发送的最后一个子节。 附加在报文后面的 CRC 的值由发送设备计算。接收设备在接收报文时重新计算 CRC 的值, 并将计算结果于实际接收到的 CRC 值相比较。如果两个值不相等,则为错误。

CRC 的计算, 开始对一个 16 位寄存器预装全 1。 然后将报文中的连续的 8 位子节对其进行后 续的计算。只有字符中的 8 个数据位参与生成 CRC 的运算,起始位,停止位和校验位不参与 CRC 计算。

CRC 的生成过程中, 每个 8–位字符与寄存器中的值异或。然后结果向最低有效位(LSB)方向 移动(Shift) 1 位,而最高有效位(MSB)位置充零。 然后提取并检查 LSB:如果 LSB 为 1, 则寄存 器中的值与一个固定的预置值异或;如果 LSB 为 0, 则不进行异或操作。

这个过程将重复直到执行完 8 次移位。完成最后一次(第 8 次)移位及相关操作后,下一个 8 位字节与寄存器的当前值异或,然后又同上面描述过的一样重复 8 次。当所有报文中子节都运算之 后得到的寄存器忠的最终值,就是 CRC。 当 CRC 附加在报文之后时,首先附加低字节,然后是高字节。

在应用中用查表法,调用函数完成效验

数据传输模式

Modbus RTU协议数据帧是没有起始符结束符,所以两个数据帧之间需要靠时间间隔来区分。协议中规定信息帧发送至少要有3.5个字符时间的停顿间隔。即在一个数据帧的最后一个字符传输完成之后,需要至少3.5个字符时间的停顿标定数据帧的结束,一个新的数据帧可在此停顿之后开始。如果一个新消息在小于3.5个字符时间内接着前个消息开始,接受的设备将认为它是前一数据帧的延续,这将导致CRC校验出错。同时整个数据帧必须作为一连续的流传输。如果在帧完成之前有超过1.5字符时间的停顿时间,接受设备将刷新不完整的数据帧从而认为存在丢包现象。

3.5个字符时间:(1/波特率*10)*3.5

如果两个字符之间的空闲间隔大于 1.5 个字符时间,则报文帧被认为不完整应该被接收节点丢弃

那么一个字符是什么呢,他由1个bit起始位,8bit数据位,1个bit奇偶校验位,1个bit停止位组成,在发送时从起始位开始。

实验:

主机 (PC) 获取从机 (节点板) 传感器数据

物理层: USB 转串口,用USART1
应用层:       

  1. 设置定节点板的 ID
  2. 寄存器数据排序
  3. 串口接收,接收数据解析,回传响应

主机设置mudbos协议,借助RS485连接设备,来进行一系列的操作。

代码后续更新

部分内容转自:如何看懂Modbus数据帧?Modbus RTU数据帧格式 - 知乎 (zhihu.com)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值