STM32F/G系列 HAL串口USART DMA接收(不定长)汇总

摘要:

stm32串口的使用,可以说是这个芯片的灵魂了,DMA、IDLE空闲中断、不定长接收,这些都让stm32的串口产生异常强大的力量。如何用好这些特性,并避免踩坑,本文对几点主要配置和逻辑做了整理。感谢CSDN上各位大虾的无偿分享,本文因知识点太零散,所以无法一一做好文章引用,如果哪位大虾发现本文的某些内容是来自您的文章,请私信我添加您的链接,这里先行谢过了。


正文:

先提供一份测试完全没问题的代码,后面详解一下,以下是在中断文件中的回调函数。

void usart_rx_cb(UART_HandleTypeDef* huart)
{
		__HAL_UART_CLEAR_IDLEFLAG(huart);  
		HAL_UART_DMAStop(huart);

		rec_len = (USART_RX_MAX - __HAL_DMA_GET_COUNTER(&USART_DMA_RX));
		if(( rec_len > 3) && (rec_len!= USART_RX_MAX)) 
		{	rx_f = 1;	}
		HAL_UART_Receive_DMA(huart,rx_buf,USART_RX_MAX);  //重新打开DMA接收
}

下面是串口中断函数配置

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(0 != __HAL_UART_GET_FLAG(&USART_BUS, UART_FLAG_IDLE))
	  usart_rx_cb(&USART_BUS);
  /* USER CODE END USART1_IRQn 1 */
}

然后,我们从头开始说起。

一、串口配置问题

串口配置属于比较基础的,推荐使用stm32cubeide,简直是懒人神器,配置时注意几个点即可。

1、串口接收引脚要上拉

这个一般不会有什么影响,但是有时485芯片电压不稳的时候,会产生非常多的接收信号干扰,造成程序跑飞,做了上拉,就可以避免这个问题。

2、串口根据波特率设置对应的引脚速度

一般9600及以下选择LOW即可,19200~115200选择MEDIUM, 再往上的直接用HIGH就行。这里要注意,有一个output level,这个是设置GPIO初始电平高低的,串口输入尽量配置LOW。

3、串口驱动方式

用推挽PUSH PULL比较合适,开漏OPEN Drain有时会有问题,这个跟电路设计有关系。

4、采样率分频(对应图中1、2)

SMT32的串口一般使用16倍过采样,这就需要分频要设置合理范围,STM32CUBEIDE一般默认设置为1,在大多数情况下,不需要修改这个数值。

如果实在需要修改,可以参考下面的计算方法来粗略估算一下:

如19200bps,每个bit位约52us,主频64M来说,每次采集间隔为1/64us,16倍过采样,则对应1/4us,对于串口接收的一个bit来说,16倍采样点尽量落在前1/2处,相对应需要1/2us。用52us来除1/2us,得到最大的分频率为104,所以,19200bps如果使用128分频,可能会造成接收信号判断错误,引起串口接收异常。

5、DMA配置

1)接收启用DMA,发送尽量不用DMA。这里有一个DMA的逻辑陷阱,对于DMA发送来说,它不管你要发送的数据又多少,只要放到寄存器中,就启动发送程序,如果转移数据的速度没有发送快,或者分几波放置数据的方式,你会发觉发送的数据就没有完整的时候。所以,为了避免这些麻烦,用阻塞方式来做串口发送会是相对来说简单的选择。除非要发送的数据量非常大,这个时候就需要好好设计一下DMA发送的逻辑和时序配合了。

2)DMA与USART的初始化顺序问题,有时配置串口时,发现接收一直为0,反反复复找不到原因所在,最终找到原因,原来是DMA的初始化放到了USART后面。这里的顺序导致接收不到的原因是,DMA的时钟开启,必须在USART之前,否则USART是无法使用DMA进行数据接收的。所以,遇到DMA串口接收,没有数据进入的情况,首先查一下DMA与USART的初始化顺序是否正确。以下为正确的顺序:

  MX_DMA_Init();
  MX_USART1_UART_Init();

6、串口初始化

使用stm32cubeide有一个优势,它会自动帮你把外设用较好的顺序进行了初始化,这样就不会因为时钟初始化顺序问题,引起一些莫名其妙的问题。

但是串口中断及DMA初始化,一般还是需要单独做的。一般是先使用IDLE,后使能DMA,有很多文章提到要先初始化DMA,后初始化IDLE,以防一上电就产生接收,我觉得这个不是问题,只要做好接收数据判断即可。但是先使能DMA会造成串口干扰导致中断频发。

    __HAL_UART_ENABLE_IT(&USART, UART_IT_IDLE); //使能IDLE中断
    HAL_UART_Receive_DMA(&USART,rx_buf,USART_RX_MAX);

7、高级特性设置(对应图中3)

之前遇到过一个特殊的现象,串口在板子刚上电启动时,可以正常收发,但是到了一定时间后,就会出现无法完整接收数据的现象,反复很多次,最终发现,是高级特性惹的祸。

串口配置中,Advanced features,有两个是默认enable的,一个是overrun,一个是dma or rx error。这两个本意是好的,意在增强串口的健壮性,但是在串口可能有干扰信号的情况下,这两个特性会造成串口异常。省力的方法就是关闭(disable)这两个特性。麻烦的方法,是在串口中断接收程序中,做overrun和rx error判断,清除对应的错误位,或者重写void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)函数,这样才能让串口不会出现接收异常的情况。

经过试验测试,即使关闭了这个高级特性,串口接收错误仍然有可能发生,并导致串口一直带着错误位在跑,子程序使用串口不当时,就会发生硬件错误使主程序挂掉。

转载一篇对overrun说的比较清楚的文章:STM32串口溢出错误Overrun使用不当导致的串口死机_TrueLink的博客-CSDN博客

但,这里要千万注意,errorCallback中需要重启的中断,必须是你使用的中断。比如使用了IDLE和DMA来配置的,那就__HAL_UART_ENABLE_IT(&huart, UART_IT_IDLE)。如果未使用IDLE和DMA,那就按照上述博客中提到的,HAL_UART_Receive_IT(&huart1,buf,1),否则,轻则程序卡顿,重则串口又出现假死状态。

二、串口DMA接收的配置

很多文章和帖子中都做了配置说明,经过长时间的试错,可以确认以下几件事情,避免让大家进坑。

1,DMA是否需要开中断?

答案是,不需要开中断。 DMA传输只要配置到串口接收端口上,在串口数据(一般会使用IDLE空闲中断)来临会自动调用DMA程序,DMA负责把串口上的数据转移到片上RAM中,记得,DMA转移时不需要DMA中断动作的,如果加了DMA中断,轻则程序不丝滑,重则程序卡死而找不到原因。

2,DMA与串口是否有对应关系?

答案是,看具体芯片而定。对于stm32F系列芯片来说,很多都需要做对应关系,但是对于stm32G系列芯片来说,已经完全不需要做对应关系了。这个前期去芯片手册中查找清楚即可。

3,DMA优先级如何设置?

答案是,一般不需要特殊配置,从LOW到HIGH,配置起来问题都不大。重点是,需要调用DMA的外设,尽量避免同时持续不断的进行数据传输,一般来说,串口或者ADC通过DMA转移到片内RAM的时间,是以指令数来计的,对于48M以上的stm32时间一般在us级以下,所以,优先级在使用少量DMA的时候,完全不是问题。只有在DMA使用超过5个以上,且外设数据持续不停、通信速率又非常高的时候,才会发生抢占优先级的问题。

三、程序解析

1、串口中断,为什么把回调函数放在系统函数后面?

  HAL_UART_IRQHandler(&huart1);

  if(0 != __HAL_UART_GET_FLAG(&USART_BUS, UART_FLAG_IDLE))
	  usart_rx_cb(&USART_BUS);

答案是,为了避免寄存器异常。系统函数内的判断机制相对完善,寄存器的配置做了大量的处理,这里我们把回调函数放在前面,会在串口寄存器没有配置到合理状态时,就做了一系列操作,大部分时候不会出什么问题,但会经常性冒出一些串口接收异常出来,造成程序出现hardfault。

2、回调函数的详解。

  __HAL_UART_CLEAR_IDLEFLAG(huart);  //此句清除IDLE串口空闲中断标志位,使得串口不会持续进入空闲中断(空闲中断的原理是检测带上升沿的高电平,一点点干扰就能引起,所以尽量接收完数据就先清除掉标志位

        HAL_UART_DMAStop(huart);        //这一句比较关键,这是停止DMA传输的命令,如果不做这个动作,你会发现,第一次接收的时候,可以完整接收数据,后续再接收时,每次只能接收到1个字节,这也是很多人使用DMA容易出现的错误

        rec_len = (USART_RX_MAX - __HAL_DMA_GET_COUNTER(&USART_DMA_RX));        //获取接收到的数据长度,这个一般是用来后续判断使用的,接收到正常长度的数据,就可以认为完成了一次完整接收,否则就抛弃本次接收的数据

        if(( rec_len > 3) && (rec_len!= USART_RX_MAX))  //这里加了一个不等于全长的判断,避免很多时候明明没有接收到数据,异常导致的中断,结果使得程序一直向错误的方向飞奔
        {

            rx_f = 1;        //接收完成标志,这个标志还可以添加更多判断条件,设置标志的目的是为了让中断尽快结束,同时,让主程序知道串口中断已经完成,方便调用处理函数。
        }

        HAL_UART_Receive_DMA(huart, rx_buf,USART_RX_MAX);  //重新打开DMA接收,并且,只有在这个环节,才能重新打开DMA,使用其他的DMA指令都会造成DMA启动异常,查看一下原代码,能找到对应函数的问题及关键。

版权声明:本文为CSDN博主「qinxin4」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qinxin4/article/details/121271375

摘要:

stm32串口的使用,可以说是这个芯片的灵魂了,DMA、IDLE空闲中断、不定长接收,这些都让stm32的串口产生异常强大的力量。如何用好这些特性,并避免踩坑,本文对几点主要配置和逻辑做了整理。感谢CSDN上各位大虾的无偿分享,本文因知识点太零散,所以无法一一做好文章引用,如果哪位大虾发现本文的某些内容是来自您的文章,请私信我添加您的链接,这里先行谢过了。


正文:

先提供一份测试完全没问题的代码,后面详解一下,以下是在中断文件中的回调函数。

void usart_rx_cb(UART_HandleTypeDef* huart)
{
		__HAL_UART_CLEAR_IDLEFLAG(huart);  
		HAL_UART_DMAStop(huart);

		rec_len = (USART_RX_MAX - __HAL_DMA_GET_COUNTER(&USART_DMA_RX));
		if(( rec_len > 3) && (rec_len!= USART_RX_MAX)) 
		{	rx_f = 1;	}
		HAL_UART_Receive_DMA(huart,rx_buf,USART_RX_MAX);  //重新打开DMA接收
}

下面是串口中断函数配置

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(0 != __HAL_UART_GET_FLAG(&USART_BUS, UART_FLAG_IDLE))
	  usart_rx_cb(&USART_BUS);
  /* USER CODE END USART1_IRQn 1 */
}

然后,我们从头开始说起。

一、串口配置问题

串口配置属于比较基础的,推荐使用stm32cubeide,简直是懒人神器,配置时注意几个点即可。

1、串口接收引脚要上拉

这个一般不会有什么影响,但是有时485芯片电压不稳的时候,会产生非常多的接收信号干扰,造成程序跑飞,做了上拉,就可以避免这个问题。

2、串口根据波特率设置对应的引脚速度

一般9600及以下选择LOW即可,19200~115200选择MEDIUM, 再往上的直接用HIGH就行。这里要注意,有一个output level,这个是设置GPIO初始电平高低的,串口输入尽量配置LOW。

3、串口驱动方式

用推挽PUSH PULL比较合适,开漏OPEN Drain有时会有问题,这个跟电路设计有关系。

4、采样率分频(对应图中1、2)

SMT32的串口一般使用16倍过采样,这就需要分频要设置合理范围,STM32CUBEIDE一般默认设置为1,在大多数情况下,不需要修改这个数值。

如果实在需要修改,可以参考下面的计算方法来粗略估算一下:

如19200bps,每个bit位约52us,主频64M来说,每次采集间隔为1/64us,16倍过采样,则对应1/4us,对于串口接收的一个bit来说,16倍采样点尽量落在前1/2处,相对应需要1/2us。用52us来除1/2us,得到最大的分频率为104,所以,19200bps如果使用128分频,可能会造成接收信号判断错误,引起串口接收异常。

5、DMA配置

1)接收启用DMA,发送尽量不用DMA。这里有一个DMA的逻辑陷阱,对于DMA发送来说,它不管你要发送的数据又多少,只要放到寄存器中,就启动发送程序,如果转移数据的速度没有发送快,或者分几波放置数据的方式,你会发觉发送的数据就没有完整的时候。所以,为了避免这些麻烦,用阻塞方式来做串口发送会是相对来说简单的选择。除非要发送的数据量非常大,这个时候就需要好好设计一下DMA发送的逻辑和时序配合了。

2)DMA与USART的初始化顺序问题,有时配置串口时,发现接收一直为0,反反复复找不到原因所在,最终找到原因,原来是DMA的初始化放到了USART后面。这里的顺序导致接收不到的原因是,DMA的时钟开启,必须在USART之前,否则USART是无法使用DMA进行数据接收的。所以,遇到DMA串口接收,没有数据进入的情况,首先查一下DMA与USART的初始化顺序是否正确。以下为正确的顺序:

  MX_DMA_Init();
  MX_USART1_UART_Init();

6、串口初始化

使用stm32cubeide有一个优势,它会自动帮你把外设用较好的顺序进行了初始化,这样就不会因为时钟初始化顺序问题,引起一些莫名其妙的问题。

但是串口中断及DMA初始化,一般还是需要单独做的。一般是先使用IDLE,后使能DMA,有很多文章提到要先初始化DMA,后初始化IDLE,以防一上电就产生接收,我觉得这个不是问题,只要做好接收数据判断即可。但是先使能DMA会造成串口干扰导致中断频发。

    __HAL_UART_ENABLE_IT(&USART, UART_IT_IDLE); //使能IDLE中断
    HAL_UART_Receive_DMA(&USART,rx_buf,USART_RX_MAX);

7、高级特性设置(对应图中3)

之前遇到过一个特殊的现象,串口在板子刚上电启动时,可以正常收发,但是到了一定时间后,就会出现无法完整接收数据的现象,反复很多次,最终发现,是高级特性惹的祸。

串口配置中,Advanced features,有两个是默认enable的,一个是overrun,一个是dma or rx error。这两个本意是好的,意在增强串口的健壮性,但是在串口可能有干扰信号的情况下,这两个特性会造成串口异常。省力的方法就是关闭(disable)这两个特性。麻烦的方法,是在串口中断接收程序中,做overrun和rx error判断,清除对应的错误位,或者重写void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)函数,这样才能让串口不会出现接收异常的情况。

经过试验测试,即使关闭了这个高级特性,串口接收错误仍然有可能发生,并导致串口一直带着错误位在跑,子程序使用串口不当时,就会发生硬件错误使主程序挂掉。

转载一篇对overrun说的比较清楚的文章:STM32串口溢出错误Overrun使用不当导致的串口死机_TrueLink的博客-CSDN博客

但,这里要千万注意,errorCallback中需要重启的中断,必须是你使用的中断。比如使用了IDLE和DMA来配置的,那就__HAL_UART_ENABLE_IT(&huart, UART_IT_IDLE)。如果未使用IDLE和DMA,那就按照上述博客中提到的,HAL_UART_Receive_IT(&huart1,buf,1),否则,轻则程序卡顿,重则串口又出现假死状态。

二、串口DMA接收的配置

很多文章和帖子中都做了配置说明,经过长时间的试错,可以确认以下几件事情,避免让大家进坑。

1,DMA是否需要开中断?

答案是,不需要开中断。 DMA传输只要配置到串口接收端口上,在串口数据(一般会使用IDLE空闲中断)来临会自动调用DMA程序,DMA负责把串口上的数据转移到片上RAM中,记得,DMA转移时不需要DMA中断动作的,如果加了DMA中断,轻则程序不丝滑,重则程序卡死而找不到原因。

2,DMA与串口是否有对应关系?

答案是,看具体芯片而定。对于stm32F系列芯片来说,很多都需要做对应关系,但是对于stm32G系列芯片来说,已经完全不需要做对应关系了。这个前期去芯片手册中查找清楚即可。

3,DMA优先级如何设置?

答案是,一般不需要特殊配置,从LOW到HIGH,配置起来问题都不大。重点是,需要调用DMA的外设,尽量避免同时持续不断的进行数据传输,一般来说,串口或者ADC通过DMA转移到片内RAM的时间,是以指令数来计的,对于48M以上的stm32时间一般在us级以下,所以,优先级在使用少量DMA的时候,完全不是问题。只有在DMA使用超过5个以上,且外设数据持续不停、通信速率又非常高的时候,才会发生抢占优先级的问题。

三、程序解析

1、串口中断,为什么把回调函数放在系统函数后面?

  HAL_UART_IRQHandler(&huart1);

  if(0 != __HAL_UART_GET_FLAG(&USART_BUS, UART_FLAG_IDLE))
	  usart_rx_cb(&USART_BUS);

答案是,为了避免寄存器异常。系统函数内的判断机制相对完善,寄存器的配置做了大量的处理,这里我们把回调函数放在前面,会在串口寄存器没有配置到合理状态时,就做了一系列操作,大部分时候不会出什么问题,但会经常性冒出一些串口接收异常出来,造成程序出现hardfault。

2、回调函数的详解。

  __HAL_UART_CLEAR_IDLEFLAG(huart);  //此句清除IDLE串口空闲中断标志位,使得串口不会持续进入空闲中断(空闲中断的原理是检测带上升沿的高电平,一点点干扰就能引起,所以尽量接收完数据就先清除掉标志位

        HAL_UART_DMAStop(huart);        //这一句比较关键,这是停止DMA传输的命令,如果不做这个动作,你会发现,第一次接收的时候,可以完整接收数据,后续再接收时,每次只能接收到1个字节,这也是很多人使用DMA容易出现的错误

        rec_len = (USART_RX_MAX - __HAL_DMA_GET_COUNTER(&USART_DMA_RX));        //获取接收到的数据长度,这个一般是用来后续判断使用的,接收到正常长度的数据,就可以认为完成了一次完整接收,否则就抛弃本次接收的数据

        if(( rec_len > 3) && (rec_len!= USART_RX_MAX))  //这里加了一个不等于全长的判断,避免很多时候明明没有接收到数据,异常导致的中断,结果使得程序一直向错误的方向飞奔
        {

            rx_f = 1;        //接收完成标志,这个标志还可以添加更多判断条件,设置标志的目的是为了让中断尽快结束,同时,让主程序知道串口中断已经完成,方便调用处理函数。
        }

        HAL_UART_Receive_DMA(huart, rx_buf,USART_RX_MAX);  //重新打开DMA接收,并且,只有在这个环节,才能重新打开DMA,使用其他的DMA指令都会造成DMA启动异常,查看一下原代码,能找到对应函数的问题及关键。

版权声明:本文为CSDN博主「qinxin4」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qinxin4/article/details/121271375

生成海报
点赞 0

qinxin4

我还没有学会写个人说明!

暂无评论

发表评论

相关推荐

STM32串口输出字符串

串口 串口全称为串行接口,采用 全双工、异步通信的通信方式,一次只能传输一帧,一帧中包含 起始位、数据位(一般为 8bit )、校验位、停止位。由于采用异步通信&#xff0