RS485介绍:http://t.csdn.cn/bOuFX
Modbus协议:http://t.csdn.cn/mgioX
RS-485通信使用到了串口3,TX接到SP3485芯片的DI,RX接到RO,芯片使能脚RS485_DE_nRE接到PG10
此外实验还使用到了SHT30数字温湿度传感器获取环境温湿度,继电器和蜂鸣器可通过触摸按键1和触摸按键2控制启动和关闭,数码管实时显示温湿度
因为Modbus协议多用于工业领域,环境会多干扰,所以波特率常用9600,串口3就配置为9600
开启串口3的发送DMA和接收DMA,都选择普通模式,内存地址增加,因为发送和接收都是放在一个数组中
发送:采用DMA+TC中断
接收:采用DMA+空闲中断
NVIC配置一定要注意,在代码写完后发现串口通信不了,排查后才发现是NVIC几个中断的优先级有问题,改了优先级就好了
DMA是搬运串口数据的,所以优先级要最高,因为采用DMA+空闲中断接收,代码中采用判断标志位来调用自己的回调函数,所以这里DMA1通道3就不使能中断,没用到。串口3的优先级设为1,定时器6作用较小,优先级设最低
一、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();
}
}
因为最新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协议)通信成功