最近在搞STM32L051系列一个小MCU,要用这个去采集两路ADC作为输入。期间也碰到过一些问题,顺便记录下。
ADC采集原理不说了,主要采集电压,用数字进行细分,这样就可以知道输入电压多少了,网上也有很多相关文章。
我这边主要以下几个问题或知识点
1. 老工程里添加ADC模块
工程是原先的工程,虽然还是保留着ioc文件,理论可以通过STM32CubeMX进行配置。但这样比较麻烦要核对原先的其他有没有改动,前期工程包括功能又不想再调一遍。因此我这边的思路:先用STM32CubeMX进行配置,然后再把ADC相关一些代码移植过来。
配置前预习知识:
ADC采集总体来说,目标还是要读取到最终的值,对于MCU而言,读取ADC值有如下几种常见操作:
>> 需要的时候,开启采集,并读取采集到的值。很显然这种按需分配比较“省事”,对于实时性要求不高的可参考。但带来问题,明显采集会占时间周期,会打破原先逻辑。
>> 开启采集之后,一直轮询采集,并放到一个指定的缓存里(一般需要DMA技术),然后业务上需要的时候去读取DMA的值。相对于上面读取DMA的值要比采集一次快很多很多。
>> 开启采集之后,一直轮询采集,并放到一个指定的缓存里(需要DMA技术),然后DMA产生中断,在中断函数里将DMA数据转走。这样采集的数据更新更快,但也会带来更多的中断,如果对采集实时要求没那么高地方,可能不需要那么多的中断,此处需要权衡。当然DMA可以搞大一点,比如采集16次 再产生中断,然后计算16次的平均值,这样即可以满足采集数据可靠性,也可以减少中断次数,是一种比较不错的方式。
还有其他方式吧,没有细细去想。
很显然,第二,第三方式比较合适,因此,STM32CubeMX需要配置ADC、以及DMA
ADC配置,其实就是属性的设置(不同芯片略有不同):
这里配置关键点:
分频值(切记要好好理解下)、采集周期
需要理解下其含义,根据实际情况去调整。
DMA要配置:
生成代码,
然后进入main.c 把 void MX_ADC_Init(void) 这个函数拷贝自己工程里面
编译:直接出现
..... Error: L6218E: Undefined symbol LL_ADC_Init (referred from .....
类似这样的错误。说明LL_ADC_Init 没有被工程包含进来,可以按以下几个方面进行问题确认:
1. 调用地方是否include 对应的h文件。#include "stm32l0xx_ll_adc.h"
2. 对应原文件 "stm32l0xx_ll_adc.c" 是否在工程里面
3. 如果是用HAL方式的话,要在stm32l0xx_hal_conf.h
#define HAL_ADC_MODULE_ENABLED 这个是否生效
理论上编译会通过。
2. 添加必要的代码
显然如果仅仅拷贝CubeMX生成的代码,还不够ADC采集功能,也不能使用DMA来采集,需要添加自己的代码。共两处代码:
- 需要开启自动采集,建立DMA映射
- DMA处理(应用)
以下代码调用顺序不要改变
void AdcDmaInit(uint32_t adcDmaBuf)
{
/* ADC DMA buf Init */
LL_ADC_StartCalibration(ADC1); //校准
while (LL_ADC_IsCalibrationOnGoing(ADC1)) ;
LL_ADC_Enable(ADC1); ///使能
while (LL_ADC_IsActiveFlag_ADRDY(ADC1) == 0){};
/ 开始ADC1采集
LL_ADC_REG_StartConversion(ADC1);
/ 开启DMA方式(注意,在多通道采集的时候,如果不这样做会有个坑!!!后面会描述)
LL_ADC_REG_SetDMATransfer(ADC1, LL_ADC_REG_DMA_TRANSFER_UNLIMITED);
//DMA使能 这个是关键。注意最后的16这个数字,还要考虑adcDmaBuf的长度。我这边是两个通道,所以DMA采集完成的时候,实际已经采集了8次。类似:IN1 IN2 IN1 IN2 IN1 IN2 IN1 IN2 ... IN1 IN2
LL_DMA_SetDataLength(DMA1,LL_DMA_CHANNEL_2, 16);
LL_DMA_SetPeriphAddress(DMA1,LL_DMA_CHANNEL_2,LL_ADC_DMA_GetRegAddr(ADC1,LL_ADC_DMA_REG_REGULAR_DATA));
//映射好
LL_DMA_SetMemoryAddress(DMA1,LL_DMA_CHANNEL_2,(uint32_t)adcDmaBuf);
LL_DMA_EnableChannel(DMA1,LL_DMA_CHANNEL_2);
}
/Channel2 定期采集
if (LL_DMA_IsActiveFlag_TC2(DMA1))
{
debugCount++;
if(debugCount > 10000)
{
printf(">>> ADC = %04X %04X, %04X %04X, %04X %04X, %04X %04X \r\n %04X %04X, %04X %04X, %04X %04X, %04X %04X \r\n", s_ptAdcValue[0],s_ptAdcValue[1],s_ptAdcValue[2],s_ptAdcValue[3],s_ptAdcValue[4],s_ptAdcValue[5],s_ptAdcValue[6],s_ptAdcValue[7], s_ptAdcValue[8],s_ptAdcValue[9],s_ptAdcValue[10],s_ptAdcValue[11],s_ptAdcValue[12],s_ptAdcValue[13],s_ptAdcValue[14],s_ptAdcValue[15]);
debugCount = 0;
}
}
3. 采集通道顺序问题
在MX_ADC_Init地方很明显采集的数据
/**ADC GPIO Configuration
PB0 ------> ADC_IN1
PB1 ------> ADC_IN2
*/
但实际从测试的时候,经常顺序颠倒来颠倒去
问题解决办法:
1. 在Init函数里,将 改成NONE
ADC_REG_InitStruct.DMATransfer = LL_ADC_REG_DMA_TRANSFER_NONE;
2. 然后在校准完毕,开启采集之后,再开启DMA(切记顺序)
LL_ADC_REG_SetDMATransfer(ADC1, LL_ADC_REG_DMA_TRANSFER_UNLIMITED);
以上就是本人本阶段处理AD采样的一些记录。