RS-485总线通信应用

概述:

掌握总线基础知识

了解Modbus通讯协议基本知识

能够进行基于Modbue串行通信协议软件开发

能够搭建RS-485总线并编程实现组网通信

一、Modbus概述

        1.什么是Modbus通信协议
Modbus通信协议由Modicon(现为施耐德电气公司的一个品牌)在1979年开发,是全球第一个真正用于工业现场的总线协议。为了更好地普及和推动Modbus在以太网上的分布式应用,目前施耐德公司已将Modbus协议的所有权移交给IDA(Interface for DistributedAutomation,分布式自动化接口)组织,并专门成立了Modbus-IDA组织。该组织的成立为Modbus未来的发展奠定了基础。
Modbus通信协议是应用于电子控制器上的一种通用协议,目前已成为一通用工业标准。通过此协议,控制器之间或者控制器经由网络(例如,以太网)与其他设备之间可以通信。Modbus使不同厂商生产的控制设备可以连成工业网络,进行集中监控。Modbus通信协议定义了一个消息帧结构,并描述了控制器请求访问其他设备的过程,控制器如何响应来自其他设备的请求,以及怎样侦测错误并记录。
在Modbus网络上通信时,每个控制器必须知道它们的设备地址,识别按地址发来的消息,决定要做何种动作。如果需要响应,则控制器将按Modbus消息帧格式生成反馈信息并发出。

2.Modbus通信协议的版本
Modbus通信协议有多个版本:基于串行链路的版本、基于TCP/IP的网络版本以及基于其他互联网协议的网络版本,其中前面两者的实际应用场景较多。
基于串行链路的Modbus通信协议有两种传输模式,分别是Modbus RTU与ModbusASCII,这两种模式在数值数据表示和协议细节方面略有不同。Modbus RTU是一种紧凑的、采用二进制数据表示的方式,而Modbus ASCII的表示方式更加冗长。在数据校验方面.Modbus RTU采用循环冗余校验方式,而Modbus ASCII采用纵向冗余校验方式。另外,配置为Modbus RTU模式的节点无法与Modbus ASCII模式的节点通信。
3.5.2 Modbus通信的请求与响应
Modbus是一种单主/多从的通信协议,即在同一段时间内总线上只能有一个主设备,但可以有一个或多个(最多247个)从设备。主设备是指发起通信的设备,从设备是接收请求并做出响应的设备。在Modbus网络中,通信总是由主设备发起,而从设备没有收到来自主设备的请求时不会主动发送数据。

二、Modbus寄存器

         寄存器是Modbus通信协议的一个重要组成部分,它用于存放数据。
Modbus寄存器最初借鉴于PLC(Programmable Logical Controller,可编程控制器)。后来随着Modbus通信协议的发展,寄存器这个概念也不再局限于具体的物理寄存器,而是逐渐拓展到了内存区域范畴。根据存放的数据类型及其读写特性,Modbus寄存器被分为4种类型。

 三、Modbus功能码

1.功能码分类
        Modbus功能码是Modbus消息帧的一部分,它代表将要执行的动作。以RTU模式为例,见表3-7,RTU消息帧的Modbus功能码占用一个字节,取值范围为1~127。
        Modbus标准规定了3类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,异或;2,CRC8;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.rxlen<M_MIN_FRAME_LENGTH) {
        m_ctrl_dev.rxlen=0;			//清除rxlen
        m_ctrl_dev.frameok=0;		//清除framok标记,以便下次可以正常接收

        return MR_FRAME_FORMAT_ERR;//帧格式错误
    }
    datalen=m_ctrl_dev.rxlen;
    DBG_B_INFO("当前数据长度 %d",m_ctrl_dev.rxlen);

    switch(m_ctrl_dev.checkmode) {
    case M_FRAME_CHECK_SUM:							//校验和
        calchkval=mc_check_sum(m_ctrl_dev.rxbuf,datalen+4);
        rxchkval=m_ctrl_dev.rxbuf[datalen+4];
        break;
    case M_FRAME_CHECK_XOR:							//异或校验
        calchkval=mc_check_xor(m_ctrl_dev.rxbuf,datalen+4);
        rxchkval=m_ctrl_dev.rxbuf[datalen+4];
        break;
    case M_FRAME_CHECK_CRC8:						//CRC8校验
        calchkval=mc_check_crc8(m_ctrl_dev.rxbuf,datalen+4);
        rxchkval=m_ctrl_dev.rxbuf[datalen+4];
        break;
    case M_FRAME_CHECK_CRC16:						//CRC16校验
        calchkval=mc_check_crc16(m_ctrl_dev.rxbuf,datalen-2);
        rxchkval=((u16)m_ctrl_dev.rxbuf[datalen-2]<<8)+m_ctrl_dev.rxbuf[datalen-1];
        break;
    }
	
    DBG_B_INFO("calchkval = 0x%x  、rxchkval  = 0x%x   、datalen = 0x%x",calchkval,rxchkval,datalen);
//  DBG_B_INFO("cmd = 0x%x ",cmd);

    m_ctrl_dev.rxlen=0;			//清除rxlen
    m_ctrl_dev.frameok=0;		//清除framok标记,以便下次可以正常接收

    if(calchkval==rxchkval) {	//校验正常
    
        address=m_ctrl_dev.rxbuf[0];
		
        if (address!= tx->address) {
			      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.程序结构框架

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

李今天摸鱼了嘛?

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值