×
单片机 > 单片机程序设计 > 详情

STM32 关于ADC采交直流问题探讨

发布时间:2020-06-06 发布时间:
|

前沿

关于STM32采样问题,相信很多人曾遇到过这样的问题,无论是关于ADC底层相关的配置还是ADC采样方案的抉择,或者是ADC软硬件滤波算法,这里博主就自己曾做过的训练题为引申,探讨ADC采样过程中的问题。


1.ADC的认识

1.1 ADC初始化参数

/* Exported types ------------------------------------------------------------*/


/**

  * @brief   ADC Init structure definition  

  */

typedef struct

{

  uint32_t ADC_Resolution;                                 /*!< Configures the ADC resolution dual mode. This parameter can be a value of @ref ADC_resolution */                                   

  FunctionalState ADC_ScanConvMode;             /*!< Specifies whether the conversion is performed in Scan (multichannels)or Single (one channel) mode.

                                                                                   This parameter can be set to ENABLE or DISABLE */

  FunctionalState ADC_ContinuousConvMode;  /*!< Specifies whether the conversion is performed in Continuous or Single mode.

                                                                                   This parameter can be set to ENABLE or DISABLE. */

  uint32_t ADC_ExternalTrigConvEdge;          /*!< Select the external trigger edge and enable the trigger of a regular group.

                                                                           This parameter can be a value of

                                                                       @ref ADC_external_trigger_edge_for_regular_channels_conversion */

  uint32_t ADC_ExternalTrigConv;                  /*!< Select the external event used to trigger the start of conversion of a regular group.

                                                                           This parameter can be a value of

                                                                       @ref ADC_extrenal_trigger_sources_for_regular_channels_conversion */

  uint32_t ADC_DataAlign;                 /*!< Specifies whether the ADC data  alignment is left or right. This parameter can be

                                                                       a value of @ref ADC_data_align */

  uint8_t  ADC_NbrOfConversion;           /*!< Specifies the number of ADC conversions  that will be done using the sequencer for

                                                                       regular channel group.This parameter must range from 1 to 16. */

}ADC_InitTypeDef;



涉及的相关参数:ADC分辨率(6/8/10/12位)、扫描或非扫描模式、连续或不连续转换、触发方式、对齐方式等。 

       这里值得注意的是规则组转化序列不能超过16。


1.2 ADC通用初始化参数

/**

  * @brief   ADC Common Init structure definition  

  */

typedef struct

{

  uint32_t ADC_Mode;                      /*!< Configures the ADC to operate in

                                               independent or multi mode.

                                               This parameter can be a value of @ref ADC_Common_mode */                                              

  uint32_t ADC_Prescaler;                 /*!< Select the frequency of the clock

                                               to the ADC. The clock is common for all the ADCs.

                                               This parameter can be a value of @ref ADC_Prescaler */

  uint32_t ADC_DMAAccessMode;             /*!< Configures the Direct memory access

                                              mode for multi ADC mode.

                                               This parameter can be a value of

                                               @ref ADC_Direct_memory_access_mode_for_multi_mode */

  uint32_t ADC_TwoSamplingDelay;          /*!< Configures the Delay between 2 sampling phases.

                                               This parameter can be a value of

                                               @ref ADC_delay_between_2_sampling_phases */


}ADC_CommonInitTypeDef;


这里涉及相关ADC单独/多重模式选择,两采样阶段采样间隔,DMA使能/失能,ADC分屏系数(注意ADC的时钟频率不超过36Mhz)


1.3 ADC相关配置库函数

/*  Function used to set the ADC configuration to the default reset state *****/  

void ADC_DeInit(void);


/* 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);


/* Analog Watchdog configuration functions ************************************/

void ADC_AnalogWatchdogCmd(ADC_TypeDef* ADCx, uint32_t ADC_AnalogWatchdog);

void ADC_AnalogWatchdogThresholdsConfig(ADC_TypeDef* ADCx, uint16_t HighThreshold,uint16_t LowThreshold);

void ADC_AnalogWatchdogSingleChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel);


/* Temperature Sensor, Vrefint and VBAT management functions ******************/

void ADC_TempSensorVrefintCmd(FunctionalState NewState);

void ADC_VBATCmd(FunctionalState NewState);


/* Regular Channels Configuration functions ***********************************/

void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);

void ADC_SoftwareStartConv(ADC_TypeDef* ADCx);

FlagStatus ADC_GetSoftwareStartConvStatus(ADC_TypeDef* ADCx);

void ADC_EOCOnEachRegularChannelCmd(ADC_TypeDef* ADCx, FunctionalState NewState);

void ADC_ContinuousModeCmd(ADC_TypeDef* ADCx, FunctionalState NewState);

void ADC_DiscModeChannelCountConfig(ADC_TypeDef* ADCx, uint8_t Number);

void ADC_DiscModeCmd(ADC_TypeDef* ADCx, FunctionalState NewState);

uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx);

uint32_t ADC_GetMultiModeConversionValue(void);


/* Regular Channels DMA Configuration functions *******************************/

void ADC_DMACmd(ADC_TypeDef* ADCx, FunctionalState NewState);

void ADC_DMARequestAfterLastTransferCmd(ADC_TypeDef* ADCx, FunctionalState NewState);

void ADC_MultiModeDMARequestAfterLastTransferCmd(FunctionalState NewState);


/* Injected channels Configuration functions **********************************/

void ADC_InjectedChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);

void ADC_InjectedSequencerLengthConfig(ADC_TypeDef* ADCx, uint8_t Length);

void ADC_SetInjectedOffset(ADC_TypeDef* ADCx, uint8_t ADC_InjectedChannel, uint16_t Offset);

void ADC_ExternalTrigInjectedConvConfig(ADC_TypeDef* ADCx, uint32_t ADC_ExternalTrigInjecConv);

void ADC_ExternalTrigInjectedConvEdgeConfig(ADC_TypeDef* ADCx, uint32_t ADC_ExternalTrigInjecConvEdge);

void ADC_SoftwareStartInjectedConv(ADC_TypeDef* ADCx);

FlagStatus ADC_GetSoftwareStartInjectedConvCmdStatus(ADC_TypeDef* ADCx);

void ADC_AutoInjectedConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);

void ADC_InjectedDiscModeCmd(ADC_TypeDef* ADCx, FunctionalState NewState);

uint16_t ADC_GetInjectedConversionValue(ADC_TypeDef* ADCx, uint8_t ADC_InjectedChannel);


/* Interrupts and flags management functions **********************************/

void ADC_ITConfig(ADC_TypeDef* ADCx, uint16_t ADC_IT, FunctionalState NewState);

FlagStatus ADC_GetFlagStatus(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);

void ADC_ClearFlag(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);

ITStatus ADC_GetITStatus(ADC_TypeDef* ADCx, uint16_t ADC_IT);

void ADC_ClearITPendingBit(ADC_TypeDef* ADCx, uint16_t ADC_IT);


2.定时器捕获模式

定时器功能:测量输入信号的脉冲长度(输入捕获)或者产生输出波形(输出比较和pwm)


2.1 初始化基本定时参数

/**

  * @brief  TIM Time Base Init structure definition  

  * @note   This structure is used with all TIMx except for TIM6 and TIM7.  

  */


typedef struct

{

  uint16_t TIM_Prescaler;         /*!< Specifies the prescaler value used to divide the TIM clock.

                                       This parameter can be a number between 0x0000 and 0xFFFF */


  uint16_t TIM_CounterMode;       /*!< Specifies the counter mode.

                                       This parameter can be a value of @ref TIM_Counter_Mode */


  uint32_t TIM_Period;            /*!< Specifies the period value to be loaded into the active

                                       Auto-Reload Register at the next update event.

                                       This parameter must be a number between 0x0000 and 0xFFFF.  */


  uint16_t TIM_ClockDivision;     /*!< Specifies the clock division.

                                      This parameter can be a value of @ref TIM_Clock_Division_CKD */


  uint8_t TIM_RepetitionCounter;  /*!< Specifies the repetition counter value. Each time the RCR downcounter

                                       reaches zero, an update event is generated and counting restarts

                                       from the RCR value (N).

                                       This means in PWM mode that (N+1) corresponds to:

                                          - the number of PWM periods in edge-aligned mode

                                          - the number of half PWM period in center-aligned mode

                                       This parameter must be a number between 0x00 and 0xFF.

                                       @note This parameter is valid only for TIM1 and TIM8. */

} TIM_TimeBaseInitTypeDef;


2.2 输入比较参数初始化

/**

  * @brief  TIM Input Capture Init structure definition  

  */


typedef struct

{


  uint16_t TIM_Channel;      /*!< Specifies the TIM channel.

                                  This parameter can be a value of @ref TIM_Channel */


  uint16_t TIM_ICPolarity;   /*!< Specifies the active edge of the input signal.

                                  This parameter can be a value of @ref TIM_Input_Capture_Polarity */


  uint16_t TIM_ICSelection;  /*!< Specifies the input.

                                  This parameter can be a value of @ref TIM_Input_Capture_Selection */


  uint16_t TIM_ICPrescaler;  /*!< Specifies the Input Capture Prescaler.

                                  This parameter can be a value of @ref TIM_Input_Capture_Prescaler */


  uint16_t TIM_ICFilter;     /*!< Specifies the input capture filter.

                                  This parameter can be a number between 0x0 and 0xF */

} TIM_ICInitTypeDef;


定时器输入捕获模式可以来测量脉冲宽度或者测量频率 


3.相关训练题及测试要求

3.1 信号采集模块

× 测量直流信号幅值:


输入可调直流信号0~1V,测量相应的信号幅值并在LCD上显示测量值。

× 测量方波信号频率:


输入方波信号频率50~1KHz, 测量其频率并在LCD上显示频率值。 


3.2发挥部分

信号源产生正弦交流信号(50~1KHz),幅值在-1v~+1v范围内可调。STM32测量正弦波信号的幅值和频率,并将测量值显示在LCD上。


显示信号幅值和频率外,在LCD上显示正弦波信号曲线。


4.总体方案流程图

  

对应软件总体框架


/*****************************************************

*   函数功能:主函数模块

*     作者:klaus 邮箱:xcf2016a@outlook.com

*     功能介绍:集成各功能模块

*   版本:1.1(暂无改动)

******************************************************/

int main(void)

{    

    TIM3_Int_Init(1000-1,840-1);

    TIM4_Int_Init(Tim4SetCnt-1,84-1);

    TIM2_pwm_input(0xffffffff,84-1); 

    BSP_ADC_Init();              

    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

    KEY_Init();

    LED_Init();    

    delay_init(168);

    OLED_Init();


    while(1)

    {

        if(flag_10ms)

        {

            key_action();//按键函数

            if(delay%50==0)    

                Oled_show();//显示函数

            system_maintain();

            sample_process();//采样函数

            flag_10ms=0;

        }

    }

}

5. 硬件电路设计

5.1 电压比较器电路

电压比较器的功能是对两个输入电压的大小进行比较,并根据比较结果输出高低电平。 

       通常用阈值电压和传输特性来描述比较器的工作特性。 阈值电压(又称门槛电平)是使比较器输出电 压发生跳变时的输入电压值,简称为阈值,用符号UTH表示。估算阈值主要应抓住输入信号使输出电压发生跳变时的临界条件。这个临界条件 是集成运放两个输入端的电位相等(两个输入端的电流也视为零),即U+=U–。


5.1.1 零电平比较器(过零比较器)

电压比较器是将一个模拟输入信号ui与一个固定的参考电压UR进行比较和鉴别的电路。   

       参考电压为零的比较器称为零 电平比较器。按输入方式的不同可分为反相输入和同相输入两种零电位比较器 


5.1.2 任意电平比较器(俘零比较器)

将零电平比较器中的接地端改接为一个参考电压UR(设 为直流电压),由于UR的大小和极性均可调整,电路成为任意电平比较器或称俘零比较器。 


5.1.3 滞回电压比较器

滞回比较器又称施密特触发器,迟滞比较器。这种比较器的特点是当输入信号ui逐渐增大或逐渐减小时,它有两个阈值,且不相等,其传输特性具有“滞回”曲线的形状。 

       滞回比较器也有反相输入和同相输入两种方式。    

       UR是某一固定电压,改变UR值能改变阈值及回差大小。 

  

本次中,我们选择电力电子中常用的LM393比较芯片,下图为比较原理图 

在输入端,我们直接输入交流正弦型号,,Dz稳压二极管会将电压抬至一定高度,供芯片比较。 

       如下图所示 


5.2 交流采样电路

当输出电压、电流是双极性的交流信号,AD只能采单极性的交流信号。采用如下图所示的直流偏移电路可以把信号转换一下。 

电解电容C取470uF/16V,R1、R2取2k,这样高通截止频率为2Hz左右,用于采样20Hz以上的信号。 

  

计算过程中,先用求平均的方式把直流偏置算出来,然后扣除该值就得到交流信号的实际值了。不过要注意这部分电路要放尽量在单片机那头。


6.软件设计

根据题目要求,我们配置ADC成ADC1和ADC2双重模式,即由主ADC1带动从ADC2进行交替采样,详细代码见源文件,具体配置过程参照前面博客,这里是链接。


6.1 直流采样问题

直流采样,让ADC一直循环取值,相关程序如下


    double adc_getvalue[10];

    u32 adSumDC[10];


    memset(&adSumDC,0,sizeof(adSumDC));

    for(k=0;k<10;k++){

            for(j=0;j<40;j++)

                adSumDC[k] += aADCConvertedValue[2];

            adc_getvalue[k]=(double)adSumDC[k]*33/(4096*400);

    }

    memset(&adSumDC,0,sizeof(adSumDC));

    AdLight=adc_filter(10,adc_getvalue);


同时为直流采样设计滤波函数,代码如下


double adc_filter(u32 num,double *adc_num_value)

{

  u8 i,j,k;

  u8 noswap=1;

    double adc_sum_tmp=0,adc_ave_tmp=0;


  for(i=0;i


      for(j=0;j

      {

          if(adc_num_value[j]>adc_num_value[j+1]){

              adc_num_value[j]=adc_num_value[j]+adc_num_value[j+1];

              adc_num_value[j+1]=adc_num_value[j]-adc_num_value[j+1];

              adc_num_value[j]=adc_num_value[j]-adc_num_value[j+1];

              noswap=0;

          }

       }

      if(noswap) break;

  }

  for(k=2;k

  //adc_sum_tmp -= (adc_num_value[0]+adc_num_value[1]+adc_num_value[num-2]+adc_num_value[num-1]);    

  adc_ave_tmp=adc_sum_tmp/(num-4);


  adc_sum_tmp=0;


    return adc_ave_tmp;

}



我们直接将采样引脚连接到3.3v和0vde得到如下结果 


6.2 交流频率测量

对于测交流正弦波信号的频率,我们将双极性的正弦波通过电压比较整形成脉冲波供单片机采样。 

       这里利用定时器的输入捕获功能,相应定时器的底层配置可参照之前博客,这里是链接。


相关计算频率过程代码如下 

相关计算频率过程代


void TIM2_IRQHandler(void)

{

    if(TIM_GetITStatus(TIM2, TIM_IT_CC2) != RESET)

    {

        TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE); 

        //ADStartTim4Flag=1;

            TIM_ClearITPendingBit(TIM2, TIM_IT_CC2);

    }

    IC2Value=TIM_GetCapture2(TIM2);

    IC1Value=TIM_GetCapture1(TIM2);

    //GPIO_SetBits(GPIOD,GPIO_Pin_1);

    if(IC2Value!=0){

        DutyCycle=(float)IC1Value*100/IC2Value;

        Frequency =(float)1000000/IC2Value;

    }

    else{

        GPIO_ResetBits(GPIOD,GPIO_Pin_1);

        DutyCycle=0;

        Frequency=0;

    }

    TIM_ClearITPendingBit(TIM2, TIM_IT_Update);

}


显示函数如下


    OLED_ShowString(0,0,"MeasureResult:",16);

    OLED_ShowString(0,16,"IC2Value:",16);

    OLED_ShowNum(72,16,IC2Value,7,16);

    OLED_ShowString(0,32,"DutyCycle:",16);

    OLED_ShowFloatNum(80,32,DutyCycle,7,16);

    OLED_ShowString(0,48,"Frequency:",16);

    OLED_ShowFloatNum(80,48,Frequency,7,16);


实验现象图 


6.3 交流采样问题

对于交流采样,采取的是同步采样过程,具体步骤是电压比较电路触发定时器TIM4实时,定时器根据输入信号频率计算定时间隔,等间隔采样。


6.3.1定时器中断程序

/*****************************************************

*   函数功能:TIM4中断程序,同步采样处理

*     作者:klaus 邮箱:xcf2016a@outlook.com

*     功能介绍:触发同步采样

*   版本:1.1(暂无改动)

******************************************************/

void TIM4_IRQHandler(void)

{

        if(TIM_GetITStatus(TIM4,TIM_IT_Update)==SET) 

        {

                ADArrayStay[ArrayCnt]=aADCConvertedValue[3];

                ArrayCnt++;

                if(ArrayCnt>=ADPointGet){

                    ArrayCnt=0;ADGetDowmFlag=1;

                    TIM_ITConfig(TIM4,TIM_IT_Update,DISABLE); 

                    cnt_test++;

                    if(cnt_test%2==0)GPIO_ResetBits(GPIOD,GPIO_Pin_1);else GPIO_SetBits(GPIOD,GPIO_Pin_1);    

        }        

    }    

    TIM_ClearITPendingBit(TIM4,TIM_IT_Update); 

}


6.3.2 AD计算程序

if(ADGetDowmFlag==1)

    {

        sampleMax=sampleMin=ADArrayStay[0];

        for(k=1;k

        {

                if(ADArrayStay[k]>=sampleMax)sampleMax=ADArrayStay[k];

                if(ADArrayStay[k]<=sampleMin && ADArrayStay[k]!=0)sampleMin=ADArrayStay[k];

        }

        sampleIndex=3.3f*(sampleMax-sampleMin)/4096;

        VppValue=sampleIndex;

        VoltageRms[AdCnt]=sampleIndex/2.828f;

        AdCnt++;

        if(AdCnt>=FILTER_POINTS){AdCnt=0;ADVoltage=adc_filter(FILTER_POINTS,VoltageRms);}

 }ADGetDowmFlag=0;


6.3.3 显示函数

OLED_ShowString(0,0,"ACModeTake:",16);

OLED_ShowString(0,16,"VppVal:",16);

OLED_ShowFloatNum(56,16,VppValue,7,16);

OLED_ShowString(104,16,"v",16);

OLED_ShowString(0,32,"RmsVal:",16);

OLED_ShowFloatNum(56,32,ADVoltage,7,16);

OLED_ShowString(104,32,"v",16);

OLED_ShowString(0,48,"Frequ:",16);

OLED_ShowFloatNum(48,48,Frequency,7,16);

OLED_ShowString(96,48,"Hz",16);


实验现象图 

至于造成峰峰值不准的原因是:没供地,即博主单片机是电脑jlink供电,电路是电源供电,输入信号是信号源供电,三者地没有连在一起。


6.4 正弦波画图函数

将AD采样回来的函数进行相应数值变化以适合OLED显示,先画点,连线成图 

程序代码如下


void sin_table_plot{

   OLED_ShowChar(0,0,'^',16,1);

   OLED_DrawLine(4,1,4,62,1);

   OLED_DrawLine(4,60,70,60,1);

   OLED_ShowChar(70,54,'>',12,1);


   for(i=4;i<66;i++){

       xi=i;

       yi=60*ADArrayStay[i-4]/4096;

       OLED_DrawLine(xn,yn,xi,yi,1);

       xn=i;

       yn=yi;

   }     

}   


实验现象图 


7.实验总图





『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
C51 特殊功能寄存器SFR的名称和地址