一,为什么使用空闲中断
之前做串口数据处理的时候,总是遇到一个问题,就是串口在接收大量数据的时候,我们总是要等所有数据发送完成才去处理(一边接收一边处理数据是不可靠的),那么这里便存在一个问题,“接收数据多大呢,什么时候才接收完成?”。
因此,针对这类不定长的数据接收,我们使用串口空闲中断,就是在接收一个字节的时间内,如果没有在这段时间内没有再接收到输出,单片机串口则触发空闲中断。
使用串口空闲中断之后,我们也不需要总是触发串口接收中断去搬运数据,这样可以提高系统的运行速度和稳定性。
在编程之前,我们需要了解一些关于串口中断触发的概念。
二,串口空闲中断介绍
下面是关于串口空闲中断的寄存器位
空闲中断触发原理:IDLE中断使能之后,当RXNE数据寄存器接收到数据,同时IDLE检测总线空闲置1之后(简单说就是串口停止接收数据之后),此时会触发串口空闲中断,用户可通过标志位去处理空闲中断串口接收的全部数据。
三,STM32CubeMx配置
下面是HAL库关于串口的一些配置,由于串口的时钟源是固定的,所以我们不需要手动去配置时钟,我们直接进入串口模块进行配置即可。
首先,选择串口的工作模式,为异步串口通信。
参数一般默认即可。
然后启动发送与接收中断。
接下来,我们我们配置串口的DMA接收与发送,设置优先级,设置传输数据大小为一字节
点开NVIC,选择中断优先级为3
接下来生成工程,进入用户配置即可。
(如果对DMA不了解的小伙伴,可以先去学习一下,了解DMA的原理即可)
四,编程实战
1,修改stm32f1xx_it.c
修改公共中断处理函数,添加用户自定义的空闲中断回调函数(HAL库没有提供相应的回调函数)
(大家记得引用usart.h头文件)
/**
* @brief This function handles USART1 global interrupt.
*/
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
if(USART1 == huart1.Instance) // 判断是否是空闲中断
{
if(RESET != __HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE))
{
__HAL_UART_CLEAR_IDLEFLAG(&huart1); // 清除空闲中断标志)
printf("\r\nUART1 Idle IQR Detected\r\n");
USAR_UART_IDLECallback(&huart1); //调用用户空闲中断回调函数
}
}
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
/* USER CODE END USART1_IRQn 1 */
}
2,修改usart.c
在生成的串口c文件中,重定义printf,同时定义我们用户串口空闲中断回调函数
/* USER CODE BEGIN 1 */
uint8_t receive_buff[255]; // DMA接收缓冲数组
/*
重定义printf
*/
int fputc(int ch, FILE *f)
{
while ((USART1->SR & 0X40) == 0);
USART1->DR = (uint8_t)ch;
return ch;
}
/*
重定义串口接收回调函数
*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
}
/*
用户自定义串口空闲中断回调函数
*/
void USAR_UART_IDLECallback(UART_HandleTypeDef *huart)
{
HAL_UART_DMAStop(&huart1); // 停止本次DMA传输
// 计算接收到的数据长度
uint8_t data_length = 255 - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
printf("Receive Data(length = %d): ",data_length);// 测试函数:将接收到的数据打印出去
HAL_UART_Transmit(&huart1,receive_buff,data_length,0x200);
printf("\r\n");
memset(receive_buff,0,data_length); // 清零接收缓冲区
data_length = 0;
// 重启开始DMA传输 每次255字节数据
HAL_UART_Receive_DMA(&huart1, (uint8_t*)receive_buff, 255);
}
/* USER CODE END 1 *
3,修改usart.h
对外调用接收缓冲数组,以及用户串口空闲中断回调函数。
extern uint8_t receive_buff[255];
/* USER CODE END Includes */
extern UART_HandleTypeDef huart1;
/* USER CODE BEGIN Private defines */
/* USER CODE END Private defines */
void MX_USART1_UART_Init(void);
/* USER CODE BEGIN Prototypes */
void USAR_UART_IDLECallback(UART_HandleTypeDef *huart);
4,修改main.c
在主函数里面,串口正常输出与DMA输出测试,开启串口空闲中断,开始串口DMA接收。
/* USER CODE BEGIN 0 */
uint8_t Senbuff[] = "Q大帅のUART DMA Test Success ! \r\n";
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
//测试普通发送及重定义printf
printf("hell world!\r\n");
//测试DMA发送
HAL_UART_Transmit_DMA(&huart1, (uint8_t *)Senbuff, sizeof(Senbuff));
//使能串口空闲中断
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
//使用DMA接收串口数据
HAL_UART_Receive_DMA(&huart1, (uint8_t*)receive_buff, 255);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
HAL_Delay(10);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
值得一提的是,单独使用串口空闲中断,不使用DMA接收也可以实现这个功能,只是速度会慢一点,本质上我们只需要添加一个串口数据接收完成的标志位即可,这部分大家有空摸索一下吧。(个人理解,实际上可能有问题)
至此,我们就完美解决对于不定长数据的接收啦,完结,撒花!!!