• STM32物联网项目-RS485通信(Modbus协议)


    RS485通信(Modbus协议)

    协议介绍

    RS485介绍:http://t.csdn.cn/bOuFX

    Modbus协议http://t.csdn.cn/mgioX

    CubeMX配置

    RS-485通信使用到了串口3,TX接到SP3485芯片的DI,RX接到RO,芯片使能脚RS485_DE_nRE接到PG10

    在这里插入图片描述

    此外实验还使用到了SHT30数字温湿度传感器获取环境温湿度,继电器和蜂鸣器可通过触摸按键1和触摸按键2控制启动和关闭,数码管实时显示温湿度

    GPIO配置

    在这里插入图片描述

    USART3配置

    因为Modbus协议多用于工业领域,环境会多干扰,所以波特率常用9600,串口3就配置为9600

    在这里插入图片描述

    开启串口3的发送DMA和接收DMA,都选择普通模式,内存地址增加,因为发送和接收都是放在一个数组中

    在这里插入图片描述

    NVIC配置

    发送:采用DMA+TC中断

    接收:采用DMA+空闲中断

    NVIC配置一定要注意,在代码写完后发现串口通信不了,排查后才发现是NVIC几个中断的优先级有问题,改了优先级就好了

    DMA是搬运串口数据的,所以优先级要最高,因为采用DMA+空闲中断接收,代码中采用判断标志位来调用自己的回调函数,所以这里DMA1通道3就不使能中断,没用到。串口3的优先级设为1,定时器6作用较小,优先级设最低

    在这里插入图片描述

    程序

    串口数据收发过程(接收采用DMA+空闲中断,写法一)

    一、MyInit.c

    在初始化函数中,初始化完定时器、数码管和IIC后,用HAL库提供的宏定义使能串口3的空闲中断,然后调用HAL_UART_Receive_DMA开启串口3的DMA接收模式,因为上位机一打开就会通过串口发送modbus协议数据,通过RS-485接口的A、B线,到达实战板的485芯片,被转化为TTL电平到达串口3,DMA就去搬运数据放到pucRec_Buffer接收缓存中

    /*
    * @name   Peripheral_Set
    * @brief  外设设置
    * @param  None
    * @retval None   
    */
    static void Peripheral_Set()
    {
      printf("---此程序实现采集SHT30温湿度值并显示功能---\r\n");
      printf("Initialization completed,system startup!\r\n");
      printf("Software version is V%.1f\r\n\r\n",SoftWare_Version);
    
      Timer6.Timer6_Start_IT();   //启动定时器
      Display.TM1620_Init();      //数码管初始化
      IIC_Soft.IIC_Init();        //IIC初始化
    
      __HAL_UART_ENABLE_IT(&huart3,UART_IT_IDLE);   //使能串口3空闲中断
      HAL_UART_Receive_DMA(&huart3,UART3.pucRec_Buffer,PUCREC_BUFFER_LEN);   //开启串口3 DMA接收模式
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    二、stm32f1xx_it.c

    待DMA搬运完协议数据后,会触发串口3全局中断USART3_IRQHandler,在该函数里判断空闲中断标志位IDLE是否被置位,是则先清除标志位,再调用自己定义的中断空闲回调函数,要在这个中断源文件前面引入自己的头文件MyApplication.h,才能调用HAL_UART_IDLECallback这个函数

    #include "MyApplication.h"
    ...
    /**
      * @brief This function handles USART3 global interrupt.
      */
    void USART3_IRQHandler(void)
    {
      /* USER CODE BEGIN USART3_IRQn 0 */
      //检测到串口空闲中断
      if(__HAL_UART_GET_FLAG(&huart3,UART_FLAG_IDLE) != 0x00u)
      {
        //先清除IDLE标志位
        __HAL_UART_CLEAR_IDLEFLAG(&huart3);
        //再调用自己的空闲中断回调函数
        HAL_UART_IDLECallback(&huart3);
      }
      /* USER CODE END USART3_IRQn 0 */
      HAL_UART_IRQHandler(&huart3);
      /* USER CODE BEGIN USART3_IRQn 1 */
    
      /* USER CODE END USART3_IRQn 1 */
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    三、CallBack.c

    在自己编写的空闲中断回调函数中调用Modbus协议解析函数Protocol_Analysis,该函数就对串口接收到的Modbus协议进行分析,函数里会先关闭DMA接收,再处理数据,同时会有发送数据的操作,协议解析函数执行完后调用HAL_UART_Receive_DMA重新开启串口DMA接收

    注意:HAL_UART_IDLECallback函数HAL库并没有,是自己命名的

    /*
    * @name   HAL_UART_IDLECallback
    * @brief  串口接收完成空闲中断回调函数
    * @param  huart:串口指针
    * @retval None   
    */ 
    void HAL_UART_IDLECallback(UART_HandleTypeDef *huart)
    {
      //接收完成串口数据后,触发空闲中断,在这里处理接收数据
      if(huart->Instance == huart3.Instance)
      {
        Modbus.Protocol_Analysis(&UART3);   //Modbus协议解析
        HAL_UART_Receive_DMA(&huart3,UART3.pucRec_Buffer,PUCREC_BUFFER_LEN);  //重新开启串口DMA接收
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    四、Modbus.c

    先关闭DMA接收,再处理数据,返回的数据通过UART3.SendArray(COM->pucSend_Buffer,13);发送,发送完就清除接收缓存,再退出执行第三步里的重新开启串口DMA接收的函数

    五、UART3.c

    SendArray函数就是串口3发送函数,要先使能485芯片的,设置为发送模式,然后调用HAL_UART_Transmit_DMA开始DMA发送,这样Modbus协议数据就会通过RS-485接口传输出去

    /*
    * @name   SendArray
    * @brief  发送数组
    * @param  p_arr:待发送的数据首地址,len:数组长度
    * @retval None   
    */
    static void SendArray(uint8_t* p_arr,uint16_t len)
    {
        UART3.RS485_Set_SendMode();
        HAL_UART_Transmit_DMA(&huart3,p_arr,len);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    六、CallBack.c

    待DMA将发送缓存的数据搬运到串口3发送完成后,DMA产生中断请求,几经调用,最终会调用HAL_UART_TxCpltCallback发送完成中断回调函数,在该函数中重新设置485芯片为接收模式,就又能在前面第三步重新开启串口DMA接收时进行数据的接收。

    这样就完成了数据接收和发送的一整个流程

    /*
    * @name   HAL_UART_TxCpltCallback
    * @brief  串口发送完成中断回调函数
    * @param  huart:串口指针
    * @retval None   
    */
    void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
    {
      /*当DMA将内存数据搬运到串口,待串口发送完后产生中断,
      经过多次函数调用,最终来到这里的中断回调函数*/
      if(huart->Instance == huart3.Instance)
      {
        //RS485设置为接收模式
        UART3.RS485_Set_RecMode();
      }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    串口数据收发过程(接收采用DMA+空闲中断,写法二)

    因为最新1.8.4版本的HAL库新增了串口DMA接收产生空闲中断的函数,也提供了现有的空闲中断回调函数,所以换这种写法来实现RS-485通信

    使用函数:HAL_UARTEx_ReceiveToIdle_DMA

    在这里插入图片描述

    空闲中断回调函数:HAL_UARTEx_RxEventCallback

    在这里插入图片描述

    使用这种写法,需要开启串口3 DMA接收的中断,从而使用回调函数

    在这里插入图片描述

    一、MyInit.c

    这次初始化函数就不用宏定义使能IDLE中断了,直接调用HAL_UARTEx_ReceiveToIdle_DMA函数开启DMA接收,接收完数据后会调用空闲中断回调函数HAL_UARTEx_RxEventCallback

    /*
    * @name   Peripheral_Set
    * @brief  外设设置
    * @param  None
    * @retval None   
    */
    static void Peripheral_Set()
    {
      printf("---此程序实现采集SHT30温湿度值并显示功能---\r\n");
      printf("Initialization completed,system startup!\r\n");
      printf("Software version is V%.1f\r\n\r\n",SoftWare_Version);
    
      Timer6.Timer6_Start_IT();   //启动定时器
      Display.TM1620_Init();      //数码管初始化
      IIC_Soft.IIC_Init();        //IIC初始化
    
      //使用库函数,使能串口DMA接收,接收完进入串口空闲中断
      HAL_UARTEx_ReceiveToIdle_DMA(&huart3,UART3.pucRec_Buffer,PUCREC_BUFFER_LEN);  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    二、stm32f1xx_it.c

    串口3全局中断中就不用判断IDLE标志位了,后面直接重写回调函数即可

    /**
      * @brief This function handles USART3 global interrupt.
      */
    void USART3_IRQHandler(void)
    {
      /* USER CODE BEGIN USART3_IRQn 0 */
    
      /* USER CODE END USART3_IRQn 0 */
      HAL_UART_IRQHandler(&huart3);
      /* USER CODE BEGIN USART3_IRQn 1 */
    
      /* USER CODE END USART3_IRQn 1 */
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    三、CallBack.c

    写法一的HAL_UART_IDLECallback函数并不是HAL库提供的,是自己仿照其命名来定义的一个用户函数,而这里的HAL_UARTEx_RxEventCallback函数是HAL库提供的串口空闲中断回调函数,直接调用,并在里面添加Modbus协议解析函数即可,这样就能很方便的接收不定长的数据了,后面的功能步骤与写法一相同

    * @name   HAL_UARTEx_RxEventCallback
    * @brief  串口接收完成空闲中断回调函数
    * @param  huart:串口指针,Size:接收数据长度
    * @retval None   
    */ 
     void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
     {
       if(huart->Instance == huart3.Instance)
       {
         Modbus.Protocol_Analysis(&UART3);   //Modbus协议解析
         HAL_UARTEx_ReceiveToIdle_DMA(&huart3,UART3.pucRec_Buffer,PUCREC_BUFFER_LEN);   //继续接收数据
       }
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    实验效果

    上位机检测到串口号后,发送Modbus协议数据,32单片机接收了并返回了应答数据,上位机能显示SHT30采集的温湿度,同时能通过按钮控制继电器和蜂鸣器,RS-485(Modbus协议)通信成功

  • 相关阅读:
    java惠购网站计算机毕业设计MyBatis+系统+LW文档+源码+调试部署
    flutter系列之:UI layout简介
    【Verilog教程】2.5编译指令
    SpringCloudAlibaba OpenFeign整合及详解
    HTML入门篇---02列表、表格、表单标签
    Rust入门:Rust如何调用C静态库的函数
    Redis(三)session共享
    java零散知识点复习--基础知识
    小区访客导航GIS方案
    【算法学习】-【双指针】-【有效三角形的个数】
  • 原文地址:https://blog.csdn.net/weixin_46251230/article/details/126728979