RS485 通信与 Modbus 协议
文章目录
- RS485 通信与 Modbus 协议
- 单片机 RS485 通信接口、控制线、原理图及程序实例
RS485 通信与 Modbus 协议
在工业控制、电力通讯、智能仪表等领域的应用中,在线交流的方式通常被采用以实现数据交换。最初采用的是RS232接口,在工业现场环境中由于存在复杂的电气设备以及电磁干扰问题而导致信号传输出现错误现象。此外该接口仅支持点对点通信不具备网络通信功能其最大传输距离仅为十几米难以满足远距离通信的需求而RS485则有效解决了这些问题数据信号采用差分传输模式能够有效抑制共模干扰其最大可达通信距离达1200米并且支持多个接收装置连接到同一总线上随着工业领域对数据通信需求的不断增加于1979年施耐德电气发布了Modbus协议这一专为工业现场设计的总线协议如今 RS485 通信系统中绝大多数场景仍沿用Modbus协议本节课将深入讲解RS485通信技术和Modbus协议的工作原理及其应用实例
单片机 RS485 通信接口、控制线、原理图及程序实例
RS232 标准是诞生于 RS485 之前的,但是 RS232 有几处不足的地方:
- 接口的信号电平值处于较高水平(达几十伏),使用不当可能导致接口芯片损坏;其电平标准与TTL电平并不相容。
- 传输速率存在一定的限制,在实际应用中通常建议不要超过几百千比特每秒。
- 采用共地模式的通信线路时需要注意的是该模式容易受到外界干扰,并且其抗干扰能力较弱。
- 由于受限于物理特性等因素的影响,在实际应用中最多只能实现几十米范围内的通信。
- 目前该接口仅支持两点间的点对点通信功能,并不具备支持多机联网网络化的条件。
基于 RS232 接口存在的局限性问题伴随这些局限性逐渐显现的问题而产生的新的接口标准中之一就是 RS485 其主要特点包括
- 采用差分信号。我们在讲 A/D 的时候,讲过差分信号输入的概念,同时也介绍了差分输入的好处,最大的优势是可以抑制共模干扰。尤其当工业现场环境比较复杂,干扰比较多时,采用差分方式可以有效的提高通信可靠性。RS485 采用两根通信线,通常用 A 和 B 或者 D+ 和 D- 来表示。逻辑“1”以两线之间的电压差为 +(0.2~6)V 表示,逻辑“0”以两线间的电压差为 -(0.2~6)V 来表示,是一种典型的差分通信。
- RS485 通信速率快,最大传输速度可以达到 10 Mb/s 以上。
- RS485 内部的物理结构,采用的是平衡驱动器和差分接收器的组合,抗干扰能力也大大增加。
- 传输距离最远可以达到1200米左右,但是它的传输速率和传输距离是成反比的,只有在 100 Kb/s 以下的传输速度,才能达到最大的通信距离,如果需要传输更远距离可以使用中继。
- 可以在总线上进行联网实现多机通信,总线上允许挂多个收发器,从现有的 RS485 芯片来看,有可以挂32、64、128、256等不同个设备的驱动器。
- RS485 的接口非常简单,与 RS232 所使用的 MAX232 是类似的,只需要一个 RS485 转换器,就可以直接与单片机的 UART 串口连接起来,并且使用完全相同的异步串行通信协议。但是由于 RS485 是差分通信,因此接收数据和发送数据是不能同时进行的,也就是说它是一种半双工通信。那我们如何判断什么时候发送,什么时候接收呢?
RS485 转换芯片很多,这节课我们以典型的 MAX485 为例讲解 RS485 通信,

为了进一步提高该转换器在实际应用环境中的稳定性以及抗干扰能力,在靠近A与B引脚的位置并联接入一个电阻即可达到预期效果。具体而言,在此电路设计中所使用的电阻值范围通常建议采用100欧姆至1千欧姆之间的标准阻值规格以确保最佳的工作性能表现
向大家介绍如何利用 KST-51 开发板进行外围电路的扩展实验。本开发板主要实现了基础功能供同学完成基本实验练习。我们希望同学们不要仅仅停留在这个开发板上。如果有兴趣进一步探索其他电路设计,则可以通过单片机开发板的扩展接口来实现。观察到蓝色背景下的单片机周围布设了32个引脚孔。这32个引脚孔就是将单片机的所有IO引脚都连接起来了,在原理图中这些引脚孔对应的元器件分别为 J4、J5、J6 和 J7 等四个模块。

这些IO口并非全部可用于外扩,在这些IO口中既有用于数据输出的功能也有用于数据输入的功能这类引脚无法被使用具体包括P3.2、P3.4、P3.6三个引脚无法应用于外扩方案当中例如对于P3.2这个引脚如果我们将其用于外扩操作则会发现若发送的数据与DS18B20芯片的时间序列存在吻合情况则会导致DS18B20将此信号拉低从而影响通信正常运转因此在本设计中我们仅能选择不包含有此三种无法使用的IO口在内的其余共剩余的其他IO口共计有其他可用的外部连接端子数量为剩下其他的数量共计为剩余的数量这样才能够满足本设计所需的外部信号扩展需求同时需要注意的是在当前的状态下若将现有的IO口全部用于实现外扩功能则会导致电路板上原本应用于主控功能的部分电路无法得到有效的激活即即这种情况下我们无法同时实现外扩功能与主控功能两者之间的选择只能二者取其一以保证电路系统的正常运行
在RS485实验过程中, 我们必须选用P3.0和P3.1引脚来进行信号传输, 同时还需要配置方向控制引脚, 采用杜邦线将其连接至P1.7端子上。RS485另一端建议使用USB转RS485接口模块, 并通过双绞线将开发板与模块上的A、B端分别连接起来, 确保信号传输的稳定性与可靠性。将USB端插入电脑后即可实现通信
在学习第13章实用串口通信方法及程序之后,这类串口通信的操作相对较为简便且具相似性。基于实用串口通信范例的思路设计了一个简单的程序,在单片机端通过串口调试助手向其发送任意数量的字符,并确保单片机接收后在末尾添加"回车+换行"标识后再返回给调试助手进行显示。现将完整代码附上以供参考。
在程序中需要注意的一点是:通常情况下会将MAX485设置为接收状态,在发送数据之前将其切换为发送状态。为了实现这一功能,在UartWrite()函数开始前需将MAX485的方向引脚拉高,并确保在函数退出前将其拉低以恢复接收状态。然而这里存在一个关键细节:当单片机处理发送或接收中断时,这些事件总是发生在停止位的一半阶段上(如果中断被启用的话)。因此,在接收操作中不会出现任何问题;但在发送操作中可能出现问题:当立即向SBUF写入下一个字节数据时,UART硬件会在完成上一个停止位的发送后启动新字节的数据传输;但如果此时尚未继续发送下一个字节而是已结束当前传输,则需要在此时将MAX485方向引脚拉低以恢复其处于接收状态;否则会导致错误的发生。具体而言,在UartWrite()函数内部需调用DelayX10us(5),这相当于人为地增加了50 us的时间延时;这额外的时间正好使剩余未完成的停止位得以完成一半的工作量;因此这个延时 duration 的长短是由通信波特率决定的,并且正好等于波特率周期的一半时间间隔
#include <reg52.h>
#include <intrins.h>
sbit RS485_DIR = P1^7; //RS485 方向选择引脚
bit flagFrame = 0; //帧接收完成标志,即接收到一帧新数据
bit flagTxd = 0; //单字节发送完成标志,用来替代 TXD 中断标志位
unsigned char cntRxd = 0; //接收字节计数器
unsigned char pdata bufRxd[64]; //接收字节缓冲区
extern void UartAction(unsigned char *buf, unsigned char len);
/* 串口配置函数,baud-通信波特率 */
void ConfigUART(unsigned int baud){
RS485_DIR = 0; //RS485 设置为接收方向
SCON = 0x50; //配置串口为模式 1
TMOD &= 0x0F; //清零 T1 的控制位
TMOD |= 0x20; //配置 T1 为模式 2
TH1 = 256 - (11059200/12/32)/baud; //计算 T1 重载值
TL1 = TH1; //初值等于重载值
ET1 = 0; //禁止 T1 中断
ES = 1; //使能串口中断
TR1 = 1; //启动 T1
}
/* 软件延时函数,延时时间(t*10)us */
void DelayX10us(unsigned char t){
do {
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
} while (--t);
}
/* 串口数据写入,即串口发送函数,buf-待发送数据的指针,len-指定的发送长度 */
void UartWrite(unsigned char *buf, unsigned char len){
RS485_DIR = 1; //RS485 设置为发送
while (len--){ //循环发送所有字节
flagTxd = 0; //清零发送标志
SBUF = *buf++; //发送一个字节数据
while (!flagTxd); //等待该字节发送完成
}
DelayX10us(5); //等待最后的停止位完成,延时时间由波特率决定
RS485_DIR = 0; //RS485 设置为接收
}
/* 串口数据读取函数,buf-接收指针,len-指定的读取长度,返回值-实际读到的长度 */
unsigned char UartRead(unsigned char *buf, unsigned char len){
unsigned char i;
//指定读取长度大于实际接收到的数据长度时,
//读取长度设置为实际接收到的数据长度
if (len > cntRxd){
len = cntRxd;
}
for (i=0; i<len; i++){ //拷贝接收到的数据到接收指针上
*buf++ = bufRxd[i];
}
cntRxd = 0; //接收计数器清零
return len; //返回实际读取长度
}
/* 串口接收监控,由空闲时间判定帧结束,需在定时中断中调用,ms-定时间隔 */
void UartRxMonitor(unsigned char ms){
static unsigned char cntbkp = 0;
static unsigned char idletmr = 0;
if (cntRxd > 0){ //接收计数器大于零时,监控总线空闲时间
if (cntbkp != cntRxd){ //接收计数器改变,即刚接收到数据时,清零空闲计时
cntbkp = cntRxd;
idletmr = 0;
}else{ //接收计数器未改变,即总线空闲时,累积空闲时间
if (idletmr < 30){ //空闲计时小于 30ms 时,持续累加
idletmr += ms;
if (idletmr >= 30){ //空闲时间达到 30ms 时,即判定为一帧接收完毕
flagFrame = 1; //设置帧接收完成标志
}
}
}
}else{
cntbkp = 0;
}
}
/* 串口驱动函数,监测数据帧的接收,调度功能函数,需在主循环中调用 */
void UartDriver(){
unsigned char len;
unsigned char pdata buf[40];
if (flagFrame){ //有命令到达时,读取处理该命令
flagFrame = 0;
len = UartRead(buf, sizeof(buf)-2); //将接收到的命令读取到缓冲区中
UartAction(buf, len); //传递数据帧,调用动作执行函数
}
}
/* 串口中断服务函数 */
void InterruptUART() interrupt 4{
if (RI){ //接收到新字节
RI = 0; //清零接收中断标志位
//接收缓冲区尚未用完时,保存接收字节,并递增计数器
if (cntRxd < sizeof(bufRxd)){
bufRxd[cntRxd++] = SBUF;
}
}
if (TI){ //字节发送完毕
TI = 0; //清零发送中断标志位
flagTxd = 1; //设置字节发送完成标志
}
}
现在看来这种串口程序挺简单的呢!我们在一再练习串口通信程序的过程中发现:随着学习模块越来越多、实践也越多越容易掌握的东西会越来越简单了。通过设备管理器我们可以轻松查看所有的COM号码:下载使用的程序是COM4;而USB转RS485虚拟成COM5的方式是这样的:在实际操作中使用COM5来进行通信就可以完成数据传输功能。

