目录
6.2.1. 如果使能中断接收的话,会执行串口 2 的中断接收配置,USART2_IRQHandler()函数用于中断接收来自485总线的数据,将其存放在 RS485_RX_BUF 里面;
6.2.2. RS485_Init()是485初始化函数,完成对串口2的配置;
6.2.4. 最后,RS485_TX_Set() 函数,用于通过 PCF8574 控制RS485_RE脚。
前言
我们是气象水利行业,经常接触到的项目都是关于以下系统方案,其中 RS-485 是通讯中及其重要的连接方式:
其实,我们在项目中经常用到的“通信”到底是什么?
通信 = 通信协议 + 通信元素 + ...
- 通信协议
又称通信规程,是指通信双方对数据传送控制的一种约定。约定中包括对 {数据格式,同步方式,传送速度,传送步骤,检纠错方式,控制字符定义,等} 问题做出统一规定,通信双方必须共同遵守,它也叫做链路控制规程。
- 通信元素
信息表示、解析方法 + 信息的传输方法。
- 目前常见的串行通信接口标准有:
RS-232、RS-422、RS-485等。 另外,SPI(串行外设接口)、IIC(内置集成电路)和CAN(控制器局域网)也属于串口通信。
1. RS485简介
要了解485之前,我们需要做一下拆词分析:{串口,串行通信,并行通信,...}
1.1. 什么是串口
串口是一种接口标准,它规定了接口的电气标准,简单说只是物理层的一个标准。没有规定接口插件电缆以及使用的协议,所以只要我们使用的接口插件电缆符合串口标准就可以在实际中灵活使用,在串口接口标准上使用各种协议进行通讯及设备控制。
1.2. 什么是串行通信
是指计算机主机与外设之间以及主机系统与主机系统之间数据的串行传送。使用一条数据线,将数据一位一位地依次传输,每一位数据占据一个固定的时间长度。其只需要少数几条线就可以在系统间交换信息,特别适用于计算机与计算机、计算机与外设之间的远距离通信。
1.3. 什么是并行通信
就是指数据的每一位同时在多根数据线上发送或者接收。可以以字或字节为单位并行进行。并行通信速度快,但用的通信线多、成本高,故不宜进行远距离通信。计算机或PLC各种内部总线就是以并行方式传送数据的。
RS485和RS232是典型的串行通讯标准。
RS485是 美国电子工业协会(Electronic Industries Association,EIA)于1983年发布的串行通信接口标准,经通讯工业协会(TIA)修订后命名为 TIA/EIA-485-A,其中RS 是 Recommended Standard 的缩写。
EIA-485(过去叫做RS-485或者RS485)是隶属于OSI模型物理层的电气特性规定为2线、半双工、平衡传输线多点通信的标准。在线性多点总线的配置下,可以在一个网络上有多个接收器。是一种工业控制环境中常用的通讯协议,因此适用在工业环境中。
RS485是半双工异步串行通信。
2. 接口原理
2.1. 连接方式
RS485推荐使用在点对点网络中,线型,总线型,不能是星型,环型网络。理想情况下RS485需要2个匹配电阻,其阻值要求等于传输电缆的特性阻抗(一般为120Ω)。没有特性阻抗的话,当所有的设备都静止或者没有能量的时候就会产生噪声,而且线移需要双端的电压差。没有终接电阻的话,会使得较快速的发送端产生多个数据信号的边缘,导致数据传输出错。
RS485有两线制和四线制两种接线,四线制只能实现点对点的通信方式,现很少采用,多采用的是两线制接线方式,这种接线方式为总线拓扑结构,
在同一总线上最多可以挂接 32 个节点。
RS485总线同IIC,也是主从模式,支持点对点单从机模式,也支持多从机模式,不支持多主机模式。
下图为RS485推荐的多设备连接方式:
- 发送数据时,串口控制器的TX引脚信号经过收发器转换成差分信号传输到总线上。
- 接收数据时,收发器把总线上的差分信号转化成 TTL信号通过RX引脚传输到串口控制器中。
串口控制器与收发器之间一般使用 TTL 信号传输,收发器与总线则使用差分信号来传输。
2.2. 差分信号
长距离布线会有信号衰减,而且引入噪声和干扰的可能性更大,在线缆 A 和 B 上的表现就是电压幅度的变化,但是,采用差分线的好处就是,差值相减就会忽略掉干扰依旧能输出正常的信号,把这种差分接收器忽略两条信号线上相同电压的能力称为共模抑制。
共模干扰问题:RS-485接口采用差分方式传输信号方式,并不需要相对于某个参照点来检测信号,系统只需检测两线之间的电位差就可以。
通俗来说,就是如果在传输过程中遇到干扰,那A、B两根线的电压都会发生变化,可能本来A是5V,B是2V,被干扰成了A是8V,B是5V,但由于485通信检测的是两根线之间的电压差,所以AB间的电压差并没有发生改变,仍然是3V,所以接收器检测到的仍然是正确的信号
相反,像串口和RS232这些单端的通信方式,因为只有一根信号线+一根地线,并会规定某个电平状态表示一种逻辑,如5V表示1,0V表示0,当在传输过程中发生干扰时,5V可能变成了2V,这时候就没法判断到底是1还是0了,导致传输的数据不正确
3. 485通讯接口的优势
3.1. 接口电平低, 不易损坏芯片。
RS485的电气特性: 逻辑“1”以两线间的电压差为+(2~6)V表示; 逻辑“0”以两线间的电压差为-(2~6)V表示。
3.2. 传输速率高
10米时,RS485的数据最高传输速率可达35Mbps,在1200m时,传输速度可达100Kbps。
3.3. 抗干扰能力强
RS485接口是采用平衡驱动器和差分接收器的组合,抗共模干扰能力增强,即抗噪声干扰性好。
3.4. 传输距离远,支持节点多。
RS485总线最长可以传输1200m以上(速率≤100Kbps)一般最大支持32个节点,如果使用特制的485芯片,可以达到128个或者256个节点,最大的可以支持到400个节点。
4. 常见RS485电路图
4.1. MAX485
序号 | 引脚 | 功能 |
1 | R0 | 接收器输出 |
2 | RE | 接收器输出使能(低电平有效) |
3 | DE | 驱动器输出使能(高电平有效) |
4 | DI | 驱动器输入 |
5 | GND | 连接地 |
6 | A | 驱动器输出/接收器输入(同相) |
7 | B | 驱动器输出/接收器输入(反相) |
8 | VCC | 芯片供电 |
图中A、B总线接口,用于连接485总线。RO是接收输出端,DI是发送数据收入端,RE是接收使能信号(低电平有效),DE是发送使能信号(高电平有效)。
4.2. SP3485
下图是:STM32F429 与 SP3485连接电路图
我们只需要配置好串口USART2,就可以实现正常的485通信了。
不管是SP3485还是MAX485它们电路都是一样的。
5. Modbus协议
是一个串行通信协议,首次出现于1979年,是连接行业设备实际使用的标准协议用于“短距离设备连接”的本地协议
Modbus协议是一个master/slave架构的协议。有一个节点是master节点,其他使用Modbus协议参与通信的节点是slave节点。每一个slave设备都有一个唯一的地址。
一个Modbus命令包含了打算执行的设备的Modbus地址。所有设备都会收到命令,但只有指定位置的设备会执行及回应指令(地址0例外,指定地址0的指令是广播指令,所有收到指令的设备都会运行,不过不回应指令)。所有的Modbus命令包含了检查码(crc),以确定到达的命令没有被破坏。基本的ModBus命令能指令一个RTU改变它的寄存器的某个值,控制或者读取一个I/O端口,以及指挥设备回送一个或者多个其寄存器中的数据。
5.1. Modbus与MQTT的联系
5.2. Modbus协议有三种类型
5.2.1. Modbus-TCP
5.2.2. Modbus-ASCII
5.2.3. Modbus-RTU
帧结构 = 地址 + 功能码 + 数据 + 校验
地址 | 占用一个字节,范围0-255,其中有效范围是1-247,其他有特殊用途,比如255是广播地址(广播地址就是应答所有地址,正常的需要两个设备的地址一样才能进行查询和回复)。 |
功能 | 占用一个字节,功能码的意义就是,知道这个指令是干啥的,比如你可以查询从机的数据,也可以修改数据,所以不同功能码对应不同功能。 |
数据 | 根据功能码不同,有不同结构,在下面的实例中有说明。 |
校验 | 为了保证数据不错误,增加这个,然后再把前面的数据进行计算看数据是否一致,如果一致,就说明这帧数据是正确的,我再回复;如果不一样,说明你这个数据在传输的时候出了问题,数据不对的,所以就抛弃了。 |
- 下图为串口通讯常用的调试工具SSCOM
- 主机发送:01 03 00 00 00 01 84 0A
- 从机回复:01 03 02 19 98 B2 7E
下图为某传感器的寄存器参数
6. 实战
基于STM32F429的UART2开发,我们需要 rs485.c 和 rs485.h 两个文件:
6.1. rs485.h
#ifndef __RS485_H
#define __RS485_H
#include "sys.h"
extern u8 RS485_RX_BUF[64]; //接收缓冲,最大 64 个字节
extern u8 RS485_RX_CNT; //接收到的数据长度
//如果想串口中断接收,设置 EN_USART2_RX 为 1,否则设置为 0
#define EN_USART2_RX 1 //0,不接收;1,接收.
void RS485_Init(u32 pclk2,u32 bound);
void RS485_Send_Data(u8 *buf,u8 len);
void RS485_Receive_Data(u8 *buf,u8 *len);
void RS485_TX_Set(u8 en);
#endif
6.2. rs485.c
6.2.1. 如果使能中断接收的话,会执行串口 2 的中断接收配置,USART2_IRQHandler()函数用于中断接收来自485总线的数据,将其存放在 RS485_RX_BUF 里面;
#if EN_USART2_RX
//如果使能了接收
//接收缓存区
u8 RS485_RX_BUF[64];
//接收缓冲,最大 64 个字节.
//接收到的数据长度
u8 RS485_RX_CNT=0;
void USART2_IRQHandler(void)
{
u8 res;
if(USART2->SR&(1<<5))//接收到数据
{
res=USART2->DR;
if(RS485_RX_CNT<64)
{
RS485_RX_BUF[RS485_RX_CNT]=res; //记录接收到的值
RS485_RX_CNT++; //接收数据增加 1
}
}
}
#endif
6.2.2. RS485_Init()是485初始化函数,完成对串口2的配置;
//初始化 IO 串口 2
//pclk1:PCLK1 时钟频率(Mhz),APB1 一般为 48Mhz
//bound:波特率
void RS485_Init(u32 pclk1,u32 bound)
{
float temp;
u16 mantissa;
u16 fraction;
temp=(float)(pclk1*1000000)/(bound*16); //得到 USARTDIV
mantissa=temp; //得到整数部分
fraction=(temp-mantissa)*16; //得到小数部分
mantissa<<=4;
mantissa+=fraction;
PCF8574_Init(); //初始化 PCF8574,用于控制 RE 脚
RCC->AHB1ENR|=1<<0; //使能 PORTA 口时钟
GPIO_Set(GPIOA,PIN2|PIN3,GPIO_MODE_AF,GPIO_OTYPE_PP,GPIO_SPEED_50M,
GPIO_PUPD_PU); //PA2,PA3,复用功能,上拉
GPIO_AF_Set(GPIOA,2,7); //PA2,AF7
GPIO_AF_Set(GPIOA,3,7); //PA3,AF7
RCC->APB1ENR|=1<<17; //使能串口 2 时钟
RCC->APB1RSTR|=1<<17; //复位串口 2
RCC->APB1RSTR&=~(1<<17); //停止复位
USART2->BRR=mantissa; // 波特率设置
USART2->CR1|=0X200C; //1 位停止,无校验位.
#if EN_USART2_RX //如果使能了接收
//使能接收中断
USART2->CR1|=1<<2; //串口接收使能
USART2->CR1|=1<<5; //接收缓冲区非空中断使能
MY_NVIC_Init(3,3,USART2_IRQn,2); //组 2,最低优先级
#endif
RS485_TX_Set(0); //默认设置为接收模式
}
6.2.3. RS485_Send_Data() 和 RS485_Receive_Data() 这两个函 数用来发送数据到 485 总线和读取从485总线收到的数据首先令 rxlen=RS485_RX_CNT,记录当前接收到的字节数,随后,等待10ms,如果在这个 10ms 里面,没有接收到任何数据(RS485_RX_CNT 的值未增加),那么就说明接收完成了。如果有接收到其他数据(RS485_RX_CNT变大了),那么说明还在继续接收数据,需等到下一个循环再处理;
//RS485 发送 len 个字节.
//buf:发送区首地址
//len:发送的字节数(为了和本代码的接收匹配,这里建议不要超过 64 个字节)
void RS485_Send_Data(u8 *buf,u8 len)
{
u8 t;
RS485_TX_Set(1); //设置为发送模式
for(t=0;t<len;t++)
//循环发送数据
{
while((USART2->SR&0X40)==0); //等待发送结束
USART2->DR=buf[t];
}
while((USART2->SR&0X40)==0); //等待发送结束
RS485_RX_CNT=0;
RS485_TX_Set(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; //清零
}
}
6.2.4. 最后,RS485_TX_Set() 函数,用于通过 PCF8574 控制RS485_RE脚。
//RS485 模式控制.
//en:0,接收;1,发送.
void RS485_TX_Set(u8 en)
{
PCF8574_WriteBit(RS485_RE_IO,en);
}
(* ̄︶ ̄)创作不易!期待你们的 点赞、收藏和评论喔。
本文来源网络,免费分享知识,版权归原作者所有。如涉及作品版权问题,请联系我进行删除!