STM32+FreeRTOS+CUBEMX_学习笔记(五)HAL串口终极总结+代码:空闲中断接受不定长,DMA接受不定长,帧头尾接受不定长,HAL库函数分析,源码分享,看这篇真的够了,我不看都后悔

目录

  • 前言:
  • 目的:
  • 环境和平台:
  • 一、HAL库的串口函数学习:
    • 1.1、hal库:
    • 1.2、hal库资料:
    • 1.3、HAL串口函数都有什么用:
      • 1.3.1、串口中断处理函数:
      • 1.3.2、串口中断回调函数:
      • 1.3.3、串口发送函数:
      • 1.3.4、串口接受函数:
  • 二、串口的中断收发固定长数据:
    • 2.1、思路:
    • 2.2、cube配置打开中断:
    • 2.3、开启UART中断接受:
    • 2.3、编写中断函数:
    • 2.4、结果:
  • 三、帧头帧尾判断收发不定长数据:
    • 3.1、思路:
    • 3.1、定义一些全局变量用于串口:
    • 3.2、使用中断接受一个数据到buffer:
    • 3.4、在循环中查找是否有数据:
    • 3.5、实验结果:
  • 四、空闲中断收发不定长数据:
    • 4.0、思路:
    • 4.1、首先要搞清楚STM32串口几个不同中断的工作方式
    • 4.2、使能空闲和接受中断:
    • 4.3、编写中断函数:
    • 4.4、结果:
  • 五、实现串口的DMA收发不定长数据:
    • 5.1、cube配置
    • 5.2、打开中断使能和定义全局变量:
    • 5.3、编写中断函数:
    • 5.4、在循环中看是否有数据:
    • 5.5、结果:
  • 六、注意点
    • 6.1、HAL库怎么产生串口接受中断:
      • 6.1.2、打开接受中断方式一,使用中断接受一个数据:
      • 6.1.2、打开接受中断方式二,直接使能串口接受中断:
  • 七、源码分享:
    • 7.1、 sayhi_hal_uart
  • 八八八,发发发、==感谢大家,希望大家多多收藏、点赞,祝大家新年快乐==

本系列文章由江山(csdn名:补不补布)(github:jianggogogo)自己写成,当中用到引用时都已经标记出来,如果出现版权问题,请直接联系我修改。当然,技术在于分享,欢迎大家转载,不过请注明出处。最后,如果出现有错误的地方欢迎大家指正。


前言:

  • 在写这篇文章的过程中,我参考了很多的博文。这些博文给了我很多帮助,但是不得不说,网上的博文终究是层次不齐的。当遇到一些关于原理的地方,还是官方文档比较靠谱。毕竟官方要保证这些文档的可靠性。
  • 如果只是想找对应的方式,直接看目录到对应的地方就可以啦
  • 注意,源码库在最后。

目的:

  1. 学习HAL库串口操作函数
  2. 实现串口的中断收发定长数据
  3. 实现串口的帧头帧尾判断收发不定长数据
  4. 实现串口的空闲中断收发不定长数据
  5. 实现串口的DMA收发不定长数据

环境和平台:

  1. keil5
  2. cubemx
  3. stm32c8t6

一、HAL库的串口函数学习:

1.1、hal库:

HAL库是ST新推出的官方库,该库同图形化开发工具CUBEMX可以结合使用,从而方便开发者的快速操作。
但是在实际的使用中,也会存在一些问题。不过,看着ST的趋势似乎是要将该库作为主流来发展,大有淘汰原来的库文件的趋势。我们也应该多看看。

1.2、hal库资料:

当然在博客里面有很多文章写的很好,不过系统的文档、源码资料大家可以去原子或者野火的论坛找,那里资料很全:

正点原子的HAL例程

1.3、HAL串口函数都有什么用:

1.3.1、串口中断处理函数:

首先,串口中断处理函数,这个函数是我们在cube里面设置打开串口全局中断之后,就会自动生成的。
在这个函数里面,系统会进行中断的处理和实现:

/**
  * @brief 	       处理串口中断请求
  * @param  huart  串口信息结构体指针
  * @retval None   无返回值
  */
void HAL_UART_IRQHandler(UART_HandleTypeDef *huart);

1.3.2、串口中断回调函数:

下面是很多的中断回调函数,但是我们常用的中断回调函数也就是接受中断:

void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);
void HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart);
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);
void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart);
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart);
void HAL_UART_AbortCpltCallback(UART_HandleTypeDef *huart);
void HAL_UART_AbortTransmitCpltCallback(UART_HandleTypeDef *huart);
void HAL_UART_AbortReceiveCpltCallback(UART_HandleTypeDef *huart);

接受中断回调函数:
我们可以看到这是一个弱定义函数,也就是我们需要自己重新写一下这个函数的定义:
注意,回调函数的实现其实也是大多数也在中断中实现,所以我们使用回调的时候也要注意在中断中操作的特殊性:

/**
  * @brief  接受回调
  * @param  huart  Pointer to a UART_HandleTypeDef structure that contains
  *                the configuration information for the specified UART module.
  * @retval None
  */
__weak void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  /* Prevent unused argument(s) compilation warning */
  UNUSED(huart);
  /* NOTE: This function should not be modified, when the callback is needed,
           the HAL_UART_RxCpltCallback could be implemented in the user file
   */
}

1.3.3、串口发送函数:

串口发送函数,分为两种,一个是普通发送,另一个则是中断发送:
区别在于:是否为blocking mode 也就是是否为阻塞模式
关于blocking mode的解释

阻塞模式就是要等到一些通道,但是中断里面不可能去等待什么,必须立即执行,所以采用非阻塞模式。

/**
  * @brief  阻塞模式发送数据
  */
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);

/**
  * @brief  非阻塞模式发送数据
  */
HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);

1.3.4、串口接受函数:

同样,我们也可以设置为不同的接受模式,中断或者非中断。
注意,我们打开全局中断之后,接受中断并不会自动打开,采用HAL_UART_Receive_IT这个函数可以进入一次接受中断

/**
  * @brief 阻塞模式接受信号
  */
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
/**
  * @brief 非阻塞模式接收信号
  */
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);

二、串口的中断收发固定长数据:

2.1、思路:

  • 其实,串口主要的方式也就是中断收发或者轮询收发。中断的关键点在于,只有串口产生中断信号之后,CPU才会去处理该信号。
  • 所以我们的操作流程为:
    1、开启UART中断。
    2、开启中断接受。
    2、编写中断处理函数。
    STM32+FreeRTOS+CUBEMX_学习笔记(五)HAL串口终极总结+代码:空闲中断接受不定长,DMA接受不定长,帧头尾接受不定长,HAL库函数分析,源码分享,看这篇真的够了,我不看都后悔_第1张图片

2.2、cube配置打开中断:

  • 打开UART1中断:
    STM32+FreeRTOS+CUBEMX_学习笔记(五)HAL串口终极总结+代码:空闲中断接受不定长,DMA接受不定长,帧头尾接受不定长,HAL库函数分析,源码分享,看这篇真的够了,我不看都后悔_第2张图片

2.3、开启UART中断接受:

注意,我们打开全局中断之后,接受中断并不会自动打开,采用HAL_UART_Receive_IT这个函数可以进入一次接受中断
初始化uart1之后,我们要注意,现在一旦uart1接收到了数据,我们是没有办法接受到的,因为我们没有设置对应的buffer来存储发来的数据
在主函数中开启接受,定义一个自己的buffer,接受数据:

uint8_t Uart_rx_buffer[256] ={0};
//数字一可以改为你想收的数据
HAL_UART_Receive_IT(&huart1,Uart_rx_buffer,1); //将uart的一个数据接受到我们对应的buffer

代码位置 user code end 2下面 :
STM32+FreeRTOS+CUBEMX_学习笔记(五)HAL串口终极总结+代码:空闲中断接受不定长,DMA接受不定长,帧头尾接受不定长,HAL库函数分析,源码分享,看这篇真的够了,我不看都后悔_第3张图片

2.3、编写中断函数:

在stm32_it.c里面会有产生的各种中断函数,我们找到对应的函数添加内容:
添加继续接受的函数和发送的函数:
这里直接将收到的函数发送出去,测试是否可行
注意,这里是为了测试中断是否成功才采用发送函数。建议大家不要在中断中采用打印函数

  • 要添加的函数:
	HAL_UART_Receive_IT(&huart1, (uint8_t *)&uart_1, 1);   //再开启接收中断
	//注意,这里的100可以改成你想要的长度,来实现定长数据收发
	HAL_UART_Transmit(&huart1,uart_1,1,100);			//发送接收到的数据
  • 位置:
    STM32+FreeRTOS+CUBEMX_学习笔记(五)HAL串口终极总结+代码:空闲中断接受不定长,DMA接受不定长,帧头尾接受不定长,HAL库函数分析,源码分享,看这篇真的够了,我不看都后悔_第4张图片

2.4、结果:

在这里插入图片描述


三、帧头帧尾判断收发不定长数据:

该cubemx配置同 二、串口的收发定长数据

3.1、思路:

  • 使用标志位的思路,也就是在上面的定长数据中添加标志位的判断就可以实现了。

该例程参考了正点原子的串口教程,大家可以去看看:
设置串口数据帧头为:0x0a,帧尾为0x0d。

  • 下面为步骤

3.1、定义一些全局变量用于串口:

//注意,读取USARTx->SR能避免莫名其妙的错误   	
uint8_t USART_RX_BUF[256];     //接收缓冲,最大USART_REC_LEN个字节.

//接收状态
//bit15,	接收完成标志
//bit14,	接收到0x0d
//bit13~0,	接收到的有效字节数目
uint16_t USART_RX_STA=0;       //接收状态标记	  

uint8_t aRxBuffer[256];//HAL库使用的串口接收缓冲
## 3.1、设置一个buffer接受数据:
```c
	HAL_UART_Receive_IT(&huart1,USART_RX_BUF,1);

3.2、使用中断接受一个数据到buffer:

注意,我们打开全局中断之后,接受中断并不会自动打开,采用HAL_UART_Receive_IT这个函数可以进入一次接受中断

	HAL_UART_Receive_IT(&huart1,USART_RX_BUF,1);
## 3.3、编写中断函数:
- 注意这一步是关键,在中断中实现对串口消息的帧头帧尾的判断:
```c
void USART1_IRQHandler(void)
{

  /* USER CODE BEGIN USART1_IRQn 0 */
	
	uint8_t Res;
	HAL_StatusTypeDef err;

	if((__HAL_UART_GET_FLAG(&huart1,UART_FLAG_RXNE)!=RESET))  //接收中断(接收到的数据必须是0x0d 0x0a结尾)
	{
		Res=USART1->DR; 
		if((USART_RX_STA&0x8000)==0)//接收未完成
		{
			if(USART_RX_STA&0x4000)//接收到了0x0d
			{
				if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
				else USART_RX_STA|=0x8000;	//接收完成了 
			}
			else //还没收到0X0D
			{	
				if(Res==0x0d)USART_RX_STA|=0x4000;
				else
				{
					USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
					USART_RX_STA++;
					if(USART_RX_STA>(256-1))USART_RX_STA=0;//接收数据错误,重新开始接收	  
				}		 
			}
		}   		 
	}
  /* USER CODE END USART1_IRQn 0 */
	HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */

	HAL_UART_Receive_IT(&huart1,USART_RX_BUF,1);   //再开启接收中断
	/* USER CODE END USART1_IRQn 1 */
}

3.4、在循环中查找是否有数据:

如果有数据,就会打印出来。

  • 我们可以随意定义一个循环来判断标志位是否为接收到了数据:
    注意0x0d是空格的意思,所以要加空格发送,如果是十六进制,直接添加0x0a为头,0x0d为尾也是可以的
  while (1)
  {
    /* USER CODE END WHILE */
      if(USART_RX_STA&0x8000)
		{					   
			len=USART_RX_STA&0x3fff;//得到此次接收到的数据长度
			printf("\r\n您发送的消息为:\r\n");
			HAL_UART_Transmit(&huart1,(uint8_t*)USART_RX_BUF,len,1000);	//发送接收到的数据
			while(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_TC)!=SET);		//等待发送结束
			printf("\r\n\r\n");//插入换行
			USART_RX_STA=0;
		}
		else
		{
			times++;
			if(times%5000==0)
			{
				printf("\r\nALIENTEK 精英STM32开发板 串口实验\r\n");
				printf("正点原子@ALIENTEK\r\n\r\n\r\n");
			}
			if(times%200==0)printf("请输入数据,以回车键结束\r\n");  
			HAL_Delay(10);   
		} 
    /* USER CODE BEGIN 3 */
  }

3.5、实验结果:

发送0+回车的结果
STM32+FreeRTOS+CUBEMX_学习笔记(五)HAL串口终极总结+代码:空闲中断接受不定长,DMA接受不定长,帧头尾接受不定长,HAL库函数分析,源码分享,看这篇真的够了,我不看都后悔_第5张图片
发送0x0a,0x31,0x32,0x33,0x0d的结果,注意都是十六进制:
iSTM32+FreeRTOS+CUBEMX_学习笔记(五)HAL串口终极总结+代码:空闲中断接受不定长,DMA接受不定长,帧头尾接受不定长,HAL库函数分析,源码分享,看这篇真的够了,我不看都后悔_第6张图片

四、空闲中断收发不定长数据:

该cubemx配置同 二、串口的收发定长数据

4.0、思路:

- 在接受中断中接受数据,然后在空闲中断中实现判断一帧是否完成,空闲中断产生,则一帧结束。
注意,我们打开全局中断之后,接受中断并不会自动打开,采用HAL_UART_Receive_IT这个函数可以进入一次接受中断。
但是,这里我们也可以不采用HAL_UART_Receive_IT进入一次中断,而是直接打开接受中断,然后我们自己清除标志位。

4.1、首先要搞清楚STM32串口几个不同中断的工作方式

  • 在调用空闲中断的时候,我看到有一篇博主的文章上面,写道为了避免以直接进入空闲中断,所以当接受到一个数据时才打开空闲中断。可是我按照他的方法是,发现由于空闲中断刚刚打开,所以如果是单个数据根本就没有办法进入空闲中断。
  • 后来,询问了大佬之后才发现。空闲中断是很智能的东西,他会在接收到数据之后才开始使用,也就是说,没有接到数据的时候,是不会开始工作的。
    知道这些的我眼泪流下来。
  • 其实很多东西,st已经帮我们实现了,我们要注意学习文档
  1. 串口全局中断:这个打开,才能打开别的中断。
  2. 串口接受中断:每次,接收到一个字节的数据就会产生这个中断。
  3. 串口空闲中断:当stm32接受到一个字节的时候,空闲中断开始工作,当一段时间内没有接受任何数据时,产生空闲中断。

放几个大佬的参考:
用自己的中断函数来替代别人的
编写一个自己的回调函数,处理中断

下面为流程

4.2、使能空闲和接受中断:

  • 这一步推荐大家放在系统生成的uart初始化下面,当然放在main里面也没有问题
  • 不采用HAL_UART_Receive_IT,而是直接打开接受中断和空闲中断,但是在中断里面要清除中断标志位。
 	__HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE);
	__HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE);//打开空闲中断 

4.3、编写中断函数:

//这些都是数据接受状态标志位
#define UART_RX_STATE_START 0
#define UART_RX_STATE_READY 2
#define UART_RX_STATE_DEAL 1


/**
  * @brief This function handles USART1 global interrupt.
  */
void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */
	uint8_t clear = clear;
	if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_RXNE)!=RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾)
	{
		__HAL_UART_CLEAR_FLAG(&huart1,UART_FLAG_RXNE);
		uart1RxState  =  UART_RX_STATE_READY; 
		uart1RxBuf[uart1RxCounter]=(uint8_t)(huart1.Instance->DR&(uint8_t)0x00FF);
		uart1RxCounter++;
	} 
	if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE)!=RESET)
	{
		clear=USART1->SR;  
		clear=USART1->DR; 
		uart1RxState = UART_RX_STATE_DEAL;//状态表明一帧数据接收完成了,需要处理。处理完以后再把接收中断打开
	}
  /* USER CODE END USART1_IRQn 0 */
	HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */

  /* USER CODE END USART1_IRQn 1 */
}

4.4、结果:

发送数据后,打印数据长度,结果成功

1 
01 
123456789864564561456 
15 

五、实现串口的DMA收发不定长数据:

采用DMA会减少CPU的运行压力,这里是推荐大家使用的:

5.1、cube配置

在串口的配置界面,添加DMA发送和接受:
STM32+FreeRTOS+CUBEMX_学习笔记(五)HAL串口终极总结+代码:空闲中断接受不定长,DMA接受不定长,帧头尾接受不定长,HAL库函数分析,源码分享,看这篇真的够了,我不看都后悔_第7张图片

5.2、打开中断使能和定义全局变量:

#define Rx_uart1_Max 256

extern uint8_t		Rx_uart1_flag;
extern uint16_t	Rx_uart1_len;
extern uint8_t		Rx_uart1_buf[Rx_uart1_Max];	

//在uart初始化或者和main里面使能
  	/*Enable DMA IRQ*/
	HAL_UART_Receive_DMA(&huart1, Rx_uart1_buf, Rx_uart1_Max);  
	__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); 
	
  • 位置:
    STM32+FreeRTOS+CUBEMX_学习笔记(五)HAL串口终极总结+代码:空闲中断接受不定长,DMA接受不定长,帧头尾接受不定长,HAL库函数分析,源码分享,看这篇真的够了,我不看都后悔_第8张图片

5.3、编写中断函数:

/**
  * @brief This function handles USART1 global interrupt.
  */
void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */

	uint32_t temp;
	if((__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE) != RESET))  
	{   
		/*清除状态寄存器和串口数据寄存器*/
		__HAL_UART_CLEAR_IDLEFLAG(&huart1); 

		/*失能DMA接收*/
		HAL_UART_DMAStop(&huart1);  

		/*读取接收长度,总大小-剩余大小*/
		temp = huart1.hdmarx->Instance->CNDTR; 
		Rx_uart1_len = Rx_uart1_Max - temp; 

		
		/*接收标志位置1*/
		Rx_uart1_flag=1;  

		/*使能接收DMA接收*/
		HAL_UART_Receive_DMA(&huart1,Rx_uart1_buf,Rx_uart1_Max);  
	}

	

  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */

  /* USER CODE END USART1_IRQn 1 */
}

5.4、在循环中看是否有数据:

  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
	  
	  if(Rx_uart1_flag)    	// Receive flag
		{  
			Rx_uart1_flag=0;	// clean flag
       
			//这里写处理函数:
			
			HAL_UART_Transmit(&huart1,Rx_uart1_buf,Rx_uart1_len,10);
			
			// deal Rx_uart1_buf
		} 
		else
		{
			HAL_Delay(10);
		}
		
		
  }

5.5、结果:

可以看到,成功回传不定长数据
STM32+FreeRTOS+CUBEMX_学习笔记(五)HAL串口终极总结+代码:空闲中断接受不定长,DMA接受不定长,帧头尾接受不定长,HAL库函数分析,源码分享,看这篇真的够了,我不看都后悔_第9张图片

六、注意点

6.1、HAL库怎么产生串口接受中断:

  • 要注意,在cube里面打开串口的全局中断之后,接受中断,发送中断和空闲中断其实并没有打开。所以,我们需要分别打开和使用。

6.1.2、打开接受中断方式一,使用中断接受一个数据:

在任意地方调用这一个函数,都可以进入一次接受中断。所以,我们使用这种方法的时候要注意,在接受中断处理完毕之后要再调用一次,从而可以进入下一次中断

	HAL_UART_Receive_IT(&huart1,USART_RX_BUF,1);

6.1.2、打开接受中断方式二,直接使能串口接受中断:

  • 采用这个方法的特点就是,串口接受中断一直打开,但是我们要自己来清除标志位,以便下一次进入。
 	__HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE);

七、源码分享:

  • 这是我自己做的一个stm32的源码分享库,方便大家,如果里面有问题,请及时告知我修改:
    - 这里面,都是已经建好的工程,大家移植的时候参考我上面写的对应的章节,然后和工程对照就可以快速实现串口的操作了。

链接:https://github.com/jianggogogo/sayhi_stm_drive/tree/master/sayhi_hal_uart

7.1、 sayhi_hal_uart

目录 内容
m_uart 定长数据的接受和发送
uart_head_tail 采用帧头帧尾接受不定长数据
uart_idle 采用空闲中断接受不定长数据
uart_dma 采用dma接受不定长数据

八八八,发发发、感谢大家,希望大家多多收藏、点赞,祝大家新年快乐

你可能感兴趣的:(#,stm32,单片机)