你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】STM32F2XX——ADC多通道DMA采集时AD值大于4095的问题解决方法

[复制链接]
STMCU小助手 发布时间:2021-12-2 16:03
前言
  最近在调试STM32F2XX系列ADC多通道DMA采集时,发现采集的AD值大于4095,有的65000多了,这是什么节奏?adc不是12位吗,最大才0xfff,即4095,怎么会出现这种情况呢?难到是adc数据对齐方式出现问题了,adc的对齐明明设置的是右对齐啊,神马情况?

ADC结构体参数分析

  百思不得adc之姐,只能keil单步调试,一步一步查看adc结构体的参数。我使用的是adc dma方式采集,共9个通道。初始化程序如下:
  1. void ADC_DMA_Config(void)
  2. {
  3.     ADC_InitTypeDef       ADC_InitStructure;
  4.     ADC_CommonInitTypeDef ADC_CommonInitStructure;
  5.     DMA_InitTypeDef       DMA_InitStructure;
  6.     GPIO_InitTypeDef      GPIO_InitStructure;

  7.     RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2 | RCC_AHB1Periph_GPIOA | RCC_AHB1Periph_GPIOC, ENABLE);
  8.     RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);

  9.     DMA_InitStructure.DMA_Channel = DMA_Channel_0;  
  10.     DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
  11.     DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)&ADC_ConvertedValue;  
  12.     DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
  13.     DMA_InitStructure.DMA_BufferSize = Sample_Num*Channel_Num;         
  14.     DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  15.     DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
  16.     DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
  17.     DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
  18.     DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
  19.     DMA_InitStructure.DMA_Priority = DMA_Priority_High;
  20.     DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;         
  21.     DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
  22.     DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
  23.     DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
  24.     DMA_Init(DMA2_Stream0, &DMA_InitStructure);
  25.     DMA_Cmd(DMA2_Stream0, ENABLE);


  26.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 |GPIO_Pin_2 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7;
  27.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
  28.     GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;
  29.     GPIO_Init(GPIOA, &GPIO_InitStructure);

  30.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_3 | GPIO_Pin_5;
  31.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
  32.     GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;
  33.     GPIO_Init(GPIOC, &GPIO_InitStructure);

  34.     /* ADC Common Init **********************************************************/
  35.     ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;
  36.     ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div2;
  37.     ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
  38.     ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;
  39.     ADC_CommonInit(&ADC_CommonInitStructure);

  40.     ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
  41.     ADC_InitStructure.ADC_ScanConvMode = ENABLE;
  42.     ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
  43.     ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
  44.     ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
  45.     ADC_InitStructure.ADC_NbrOfConversion = 9;
  46.     ADC_Init(ADC1, &ADC_InitStructure);


  47.     ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_3Cycles);
  48.     ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_3Cycles);
  49.     ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_3Cycles);
  50.     ADC_RegularChannelConfig(ADC1, ADC_Channel_4, 4, ADC_SampleTime_3Cycles);
  51.     ADC_RegularChannelConfig(ADC1, ADC_Channel_5, 5, ADC_SampleTime_3Cycles);
  52.     ADC_RegularChannelConfig(ADC1, ADC_Channel_7, 6, ADC_SampleTime_3Cycles);
  53.     ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 7, ADC_SampleTime_3Cycles);
  54.     ADC_RegularChannelConfig(ADC1, ADC_Channel_13, 8, ADC_SampleTime_3Cycles);
  55.     ADC_RegularChannelConfig(ADC1, ADC_Channel_15, 9, ADC_SampleTime_3Cycles);

  56.     ADC_DMARequestAfterLastTransferCmd(ADC1, ENABLE);
  57.     ADC_DMACmd(ADC1, ENABLE);
  58.     ADC_Cmd(ADC1, ENABLE);

  59. }
复制代码

  其中涉及adc的初始化结构体代码部分如下:

    ADC_InitTypeDef       ADC_InitStructure;
  1.     ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
  2.     ADC_InitStructure.ADC_ScanConvMode = ENABLE;
  3.     ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
  4.     ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
  5.     ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
  6.     ADC_InitStructure.ADC_NbrOfConversion = 9;
  7.     ADC_Init(ADC1, &ADC_InitStructure);
复制代码
  一步一步调式发现结构体有一项ADC_ExternalTrigConv是0x08002d54,这一项并没有初始化对其赋值,忘记给结构体的这一项赋值了。最后这个值通过ADC_Init函数赋值给CR2的时候,第11位(也就是数据对其位)是1了,左对齐了!!!!!

20180823193630758.png


  下面来重点分析下adc初始化结构体中的各项参数的意义:
  对于STM32,在使用ADC的时候需要配置几个参数。
(1) 第一个参数是ADC_Mode,这里设置为独立模式:

  1. ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
复制代码

在这个模式下,双ADC不能同步,每个ADC接口独立工作。所以如果不需要ADC同步或者只是用了一个ADC的时候,就应该设成独立模式了。

(2) 第二个参数是ADC_ScanConvMode,这里设置为DISABLE。
  1. ADC_InitStructure.ADC_ScanConvMode = ENABLE;
复制代码

如果只是用了一个通道的话,DISABLE就可以了,如果使用了多个通道的话,则必须将其设置为ENABLE。

(3) 第三个参数是ADC_ContinuousConvMode,这里设置为ENABLE,即连续转换。
如果设置为DISABLE,则是单次转换。两者的区别在于连续转换直到所有的数据转换完成后才停止转换,而单次转换则只转换一次数据就停止,要再次触发转换才可以。所以如果需要一次性采集1024个数据或者更多,则采用连续转换。

(4) 第四个参数是ADC_ExternalTrigConv,即选择外部触发模式。这里只讲三种:

1、第一种是最简单的软件触发,参数一般为ADC_ExternalTrigConv_None(注意STM32F2XX系列这个有点蹊跷,后面会讲这个)。设置好后还要记得调用库函数:
  1. ADC_SoftwareStartConvCmd(ADC1, ENABLE);
复制代码
  1. void ADC_SoftwareStartConv(ADC_TypeDef* ADCx)
  2. {
  3.   /* Check the parameters */
  4.   assert_param(IS_ADC_ALL_PERIPH(ADCx));

  5.   /* Enable the selected ADC conversion for regular group */
  6.   ADCx->CR2 |= (uint32_t)ADC_CR2_SWSTART;
  7. }
复制代码
  1. #define  ADC_CR2_SWSTART                     ((uint32_t)0x40000000)      
  2. /*!<Start Conversion of regular channels */
复制代码

ADC_SoftwareStartConv函数里直接置位CR2的ADC_CR2_SWSTART位,这样触发就启动了。

2、第二种是定时器通道输出触发。共有这几种:ADC_ExternalTrigConv_T1_CC1、ADC_ExternalTrigConv_T1_CC2、ADC_ExternalTrigConv_T2_CC2、

ADC_ExternalTrigConv_T3_T以及ADC_ExternalTrigConv_T4_CC4。定时器输出触发比较麻烦,还需要设置相应的定时器。以

ADC_ExternalTrigConv_T2_CC2触发为例设置相应的定时器:

  1. void TIM2_Configuration(void)

  2. {

  3. TIM_TimeBaseInitTypeDef   TIM_TimeBaseStructure;

  4. TIM_OCInitTypeDef         TIM_OCInitStructure;

  5. TIM_TimeBaseStructure.TIM_Prescaler = 4;

  6. TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;

  7. TIM_TimeBaseStructure.TIM_Period = 0XFF;

  8. TIM_TimeBaseStructure.TIM_ClockDivision = 0;

  9. TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;

  10. TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);


  11. TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;

  12. TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;

  13. TIM_OCInitStructure.TIM_Pulse = 0X7F;

  14. TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;

  15. TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set;        

  16. //配置CC2的属性。若CC3作为ADC的触发源,则应改为TIM_OC3Init(TIM2, &TIM_OCInitStructure);

  17. TIM_OC2Init(TIM2, &TIM_OCInitStructure);

  18. TIM_Cmd(TIM2, ENABLE);
  19. TIM_CtrlPWMOutputs(TIM2, ENABLE);
  20. }
复制代码


这样设置之后就可以用定时器2的输出触发了,至于触发的周期,设置TIM2的时间即可。这里不再赘述。

3、第三种是外部引脚触发,对于规则通道,选择EXTI线11和TIM8_TRGO作为外部触发事件;而注入通道组则选择EXTI线15和TIM8_CC4作为外部触发事件。

(5) 第五个参数是ADC_DataAlign,这里设置为ADC_DataAlign_Right右对齐方式。建议采用右对齐方式,因为这样处理数据会比较方便。当然如果要从高位开始传输数据,那么采用左对齐优势就明显了。

(6) 第六个参数是ADC_NbrOfChannel,顾名思义:通道的数量。要是到多个通道采集数据的话就得设置一下这个参数。此外在规则通道组的配置函数中也许将各个通道的顺序定义一下,如:
  1.       ADC_RegularChannelConfig(ADC1,ADC_Channel_13,1,ADC_SampleTime_13Cycles5);

  2.       ADC_RegularChannelConfig(ADC1,ADC_Channel_14,2,ADC_SampleTime_13Cycles5);
复制代码


  多通道数据传输时有一点还要注意:若一个数组为ADC_ValueTab[4],且设置了两个通道:通道1和通道2,则转换结束后,ADC_ValueTab[0]和ADC_ValueTab[2]存储的是通道1的数据,而ADC_ValueTab[1]和ADC_ValueTab[3]存储的是通道2的数据。如果数组容量大则依次类推。

  补充一点:在使用DMA传输数据的时候,需要设置外设地址和存储器地址,外设地址当然就是ADC的地址了,而存储器的地址如果使用8位数据的话,存储器必须定义为8位缓冲区;如果使用16位数据格式的话,存储器则为16位缓冲器,不可定义为32位或更多,否则,数据将出错。

问题的解决方法

  上面分析了adc结构体的参数,下面再来单独看看ADC_ExternalTrigConv这一项。
  在stm32f2xx_adc.h文件中有这个成员的参数定义,发现里面并没有ADC_ExternalTrigConv_None这个常见的定义。

  1. /** @defgroup ADC_extrenal_trigger_sources_for_regular_channels_conversion
  2.   * @{
  3.   */
  4. #define ADC_ExternalTrigConv_T1_CC1                ((uint32_t)0x00000000)
  5. #define ADC_ExternalTrigConv_T1_CC2                ((uint32_t)0x01000000)
  6. #define ADC_ExternalTrigConv_T1_CC3                ((uint32_t)0x02000000)
  7. #define ADC_ExternalTrigConv_T2_CC2                ((uint32_t)0x03000000)
  8. #define ADC_ExternalTrigConv_T2_CC3                ((uint32_t)0x04000000)
  9. #define ADC_ExternalTrigConv_T2_CC4                ((uint32_t)0x05000000)
  10. #define ADC_ExternalTrigConv_T2_TRGO               ((uint32_t)0x06000000)
  11. #define ADC_ExternalTrigConv_T3_CC1                ((uint32_t)0x07000000)
  12. #define ADC_ExternalTrigConv_T3_TRGO               ((uint32_t)0x08000000)
  13. #define ADC_ExternalTrigConv_T4_CC4                ((uint32_t)0x09000000)
  14. #define ADC_ExternalTrigConv_T5_CC1                ((uint32_t)0x0A000000)
  15. #define ADC_ExternalTrigConv_T5_CC2                ((uint32_t)0x0B000000)
  16. #define ADC_ExternalTrigConv_T5_CC3                ((uint32_t)0x0C000000)
  17. #define ADC_ExternalTrigConv_T8_CC1                ((uint32_t)0x0D000000)
  18. #define ADC_ExternalTrigConv_T8_TRGO               ((uint32_t)0x0E000000)
  19. #define ADC_ExternalTrigConv_Ext_IT11              ((uint32_t)0x0F000000)
复制代码

  再来看看stm32f10x_adc.h文件中的ADC_ExternalTrigConv的参数定义:
  1. /** @defgroup ADC_external_trigger_sources_for_regular_channels_conversion
  2.   * @{
  3.   */

  4. #define ADC_ExternalTrigConv_T1_CC1                ((uint32_t)0x00000000) /*!< For ADC1 and ADC2 */
  5. #define ADC_ExternalTrigConv_T1_CC2                ((uint32_t)0x00020000) /*!< For ADC1 and ADC2 */
  6. #define ADC_ExternalTrigConv_T2_CC2                ((uint32_t)0x00060000) /*!< For ADC1 and ADC2 */
  7. #define ADC_ExternalTrigConv_T3_TRGO               ((uint32_t)0x00080000) /*!< For ADC1 and ADC2 */
  8. #define ADC_ExternalTrigConv_T4_CC4                ((uint32_t)0x000A0000) /*!< For ADC1 and ADC2 */
  9. #define ADC_ExternalTrigConv_Ext_IT11_TIM8_TRGO    ((uint32_t)0x000C0000) /*!< For ADC1 and ADC2 */

  10. #define ADC_ExternalTrigConv_T1_CC3                ((uint32_t)0x00040000) /*!< For ADC1, ADC2 and ADC3 */
  11. #define ADC_ExternalTrigConv_None                  ((uint32_t)0x000E0000) /*!< For ADC1, ADC2 and ADC3 */   //f2xx系列并没有这个定义

  12. #define ADC_ExternalTrigConv_T3_CC1                ((uint32_t)0x00000000) /*!< For ADC3 only */
  13. #define ADC_ExternalTrigConv_T2_CC3                ((uint32_t)0x00020000) /*!< For ADC3 only */
  14. #define ADC_ExternalTrigConv_T8_CC1                ((uint32_t)0x00060000) /*!< For ADC3 only */
  15. #define ADC_ExternalTrigConv_T8_TRGO               ((uint32_t)0x00080000) /*!< For ADC3 only */
  16. #define ADC_ExternalTrigConv_T5_CC1                ((uint32_t)0x000A0000) /*!< For ADC3 only */
  17. #define ADC_ExternalTrigConv_T5_CC3                ((uint32_t)0x000C0000) /*!< For ADC3 only */
复制代码

  对比发现stm32f2xx系列并没有ADC_ExternalTrigConv_None这个定义,很是奇怪,现在还不明白ST的工程师为什么做这个变动。
  那么问题怎么解决呢?
  一种是进行adc结构体的初始化操作,在配置之前恢复默认值。即调用ADC_StructInit(&ADC_InitStructure);修改如下:
  1.     ADC_StructInit(&ADC_InitStructure);//新增加的
  2.     ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
  3.     ADC_InitStructure.ADC_ScanConvMode = ENABLE;
  4.     ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
  5.     ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
  6.     ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
  7.     ADC_InitStructure.ADC_NbrOfConversion = 9;
  8.     ADC_Init(ADC1, &ADC_InitStructure);
复制代码
  调用ADC_StructInit(&ADC_InitStructure)语句,ADC_ExternalTrigConv会恢复默认值0。如下:

20180823201211262.png


  另一种方法是直接配置该参数为0,如下:
  1.     ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
  2.     ADC_InitStructure.ADC_ScanConvMode = ENABLE;
  3.     ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
  4.     ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
  5.     ADC_InitStructure.ADC_ExternalTrigConv= 0;//新增加的
  6.     ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
  7.     ADC_InitStructure.ADC_NbrOfConversion = 9;
  8.     ADC_Init(ADC1, &ADC_InitStructure);
复制代码

  这两种方法都可以解决adc采样对齐方式异常问题。
  但是ADC_ExternalTrigConv= 0在stm32f2xx_adc.h中其实是对应ADC_ExternalTrigConv_T1_CC1的。

20180823201832969.png


  也就是说调用软件触发函数ADC_SoftwareStartConv函数里直接置位CR2的ADC_CR2_SWSTART位也可以触发定时器触发方式的adc通道。



收藏 评论0 发布时间:2021-12-2 16:03

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版