STM32+ADC+DMA(Normal) 中断后重新开启 DMA 踩坑小记

在编写单片机程序进行 ADC 采样并配合 DMA 传输时,发现单片机仅在启动后第一次采样有效。而进行下一次采样时,单片机卡死。

也就是说,单片机只能进入一次 DMA 中断,而在中断后就无法继续启用 DMA 并进行采样。

在花了亿点点时间后,终于 get 了再次开启采样的正确姿势,故有此踩坑小记。

背景介绍

单片机型号 STM32F103C8T6,项目代码基于 HAL 库。

ADC 配置为双重快速交替模式(独立模式也可以),关闭扫描转换、连续转换、非连续转换。

DMA 配置为 NormalHalf Word,开启 DMA1_Channel1 中断。

问题现象

在首次采样完毕,执行回调函数后,我通过按钮触发第二次采样,此时单片机卡死。

在调试模式下,发现此时单片机进入 HardFault_Handler 函数,追踪调用栈,定位至 HAL_ADCEx_MultiModeStart_DMA 函数。这正是我用于第二次开启 DMA 的函数。(注:如果是独立模式,应为 HAL_ADC_Start_DMA

原因分析

查阅资料后发现,DMA 中断完成后,单片机会设置中断标志位,取消中断使能,同时将 DMA 传输长度清零。

因此,在重新启用 DMA 之前,应该进行这些操作:

  • 清除 DMA 中断标志
  • 关闭特定 DMA 通道
  • 设置数据传输长度
  • 使能 DMA 中断
  • 开启 DMA 通道

解决方案

依次按照上述步骤操作就行,下面给出具体代码。

0x00

首先是 "清除 DMA 中断标志" 和 "关闭特定 DMA 通道",这个可以写到中断回调函数中。比如说 DMA1_Channel1_IRQHandler 或者 HAL_ADC_ConvCpltCallback,代码如下:

1
2
DMA1_Channel1->CCR &= ~(1 << 0);                // 关闭DMA传输 若不关闭 无法配置DMA
__HAL_DMA_CLEAR_FLAG(&hdma_adc1, DMA_FLAG_TC1); // 清除传输完成中断标志,标志名称手册里有

注1:前后两个回调是有区别的。如果你开启了半传输中断和全传输中断、传输错误中断。前者是不论在哪个中断都会进入,而后者只在ADC全部转换完成后才会被调用。这样我们可以更灵活地开关DMA以及进行数据处理。

注2:其实“清除中断标志”这一操作,HAL 库帮我们完成了,在 DMA1_Channel1_IRQHandler 函数中默认调用了 HAL_DMA_IRQHandler(&hdma_adc1) ,此处清除了中断标志。这里留下寄存器操作的代码作为知识补充。

0x01

设置 DMA 的数据传输量:

1
DMA1_Channel1->CNDTR = 1024;   // DMA1,传输数据量

数据量大小通常为 DMA 目标数组的长度,当然可以根据实际情况调整。

0x02

设置需要启用的中断:

1
__HAL_DMA_ENABLE_IT(&hdma_adc1, DMA_IT_HT | DMA_IT_TC | DMA_IT_TE);

DMA_IT_HTDMA_IT_TCDMA_IT_TE 分别对应“半传输完成”“全传输完成”“传输错误”这三个中断。

如果不设置,即使开启了 DMA ,下次也不会进入中断回调函数。

0x03

开启 DMA 特定通道:

1
DMA1_Channel1->CCR |= 1 << 0;   //开启DMA传输

此命令执行完毕后,新一轮采样将立刻开始。

补充

对于 0x01 之后的操作,可以封装在一个函数中,以便于调用。

END