RS-485总线通信应用
时间:2023-11-28 03:37:02
概述:
掌握总线的基础知识
了解Modbus通信协议的基本知识
能够进行基础Modbue串行通信协议软件开发
能够搭建RS-485总线和编程实现了网络通信
一、Modbus概述
1.什么是Modbus通信协议
Modbus通信协议由Modicon(现为施耐德电气公司品牌)1979年开发,是世界上第一个真正用于工业场所的总线协议。为了更好地普及和促进Modbus施耐德将在以太网上分布式应用Modbus交给协议的所有权IDA(InterfaceforDistributedAutomation,组织分布式自动化接口,专门成立Modbus-IDA组织。该组织的成立为Modbus为未来的发展奠定了基础。
Modbus通信协议是应用于电子控制器的通用协议,已成为一般工业标准。通过本协议,控制器或控制器可以通过网络(如以太网)和其他设备通信。Modbus使不同厂家生产的控制设备连接成工业网络进行集中监控。Modbus通信协议定义了信息帧结构,描述了控制器要求访问其他设备的过程,控制器如何响应其他设备的要求,以及如何检测和记录错误。
在Modbus在互联网上通信时,每个控制器必须知道它们的设备地址,识别根据地址发送的信息,并决定做什么。如果需要响应,控制器将按下Modbus产生反馈信息并发送消息帧格式。
2.Modbus通信协议版本
Modbus通信协议有多个版本:基于串行链路的版本TCP/IP网络版本和基于其他互联网协议的网络版本,其中前两个有更多的实际应用场景。
基于串行链路Modbus通信协议有两种传输方式,即ModbusRTU与ModbusASCII,这两种模式在数值数据表示和协议细节上略有不同。ModbusRTU采用二进制数据表示的紧凑方式,ModbusASCII表达方式比较冗长。数据验证.ModbusRTU采用循环冗余校验方式,而ModbusASCII纵向冗余校验。另外,配置为ModbusRTU无法与模式节点相匹配ModbusASCII节点通信模式。
3.5.2Modbus通信请求及响应
Modbus它是一种单主/多从的通信协议,即在同一时间内,总线上只能有一个主设备,但从设备中可以有一个或多个(最多247个)。主要设备是指启动通信的设备,从接收请求并做出响应的设备。Modbus在网络中,通信总是由主设备启动,当设备未收到独立设备的请求时,数据不会主动发送。
二、Modbus寄存器
寄存器是Modbus存储数据是通信协议的重要组成部分。
Modbus寄存器最初借鉴PLC(ProgrammableLogicalController,可编程控制器)。后来随着Modbus随着通信协议的发展,寄存器的概念不再局限于具体的物理寄存器,而是逐渐扩展到内存区域。根据存储的数据类型及其读写特性,Modbus寄存器被分为4种类型。
三、Modbus功能码
1.功能码分类
Modbus功能码是Modbus新闻帧的一部分代表了要执行的动作。RTU以模式为例,见表3-7,RTU消息帧的Modbus功能代码占1~127字节。
Modbus标准规定了三类Modbus功能代码:公共功能代码、用户自定义功能代码和保留功能代码。公共功能代码通过Modbus协会确认的功能代码是独一无二的。
四、实验
(1)485主机每隔0.5S从传感器数据中查询Modbus帧。
(2)在485网络中从机接收通信帧后,分析内容,判断是否发送给自己,然后根据功能码要求向主机收集响应传感数据。
(3)主机收到传感数据后,向网关报告
(4)网关通过TCP上报网关
1.定义Modbus帧与Modbus协议管理结构
在portocol.h中定义:
//类modbus 接收帧定义 __packed typedef struct { u8 address; //设备地址:0,广播地址;1~255、设备地址。 u8 function; //帧功能,0~255 // u8 count; //帧编号 // u8 datalen; //有效数据长度 u8 *data; ///数据存储区 u16 chkval; //校验值 } m_rev_frame_typedef; //Modbus协议管理结构 typedef struct { u8* rxbuf; //接收缓存区 u16 rxlen; ///接收数据的长度 u8 frameok; //一帧数据接收完成标记:0,尚未完成;1.完成一帧数据的接收 u8 checkmode; //验证模式:0、验证和;1、异或;2CRC8;3,CRC16
2.Modbus通信帧分析函数
///分析一帧数据,存储分析结果fx里面 //注意将使用此函数malloc给fx数据指针申请内存,后续用完fx,一定要释放内存!! //否则会导致内存泄漏!!! //fx:帧指针 //buf:输入数据缓冲区(串口接收到的数据) //len:输入数据长度 //返回值:分析结果,0,OK,另外,代码错误。 m_result mb_unpack_frame(m_send_frame_typedef *tx,m_rev_frame_typedef *rx) { u16 rxchkval=0; ///接收到的验证值 u16 calchkval=0; ////计算获得的 u8 cmd = 0 ; //计算功能码 u8 datalen=0; //有效数据长度 u8 address=0; u8 recbyte=0; u8 res; DBG_B_INFO("主机分析包程序 "); // fx->datalen=0; ///清零数据长度 if(m_ctrl_dev.rxlen>M_MAX_FRAME_LENGTH||m_ctrl_dev.rxlenaddress) {
DBG_R_E("返回地址与发送地址不统一");
return MR_FRAME_SLAVE_ADDRESS; //地址错误
}
cmd=m_ctrl_dev.rxbuf[1];
if (cmd!=tx->function) {
DBG_R_E("发送命令与返回命令不统一");
return MR_FRANE_ILLEGAL_FUNCTION; //命令帧错误
}
switch (cmd) {
case 0x02:res=unpack_disc_reg(tx,rx);
break;
case 0x03:res=unpack_readhold_reg(tx,rx);
break;
case 0x04:res=unpack_readinput_reg(tx,rx);
break;
case 0x06:res=unpack_writehold_reg(tx,rx);
break;
default :
break;
}
} else {
return MR_FRAME_CHECK_ERR;
}
return MR_OK;
}
3.编写读取传感器数据并回复响应帧函数
主机发送读取传感器数据命令,从机解析完主机请求帧后,编写响应的函数。
u8 ReadInputRegister(void)
{
u16 regaddress;u16 regcount;
u16 *input_value_p;
u16 iregindex;
//发送缓冲区
u8 sendbuf|20];
u8 send_cnt=0;
//计算得到的校验值
u16 calchkval=0;
//取出主机请求帧中的素统
regaddress=(u16)(m_ctrl_dev.rxbuf|2)<<8);14.
regaddress|=(u16/(m_ctrl_dev.rxbuf|3]);
//取出主机请求帧中的素
regcount=(u16)(m_ctrl_dev.rxbuf(4]<<8);17.
regcount|=(u16)(m_ctrl_dev.rxbuf(5]);
input_value_p=inbuf;
//组建响应帧
if((1<=regcount)&&(regcount<4)){
if((regaddress>=0)&&(regaddress<=3)){
sendbuf[send_cnt]=SLAVE_ADDRESS;
//从机地址
send_cnt++;
sendbuf[send_cnt]=0x04;
//功能码0x04
send_cnt++;
sendbuf[send_cnt]=regcount*2;
//字节长度
send_cnt++;
iregindex=regaddress-0;
//将寄存器内容赋值给响应帧
while(regcount>0){
sendbuf[send_cnt]=(u8)(input_value_pliregindex]>>8);
send_cnt++;
sendbuf[send_cnt]=(u8)(input_value_pliregindex]&0xFF);
send_cnt++;
iregindex++;
regcount--;
}
switch(m_ctrl_dev.checkmode)
{
case M_FRAME_CHECK_SUM:
//校验和
calchkval=mc_check_sum(sendbuf,send_cnt);
break;
calchkval=me_check_xor(sendbuf,send_cnt);
case M_FRAME_CHECK_XOR://异或校验48.49.break;
case M_FRAME_CHECK_CRC8://CRC8校验
calchkval=me_check_crc8(sendbuf,send_cnt);break;
case M_FRAME_CHECK_CRC16:
//CRC16校验
calchkval=mc_check_crc16(sendbuf,send_cnt);
break;
}
if(m_ctrl_dev.checkmode==M_FRAME_CHECK_CRC16) //如果是CRC16,则有2个字节的CRC
{
sendbuf[send_cnt]=(calchkval>>8)&0XFF; //高字节在前
send_cnt++;
sendbuf[send_cnt]=calchkval&0XFF; //低字节在后
}
RS4851_Send_Buffer(sendbuf,send_cnt+1); //发送这一帧数据
}
}
else
{
return 1;
}
return 0;
}
4.程序结构框架