在编写单片机程序进行 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,代码如下:
12DMA1_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 的数据传输量:
1DMA1_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 特定通道:
1DMA1_Channel1->CCR |= 1 << 0; //开启DMA传输
此命令执行完毕后,新一轮采样将立刻开始。
补充
对于 0x01 之后的操作,可以封装在一个函数中,以便于调用。
END