首先先在CubeIDE新建一个工程,具体新建过程不作详细介绍了

在下列说明中使用STM32F429xx为例子做说明。

1、CubeIDE设置串口

此处设置为 Usart1 作为例子

设置为异步通信,波特率等参数根据自己需求进行设置,此处以115200为例。

cubemx 配置dma串口空闲 cubeide串口_单片机

 设置完成后 Ctrl+S 保存,系统会自动生成代码

2、串口常用函数详解

该函数均在 stm32f4xx_hal_uart 文件中体现

串口操作的常用HAL函数

分组

函数名

功能说明

初始化和总体功能

HAL_UART_Init

串口初始化,设置串口通信参数

HAL_UART_MspInit

串口初始化的 MSP 弱函数,在 HAL UART_ Tnito中被调用。重新实现的这个函数一般用于串口引脚的GPIO 初始化和中断设置

HAL_UART_GetState

获取串口当前状态

HAL_UART_GetError

返回串口错误代码

阻塞方式传输

HAL_UART_Transmit

阻塞方式发送一个缓冲区的数据,发送完成或超时后才返回

HAL_UART_Receive

阻塞方式將数据接收到一个缓沖区,接收完成或超时后才返回

中断方式传输

HAL_UART_Transmit_IT

以中断方式(非阻塞式)发送一个缓冲区的数据

HAL_UART_Receive_IT

以中断方式(非阻塞式)将指定长度的数据接收到缓冲区

DAM方式传输

HAL_UART_Transmit_DMA

以 DMA 方式发送一个缓冲区的数据

HAL_UART_Receive_DMA

以 DMA 方式发送一个缓冲区的数据

HAL_UART_DMAPause

暂停 DMA 传输过程

HAL_UART_DMAResume

继续先前暂停的 DMA 传输过程

HAL_UART_DMAStop

停止 DMA 传输过程

取消数据传输

HAL_UART_Abort

终止以中断方式或 DMA 方式启动的传输过程,函数自身以阻塞方式运行

HAL_UART_AbortTransmit

终止以中断方式或 DMA 方式启动的数据发送过程,函数自身以阻塞方式运行

HAL_UART_AbortReceive

终止以中断方式或 DMA 方式启动的数据接收过程,函数自身以阻塞方式运行

HAL_UART_Abort_IT

终止以中断方式或 DMA 方式启动的传输过程,函数自身以非阻塞方式运行

HAL_UART_AbortTransmit_IT

终止以中断方式或 DMA 方式启动的数据发送过程,函数自身以非阻塞方式运行

HAL_UART_AbortReceive_IT

终止以中断方式或 DMA 方式启动的数据接收过程,函数自身以非阻塞方式运行

 有

由上述可知,主要分三种方式传输,阻塞、中断以及DMA,下面将这三种方式进行详

3、串口printf()重定义 

首先重定向printf()函数使printf通过串口打印字符串

在usart.c文件下 在/* USER CODE BEGIN 1 */后面添加以代码,

在usart.h文件下添加 #include <stdio.h>

/*****************  重新编写printf函数 **********************/
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#define GETCHAR_PROTOTYPE int __io_getchar(FILE *f)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#define GETCHAR_PROTOTYPE int fgetc(FILE *f)
#endif /* __GNUC__ */
PUTCHAR_PROTOTYPE
{
	HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);

	return ch;
}
GETCHAR_PROTOTYPE
{
	uint8_t ch = 0;
	HAL_UART_Receive(&huart1,(uint8_t *)&ch, 1, 0xFFFF);

	if (ch == '\r')
	{
		__io_putchar('\r');
		ch = '\n';
	}

	return __io_putchar(ch);
}
/*********************************************************/
/* USER CODE BEGIN Includes */
#include <stdio.h>
/* USER CODE END Includes */

完成以后就可以直接使用 printf 函数打印数据了!!!

4、阻塞方式

 在main函数中定义一个数组

/* USER CODE BEGIN 1 */
  uint8_t RxBuffer[10] = {0};
  /* USER CODE END 1 */

然后在while函数中编写函数

/* USER CODE BEGIN WHILE */
  while (1)
  {
    if(HAL_UART_Receive(&huart1,RxBuffer,sizeof(RxBuffer),1000) == HAL_OK)
    {
	    HAL_UART_Transmit(&huart1,RxBuffer,sizeof(RxBuffer),100);
    }
   /*
    * HAL_UART_Transmit()函数和HAL_UART_Receive()一样,有四个参数
    * 第一个参数是要使用的串口句柄地址,比如要使用U(S)ART1,参数就设置为U(S)ART1的句柄地址&huart1
    * 第二个参数是要接受/发送的数据缓冲区首地址
    * 第三个参数是接受/发送的数据长度,这里可以直接用sizeof()函数获取发送缓冲区的长度
    * 第四个参数是超时时间,单位是ms,如果超过设置的时间,则函数返回HAL_TIMEOUT,如果设置为HAL_MAX_DELAY,处理器就会一直等到数据发送完成再执行下一条语句。
    */
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */

编写完后就可以编译下载烧录了,然后打开串口调试助手,效果如下

cubemx 配置dma串口空闲 cubeide串口_数据_02

但是这里会有一个问题,本文RxBuffer的长度为10,如果我发送的数据长度不为10个就会出现问题,这里设置的超时时间为1S,若1s内没接收到10个数据,函数就会跳出if语句,不进行发送。如果1s内发送超过10个数据,则只会发出10个数据出来。

为了解决上述,建议增加数据包判断,每次只接受一个字节,接受到头码尾码再后续处理数据包内容。

 5、中断方式

首先修改CubeIED内的图形化设置

cubemx 配置dma串口空闲 cubeide串口_单片机_03

 设置完成后,保存,系统自动修改代码

具体的函数流程以及中断过程在此处不做具体详解,具体请查看

在中断过程中,最终会跑到 HAL_UART_RxCpltCallback() 函数中,在stm32f4xx_hal_usart.c中有__weak前缀,是弱定义项,我们可以在其他文件中重新写一个 HAL_UART_RxCpltCallback() 函数,这样中断最后就会跑到我们自己编写的 HAL_UART_RxCpltCallback() 函数中。

在 MX_USART1_UART_Init 函数中加入 HAL_UART_Receive_IT()(必须加有此函数,该函数不但是一个接收函数,还是一个初始化开始下一次中断的函数)

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_IT(&huart1,&RxBuf,1);//加入此函数,开始中断,并将接受到一个字节放到RxBuf地址中
  /* USER CODE END USART1_Init 2 */

}

编写一个 HAL_UART_RxCpltCallback() 放在 usart.c 文件中,本例子只编写了接受一个字节,若需要多个字节请自行编写。

//接受一个字节
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  uint8_t res=0; 
	if(huart->Instance == USART1)  //如果中断号为串口1
	{		
        res = RxBuf[0];
		HAL_UART_Transmit( &huart1,&res ,1,1 );    
        HAL_UART_Receive_IT(&huart1,&RxBuf,1);//重新开始下一轮中断
	}
}

main函数的初始化以及while中不需要增加任何东西,保持空白即可

试验现象: 

cubemx 配置dma串口空闲 cubeide串口_单片机_04

 可以看到,我发送一个0x55的字节,收到后返回了一个0x55字节,表示已经成功了。

 6、DMA方式

首先修改CubeIED内的图形化设置 TX以及RX

cubemx 配置dma串口空闲 cubeide串口_数据_05

cubemx 配置dma串口空闲 cubeide串口_单片机_06

保存并且生成代码即可,

6.1 DMA发送函数 HAL_UART_Transmit_DMA

如果发送数据正常,进入2次进入DMA中断(DMA 半传输和DMA传输完成);错误的话进入DMA传输出错中断;从这里也能看出HAL库比标准库严谨但效率低,DMA 半传输中断如果觉得效率低可以在程序中屏蔽掉,这样数据正常发送完成就只会进入一次DMA完成中断,三种DMA中断其实是同一个函数HAL_DMA_IRQHandler(DMA_HandleTypeDef *hdma)
此函数功能是处理DMA中断请求,主要工作是清除中断标志位,改写DMA的状态,只有把状态改成HAL_DMA_STATE_READY,下一次才能正常使用DMA功能,否则会进入 HAL_BUSY 状态

        如果是HAL_BUSY状态,则无法连续使用HAL_UART_Transmit_DMA()函数发送数据,第二次会发不出来数据,而且第二次函数会进入HAL_BUSY状态,所以要想使用HAL_UART_Transmit_DMA()函数连续发送数据,相邻两次之间要有延时间隔或者检测DMA数据是否完成。

修改DMA半传输中断

stm32f4xx_hal_dma.c 文件中的 DMA HAL_DMA_Start_IT 函数中,将所有的中断都打开了,我们只需要将需要打开的中断使能位置1即可将半传输完成中断屏蔽,如下:

if(hdma->XferHalfCpltCallback != NULL)
    {
      //hdma->Instance->CR  |= DMA_IT_HT;//关闭DMA半传输中断
    }

6.2 DMA接收函数 HAL_UART_Receive_DMA()

        HAL_UART_Receive_DMA()具有接收以及初始化函数的作用。在开始时需要先调用该函数,否则无法进入中断。,接收DMA函数同样具有三个中断DMA 半传输(数据接收到一半会进入一次中断),DMA接收完成和DMA接收出错。关闭DMA半传输函数与上述一样。

6.3 空闲中断

空闲中断是接受数据后出现一个byte的高电平(空闲)状态,就会触发空闲中断.并不是空闲就会一直中断,准确的说应该是上升沿(停止位)后一个byte,如果一直是低电平是不会触发空闲中断的(会触发break中断)。所以为了减少误进入串口空闲中断,串口RX的IO管脚一定设置成Pull-up<上拉模式>,串口空闲中断只是接收的数据时触发,发送时不触发。

串口空闲中断的判定是:当串口开始接收数据后,检测到1字节数据的时间内没有数据发生,则认为串口空闲了,进入相应的串口中断。在中断内清除空闲中断标志位和调用串口回调函数,在回调函数内处理读取,判断,处理接收的一帧数据。处理完一帧数据以后我再把串口中断打开重复上面的流程,就可以完整的接收一帧一帧的数据。同时利用空闲中断也可以省去很多的的判断。

首先在main()之前初始化的时候调用__HAL_UART_ENABLE_IT(&huart2,UART_IT_RXNE);
函数的功能是打开了串口的接收中断。注意这个时候我还没有打开空闲中断。而是在接收到了一个byte以后打开空闲中断。
当发送一帧数据接收完成后,会进入串口中断函数,如下函数
HAL_UART_IRQHandler(UART_HandleTypeDef *huart)
HAL库提供的这个串口中断函数,并没有针对空闲中断的处理,所以得我们自己加相应的代码。

if(RESET != __HAL_UART_GET_FLAG(&huart2, UART_FLAG_IDLE))   //判断是否是空闲中断
{
    __HAL_UART_CLEAR_IDLEFLAG(&huart2);                     //清楚空闲中断标志(否则会一直不断进入中断)
    USAR_UART_IDLECallback(huart);                          //调用中断回调函数
}

6.4 DMA中断程序编写

在 main 函数的 /* USER CODE BEGIN 2 */ 后面开始编写

/* USER CODE BEGIN 2 */ 
 __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);                          //使能串UART1 IDLE中断
  HAL_UART_Receive_DMA(&huart1, (uint8_t*)receive_buff, BUFFER_SIZE);   //设置DMA传输,讲串口2的数据搬运到recvive_buff中,
  printf("DMA串口中断接收回显函数实验\n");
  HAL_UART_Transmit_DMA(&huart1,tx_buffer,sizeof(tx_buffer));           //串口DMA发送一帧数据
 /* USER CODE END 2 */

 在 usart.c 文件的 /* USER CODE BEGIN 1 */ 后编写

void USER_UART_IDLECallback(UART_HandleTypeDef *huart)
{
    HAL_UART_DMAStop(huart);                                                     //停止本次DMA传输

    uint8_t data_length  = BUFFER_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);//计算接收到的数据长度
    printf("\r\ndata length = %d\r\n",data_length);                             //接收到的数据长度
	  HAL_UART_Transmit_DMA(huart,receive_buff,data_length);                      //DMA发送函数:将接收到的数据打印出去
    HAL_UART_Receive_DMA(huart, (uint8_t*)receive_buff, BUFFER_SIZE);           //重新开始始下一轮的DMA中断
    data_length = 0;
}

void USER_UART_IRQHandler(UART_HandleTypeDef *huart)
{
    if(huart->Instance == USART1)                                 //判断是否是串口1
    {
        if(RESET != __HAL_UART_GET_FLAG(huart, UART_FLAG_IDLE))   //判断是否是空闲中断
        {
            __HAL_UART_CLEAR_IDLEFLAG(huart);                     //清楚空闲中断标志(否则会一直不断进入中断)
            USER_UART_IDLECallback(huart);                        //调用中断处理函数
        }
    }
}

 在 usart.h 文件中添加

/* USER CODE BEGIN Prototypes */
void USER_UART_IDLECallback(UART_HandleTypeDef *huart);
void USER_UART_IRQHandler(UART_HandleTypeDef *huart);
/* USER CODE END Prototypes */

最后在文件 stm32f4xx_it.c 文件中修改串口中断函数

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 */
  USER_UART_IRQHandler(&huart1);       //新添加的函数,用来处理串口空闲中断
  /* USER CODE END USART1_IRQn 1 */
}