打印
[STM32F4]

STM32F4XX的12位ADC采集数值超过4096&右对齐模式设置失败

[复制链接]
187|2
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
tpgf|  楼主 | 2024-2-27 08:11 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
一、前言
最近在学习STM32的ADC功能,遇到了一个奇怪的问题。
使用芯片:STM32F407ZGT6
使用函数:库函数
使用代码:正点原子的例程《实验16 ADC实验》
串口工具:VOFA

二、问题1:数值超过4096
博主直接使用了正点原子的程序,如下面所示,使用的12位的ADC1,端口是PA5

//初始化ADC                                                                                                                          
void  Adc_Init(void)
{   
  GPIO_InitTypeDef  GPIO_InitStructure;
  ADC_CommonInitTypeDef ADC_CommonInitStructure;
  ADC_InitTypeDef       ADC_InitStructure;
       
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);//使能GPIOA时钟
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); //使能ADC1时钟

  //先初始化ADC1通道5 IO口
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;//PA5 通道5
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;//模拟输入
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;//不带上下拉
  GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化  

  RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC1,ENABLE);          //ADC1复位
  RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC1,DISABLE);        //复位结束         

  ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;//独立模式
  ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;//两个采样阶段之间的延迟5个时钟
  ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled; //DMA失能
  ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4;//预分频4分频。ADCCLK=PCLK2/4=84/4=21Mhz,ADC时钟最好不要超过36Mhz
  ADC_CommonInit(&ADC_CommonInitStructure);//初始化
       
  ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;//12位模式
  ADC_InitStructure.ADC_ScanConvMode = DISABLE;//非扫描模式       
  ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;//关闭连续转换
  ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;//禁止触发检测,使用软件触发
  ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//右对齐       
  ADC_InitStructure.ADC_NbrOfConversion = 1;//1个转换在规则序列中 也就是只转换规则序列1
  ADC_Init(ADC1, &ADC_InitStructure);//ADC初始化
  ADC_Cmd(ADC1, ENABLE);//开启AD转换器       
}



为了容易查看数值,博主将adc.h和adc.c移植到了串口打印的程序之中,这样子可以在电脑的串口工具查看相对于的数值。
主函数的代码如下,将ADC采集到的原始数值和转化为电压之后的数值用串口打印出来

adcx=Get_Adc_Average(ADC_Channel_5,20);//获取通道5的转换值,20次取平均
temp=(float)adcx*(3.3/4096);          //获取计算后的带小数的实际电压值,比如3.1111
printf("%d,%f\n",adcx,temp);



从下面的结果可以看出超过我们的12位ADC的最大值4096(2^12=4096)。



三、问题1的排错过程
首先,博主将ADC检测引脚接到3.3V,出现了下面的状况:ADC采集到的数值很大,接近于65535,也就是2^16。


这让博主想到应该是ADC设置模型的时候设置为左对齐模型,查看了代码(如下)后发现设置的是右对齐模式啊

ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//右对齐


根据《STM32F407参考手册》P255可以得到下面结论:

右对齐:将采集到的数据放在低12位
左对齐:将采集到的数据放在高12位


为了验证是否真的是左对齐,博主先假设其位左对齐,根据上面的知识可以知道,想要实现右对齐,只需要将采集到的数值向右移动四位即可,因此,博主将主函数改为如下的代码:

adcx=Get_Adc_Average(ADC_Channel_5,20);//获取通道5的转换值,20次取平均
adcx = adcx >> 4;
temp=(float)adcx*(3.3/4096);          //获取计算后的带小数的实际电压值,比如3.1111
printf("%d,%f\n",adcx,temp);



结果如下图所示,左边是浮空状态下的数值,右边是将ADC检测引脚接到3.3V的数值,可以看出结果小于4096。


综上可得:虽然代码设置了右对齐,但是实际上是左对齐。

四、问题2:右对齐模式设置失败
为了探究对齐模式设置失败的问题,博主先学习了ADC的寄存器,在《STM32F407参考手册》P276里面找到对齐模式的设置存在于寄存器ADC_CR2里面的第11位,名称是ALIGN。





打开Keil的调试模式,查看到代码运行过ADC的初始化ADC_Init(ADC1, &ADC_InitStructure)之后,低11位的ALIGN居然是打勾的,也就是1,代表就是左对齐!!!


为了进一步研究问题所在,博主进入函数ADC_Init(),查看到给CR2(就是前面学习到设置对齐模式的寄存器)赋值的是tmpreg1 ,可以看到tmpreg1和四个值进行了 或操作 ,根据之前的博客《STM32需要的基本知识——位运算》可知,如果这四个值的第11位是1的话,那么或操作之后整个CR2的第11位就是1。

  tmpreg1 |= (uint32_t)(ADC_InitStruct->ADC_DataAlign | \
                        ADC_InitStruct->ADC_ExternalTrigConv |
                        ADC_InitStruct->ADC_ExternalTrigConvEdge | \
                        ((uint32_t)ADC_InitStruct->ADC_ContinuousConvMode << 1));

  /* Write to ADCx CR2 */
  ADCx->CR2 = tmpreg1;



接下来便进一步调试查看这四个值的数值,查看对应的二进制:

ADC_DataAlign:0x00000000
ADC_ExternalTrigConv:0x0501BD00
ADC_ExternalTrigConvEdge :0x00000000
ADC_ContinuousConvMode:0x00



ADC_ContinuousConvMode << 1 = 0x00 << 1 = 0x00
ADC_ExternalTrigConv:0x0501BD00= 0101 0001 0000 1011 1101 0000 0000
可以看到第11位是1,那么根据上面的结论可以知道tmpreg1的第11位也是1,即整个CR2的第11位就是1。
也就是说是因为ADC_ExternalTrigConv导致我们设置的对齐模式位左对齐。
进一步回顾正点原子的ADC初始化代码(如下)可以发现,确实没有对这一项进行初始化。
至于为什么他没有初始化,这个博主就不得而知了。

  ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;//独立模式
  ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;//两个采样阶段之间的延迟5个时钟
  ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled; //DMA失能
  ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4;//预分频4分频。ADCCLK=PCLK2/4=84/4=21Mhz,ADC时钟最好不要超过36Mhz
  ADC_CommonInit(&ADC_CommonInitStructure);//初始化
       
  ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;//12位模式
  ADC_InitStructure.ADC_ScanConvMode = DISABLE;//非扫描模式       
  ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;//关闭连续转换
  ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;//禁止触发检测,使用软件触发
  ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//右对齐       
  ADC_InitStructure.ADC_NbrOfConversion = 1;//1个转换在规则序列中 也就是只转换规则序列1
  ADC_Init(ADC1, &ADC_InitStructure);//ADC初始化



综上可得:右对齐模式设置失败的原因是没对ADC_InitStructure.ADC_ExternalTrigConv进行初始化设置。

五、问题2的解决方法
知道了问题所在,博主目前研究两种解决方法:
第一种:将ADC_ExternalTrigConv设置为0;
第二种:使用ADC_StructInit(ADC_InitTypeDef* ADC_InitStruct);初始化ADC_ExternalTrigConv;

5.1 将ADC_ExternalTrigConv设置为0
既然是ADC_ExternalTrigConv不为零导致的,那直接将其设置为0,即添加代码

  ADC_InitStructure.ADC_ExternalTrigConv = 0;//添加这一行
1
ADC的完整代码如下:

//初始化ADC                                                                                                                          
void  Adc_Init(void)
{   
  GPIO_InitTypeDef  GPIO_InitStructure;
  ADC_CommonInitTypeDef ADC_CommonInitStructure;
  ADC_InitTypeDef       ADC_InitStructure;
       
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);//使能GPIOA时钟
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); //使能ADC1时钟

  //先初始化ADC1通道5 IO口
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;//PA5 通道5
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;//模拟输入
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;//不带上下拉
  GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化  

  RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC1,ENABLE);          //ADC1复位
  RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC1,DISABLE);        //复位结束         

  ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;//独立模式
  ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;//两个采样阶段之间的延迟5个时钟
  ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled; //DMA失能
  ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4;//预分频4分频。ADCCLK=PCLK2/4=84/4=21Mhz,ADC时钟最好不要超过36Mhz
  ADC_CommonInit(&ADC_CommonInitStructure);//初始化
       
  ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;//12位模式
  ADC_InitStructure.ADC_ScanConvMode = DISABLE;//非扫描模式       
  ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;//关闭连续转换
  ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;//禁止触发检测,使用软件触发
  ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//右对齐       
  ADC_InitStructure.ADC_NbrOfConversion = 1;//1个转换在规则序列中 也就是只转换规则序列1
  //====================================================//
  ADC_InitStructure.ADC_ExternalTrigConv = 0;//添加这一行
  //====================================================//
  ADC_Init(ADC1, &ADC_InitStructure);//ADC初始化
  ADC_Cmd(ADC1, ENABLE);//开启AD转换器       
}



5.2 使用ADC_StructInit()函数
从上面的代码可以看出 ADC_ExternalTrigConv 是结构 ADC_InitTypeDef 的一个成员,那看看有没有官方自带的初始化函数。

ADC_InitTypeDef       ADC_InitStructure;
ADC_InitStructure.ADC_ExternalTrigConv = 0


在stm32f4xx_adc.h里面看到有对结构体进行初始化的函数,且有对 ADC_InitTypeDef 进行初始化的。

/* Initialization and Configuration functions *********************************/
void ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef* ADC_InitStruct);
void ADC_StructInit(ADC_InitTypeDef* ADC_InitStruct);
void ADC_CommonInit(ADC_CommonInitTypeDef* ADC_CommonInitStruct);
void ADC_CommonStructInit(ADC_CommonInitTypeDef* ADC_CommonInitStruct);
void ADC_Cmd(ADC_TypeDef* ADCx, FunctionalState NewState);



在stm32f4xx_adc.c进一步研究该函数的代码:

/**
  * @brief  Fills each ADC_InitStruct member with its default value.
  * @NOTE   This function is used to initialize the global features of the ADC (
  *         Resolution and Data Alignment), however, the rest of the configuration
  *         parameters are specific to the regular channels group (scan mode
  *         activation, continuous mode activation, External trigger source and
  *         edge, number of conversion in the regular channels group sequencer).  
  * @param  ADC_InitStruct: pointer to an ADC_InitTypeDef structure which will
  *         be initialized.
  * @retval None
  */
void ADC_StructInit(ADC_InitTypeDef* ADC_InitStruct)
{
  /* Initialize the ADC_Mode member */
  ADC_InitStruct->ADC_Resolution = ADC_Resolution_12b;

  /* initialize the ADC_ScanConvMode member */
  ADC_InitStruct->ADC_ScanConvMode = DISABLE;

  /* Initialize the ADC_ContinuousConvMode member */
  ADC_InitStruct->ADC_ContinuousConvMode = DISABLE;

  /* Initialize the ADC_ExternalTrigConvEdge member */
  ADC_InitStruct->ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;

  /* Initialize the ADC_ExternalTrigConv member */
  ADC_InitStruct->ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1;

  /* Initialize the ADC_DataAlign member */
  ADC_InitStruct->ADC_DataAlign = ADC_DataAlign_Right;

  /* Initialize the ADC_NbrOfConversion member */
  ADC_InitStruct->ADC_NbrOfConversion = 1;
}



其中的 ADC_ExternalTrigConv_T1_CC1 确实设置的是0。

#define ADC_ExternalTrigConv_T1_CC1                ((uint32_t)0x00000000)
#define ADC_ExternalTrigConv_T1_CC2                ((uint32_t)0x01000000)
#define ADC_ExternalTrigConv_T1_CC3                ((uint32_t)0x02000000)
#define ADC_ExternalTrigConv_T2_CC2                ((uint32_t)0x03000000)
#define ADC_ExternalTrigConv_T2_CC3                ((uint32_t)0x04000000)
#define ADC_ExternalTrigConv_T2_CC4                ((uint32_t)0x05000000)
#define ADC_ExternalTrigConv_T2_TRGO               ((uint32_t)0x06000000)
#define ADC_ExternalTrigConv_T3_CC1                ((uint32_t)0x07000000)
#define ADC_ExternalTrigConv_T3_TRGO               ((uint32_t)0x08000000)
#define ADC_ExternalTrigConv_T4_CC4                ((uint32_t)0x09000000)
#define ADC_ExternalTrigConv_T5_CC1                ((uint32_t)0x0A000000)
#define ADC_ExternalTrigConv_T5_CC2                ((uint32_t)0x0B000000)
#define ADC_ExternalTrigConv_T5_CC3                ((uint32_t)0x0C000000)
#define ADC_ExternalTrigConv_T8_CC1                ((uint32_t)0x0D000000)
#define ADC_ExternalTrigConv_T8_TRGO               ((uint32_t)0x0E000000)
#define ADC_ExternalTrigConv_Ext_IT11              ((uint32_t)0x0F000000)



所以加一行对结构体的初始化即可:

ADC_StructInit(&ADC_InitStructure);
1
完整代码如下:

//初始化ADC                                                                                                                          
void  Adc_Init(void)
{   
  GPIO_InitTypeDef  GPIO_InitStructure;
  ADC_CommonInitTypeDef ADC_CommonInitStructure;
  ADC_InitTypeDef       ADC_InitStructure;
       
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);//使能GPIOA时钟
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); //使能ADC1时钟

  //先初始化ADC1通道5 IO口
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;//PA5 通道5
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;//模拟输入
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;//不带上下拉
  GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化  

  ADC_DeInit();//ADC复位

  ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;//独立模式
  ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;//两个采样阶段之间的延迟5个时钟
  ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled; //DMA失能
  ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4;//预分频4分频。ADCCLK=PCLK2/4=84/4=21Mhz,ADC时钟最好不要超过36Mhz
  ADC_CommonInit(&ADC_CommonInitStructure);//初始化
       
  ADC_StructInit(&ADC_InitStructure);
  ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;//12位模式
  ADC_InitStructure.ADC_ScanConvMode = DISABLE;//非扫描模式       
  ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;//关闭连续转换
  ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;//禁止触发检测,使用软件触发
  ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//右对齐       
  ADC_InitStructure.ADC_NbrOfConversion = 1;//1个转换在规则序列中 也就是只转换规则序列1
  ADC_Init(ADC1, &ADC_InitStructure);//ADC初始化

  ADC_Cmd(ADC1, ENABLE);//开启AD转换器       
}


六、F1和F4关于ADC的小差别
对于问题2的第一种解决方法(直接设置为0),博主感觉很奇怪,按道理来说应该是要有个宏定义的才对,然后在查看别人看法的时候发现了F1系列里面的stm32f10x_adc.h里面确实有一个这种作用的宏定义:ADC_ExternalTrigConv_None,具体如下:

/** @defgroup ADC_external_trigger_sources_for_regular_channels_conversion
  * @{
  */
#define ADC_ExternalTrigConv_T1_CC1                ((uint32_t)0x00000000) /*!< For ADC1 and ADC2 */
#define ADC_ExternalTrigConv_T1_CC2                ((uint32_t)0x00020000) /*!< For ADC1 and ADC2 */
#define ADC_ExternalTrigConv_T2_CC2                ((uint32_t)0x00060000) /*!< For ADC1 and ADC2 */
#define ADC_ExternalTrigConv_T3_TRGO               ((uint32_t)0x00080000) /*!< For ADC1 and ADC2 */
#define ADC_ExternalTrigConv_T4_CC4                ((uint32_t)0x000A0000) /*!< For ADC1 and ADC2 */
#define ADC_ExternalTrigConv_Ext_IT11_TIM8_TRGO    ((uint32_t)0x000C0000) /*!< For ADC1 and ADC2 */

#define ADC_ExternalTrigConv_T1_CC3                ((uint32_t)0x00040000) /*!< For ADC1, ADC2 and ADC3 */
#define ADC_ExternalTrigConv_None                  ((uint32_t)0x000E0000) /*!< For ADC1, ADC2 and ADC3 */

#define ADC_ExternalTrigConv_T3_CC1                ((uint32_t)0x00000000) /*!< For ADC3 only */
#define ADC_ExternalTrigConv_T2_CC3                ((uint32_t)0x00020000) /*!< For ADC3 only */
#define ADC_ExternalTrigConv_T8_CC1                ((uint32_t)0x00060000) /*!< For ADC3 only */
#define ADC_ExternalTrigConv_T8_TRGO               ((uint32_t)0x00080000) /*!< For ADC3 only */
#define ADC_ExternalTrigConv_T5_CC1                ((uint32_t)0x000A0000) /*!< For ADC3 only */
#define ADC_ExternalTrigConv_T5_CC3                ((uint32_t)0x000C0000) /*!< For ADC3 only */



但是在F4里面却没有了这个宏定义,具体原因也还不知道。
————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/weixin_52296952/article/details/135490165

使用特权

评论回复
沙发
21mengnan| | 2024-2-29 23:00 | 只看该作者
用CubeMX配置妥当一些。

使用特权

评论回复
板凳
wanduzi| | 2024-2-29 23:19 | 只看该作者
正常是不会超过的啊,你怎么超过了。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

1360

主题

13960

帖子

8

粉丝