1. 程式人生 > >STM32之ADC例項(基於DMA方式)

STM32之ADC例項(基於DMA方式)

ADC簡介:

    ADC(Analog-to-Digital Converter,模/ 數轉換器)。也就是將模擬訊號轉換為數字訊號進行處理,在儲存或傳輸時,模數轉換器幾乎必不可少。

   STM32在片上整合的ADC外設非常強大,我使用的奮鬥開發板是STM32F103VET6,屬於增強型的CPU,它有18個通道,可測量16個外部和2個內部訊號源。各通道的A/D轉換可以單次,連續,掃描或間斷模式執行,ADC的結果可以左對齊或右對齊方式儲存在16位資料暫存器中。



ADC工作過程分析:

   我們以ADC規則通道轉換過程來分析,如上圖,所有的器件都是圍繞中間的模擬至數字轉換器部分展開的。它的左端V

REF+,VREF- 等ADC參考電壓,ADCx_IN0 ~ ADCx_IN15為ADC的輸入訊號通道,即某些GPIO引腳。輸入訊號經過這些通道被送到ADC器件,ADC器件需要收到觸發訊號才開始進行轉換,如EXTI外部觸發,定時器觸發,也可以使用軟體觸發。ADC部件接受到觸發訊號後,在ADCCLK時鐘的驅動下對輸入通道的訊號進行取樣,並進行模數轉換,其中ADCCLK是來自ADC預分頻器。

    ADC部件轉換後的數值被儲存到一個16位的規則通道資料暫存器(或注入通道資料暫存器)中,我們可以通過CPU指令或DMA把它讀到記憶體(變數),模數轉換之後,可以出發DMA請求或者觸發ADC轉換結束事件,如果配置了模擬看門狗,並且採集的電壓大於閾值,會觸發看門狗中斷。

   其實對於ADC取樣,軟體程式設計主要就是ADC的配置,當然我是基於DMA方式的,所以DMA的配置也是關鍵!話不多說看程式碼!

主函式:main.c

#include "printf.h"
#include "adc.h"
#include "stm32f10x.h"

extern __IO uint16_t ADC_ConvertedValue;
float ADC_ConvertedValueLocal;
void Delay(__IO uint32_t nCount)
{
   for(;nCount !=0;nCount--);
}
int main(void)
{  
  printf_init();	
  adc_init();
  printf("******This is a ADC test******\n");
		
  while(1)
    {
        ADC_ConvertedValueLocal =(float) ADC_ConvertedValue/4096*3.3;
        printf("The current AD value =0x%04X\n",ADC_ConvertedValue);
	printf("The current AD value =%f V\n",ADC_ConvertedValueLocal);
		
	Delay(0xffffee);
    }
  return 0;	
}
注意ADC_ConvertedValueLocal儲存了由轉換值計算出來的電壓值,計算公式是:實際電壓值=ADC轉換值 x LSB ,這裡由於我的板子VREF+接的參考電壓為3.3V,所以LSB=3.3/4096,STM32的ADC的精度為12位。

ADC與DMA配置:adc.c

#include "adc.h"
volatile uint16_t ADC_ConvertedValue;
void adc_init()
{
	GPIO_InitTypeDef GPIO_InitStructure;
	ADC_InitTypeDef ADC_InitStructure;
	DMA_InitTypeDef DMA_InitStructure;
	
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC|RCC_APB2Periph_ADC1,ENABLE);	

	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1;		 
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN;
	GPIO_Init(GPIOC,&GPIO_InitStructure);
	DMA_DeInit(DMA1_Channel1);
	DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address;//ADC地址
	DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&ADC_ConvertedValue; //記憶體地址
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //方向(從外設到記憶體)
	DMA_InitStructure.DMA_BufferSize = 1; //傳輸內容的大小
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外設地址固定
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable; //記憶體地址固定
	DMA_InitStructure.DMA_PeripheralDataSize = 
	DMA_PeripheralDataSize_HalfWord ; //外設資料單位
	DMA_InitStructure.DMA_MemoryDataSize = 
	DMA_MemoryDataSize_HalfWord ;    //記憶體資料單位
	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular  ; //DMA模式:迴圈傳輸
	DMA_InitStructure.DMA_Priority = DMA_Priority_High ; //優先順序:高
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;   //禁止記憶體到記憶體的傳輸
	
	DMA_Init(DMA1_Channel1, &DMA_InitStructure);  //配置DMA1的4通道
	DMA_Cmd(DMA1_Channel1,ENABLE);
 
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //獨立ADC模式
	ADC_InitStructure.ADC_ScanConvMode = DISABLE;  //禁止掃描方式
	ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;//開啟連續轉換模式 
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //不使用外部觸發轉換
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //採集資料右對齊
	ADC_InitStructure.ADC_NbrOfChannel = 1; //要轉換的通道數目
	ADC_Init(ADC1, &ADC_InitStructure);
	
	RCC_ADCCLKConfig(RCC_PCLK2_Div8);//配置ADC時鐘,為PCLK2的8分頻,即9Hz
	ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 1, ADC_SampleTime_55Cycles5);//配置ADC1通道11為55.5個取樣週期 
	
	ADC_DMACmd(ADC1,ENABLE);
	ADC_Cmd(ADC1,ENABLE);

	ADC_ResetCalibration(ADC1);//復位校準暫存器
	while(ADC_GetResetCalibrationStatus(ADC1));//等待校準暫存器復位完成

	ADC_StartCalibration(ADC1);//ADC校準
	while(ADC_GetCalibrationStatus(ADC1));//等待校準完成

	ADC_SoftwareStartConvCmd(ADC1, ENABLE);//由於沒有采用外部觸發,所以使用軟體觸發ADC轉換
}

ADC配置還是比較簡單的,畢竟只配置了單通道,還是分析一下吧!這裡我是把ADC1的通道11使用的GPIO引腳PC1配置成模擬輸入模式,在作為ADC的輸入時,必須使用模擬輸入。對於ADC通道,每個ADC通道對應一個GPIO引腳埠,GPIO的引腳在設為模擬輸入模式後可用於模擬電壓的輸入。STM32F103VET6有三個ADC,這三個ADC公用16個外部通道。
DMA的整體配置為:使用DMA1的通道1,資料從ADC外設的資料暫存器(ADC1_DR_Address)轉移到記憶體(ADC_ConvertedValue變數),記憶體外設地址都固定,每次傳輸的大小為半字(16位),使用DMA迴圈傳輸模式。

DMA傳輸的外設地址,也就是ADC1的地址為0x40012400+0x4c,這個地址可查STM32 datasheet獲得,如圖;



要特別注意ADC轉換時間配置,由於ADC時鐘頻率越高,轉換速度越快,那是不是就把ADC的時鐘頻率設的越大越好呢?其實不然,根據ADC時鐘圖可知,ADC時鐘有上限值,即不能超過14MHz,如圖:


這裡ADC預分頻器的輸入為高速外設時鐘(PCLK2),使用RCC_ADCCLKConfig()庫函式來設定ADC預分頻的分頻值,PCLK2常用時鐘為72MHz,而ADCCLK必須小於14MHz,所以這裡ADCCLK為PCLK2的6分頻,即12MHz,而我的程式中只是隨便設為8分頻,9MHz,若希望ADC以最高頻率14MHz執行,可以把PCLK2設定為56MHz,然後再4分頻得到ACCLK。

ADC的轉換時間不僅與ADC的時鐘有關,還與取樣週期有關。每個ADC通道可以設定為不同的取樣週期。STM32的ADC取樣時間計算公式為:

  T=取樣週期+12.5個週期

公式中的取樣週期就是函式中配置的 ADC_SampleTime,而後邊加上的12.5個週期為固定值,則ADC1通道11的轉換時間為T=(55.5+12.5) x 1/9=7.56us。

補充:在adc.c檔案中定義了ADC_ConvertedValue變數,要注意這個變數是由關鍵字volatile修飾的,volatile的作用是讓編譯器不要去優化這個變數,這樣每次用到這個變數時都要回到相應變數的記憶體中去取值,而如果不使用volatile進行修飾的話,ADC_ConvertedValue變數在被訪問的時候可能會直接從CPU的暫存器中取出(因為之前該變數被訪問過,也就是說之前就從記憶體中取出ADC_ConvertedValue的值儲存到某個CPU暫存器中),之所以直接從暫存器中去取值而不去記憶體中取值,這是編譯器優化程式碼的結果(訪問CPU暫存器比訪問記憶體快得多)。這裡的CPU暫存器指R0,R1等CPU通用暫存器,用於CPU運算及暫存資料,不是指外設中的暫存器。

         因為ADC_ConvertedValue這個變數值隨時都會被DMA控制器改變的,所以用volatile來修飾它,確保每次讀取到的都是實時ADC轉換值。

adc.h:

#ifndef _adc_H
#define _adc_H
#include "stm32f10x.h"
#include "stm32f10x_dma.h"
#include "stm32f10x_adc.h"
#define ADC1_DR_Address  ((uint32_t)0x4001244c);

void adc_init(void);

#endif
效果圖:

由於我的開發板沒有滑動變阻器,所以我就將電壓的輸入端接入通用IO口的3V引腳。如圖: