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

STM32物联网项目-RS485通信(Modbus协议)_第1张图片

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

GPIO配置

STM32物联网项目-RS485通信(Modbus协议)_第2张图片

USART3配置

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

STM32物联网项目-RS485通信(Modbus协议)_第3张图片

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

STM32物联网项目-RS485通信(Modbus协议)_第4张图片

NVIC配置

发送:采用DMA+TC中断

接收:采用DMA+空闲中断

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

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

STM32物联网项目-RS485通信(Modbus协议)_第5张图片

程序

串口数据收发过程(接收采用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接收模式
}

二、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 */
}

三、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接收
  }
}

四、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);
}

六、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();
  }
}

串口数据收发过程(接收采用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);  
}

二、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 */
}

三、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);   //继续接收数据
   }
 }

实验效果

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

你可能感兴趣的:(STM32物联网项目,stm32,物联网,单片机,学习,arm)