STM32+ADC+DMA(Normal) 中断后重新开启 DMA 踩坑小记
在编写单片机程序进行 ADC 采样并配合 DMA 传输时,发现单片机仅在启动后第一次采样有效。而进行下一次采样时,单片机卡死。
也就是说,单片机只能进入一次 DMA 中断,而在中断后就无法继续启用 DMA 并进行采样。
在花了亿点点时间后,终于 get 了再次开启采样的正确姿势,故有此踩坑小记。
背景介绍
单片机型号 STM32F103C8T6
,项目代码基于 HAL 库。
ADC 配置为双重快速交替模式(独立模式也可以),关闭扫描转换、连续转换、非连续转换。
DMA 配置为 Normal
,Half 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 | DMA1_Channel1->CCR &= ~(1 << 0); // 关闭DMA传输 若不关闭 无法配置DMA |
注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_HT
、DMA_IT_TC
、DMA_IT_TE
分别对应“半传输完成”“全传输完成”“传输错误”这三个中断。
如果不设置,即使开启了 DMA ,下次也不会进入中断回调函数。
0x03
开启 DMA 特定通道:
1 | DMA1_Channel1->CCR |= 1 << 0; //开启DMA传输 |
此命令执行完毕后,新一轮采样将立刻开始。
补充
对于 0x01 之后的操作,可以封装在一个函数中,以便于调用。