在实际应用中ADC往往是要和DMA一起使用的,DMA将ADC转换值放入预设好的变量中,该过程不占用CPU资源,需要读取ADC输入时再让CPU读取变量即可。
下面记录使用cubemx配置多通道ADC采集的具体流程,并编写对ADC采样值进行滤波的程序(选择开发板、开启外部时钟和SW调试接口、Project Manager设置相关操作略过)
1.选择要使用的ADC和对应通道
f407内部ADC资源如下,我们选择ADC3的通道10、11、12、13进行ADC采集
图片来源29. ADC—电压采集 — [野火]STM32 HAL库开发实战指南——基于野火F4系列开发板 文档
在cubemx中选择
2.配置ADC参数
Scan Conversion Mode:如果是单通道转换设置为DISABLE,如果是多通道AD转换设置为ENABLE;
Continuous Conversion Mode:配置是启动自动连续转换还是单次转换。选择ENABLE配置为使能自动连续转换,如果是DISABLE则配置为单次转换,转换一次后停止需要手动控制才重新启动转换;
Number of Conversion:进行规则组的通道配置。如果没有特殊需要把所有的通道都放在规则组即可,这里使用了4个通道就把number of conversion设置为4,并把channel分配到每一个RANK
Sampling Time:越大则ADC结果越准确。由于我们使用了DMA,采样时并不需要CPU等待,所以这里的耗时几乎对程序没什么影响。而ADC转换精度是我们很在意的,所以直接将其设为最大的480时钟周期。
DMA Continuous Requests:选择Enable,开启DMA传输(在没有分配DMA时是不可以Enable的,进行完3再来设置)。
3.配置DMA
点击Add添加ADC3的DMA传输通道,并将Mode设置为周期模式即可(即不断的将ADC转换值搬运到指定区域)
4.生成代码,开启DMA传输
首先DMA需要一个目的地,即把ADC转换的结果往哪个变量中搬运,所以我们先创建一个变量用于储存ADC搬运的结果。由于我们还要对ADC转换值进行平均滤波,所以这里把计算过程中需要的变量全部创建了,并保存在结构体ADC3_sampling中
#define Filter_width 30
typedef struct
{
uint8_t adc_count;
uint16_t adc3_dma_storage[4*Filter_width];
uint32_t adc3_sum_A1, adc3_sum_A2, adc3_sum_A3, adc3_sum_A4;
uint16_t adc3_average_A1, adc3_average_A2, adc3_average_A3, adc3_average_A4;
uint32_t time;
}ADC3_SAMPLING;
extern ADC3_SAMPLING ADC3_sampling;
由于我们的结构体ADC3_sampling在main中定义,而我们要把滤波函数写到adc对应的文件中,所以用extern声明。filter width是我们定义的滤波器宽度
ADC转换值储存只能使用uint16_t数据类型!不然DMA搬运时会混乱!
PS:由于滤波是对adc采样值的处理,只与adc相关的函数就放到adc文件中,这样做是为了整个工程更有条理。而把变量定义到main中是为了后续的调试方便,可以在main中就看到所有的变量
开启DMA传输(注意要MX_ADC3_Init()的后面)
HAL_ADC_Start_DMA(&hadc3, (uint32_t *)ADC3_sampling.adc3_dma_storage, 4*Filter_width);
5.对采样值进行滤波
在我的实际项目中,虽然Sampling Time已被设为480,但不进行滤波的话ADC每次的采样值任然存在很大偏差,所以进行滤波是必要的。
首先重定义HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc),该函数在DMA将ADC转换结果传输完成时被调用。然后在回调函数中进行滤波操作。
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
for(uint8_t i=0; i<30; i++)
{
// 计算累计值
ADC3_sampling.adc3_sum_A4 += ADC3_sampling.adc3_dma_storage[4*i];
ADC3_sampling.adc3_sum_A3 += ADC3_sampling.adc3_dma_storage[4*i+1];
ADC3_sampling.adc3_sum_A2 += ADC3_sampling.adc3_dma_storage[4*i+2];
ADC3_sampling.adc3_sum_A1 += ADC3_sampling.adc3_dma_storage[4*i+3];
}
// 计算均值
ADC3_sampling.adc3_average_A4 = 0.8057 * ADC3_sampling.adc3_sum_A4/Filter_width;
ADC3_sampling.adc3_average_A3 = 0.8057 * ADC3_sampling.adc3_sum_A3/Filter_width;
ADC3_sampling.adc3_average_A2 = 0.8057 * ADC3_sampling.adc3_sum_A2/Filter_width;
ADC3_sampling.adc3_average_A1 = 0.8057 * ADC3_sampling.adc3_sum_A1/Filter_width;
// 累计值清零以待下次计算
ADC3_sampling.adc3_sum_A4 = 0;
ADC3_sampling.adc3_sum_A3 = 0;
ADC3_sampling.adc3_sum_A2 = 0;
ADC3_sampling.adc3_sum_A1 = 0;
}
注意0.8057是3300/4096得来的。分别代表3.3V和2^12
6.总结
很多教程喜欢从HAL_ADC_Start()和HAL_ADC_Start_IT()开始教,这样虽然能让理论知识理解的更加充分,但我觉得不是很有必要。因为其实HAL_ADC_Start_DMA ()内部的实现中是调用了HAL_ADC_Start_IT()的,用多了遇到问题多了自然就会去看内部实现了,然后自然而然就明白了。至于HAL_ADC_Start()这种阻塞式实现,完全不会用的到。
先会用,再精通!
补充:根据后续测试,采样4通道的数据30组并搬运耗时约2.8ms(CPU不参与),而滤波占用CPU的时间非常小,几乎可以忽略不计(小于10微秒)。应用时可以对滤波语句加上条件判断(比如设置标志位10ms进行一次ADC采样值滤波),整个ADC采样过程就几乎无感实现。
标签:DMA,HAL,多通道,滤波,adc3,ADC3,ADC From: https://blog.csdn.net/weixin_57904199/article/details/142610365