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

      最后更新:2022-01-31 00:01:23 手机定位技术交流文章

      • modbus协议

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

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

      文章目录

      • 一、modbus协议准备工作
      • 二、modbus协议软件模拟通信
        • (一)使用modbus poll(主机)和modbus slave(从机)进行模拟
        • (二)使用串口助手(主机)和modbus slave软件(从机)
        • (三)串口助手(主机)和STM32(从机)
      • 三、modbus协议编程-设备作为主机
        • (一)功能码0x03--主机读取从机的寄存器数据
        • (二)功能码0x06--主机向某个从机的寄存器中写入数据
        • (三)功能码0x10-主机向某个从机的多个寄存器中写入数据
      • 四、modbus协议编程-设备作为从机
        • (一)功能码0x03--被主机寻址后读取对应的寄存器数据
        • (二)功能码0x06--被主机寻址后向某个寄存器中写入数据
        • (三)功能码0x10-被主机寻址后向多个寄存器中写入数据
      • 五、设备作为主机时可通过按键切换为从机设备进行测试
        • (一)使用串口中断超时接收模式
        • (二)使用DMA进行数据传输模式
      • 六、使用HAL库进行开发
        • (一)STM32cube软件配置
        • (二)代码编辑
        • (三)HAL库踩过的雷

      一、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 01D5 CA
        最后两位数据是CRC校验位,发送数据时软件自动计算的数值追加到数据末尾发出去(软件右下角选择加校验ModbusCRC16,如果软件不支持需要使用在线的CRC校验计算之后手动添加校验位到数据末尾再发送)
        在这里插入图片描述

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

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

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

        在这里插入图片描述
        主机发送指令01 06 00 02 00 0DE9 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协议编程-设备作为主机

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

      如果是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个字节)–要读取的寄存器个数

      • 2-主机对从机返回数据的处理:

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

      • 3-真正的数据处理函数void Host_Func3()

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

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

      • 功能号0x06

        在这里插入图片描述

      • 1-主机发送:

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

      • 2-主机接收的从机数据处理:

        从机地址+功能码+起始地址+成功写入的寄存器个数+CRC
        只是辅助一下从机已经根据指令往对应的寄存器中写入了数据

      (三)功能码0x10-主机向某个从机的多个寄存器中写入数据

      • 功能码0x10

        在这里插入图片描述

      • 主机发送数据的数组填充内容:

        只需要根据上面将缺少的内容再自主增加至参数按照顺序依次对数组进行填充数据即可
        从机地址+功能码+起始地址+寄存器个数+写入字节数+写入的具体数据+CRC校验

      • 从机返回数据处理:

        根据上面的将0x06功能码修改为0x10即可

      四、modbus协议编程-设备作为从机

      设备作为从机使用时肯定有自己专们的一个地址和相关的寄存器,先定义一个寄存器:主机读数据和写数据时操作的寄存器

      • 1-本设备作为从机时的地址

      • 2-只有当数据接收完毕时才进行数据处理

        (1)首先判断自主计算的CRC校验位和接收到数据的校验位是否一致
        (2)其次判断从机地址是不是自己的地址
        (3)数据传输正确且从机地址正确的情况下再根据不同的功能码去执行对应的函数操作
        0x03读取寄存器数据
        0x06写入一个寄存器数据
        0x10写入多个寄存器数据

      • 3-事件处理总体函数

      (一)功能码0x03–被主机寻址后读取对应的寄存器数据

      • 1-作为从机时返回的数据内容:对数组进行填充

        第一个字节必然是从机地址
        第二个字节是功能码
        第三个字节是我要给主机返回几个字节的数据
        第四个字节开始对应寄存器的具体内容(每个寄存器占2个字节)

        第n个字节具体数据内容结束
        对前面所有字节进行CRC校验计算并将CRC计算的数据追加到数组的结尾
        数据封装完毕之后将封装好的数组数据发送出去

      (二)功能码0x06–被主机寻址后向某个寄存器中写入数据

      • 这是从机接收从机的指令往1个寄存器中写入数据

        在这里插入图片描述
        第3-4个字节是要写入的地址
        第5-6个字节是要写入的数据

      • 从机返回数组填充内容:将接收到的数据原路返回即可

      (三)功能码0x10-被主机寻址后向多个寄存器中写入数据

      • 向多个寄存器中写入数据

        在这里插入图片描述
        按照主机的指令往设备的寄存器地址中写入数据
        第1个字节是本设备地址(从机)
        第2个字节是功能码0X06
        第3、4个字节是写入数据的起始地址
        第5、6个字节是写入的寄存器个数
        第7个字节是写入的字节个数(字节个数=寄存器个数*2)
        第8个字节开始是要写入的数据

      • 从机要返回的数据:

        只需要把前6个字节装入数组,再对这6个字节进行CRC校验,将计算的数值追加到数组末尾再发送出去即可

        以上所有部分便是MODBUS RTU协议代码的编写部分,接下来便是其他代码部分的编写了

      五、设备作为主机时可通过按键切换为从机设备进行测试

      (一)使用串口中断超时接收模式

      Modbus协议是建立在485通信基础上的,RS485通信与普通串口通信的本质区别就是多了一个发送数据和接收数据控制位。
      整个测试中使用串口1进行打印调试
      使用串口2作为RS485通信时的收发数据

      1、串口

      STM32串口学习部分的博客—参考链接

      • 串口2初始化部分

        (直接复制原子哥的串口1代码改为串口2)

      • 定义一个串口2发送字节的函数

        如果不使用RS485通信的情况下直接调用这个函数进行发送数据即可,由于此程序是建立在485通信的基础之上,所以在发送数据前需要将将485发送数据控制引脚使能,发送数据完毕之后需要再切换回数据接收模式

      • 485通信

        第一行代码便是启动485通信发送模式,最户一行便是关闭发送,启动接收模式

        通过以上代码便可以将数组中的数据发送出去了,其中i是要发送的字节个数

      • 串口中断函数

        主要是将接收到的数据依次存放到对应的数组中
        当modbus.reflag==1表示还有数据正在处理中,反之则进行数据存储,当开始存储第二个数据时开启定时器计时,主要目的判断接收数据是否完毕,如果超过一段时间没有数据,则表明这一次数据接收完毕

      2、定时器

      • 定时器中断函数

        定时器设置1ms进入中断1次,运行时间不为0的情况下开始计时,超过8ms则表明这一次接收数据完毕,将数据接收结束标志位置1处理(modbus.reflag = 1),当数据接收完毕,则STM32可以对接收到的数据进行数据分析和处理执行相应的操作了
        下面的变量主要是实现1s计时操作

      3、main.c部分

      • 变量定义:

      • 按键部分

        //按键1查看从机01的数据
        //按键2查看从机02的数据
        //按键3查看从机03的数据
        //按键4由主机切换到从机模式(此设备作为从机地址0x02)

      • 主函数部分

      • 测试:

        在这里插入图片描述
        在这里插入图片描述
        在这里插入图片描述

      • 按下按键4之后使用modbus poll作为主机链接之后如图

        读操作:
        在这里插入图片描述

        写操作

        在这里插入图片描述

        写多个寄存器
        在这里插入图片描述
        在这里插入图片描述

      • 代妈下载

        模式1代码下载链接-STM32+RS485+MODBUS协议(主机+从机代码)+串口+定时器

      (二)使用DMA进行数据传输模式

      使用DMA数据进行传输
      DMA部分的使用参考链接:对DMA的使用很有帮助
      STM32-DMA数据传输(USART-ADC-数组)–链接1

      STM32-ADC(独立模式、双重模式)+DMA读取数据+部分基础知识–链接2

      在这里插入图片描述

      DMA数据传输有常规模式和循环模式两种,此处使用的是常规模式,由于常规模式下只能发送一次数据,只发送一次数据肯定是不可以的,因为我们要多次发送数据,因此要先解决这个问题,实现可以多次发送数据。

      在使用之前肯定需要先在串口发送数据的基础上进行DMA发送数据改造最终实现多次发送数据,具体理解参考下面博主的博客进行学习,下面直接上代码。
      STM32 DMA正常模式等待传输完成和开始下一次传输–链接

      • 重新使能DMA

        在DMA每次发送数据之前都需要调用此函数进行重新使能
        主机寻址从机时发送的字节个数永远是8个字节数据

        既然发送数据需要重新使能,那我们接收数据也需要重新使能,不可能只接收一次数据,主机寻址从机时,从机返回的数据字节个数=固定的5个字节+寄存器个数*2

      • DMA发送数据函数

      • 主机填充发送的数据+对从机返回的数据进行处理

      • 函数整合

        对上面函数的调用进行整合使用,在主函数调用此函数即可实现数据的发送和接收数据的处理

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

      • DMA中断函数

        (只开启DMA的接收中断即可,串口2除了基本的初始化配置不需要开启中断,不需要编写中断服务函数)

      • main.c文件的内容

      功能和使用介绍如下:

      • 宏定义设置

        //宏定义设置区
        #define sl_ID 0x01 //从机地址
        #define st_address 0x0000 //起始地址
        #define sl_num 3 //读取寄存器个数
        #define slave_count 3 //要读取的从机个数

      • 主函数内容

      • 测试:

        在这里插入图片描述
        在这里插入图片描述

      • 代码下载

        模式2代码下载链接-STM32+RS485+DMA+modbus协议

      六、使用HAL库进行开发

      • 初始HAL库

        这次HAL库是个意外,也许是个新的开始。老师以为我一直在学HAL库的使用,其实我一直在看STM32标准库,以至于我把写好的代码发给老师后,第二天老师问我咋配置的,我才知道我俩整差了,然后利用一天时间在标准库的基础上把代码改成了HAL库(直接复制标准库下的modbus文件夹过去稍作修改,只用stm32cube进行定时器,串口、RS485部分的配置即可)

        才开始打算直接用HAL库改DMA形式传输数据,改了好多次没成功就放弃,直接用串口超时接收的形式进行数据收发了,经历过DMA后这次认怂比较快,用串口肯定还是不能直接干,当然要从点灯开始,首先学着使用STM32cube生成代码去点灯,做按键实验,然后有点感觉了才开始搞点大动作,串口1,串口2,定时器,RS485控制引脚齐上阵。

        由于第一次使用HAL库着实踩了不少雷,和标准库开发还是有不少差距的,HAL库是主流,也许该学HAL库了。

      (一)STM32cube软件配置

      • 使用过程中主要参考以下博客

        1-STM32CubeMX教程–功能介绍

        2-STM32串口接收中断——基于HAL库

        3-STM32 HAL库 CubeMX教程(二)定时器基本使用

      • 主要用到的部分

        在生成代码的过程中主要用到了RS485发送/接收数据的使能引脚、串口1(用于打印调试信息)、串口2(用于485通信)、定时器1用于定时计数

      • 485使能引脚

        485使能引脚是PD7,设置为输出模式即可,还给它起了个名字叫RS485_contrl
        在这里插入图片描述

      • RCC设置

        在这里插入图片描述

      • sys设置如下:

        我严重怀疑最初是因为这里忘了选择导致仿真器无法使用
        在这里插入图片描述

      • 串口1

        在这里插入图片描述

      • 串口2

        串口2要使能中断了(接收数据的时候需要用到),其他设置默认
        在这里插入图片描述

      • 定时器

        对定时器1进行配置定时1ms配置(其他设置默认)
        定时器1使用软件进行配置的过程可完全参考这个博客
        在这里插入图片描述

      • NVIC部分

        在这里插入图片描述

      • 时钟配置:

        根据图中的指定位置输入72回车自动配置即可,
        在这里插入图片描述

      • 生成代码

        在这里插入图片描述

        在这里插入图片描述
        这样简单的几步就可以完成软件部分的配置了,可以生成我们需要的部分代码,然后再生成的代码基础上进行修改即可

      (二)代码编辑

      • 串口1重定向

        首先对串口1部分进行代码改造-重定向printf
        STM32串口重定向printf出现FILE未定义问题—参考链接1
        STM32 printf 死机 printf半主机模式–参考链接2
        在uart.c文件中添加以下代码,为了可以直接使用printf打印调试信息------别忘了调用stdio.h头文件

      • 串口2中断服务函数中加入以下代码

        在这里插入图片描述

      • MODBUS文件

        modbus.c文件和modbus.h文件直接复制过来添加上即可,该调用头文件的地方调用对应的头文件即可
        也要对其进行替换
        u16替换为uint16_t
        u8替换为uint8_t
        在这里插入图片描述

      • 在main.h中加入以下代码

        在这里插入图片描述

      • 在main.c中添加以下代码

        添加一个使用串口2发送数据的函数(485通信)
        在这里插入图片描述

      • 在主函数下方添加定时器部分的代码

      • 主函数的初始化部分加入以下代码

      • while(1)主要代码

        共包含3部分测试(每一部分需单独测试)
        1-主机读取从机数据测试(已经打开注释)
        2-主机向从机的一个寄存器中写入数据
        3-本设备作为从机使用,作为从机时地址为0x02,测试完一个注释掉,再打开另一个测试

        STM32标准库和HAL库均介绍完毕。

      • 代码下载

        HAL库代码下载链接–STM32HAL库+RS485+串口+定时器+Modbus协议(主机+从机测试)

      (三)HAL库踩过的雷

      最后说下使用HAL库踩的雷:

      • 1- 代码不放在指定位置丢失

        刚开始不知道自己写的代码必须写到指定区域,如果不是用户区域的位置,重新生成代码加载的时候之前写的代码就白写了,真实消失的一干二净。—代码丢失了2次才发觉这个问题

      • 2- 仿真器出故障板子无法下载

        使用的仿真器是免驱动类型,下载了某个程序后确提示检测不到仿真器,无法下载程序,换板子仍然是同样的情况,-尝试了百度上很多方法都不行,就差改下载模式了(没敢试)
        这里真的浪费了不少时间,得认怂了,仿真器不行还有串口下载,然后使用串口下载程序,发现可以,就又尝试仿真器下载,居然可以用了–后来也找到了正解。

        参考链接1
        参考链接2
        避免出现这种错误:切记不要忘记选择debug方式
        在这里插入图片描述
        解决问题最直接方式:使用串口下载程序即可解决此问题

        在这里插入图片描述

      • 3、使用STM32cube生成代码错误

        参考链接3-解决办法

      • 4、文件保存失败–真是个头大的问题

        对于重新生成的代码需要加载一下,大多数情况点“是”选项代码就更新了,也有好多次出现头文件需要重新保存,Ctrl+S的时候会提示保存的文件名和保存路径,如果在新的路径下保存,即使添加上路径,会报错(因为在默认的路径下还有一个此文件),保存在默认的路径下替换文件,也会出现错误。

      • 5、过完年HAL库开发估计继续踩雷

      本文由 在线网速测试 整理编辑,转载请注明出处,原文链接:https://www.wangsu123.cn/news/17695.html

          热门文章

          文章分类