引言:对于串口接收一些不定长的数据,必须面对一个问题:怎么判断一帧数据接收是否完成?通常使用RXNE非空中断配合简单的数据协议,在数据中加入帧头、帧尾,在程序中判断是否接收到帧尾来确定数据接收完毕,因此对每个字节都要触发中断进行判断,比较消耗系统资源,尤其是在一些实时性要求较高的场合,而串口空闲中断可以大大简化数据接收过程的判断。本文章介绍基于stm32cubemx使用DMA+IDLE串口空闲中断实现接收不定长数据。
STM32的IDLE空闲中断产生机制
IDLE空闲中断是在监测到数据接收后(即串口的RXNE位被置位)开始检测,当总线上在一个字节对应的周期内未再有新的数据接收时,控制触发空闲中断的IDLE位被硬件置1, 便会激发一个空闲中断,在中断处理函数中,我们可以解析接收到的不定长数据。
使用工具
软件:stm32cubemx ,keil uVision5,串口收发软件(vofa+),
硬件:stm32f103rct6,ST-link下载器,数据线.
STM32CUBEMX初始化配置
打开stm32cubemx,点击红色方框处,开始新建工程
查找芯片型号,选择对应芯片型号处双击
对sys进行如下配置,便于进行st-link下载
设置rcc,选择高速时钟(HSE)为外部晶振
配置时钟树,本文使用的是f103系列,HCLK最大为72MHz
配置串口:
1.设置MODE为异步通信(Asynchronous)
2.使用默认配置(波特率为115200 Bits/s,传输数据长度为8 Bit,奇偶检验无,停止位为1 Bit, 接收和发送都使能)
3.使能串口中断
设置DMA:
- 点击add,添加通道
- 通道配置均为默认(mode为normal,内存地址每次递增1个byte)
工程命名,选择保存位置,选择使用的编译器及版本
如下配置,点击GENERATE CODE生成代码
代码编写
注释部分为使用双缓冲区接收数据,可有效防止接收中断间隔时间非常短(即发送数据帧的速率很快),MCU来不及处理此次接收到的数据,又产生中断,导致数据会被覆盖的情况
main.h中添加
#define BUFFER_SIZE 100
void USAR_UART_IDLECallback(UART_HandleTypeDef *huart,uint8_t rxlen );
main.c
/* USER CODE BEGIN PV */
uint8_t rxbuffer1[BUFFER_SIZE]={0}; //接收数据缓存数组1
//uint8_t rxbuffer2[BUFFER_SIZE]={0}; //接收数据缓存数组2
//uint8_t flag=0;双缓冲区切换标志
/* USER CODE END PV */
main函数中注意确保DMA初始化函数放在串口初始化之前,
并在串口初始化之后使能IDLE中断、开启串口DMA接收
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();//放在串口初始化之前
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); //使能IDLE中断
HAL_UART_Receive_DMA(&huart1,rxbuffer1,BUFFER_SIZE);//开启串口DMA接收
/* USER CODE END 2 */
在/* USER CODE BEGIN 4 */下添加用户自定义IDLE空闲中断回调函数(注释部分为双缓冲接收)
void USAR_UART_IDLECallback(UART_HandleTypeDef *huart,uint8_t rxlen )
{
if(huart == &huart1) //判断是否为串口1产生中断
{
/* switch(flag)
{
case 0: flag=1;
memset(rxbuffer2,0,BUFFER_SIZE);//清空接收缓存,调用需包含string.h,本例程删去也基本无影响
HAL_UART_Receive_DMA(&huart1,rxbuffer2,BUFFER_SIZE);//重新打开DMA接收
HAL_UART_Transmit_DMA(&huart1, rxbuffer1,rxlen);//将接收到的不定长数据发送到上位机
rxlen = 0;//清除数据长度计数
break;
case 1:flag=0;
memset(rxbuffer1,0,BUFFER_SIZE);//清空接收缓存,调用需包含string.h,本例程删去也基本无影响
HAL_UART_Receive_DMA(&huart1,rxbuffer1,BUFFER_SIZE);//重新打开DMA接收
HAL_UART_Transmit_DMA(&huart1, rxbuffer2,rxlen);//将接收到的不定长数据发送到上位机
rxlen = 0;//清除数据长度计数
break;
}*/
HAL_UART_Transmit_DMA(&huart1, rxbuffer1,rxlen);//将接收到的不定长数据发送到上位机
rxlen = 0;//清除数据长度计数
HAL_UART_Receive_DMA(&huart1,rxbuffer1,BUFFER_SIZE);//重新打开DMA接收
}
}
由于hal库中没有定义IDLE空闲中断的中断处理函数,需要用户自行定义
打开stm32f1xx_it.c,找到void USART1_IRQHandler(void)函数,并添加如下代码:
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
uint8_t rxlen ; //接收一帧数据的长度
if(RESET != __HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE)) //判断idle标志被置位
{
__HAL_UART_CLEAR_IDLEFLAG(&huart1);//清除标志位
HAL_UART_DMAStop(&huart1); // 停止DMA传输
rxlen = BUFFER_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx); //总计数减去未传输的数据个数,得到已经接收的数据个数
USAR_UART_IDLECallback(&huart1,rxlen); // 调用用户定义空闲中断回调函数
}
/* USER CODE END USART1_IRQn 1 */
}
下载程序测试
蓝色为上位机发送数据,绿色为上位机接收数据
测试正常