Bootstrap

STM32的HAL库___串口数据处理


前言

在STM32常用串口协议实现与其它外设交互数据,发送数据简单,但是如何方便快捷的处理通过串口接收到的数据并没那么简单。在STM32中,USART发送接收有三种方式:轮询,中断,DMA。其中轮询的方式过于笨拙,一般本人不会选择这种方式,下文中将从中断方式,IDLE+DMA方式进行介绍,以及如何对循环数组中的数据提取当次接收的数据。


一、中断方式

发送方式:

HAL_UART_Transmit(&huart1, (uint8_t*)"test\r\n", 7, 0xFFFF);

调用此函数即可输出字符串


接收方式:

HAL_UART_Receive_IT(&huart1, data_rec, 10);

使用此函数后,即可当串口收到数据后便往data_rec数组里存数据,存了10个数据以后,会自动调用回调函数

void HAL_UART_RxCpltCallback(UART_HandleTypeDef* huart)

在此函数中,即可对存入的10个数据统一进行处理。这种方式勉强能实现功能,但是谈不上方便,缺点如下:

  • 首先HAL_UART_Receive_IT函数是一次性的,在传递10个数据以后便不再传了,需要循环调用该函数才能一直传数据,这种方式对于我这种懒人来说属实难受;
  • 另外,只有当传递完10个数据才能通过回调函数进行处理,那么对于不定长的数据岂不是很不友好?
  • 每次来一个字符就进一次中断,会占用一部分CPU的资源

为了解决上述痛点,引入下面这种解决方案

二、空闲中断+DMA

空闲中断(IDLE)简单来说,就是检测到接收数据后,在数据总线上的一个字节时间内,没有接收到数据触发空闲中断。RXNE置位一次,空闲总线就检测一次!另外为了减少误进入串口空闲中断,串口RX的IO管脚一定设置成Pull-up<上拉模式>。

__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);//使能空闲中断

DMA就不做详细介绍了,它的作用就是不断的将串口接收到的数据转移到内存中(程序中定义存数据的数组),将其配置为循环模式,则可以初始化一次,就拥有一个永久的搬运工了。当一帧数据搬完以后,空闲中断就会触发,此时代码中就可以去对数组中的数据进行处理了。

HAL_UART_Receive_DMA(&huart1, data_rec, 10);//告诉DMA传送地址及数据长度

上面介绍了IDLE和DMA相互配合的逻辑,下面介绍如何对数据进行提取。因为不同帧的数据都在同一个数组中,如何分开它们是一个问题。首先得知道DMA搬运的效果是怎么样的,利用stlink的在线仿真,能看到数据从数组的头开始存放,一直到末尾,然后多余的数据将会从头继续存放,覆盖掉原先的数据,如此往复,类似于一个循环数组。
rst

为了定位本次帧在数组中的起始位置和终止位置,需要用到函数

__HAL_DMA_GET_COUNTER()

此函数可以获得经过此次数据传递后,该数组还剩余多少空间,比如第一次传了4个数据,那么此函数返回值即为10-4=6,第二次传了3个数据,那么此函数返回值为10-3-4=3,第三次传了3个数据,理论上是10-3-3-4=0;然而此函数返回值为10,这是需要注意的地方,空的同时也是满!
基于上述的描述,提取当次帧的函数如下:

void USER_UART_IDLECallback(UART_HandleTypeDef *huart)
{
    static uint8_t pre_index,now_index = 0;
    now_index  = length - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);   //当前指向的位置
	Ble_DataProcess(pre_index,now_index);
	pre_index = now_index;
}

void Ble_DataProcess(uint8_t pre_index,uint8_t now_index)
{
	uint8_t i,j,temp_index = 0;

	if(now_index <= pre_index)
	{
		temp_index = now_index + length;

	}
	else{
		temp_index = now_index;
	}
	
	j=0;
	for(i=0;i<length;i++)//暂存数组清空
	{
		temp[j] = 0;
		j++;
	}
	
	j=0;
	for(i=pre_index;i<temp_index;i++)//将本次获得的数据进行填充到临时数组
	{
		temp[j] = data_rec[i%length];
		j++;
	}
	HAL_UART_Transmit(&huart1, temp, length, 0xFFFF);
}

大家稍微看一下应该能理清里面逻辑。

最后提一下串口DMA的发送方式

HAL_UART_Receive_DMA();

使用此函数发送数据出现一个问题:不能连续使用此函数进行数据发送,自己理解的原因大概是第一个DMA在传送数据的并没有传完,而第二个DMA数据发送请求就到了,因此第二个DMA传送函数便无法执行。解决办法:可以延时一段时间;也可以加个while循环,当判断第一个DMA传送数据完成后才跳出。
个人更倾向于直接用HAL_UART_Transmit(),毕竟人懒,就喜欢代码写的少的,哈哈哈。

总结

以后再遇到关于串口接收不定长数据的情形时就可采用上述方法,如果理解有困难或者想知道在STM32CubeMX中的相关配置,可以点击这里下载完整工程代码

;