STM32+RS485+Modbus-RTU(主机模式+从机模式)-标准库/HAL库开发

本文详细介绍了MODBUS RTU协议的编程方法,包括设备作为主机和从机的功能码使用,通过按键切换模式,并提供了STM32标准库和HAL库的实现案例。
该文章已生成可运行项目,
  • modbus协议

    完成modbus协议的编程之后,设备可以分别作为modbus协议的主机或者从机进行测试,使用模拟软件测试完毕后,完整代码以三个版本的形式进行介绍

    1、版本一:使用串口接收数据超时完成一次数据的接收(STM32标准库)
    2、版本二:进阶版-使用DMA形式进行数据发送和接收(STM32标准库)
    3、版本三:初次使用HAL库完成对以上代码的修改工作(STM32HAL库)

一、modbus协议准备工作

modbus poll和modbus slave模拟软件下载(下载可直接使用)

modbus协议辅助软件下载-百度网盘链接 提取码:8g9c

modbus模拟软件使用方法-参考博客链接

modbus协议讲解及stm32实现—modbus协议看的一个视频,讲的很详细,软件也可以加群下载

b站up主上传了三个相关的视频

  • 视频1

    讲的很详细,可以细细的看一遍,讲解的内容包含了模拟软件的使用和部分代码的编写,看懂了写代码就方便很多----强烈推荐

  • 视频2

    总时长一个小时四十分钟,前面看了一个小时没有任何实质性的讲解(可以不看),一个小时才开始步入正题,看了几分钟,后面就没耐心看直接关了

  • 视频3

    第三个视频没有看,不清楚讲了啥

    up主是随手录的,虽然有些瑕疵,但是一个很好的资源,对modbus的学习还是很有帮助的,很感谢up主的分享。
    在这里插入图片描述

  • modbus协议

    modbus协议基本知识不再进行赘述,详情点击以下链接

    Modbus RTU 协议使用汇总-modbus协议参考博客链接1

    MODBUS_RTU通讯规约–modbus协议参考博客链接2

二、modbus协议软件模拟通信

  • 数据协议格式

    在这里插入图片描述

  • 功能码0x03–读取从机寄存器的数据
    ![在这里插在这里插入图片描述
    在这里插入图片描述

  • 功能码0x06–向一个寄存器中写入数据

    在这里插入图片描述

  • 功能码0x10-多个寄存器写入数据

    在这里插入图片描述

(一)使用modbus poll(主机)和modbus slave(从机)进行模拟

1、主机可以读取从机的数据
2、从机修改数据后,主机这边会更新数据
3、主机对某个地址的值进行修改,从机那边对应更新
在这里插入图片描述

(二)使用串口助手(主机)和modbus slave软件(从机)

  • 主机读取从机1个寄存器的数据

    主机使用功能码03,读取从机地址01,起始地址为0x0001的一个寄存器数据
    主机发送:01 03 00 01 00 01 D5 CA
    最后两位数据是CRC校验位,发送数据时软件自动计算的数值追加到数据末尾发出去(软件右下角选择加校验ModbusCRC16,如果软件不支持需要使用在线的CRC校验计算之后手动添加校验位到数据末尾再发送)
    在这里插入图片描述

  • 主机读取从机2个寄存器的数据

    从机地址0x01,起始地址是0x0001的2个寄存器数据
    主机发送01 03 00 01 00 02 95 CB
    在这里插入图片描述

  • 主机向从机的1个寄存器中写入数据

    在这里插入图片描述
    主机发送指令01 06 00 02 00 0D E9 CF
    向地址0002 写入数据000D

  • 主机向从机的多个寄存器中写入数据

    在这里插入图片描述
    01 10 00 05 00 02 04 01 02 03 04 92 9F
    从00 05地址开始发送两个寄存器,四个字节的数据,数据分别发送的是01 02 03 04

(三)串口助手(主机)和STM32(从机)

  • 读取两个寄存器数据

在这里插入图片描述

  • 读取7个寄存器数据

在这里插入图片描述

  • 写入两个寄存器数据

    对第一个第二个元素的值进行写入,并且读取寄存器的数值
    在这里插入图片描述
    在这里插入图片描述

  • 7个寄存器中写入数据

    先用串口助手对7个寄存器写入数值,关闭串口后,使用modbus poll软件读取数值
    在这里插入图片描述

三、modbus协议编程-设备作为主机

  • 定义一个结构体:(设备作为主机或从机时均会用到)

     typedef struct 
     {
     	//作为从机时使用
      	 u8  myadd;        //本设备从机地址
     	u8  rcbuf[100];   //modbus接受缓冲区
     	u8  timout;       //modbus数据持续时间
     	u8  recount;      //modbus端口接收到的数据个数
     	u8  timrun;       //modbus定时器是否计时标志
     	u8  reflag;       //modbus一帧数据接受完成标志位
     	u8  sendbuf[100]; //modbus接发送缓冲区
     	
     	//作为主机添加部分
     	u8 Host_Txbuf[8];	//modbus发送数组	
     	u8 slave_add;		//要匹配的从机设备地址(做主机实验时使用)
     	u8 Host_send_flag;//主机设备发送数据完毕标志位
     	int Host_Sendtime;//发送完一帧数据后时间计数
     	u8 Host_time_flag;//发送时间到标志位,=1表示到发送数据时间了
     	u8 Host_End;//接收数据后处理完毕
     }MODBUS;
    

如果是HAL库中使用把u8换为uint8_t即可

(一)功能码0x03–主机读取从机的寄存器数据

首先看一下主机发送数据和从机发送数据的格式以及每一部分对应的含义
在这里插入图片描述
主机寻址从机时共发送8个字节的数据(从机地址-ID号1个字节,功能码1个字节,起始地址2个字节,寄存器个数2个字节,CRC校验位2个字节)

在这里插入图片描述

主机寻址从机读取寄存器数据还是比较好写的-就是填充一个8个字节的数组内容,然后通过串口将数据发出去
首先我们需要一个从机地址,然后是功能号(直接写死0X03),还有起始地址,读取的寄存器个数,CRC校验位(通过函数自主计算),那我们就只需要3个参数了

  • 1-主机寻址从机读取寄存器数据(功能码0x03)

    参数1从机地址(1个字节)–要寻址的从机
    参数2起始地址(2个字节)–开始读取数据的地址
    参数3寄存器个数(2个字节)–要读取的寄存器个数

     //参数1从机地址,参数2起始地址,参数3寄存器个数
     void Host_Read03_slave(uint8_t slave,uint16_t StartAddr,uint16_t num)
     {
     	int j;
     	uint16_t crc;//计算的CRC校验位
     	modbus.slave_add=slave;//这是先把从机地址存储下来,后面接收数据处理时会用到
     	modbus.Host_Txbuf[0]=slave;//这是要匹配的从机地址
     	modbus.Host_Txbuf[1]=0x03;//功能码
     	modbus.Host_Txbuf[2]=StartAddr/256;//起始地址高位
     	modbus.Host_Txbuf[3]=StartAddr%256;//起始地址低位
     	modbus.Host_Txbuf[4]=num/256;//寄存器个数高位
     	modbus.Host_Txbuf[5]=num%256;//寄存器个数低位
     	crc=Modbus_CRC16(&modbus.Host_Txbuf[0],6); //获取CRC校验位
     	modbus.Host_Txbuf[6]=crc/256;//寄存器个数高位
     	modbus.Host_Txbuf[7]=crc%256;//寄存器个数低位
     	//发送数据包装完毕(共8个字节)
     	//开始发送数据
     	RS485_TX_ENABLE;//使能485控制端(启动发送)  
     	for(j=0;j<i;j++)
     	{
     	 Modbus_Send_Byte(modbus.sendbuf[j]);
     	}
     	RS485_RX_ENABLE;//失能485控制端(改为接收)
     }
    
  • 2-主机对从机返回数据的处理:

    (1)首先我们要判断一下数据是否接收完毕,只有在数据接收完毕的情况下我们才能对数据进行处理(modbus.reflag==1表示接收完毕),进入下一步。
    (2)数据接收完毕之后我们需要将自行计算的CRC校验位和接收到的CRC校验位进行比较看其是否一致,如果一致的话才表明数据接收正确,进行下一步。
    (3)判断完CRC校验位之后再去判断是不是主机寻址的从机返回的数据,如果符合条件进行下一步。
    (4)以上条件都符合的条件下再去对接收到的数据做处理:从返回的数据中将有用的吧数据提取出来进行串口打印或者做其他用途

     //主机接收从机的消息进行处理功能码0x03
     void HOST_ModbusRX()
     {
     	u16 crc,rccrc;//计算crc和接收到的crc
     
       if(modbus.reflag == 0)  //如果接收未完成则返回空
     	{
     	   return;
     	}
     	//接收数据结束
     	
     	//(数组中除了最后两位CRC校验位其余全算)
     	crc = Modbus_CRC16(&modbus.rcbuf[0],modbus.recount-2); //获取CRC校验位
     	rccrc = modbus.rcbuf[modbus.recount-2]*256+modbus.rcbuf[modbus.recount-1];//计算读取的CRC校验位
     	
     	if(crc == rccrc) //CRC检验成功 开始分析包
     	{	
     	   if(modbus.rcbuf[0] == modbus.slave_add)  // 检查地址是是对应从机发过来的
     		 {
     			 if(modbus.rcbuf[1]==3)//功能码时03
     		      Host_Func3();//这是读取寄存器的有效数据位进行计算
     		 }
     		 
     	}	
     	 modbus.recount = 0;//接收计数清零
        modbus.reflag = 0; //接收标志清零
     	
     }
    
  • 3-真正的数据处理函数void Host_Func3()

    我们通过前面对从机返回数据的格式进行分析可知,数据中的第三个字节(对应modbus.rcbuf[2]位)是返回数据的有效个数,从第四个字节开始是数据的有效内容,一个寄存器数据分为高位和低位,所有两个字节是一个完整的数据,然后将其进行计算即可。

     void Host_Func3()
     {
     	int i;
     	int count=(int)modbus.rcbuf[2];//这是数据个数
     	
     	printf("从机返回 %d 个寄存器数据:\r\n",count/2);
     	for(i=0;i<count;i=i+2)
     	{
     		printf("Temp_Hbit= %d Temp_Lbit= %d temp= %d\r\n",(int)modbus.rcbuf[3+i],(int)modbus.rcbuf[4+i],(int)modbus.rcbuf[4+i]+((int)modbus.rcbuf[3+i])*256);
     	}
     }
    

(二)功能码0x06–主机向某个从机的寄存器中写入数据

  • 功能号0x06

    在这里插入图片描述

  • 1-主机发送:

    对于要发送的数组填充工作如下:只不过这次增加了一个功能码参数

     //向一个寄存器中写数据的参数设置
     void Host_write06_slave(uint8_t slave,uint8_t fun,uint16_t StartAddr,uint16_t num)
     {
     
本文章已经生成可运行项目
评论 201
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

永栀哇

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

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

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

打赏作者

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

抵扣说明:

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

余额充值