RS485
TTL电平信号转为差分信号
物理层
RE控制接收模式(低电平有限) DE 控制发送模式
在实际中,通过一个GPIO线控制 DE RE
- 主机的GPIO会控制RS-485收发器的DE管脚,设置发送模式,
- 从TXD线向RS-485收发器的数据(D或DI)线发送一个字节,收发器将在A和B线上将单端UART位流转换为差分位流,数据离开收发器后。
- 主机立即将收发器的模式切换为接收模式。
- 从机控制RS-485收发器的/RE管脚,设置为接收模式,
- 接收主机发送的比特流,将其转换为单端信号,通过从机的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;
判断数据域下的数据地址: 无效,返回异常码 2;
判断请求是数据量,请求的值是否存在:无效,返回异常码3
执行回传响应操作,过程出错,返回异常码 4;
发送响应;
Modbus 主站/从站协议原理
Modbus 串行链路协议是一个主-从协议。 在同一时刻,只有一个主节点连接于总线,一个或多个 子节点 (最大编号为 247 ) 连接于同一个串行总线。Modbus 通信总是由主节点发起。子节点在没 有收到来自主节点的请求时,从不会发送数据。子节点之间从不会互相通信。
- 单播模式:主机访问从机,从机处理完毕需要向主机返回一个报文(应答)
注意:每个子节点必须有唯一的地址 (1 到 247),这样才能区别于其它节点被独立的寻址。
- 广播模式:主节点向所有的子节点发送请求。子节点不需要返回应答。
注意:广播请求一般用于写命令。所有设备必须接受广播模式的写功能。地址 0 是专门表示广播数据的。
Modbus 地址规则
Modbus 寻址空间有 256 个不同地址。
0 | 1-47 | 55~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
应用层:
- 设置定节点板的 ID
- 寄存器数据排序
- 串口接收,接收数据解析,回传响应
主机设置mudbos协议,借助RS485连接设备,来进行一系列的操作。
代码后续更新