天天看點

STM32 FFT DMA ADC THD

利用STM32 FFT算法計算THD

一、裝置準備

——>粵嵌STM32F429IGT6開發闆 1塊

——>序列槽調試助手

二、FFT算法意義

使用FFT算法,是為了擷取信号在頻域的相關參數,即信号的頻譜。包括信号在頻譜上各點的頻率和該點的幅值。

STM32 FFT DMA ADC THD

由上面的調制信号和已調信号的頻譜圖,我們可以觀察到正弦信号在未經過調制前,其頻譜圖僅僅在0頻附近有一條譜線,這條譜線即為未調制信号的頻譜。同時,我們可以觀察到調制後的正弦信号的頻譜發生了搬移,即從零頻附近搬移到載波信号的頻譜處。

首先讓我們了解一個概念,信号的組成:任何連續測量的時序或信号,都可以表示為不同頻率的正弦波信号的無限疊加。而根據該原理創立的傅立葉變換算法利用直接測量到的原始信号,以累加方式來計算該信号中不同正弦波信号的頻率、振幅和相位。

其實,簡單的了解,觀察信号的角度從時域轉換到頻域,對觀察者最大的好處是:在頻域,信号的頻譜圖能夠反映該信号所含的各種頻率成分的信号及它們的幅值。

傅裡葉級數的意義

三、DSP實際使用方法

我們使用ST公司提供的DSP庫中的FFT算法。

使用步驟如下:

- STM32F4 DSP簡介

STM32F4采用Cortex-M4核心,相比Cortex-M3系列除了内置硬體FPU單元(浮點運算單元),FPU是專用于浮點運算的處理器,在數字信号處理方面還增加了DSP指令集,支援諸如單周期乘加指令(MAC),優化的單指令多資料指令(SIMD),飽和算數等多種數字信号處理指令集。Cortex-M4執行所有的DSP指令都可以在單周期内完成,而Cortex-M3需要多個指令和多個周期才能完成同樣的功能。

- 下載下傳DSP_Lib源碼包

找到DSP_Lib源碼包,該檔案夾目錄結構如下圖所示:

STM32 FFT DMA ADC THD

DSP_Lib源碼包的Source檔案夾是所有DSP庫的源碼,Examples檔案夾是相對應的一些測試執行個體。這些測試執行個體都是帶main函數的,也就是拿到工程中可以直接使用。

STM32 FFT DMA ADC THD

查閱原子STM32F4開發指南-寄存器版本,P699頁,了解各種源碼的功能介紹。

- 搭建DSP運作環境

隻要DSP庫運作環境搭建好了,使用DSP庫裡面的函數來做相關處理就非常簡單。在MDK裡面搭建STM32F4的DSP運作環境(使用.lib方式)是很簡單的,分為3個步驟:

1、添加檔案

在例程工程目錄下建立:DSP_LIB檔案夾,存放要添加的arm_correxM4lf_math.lib和相關頭檔案。

STM32 FFT DMA ADC THD
STM32 FFT DMA ADC THD

打開工程,建立DSP_LIB分組,并将arm_cortexM4lf_math.lib添加到工程裡面。

STM32 FFT DMA ADC THD

如上面兩圖所示,這樣添加檔案就結束了。

2、添加頭檔案包含路徑。

添加好了.lib檔案後,我們要添加頭檔案包含路徑,将第一步拷貝的Include檔案夾和DSP_LIB檔案夾,加入頭檔案包含路徑,如下圖所示。

STM32 FFT DMA ADC THD

3.添加全局變量宏

STM32 FFT DMA ADC THD

注意:這裡兩個宏之間用“,”隔開。這裡我添加的宏包括:USE_STDPERIPH_DRIVER,STM32F429_439xx,ARM_MATH_CM4,__CC_ARM,ARM_MATH_MATRIX_CHECK,ARM_MATH_ROUNDING,如果沒有在Target頁籤設定Code Generation選擇use FPU,則必須在此處手動添加_FPU_USED。

這樣,STM32F4DSP庫運作環境就搭建完成了。

四、代碼介紹

我采用在時域對信号進行采樣,将時域采樣資料進行4096點FFT變換,得到信号各頻率成分的幅值,最後計算該信号的THD。

1.DMA介紹

DMA: 全稱為: Direct Memory Access,即直接存儲器通路。 DMA 傳輸方式無需 CPU 直接控制傳輸,也沒有中斷處理方式那樣保留現場和恢複現場的過程,通過硬體為 RAM 與 I/O 裝置開辟一條直接傳送資料的通路, 能使 CPU 的效率大為提高。

STM32 最多有 2 個 DMA 控制器(DMA2 僅存在大容量産品中), DMA1 有 7 個通道。 DMA2 有 5個通道。每個通道專門用來管理來自于一個或多個外設對存儲器通路的請求。還有一個仲裁起來協調各個 DMA 請求的優先權。

2.程式設計思想

程式設計采用ADC+DMA+定時器觸發的方式,簡單的了解就是定時器具有生成PWM信号的功能,該脈沖信号在上升沿會觸發ADC采樣,采集的資料會直接被存儲在使用者自定義的記憶體單元中,當每次采集完4096個點的采樣資料後,才會被再一次傳輸到使用者自定義記憶體單元,即進行采樣資料的更新。

3.采樣定理介紹

在進行模拟/數字信号的轉換過程中,當采樣頻率fs.max大于信号中最高頻率fmax的2倍時(fs.max>2fmax),采樣之後的數字信号完整地保留了原始信号中的資訊,一般實際應用中保證采樣頻率為信号最高頻率的2.56~4倍;采樣定理又稱奈奎斯特定理。即當滿足采樣定理時,可以不失真的恢複原信号,或者說得到原始信号的有用資訊。

采用ADC+DMA+定時器的方式實作信号的采樣時,采樣頻率由定時器的頻率決定,需要注意的是,定時器的頻率必須小于ADC頻率。關于這個問題可以看這裡:STM32定時觸發ADC 采樣頻率等問題總結

4、ADC配置

ADC_InitTypeDef ADC_InitStructure;
	ADC_CommonInitTypeDef ADC_CommonInitStructure;
  // 開啟ADC時鐘
  RCC_APB2PeriphClockCmd(RHEOSTAT_ADC_CLK , ENABLE);
  // -------------------ADC Common 結構體 參數 初始化------------------------
  // 獨立ADC模式
  ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;
  // 時鐘為fpclk x分頻	
  ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4;
  // 禁止DMA直接通路模式	
  ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
  // 采樣時間間隔	
  ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;  
  ADC_CommonInit(&ADC_CommonInitStructure);

  // -------------------ADC Init 結構體 參數 初始化--------------------------
  ADC_StructInit(&ADC_InitStructure);
  // ADC 分辨率
  ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
  // 禁止掃描模式,多通道采集才需要	
  ADC_InitStructure.ADC_ScanConvMode = DISABLE; 
  // 連續轉換	
  ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; 
  //禁止外部邊沿觸發
  ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_Rising;
  //外部觸發通道,本例子使用軟體觸發,此值随便指派即可
  ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T3_TRGO;
  //資料右對齊	
  ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
  //轉換通道 1個
  ADC_InitStructure.ADC_NbrOfConversion = 1;                                    
  ADC_Init(RHEOSTAT_ADC, &ADC_InitStructure);
  //---------------------------------------------------------------------------
	
  // 配置 ADC 通道轉換順序為1,第一個轉換,采樣時間為3個時鐘周期
  ADC_RegularChannelConfig(RHEOSTAT_ADC, RHEOSTAT_ADC_CHANNEL, 1,ADC_SampleTime_15Cycles);

  // 使能DMA請求 after last transfer (Single-ADC mode)
  ADC_DMARequestAfterLastTransferCmd(RHEOSTAT_ADC, ENABLE);
  // 使能ADC DMA
  ADC_DMACmd(RHEOSTAT_ADC, ENABLE);	
  
 // 使能ADC
    ADC_Cmd(RHEOSTAT_ADC, ENABLE);  
  //開始adc轉換,軟體觸發
 //  ADC_SoftwareStartConv(RHEOSTAT_ADC);
}
           

5、DMA配置

DMA_InitTypeDef DMA_InitStructure;
   NVIC_InitTypeDef NVIC_InitStructure;
  // ------------------DMA Init 結構體參數 初始化--------------------------
  // ADC1使用DMA2,資料流0,通道0,這個是手冊固定死的
  // 開啟DMA時鐘
    RCC_AHB1PeriphClockCmd(RHEOSTAT_ADC_DMA_CLK, ENABLE); 
	// 外設基址為:ADC 資料寄存器位址
	DMA_InitStructure.DMA_PeripheralBaseAddr = RHEOSTAT_ADC_DR_ADDR;	
  // 存儲器位址,實際上就是一個内部SRAM的變量	
	DMA_InitStructure.DMA_Memory0BaseAddr = (u32)&ADC_ConvertedValue;  
  // 資料傳輸方向為外設到存儲器	
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;	
	// 緩沖區大小為,指一次傳輸的資料量
	DMA_InitStructure.DMA_BufferSize = 4096;	
	// 外設寄存器隻有一個,位址不用遞增
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  // 存儲器位址固定
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; 
   // // 外設資料大小為半字,即兩個位元組 
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; 
     //	存儲器資料大小也為半字,跟外設資料大小相同
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;	
	// 循環傳輸模式
	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
  // DMA 傳輸通道優先級為高,當使用一個DMA通道時,優先級設定不影響
	DMA_InitStructure.DMA_Priority = DMA_Priority_High;
	  // 禁止DMA FIFO	,使用直連模式
	DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;  
	  // FIFO 大小,FIFO模式禁止時,這個不用配置	
	DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
	DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
	DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;  
		// 選擇 DMA 通道,通道存在于流中
	DMA_InitStructure.DMA_Channel = RHEOSTAT_ADC_DMA_CHANNEL; 
	  //初始化DMA流,流相當于一個大的管道,管道裡面有很多通道
	DMA_Init(RHEOSTAT_ADC_DMA_STREAM, &DMA_InitStructure);
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);	

    NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream0_IRQn;

    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x01;
 
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01;
	
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

    NVIC_Init(&NVIC_InitStructure);
    
    
// 使能DMA流
    DMA_ITConfig(DMA2_Stream0,DMA_IT_TC,ENABLE); 
	 
   DMA_Cmd(RHEOSTAT_ADC_DMA_STREAM, ENABLE);
           

6、定時器配置

一般情況下,定時器觸發ADC轉換并不需要中斷,是以不需要配置定時器的中斷優先級。

//使用通用定時器 觸發ADC采樣  TIM6,由于時鐘頻率不清楚,本身進行的配置
static void TIM_Mode_Config(void)
{
    //NVIC_InitTypeDef NVIC_InitStructure;
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	// 開啟TIMx_CLK,x[6,7] 
    RCC_APB1PeriphClockCmd(GENERAL_TIM_CLK, ENABLE); 

  /* 累計 TIM_Period個後産生一個更新或者中斷*/		
    //定時器初值,采樣間隔0.05/1024s,初值為(0.05/1024)/(1/10M)=488
    //TIM_TimeBaseStructure.TIM_Period = 81 //(原始設定的數值)      
	TIM_TimeBaseStructure.TIM_Period = 3515; 
	// 通用控制定時器時鐘源TIMxCLK = HCLK/2=90MHz 
	// 設定定時器頻率為=TIMxCLK/(TIM_Prescaler+1)=10MHz
    TIM_TimeBaseStructure.TIM_Prescaler = 0;	
    // 采樣時鐘分頻
    TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
    // 計數方式
    TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
	
	// 初始化定時器TIMx, x[1,8]
	TIM_TimeBaseInit(GENERAL_TIM, &TIM_TimeBaseStructure);

	// 清除定時器更新中斷标志位
    // TIM_ClearFlag(GENERAL_TIM, TIM_FLAG_Update);
	
	// 開啟定時器更新中斷
    // TIM_ITConfig(GENERAL_TIM,TIM_IT_Update,ENABLE);
	TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_Update);
   
    //使能定時器
	//TIM_Cmd(GENERAL_TIM, ENABLE);	
   
    //使能定時器
    //TIM_Cmd(GENERAL_TIM, DISABLE);	
}
           

7、進行FFT

//在main.c檔案中調用,進行ADC DMA 定時器的先相關初始化
void Rheostat_Init(void)
{
	Rheostat_ADC_GPIO_Config();
	Rheostat_ADC_Mode_Config();
//	TIMx_NVIC_Configuration();
    TIM_Mode_Config();
}

//使能ADC DMA FFT功能,需要進行FFT運算時,調用該語句
void  ENABLE_deinit(void)
{
		TIM_Cmd(GENERAL_TIM, ENABLE);	
        ADC_Cmd(RHEOSTAT_ADC, ENABLE); 
        DMA_Cmd(RHEOSTAT_ADC_DMA_STREAM, ENABLE);
}

//失能ADC DMA FFT功能
void  DISABLE_deinit(void)
{
		TIM_Cmd(GENERAL_TIM, DISABLE);	
        ADC_Cmd(RHEOSTAT_ADC, DISABLE); 
        DMA_Cmd(RHEOSTAT_ADC_DMA_STREAM,DISABLE);
}

           
void DMA2_Stream0_IRQHandler(void)
{
   u16 i;
   if(DMA_GetITStatus(DMA2_Stream0, DMA_IT_TCIF0)) //是否進入中斷
   { 
		DISABLE_deinit();
		
	    sprintf(dispBuff,"%d fft is running... please wait",counts0++); 
		
		LCD_ClearLine(LINE(2));
		
		LCD_DisplayStringLine(LINE(2),(uint8_t* )dispBuff);		 //在第三行顯示THD
		 	
        if(DMA_GetCurrentMemoryTarget(DMA2_Stream0) == DMA_Memory_0)//
        {
			
			for(i=0;i<4096;i++){    
			ADC_Vol[i] =(float) ADC_ConvertedValue[i]/4096*(float)3.3-1;  //電壓轉換
//			printf("\r\n ADC_Vol[%d]:%f \r\n",i,ADC_Vol[i] );            //列印采樣電壓值
					FFT_IN[2*i]=ADC_Vol[i];
					FFT_IN[2*i+1]=0;                    //虛部全部為0
		 }               
             fft_transformation();                     //基4FFT
			 /*用LCD列印相關FFT變換後數組的輸出值*/
//                LCD_Clear(LCD_COLOR_WHITE);
//                LCD_DisplayStringLine(LINE(1),(uint8_t* )" FFT ");
								 
			  /*顯示頻譜*/
//                  for(i=0;i<799;i++){
                LCD_DrawUniLine(i+1, 480, i+1, (u16)(480-FFT_OUT[i]/8.53));
//			      printf("\r\n FFTout[%d]=%f\r\n ",i,FFT_OUT[i]);
//             }//先屏蔽序列槽列印測試
         }   
		//計算各種波形的總諧波失真
		THD=(sqrt(FFT_OUT[320]*FFT_OUT[320]+FFT_OUT[480]*FFT_OUT[480]+FFT_OUT[640]*FFT_OUT[640]+FFT_OUT[800]*FFT_OUT[800]))*100/FFT_OUT[160];
        //根據THD  計算的值 判斷諧波失真的類型
				 
		 sprintf(dispBuff,"1kHz-FFT_out[160]:%f  2kHz-FFT_out[320]:%f ",FFT_OUT[160],FFT_OUT[320]); 
		
		LCD_ClearLine(LINE(4));
	  
		LCD_DisplayStringLine(LINE(4),(uint8_t* )dispBuff);	
				
        sprintf(dispBuff,"3kHz-FFT_out[480]:%f  4kHz-FFT_out[640]:%f ",FFT_OUT[480],FFT_OUT[640]); 
		
		LCD_ClearLine(LINE(5));
	  
		LCD_DisplayStringLine(LINE(5),(uint8_t* )dispBuff);					 
			
		sprintf(dispBuff,"5kHz-FFT_out[800]:%f",FFT_OUT[800]); 
		
		LCD_ClearLine(LINE(6));
	  
		LCD_DisplayStringLine(LINE(6),(uint8_t* )dispBuff);					 
	
		//
		sprintf(dispBuff,"%d  DMA fft finished... a new THD",counts1++); 
				
		LCD_ClearLine(LINE(3));
				
		LCD_DisplayStringLine(LINE(3),(uint8_t* )dispBuff);	 
		//		 
		printf("THD=%f",THD);//序列槽列印顯示
				 
		sprintf(dispBuff,"%d THD=%f",counts2++,THD); 
		
		LCD_ClearLine(LINE(7));
	  
		LCD_DisplayStringLine(LINE(7),(uint8_t* )dispBuff);		 //在第三行顯示THD
      DMA_ClearITPendingBit(DMA2_Stream0, DMA_IT_TCIF0);   //清除中斷标志位
    }
}
           

8、adc.c源碼

#include "./adc/bsp_adc.h"

__IO uint16_t ADC_ConvertedValue[4096];
float FFT_IN[4096*2];
float FFT_OUT[4096];
static void Rheostat_ADC_GPIO_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	
	// 使能 GPIO 時鐘
	RCC_AHB1PeriphClockCmd(RHEOSTAT_ADC_GPIO_CLK, ENABLE);
		
	// 配置 IO
	GPIO_InitStructure.GPIO_Pin  = RHEOSTAT_ADC_GPIO_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;	    
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ; //不上拉不下拉
	GPIO_Init(RHEOSTAT_ADC_GPIO_PORT, &GPIO_InitStructure);		
}

static void Rheostat_ADC_Mode_Config(void)
{
	DMA_InitTypeDef DMA_InitStructure;
	ADC_InitTypeDef ADC_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

	ADC_CommonInitTypeDef ADC_CommonInitStructure;

  // ------------------DMA Init 結構體參數 初始化--------------------------
  // ADC1使用DMA2,資料流0,通道0,這個是手冊固定死的
  // 開啟DMA時鐘
    RCC_AHB1PeriphClockCmd(RHEOSTAT_ADC_DMA_CLK, ENABLE); 
	// 外設基址為:ADC 資料寄存器位址
	DMA_InitStructure.DMA_PeripheralBaseAddr = RHEOSTAT_ADC_DR_ADDR;	
  // 存儲器位址,實際上就是一個内部SRAM的變量	
	DMA_InitStructure.DMA_Memory0BaseAddr = (u32)&ADC_ConvertedValue;  
  // 資料傳輸方向為外設到存儲器	
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;	
	// 緩沖區大小為,指一次傳輸的資料量
	DMA_InitStructure.DMA_BufferSize = 4096;	
	// 外設寄存器隻有一個,位址不用遞增
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  // 存儲器位址固定
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; 
   // // 外設資料大小為半字,即兩個位元組 
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; 
     //	存儲器資料大小也為半字,跟外設資料大小相同
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;	
	// 循環傳輸模式
	DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
  // DMA 傳輸通道優先級為高,當使用一個DMA通道時,優先級設定不影響
	DMA_InitStructure.DMA_Priority = DMA_Priority_High;
	  // 禁止DMA FIFO	,使用直連模式
	DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;  
	  // FIFO 大小,FIFO模式禁止時,這個不用配置	
	DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
	DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
	DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;  
		// 選擇 DMA 通道,通道存在于流中
	DMA_InitStructure.DMA_Channel = RHEOSTAT_ADC_DMA_CHANNEL; 
	  //初始化DMA流,流相當于一個大的管道,管道裡面有很多通道
	DMA_Init(RHEOSTAT_ADC_DMA_STREAM, &DMA_InitStructure);
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);	

    NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream0_IRQn;

    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x01;
 
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01;
	
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

    NVIC_Init(&NVIC_InitStructure);
    
    
//使能DMA流
    DMA_ITConfig(DMA2_Stream0,DMA_IT_TC,ENABLE); 
	 
//DMA_Cmd(RHEOSTAT_ADC_DMA_STREAM, ENABLE);
	 
//DMA_Cmd(RHEOSTAT_ADC_DMA_STREAM, DISABLE);
	 
		// 開啟ADC時鐘
  RCC_APB2PeriphClockCmd(RHEOSTAT_ADC_CLK , ENABLE);
  // -------------------ADC Common 結構體 參數 初始化------------------------
	// 獨立ADC模式
  ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;
  // 時鐘為fpclk x分頻	
  ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4;
  // 禁止DMA直接通路模式	
  ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
  // 采樣時間間隔	
  ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;  
  ADC_CommonInit(&ADC_CommonInitStructure);

     // -------------------ADC Init 結構體 參數 初始化--------------------------
  ADC_StructInit(&ADC_InitStructure);
  // ADC 分辨率
  ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
  // 禁止掃描模式,多通道采集才需要	
  ADC_InitStructure.ADC_ScanConvMode = DISABLE; 
  // 連續轉換	
  ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; 
  //禁止外部邊沿觸發
  ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_Rising;
  //外部觸發通道,本例子使用軟體觸發,此值随便指派即可
  ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T3_TRGO;
  //資料右對齊	
  ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
  //轉換通道 1個
  ADC_InitStructure.ADC_NbrOfConversion = 1;                                    
  ADC_Init(RHEOSTAT_ADC, &ADC_InitStructure);
  //---------------------------------------------------------------------------
	
  // 配置 ADC 通道轉換順序為1,第一個轉換,采樣時間為3個時鐘周期
  ADC_RegularChannelConfig(RHEOSTAT_ADC, RHEOSTAT_ADC_CHANNEL, 1, ADC_SampleTime_15Cycles);

  // 使能DMA請求 after last transfer (Single-ADC mode)
  ADC_DMARequestAfterLastTransferCmd(RHEOSTAT_ADC, ENABLE);
  // 使能ADC DMA
  ADC_DMACmd(RHEOSTAT_ADC, ENABLE);
	
	// 使能ADC
//  ADC_Cmd(RHEOSTAT_ADC, ENABLE);  

    // 使能ADC
//  ADC_Cmd(RHEOSTAT_ADC, DISABLE); 
  //開始adc轉換,軟體觸發
//  ADC_SoftwareStartConv(RHEOSTAT_ADC);
}


//把DMA 和 ADC 和定時器都失能   在想要使用的時候  将定時器重新使能CMD

//使用通用定時器 觸發ADC采樣  TIM6,由于時鐘頻率不清楚,本身進行的配置
static void TIM_Mode_Config(void)
{
    //NVIC_InitTypeDef NVIC_InitStructure;
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	// 開啟TIMx_CLK,x[6,7] 
    RCC_APB1PeriphClockCmd(GENERAL_TIM_CLK, ENABLE); 

  /* 累計 TIM_Period個後産生一個更新或者中斷*/		
    //定時器初值,采樣間隔0.05/1024s,初值為(0.05/1024)/(1/10M)=488
    //TIM_TimeBaseStructure.TIM_Period = 81 //(原始設定的數值)      
	TIM_TimeBaseStructure.TIM_Period = 3515; 
	// 通用控制定時器時鐘源TIMxCLK = HCLK/2=90MHz 
	// 設定定時器頻率為=TIMxCLK/(TIM_Prescaler+1)=10MHz
    TIM_TimeBaseStructure.TIM_Prescaler = 0;	
    // 采樣時鐘分頻
    TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
    // 計數方式
    TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
	
	// 初始化定時器TIMx, x[1,8]
	TIM_TimeBaseInit(GENERAL_TIM, &TIM_TimeBaseStructure);

	// 清除定時器更新中斷标志位
    // TIM_ClearFlag(GENERAL_TIM, TIM_FLAG_Update);
	
	// 開啟定時器更新中斷
    // TIM_ITConfig(GENERAL_TIM,TIM_IT_Update,ENABLE);
	TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_Update);
   
    //使能定時器
	//TIM_Cmd(GENERAL_TIM, ENABLE);	
   
    //使能定時器
    //TIM_Cmd(GENERAL_TIM, DISABLE);	
}

//static void TIMx_NVIC_Configuration(void)
//{
//    NVIC_InitTypeDef NVIC_InitStructure; 
//    // 設定中斷組為0
//    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);		
//    // 設定中斷來源
//    NVIC_InitStructure.NVIC_IRQChannel = GENERAL_TIM_IRQn; 	
//	// 設定搶占優先級
//    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;	 
//	// 設定子優先級
//    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
//	
//    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
//	
//    NVIC_Init(&NVIC_InitStructure);
//}

//采用通用定時器  TIM3 (原本的配置)
void Rheostat_Init(void)
{
	Rheostat_ADC_GPIO_Config();
	Rheostat_ADC_Mode_Config();
//	TIMx_NVIC_Configuration();
    TIM_Mode_Config();
}

void  ENABLE_deinit(void)
{
		TIM_Cmd(GENERAL_TIM, ENABLE);	
        ADC_Cmd(RHEOSTAT_ADC, ENABLE); 
        DMA_Cmd(RHEOSTAT_ADC_DMA_STREAM, ENABLE);
}

void  DISABLE_deinit(void)
{
		TIM_Cmd(GENERAL_TIM, DISABLE);	
        ADC_Cmd(RHEOSTAT_ADC, DISABLE); 
        DMA_Cmd(RHEOSTAT_ADC_DMA_STREAM,DISABLE);
}

           

五、需要注意的問題和相關的總結

1、定時器決定采樣頻率的計算公式:

Ft=TIMxCLK /TIM_ClockDivision/(1+TIM_Prescaler)/(TIM_Prescaler)

2、關于ADC掃描模式設定

//禁止掃描模式,多通道采集才需要	
  ADC_InitStructure.ADC_ScanConvMode = DISABLE; 
           

多通道是指一個或多個ADC的多個通道對資料進行采集。在這裡我使用的是單通道。記住掃描模式對應着多通道!!果隻是用了一個通道的話,DISABLE就可以了,如果使用了多個通道的話,則必須将其設定為ENABLE。

對掃描模式的了解

3、關于ADC獨立模式

// 獨立ADC模式
 ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;
           

在這個模式下,雙ADC不能同步,每個ADC接口獨立工作。是以如果不需要ADC同步或者隻是用了一個ADC的時候,就應該設成獨立模式了。

4、關于ADC的連續轉換

// 連續轉換	
  ADC_InitStructure.ADC_ContinuousConvMode =ENABLE; 
           

第四個參數是ADC_ContinuousConvMode,這裡設定為ENABLE,即連續轉換。如果設定為DISABLE,則是單次轉換。兩者的差別在于連續轉換直到所有的資料轉換完成後才停止轉換,而單次轉換則隻轉換一次資料就停止,要再次觸發轉換才可以。是以如果需要一次性采集4096個資料或者更多,則采用連續轉換。

關于STM32連續轉換和單次轉換

5、關于DMA傳輸模式

// 循環傳輸模式
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal ;
           

DMA傳輸模式工作在正常緩存模式,那麼每當ADC轉換完4096個采樣資料時,傳輸一次後DMA就會停止工作。

關于DMA

6、關于定時器上升沿觸發ADC時,定時器選擇的問題

可以選擇通用定時器2,3,8,能夠生成PWM信号。

STM32 FFT DMA ADC THD
//外部觸發通道,本例子使用軟體觸發,此值随便指派即可
  ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T3_TRGO;

           
//定時器觸發定義
#define GENERAL_TIM           		TIM3
#define GENERAL_TIM_CLK       		RCC_APB1Periph_TIM3

           

7、DMA1通道資源

STM32 FFT DMA ADC THD

8、DMA2通道資源

STM32 FFT DMA ADC THD

9、ADC資源

STM32 FFT DMA ADC THD

10、ADC_TwoSamplingDelay 用來設定兩個采樣階段之間的延遲周期數

第1次采樣和第二次采樣之間間隔的周期數。計算方法如下:

STM32 FFT DMA ADC THD

x=ADC_TwoSamplingDelay(采樣時間)

ADCCLK=45M

STM32 FFT DMA ADC THD

一定要清楚怎麼判斷每個外設挂載的時鐘總線。看英文參考手冊!!

繼續閱讀