概要

STM32的串口接收不定长数据,最近在看超子说物联网,感觉太麻烦了不适合直接上手,然后我自己总结了两种方法:
1、不利用DMA
2、利用DMA方式

整体架构流程

这个HAL整起来是方便,但是HAL整体来说对于细节很到位就是逻辑很强逻辑,也就很繁琐,效率又不高,还缺胳膊少腿的。好用,就比如说这个串口接收,没有专门的方法调用。搞的就很麻烦,我第一次接触就是这感觉哈,大佬除外。

然后我看到超子说物联网可以用这个串口的空闲中断来搞这个串口的不定长接收。

大致的原理是:在接收每一段数据帧之后就会放开这个串口,产生了这个串口的空闲中断(IDLE),然后我们可以捕捉这个UART_FLAG_IDLE这个标志位,然后这个标志位被置位了,我们就可以说这段数据被接收完成了。

我们使用STM32F407给大家做一下,大概流程

不用DMA的方式

先选择SYS
在这里插入图片描述
在这里插入图片描述

使能串口1
在这里插入图片描述
开启中断
在这里插入图片描述
配置时钟
在这里插入图片描述
在这里插入图片描述
生成工程
在这里插入图片描述
进入代码打开usart.c的MX_USART1_UART_Init这个函数在后面加上开启中断的代码

开启串口接收和串口的空闲中断之后就是一直等待 ,等待串口接收到数据,一旦接收数据就触发串口中断进入USART1_IRQHandler函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void MX_USART1_UART_Init(void)
{

/* USER CODE BEGIN USART1_Init 0 */
/* USER CODE END USART1_Init 0 */
/* USER CODE BEGIN USART1_Init 1 */
/* USER CODE END USART1_Init 1 */
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart1) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN USART1_Init 2 */
__HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE); // 开启接收中断
__HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE); // 开启串口的空闲中断
/* USER CODE END USART1_Init 2 */

}

在stm32f4xx_it.c中,分别判断是否是接收中断还是空闲中断,如果是接收中断调用HAL_UART_Receive去接收一个数组,当一个数据帧接受完,就触发了空闲中断,以为串口接不到数据了,就直接放开串口,进入空闲中断后操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 定义变量
uint8_t rx_buffer[100];//接收数组
uint8_t rx_len = 0; //接收到的数据长度
/**
* @brief This function handles USART1 global interrupt.
*/
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
uint8_t res;
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */

if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_RXNE) != RESET) // 接收中断的标志位
{
HAL_UART_Receive(&huart1,&res,1,1000);
if(rx_len>=100)
{
rx_len = 0; //防止串口被刷爆 就是防止越界
}
rx_buffer[rx_len++] = res;
__HAL_UART_CLEAR_FLAG(&huart1,UART_FLAG_RXNE);
}

if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE) != RESET) // 空闲中断的标志位
{
HAL_UART_Transmit(&huart1,rx_buffer,rx_len,1000);
rx_len = 0;
__HAL_UART_CLEAR_IDLEFLAG(&huart1); // 清除空闲中断的标志位
}

/* USER CODE END USART1_IRQn 1 */
}

在这里插入图片描述

使用DMA方式

在创建工程的时候方法一样只需要在

在这里插入图片描述
在这里插入图片描述
还是在usart.c的MX_USART1_UART_Init这个函数在后面加上开启中断的代码,开启DMA的接收

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
void MX_USART1_UART_Init(void)
{

/* USER CODE BEGIN USART1_Init 0 */

/* USER CODE END USART1_Init 0 */

/* USER CODE BEGIN USART1_Init 1 */

/* USER CODE END USART1_Init 1 */
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart1) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN USART1_Init 2 */
HAL_UART_Receive_DMA(&huart1,rx_buffer,100); // 开启DMA接收
__HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE); // 开启串口的空闲中断
/* USER CODE END USART1_Init 2 */

}

进入中断函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#define MAX 100
uint8_t rx_buffer[MAX];//接收数组
uint8_t rx_len = 0; //接收到的数据长度
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 */

if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE) != RESET) // 空闲中断的标志位
{
HAL_UART_DMAStop(&huart1); //停止接收
rx_len = MAX - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx); // 计算接收的数据长度
HAL_UART_Transmit(&huart1,rx_buffer,rx_len,1000);
HAL_UART_Receive_DMA(&huart1,rx_buffer,100); // 开启DMA继续接收
__HAL_UART_CLEAR_IDLEFLAG(&huart1);
}

/* USER CODE END USART1_IRQn 1 */
}

在这里插入图片描述

小结

简单来说,就是利用接收数据后,数据的空闲时间来做有些事情把这些数据保存起来。