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

ADC簡介:git

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

   STM32在片上集成的ADC外設很是強大,我使用的奮鬥開發板是STM32F103VET6,屬於加強型的CPU,它有18個通道,可測量16個外部和2個內部信號源。各通道的A/D轉換能夠單次,連續,掃描或間斷模式執行,ADC的結果能夠左對齊或右對齊方式存儲在16位數據寄存器中。ide

ADC工做過程分析:函數

   咱們以ADC規則通道轉換過程來分析,如上圖,全部的器件都是圍繞中間的模擬至數字轉換器部分展開的。它的左端VREF+,VREF- 等ADC參考電壓,ADCx_IN0 ~ ADCx_IN15爲ADC的輸入信號通道,即某些GPIO引腳。輸入信號通過這些通道被送到ADC器件,ADC器件須要收到觸發信號纔開始進行轉換,如EXTI外部觸發,定時器觸發,也可使用軟件觸發。ADC部件接受到觸發信號後,在ADCCLK時鐘的驅動下對輸入通道的信號進行採樣,並進行模數轉換,其中ADCCLK是來自ADC預分頻器。優化

    ADC部件轉換後的數值被保存到一個16位的規則通道數據寄存器(或注入通道數據寄存器)中,咱們能夠經過CPU指令或DMA把它讀到內存(變量),模數轉換以後,能夠出發DMA請求或者觸發ADC轉換結束事件,若是配置了模擬看門狗,而且採集的電壓大於閾值,會觸發看門狗中斷。ui

   其實對於ADC採樣,軟件編程主要就是ADC的配置,固然我是基於DMA方式的,因此DMA的配置也是關鍵!話很少說看代碼!es5

主函數:main.cspa

  1. #include "printf.h"
  2. #include "adc.h"
  3. #include "stm32f10x.h"
  4.  
  5. extern __IO uint16_t ADC_ConvertedValue;
  6. float ADC_ConvertedValueLocal;
  7. void Delay(__IO uint32_t nCount)
  8. {
  9. for(;nCount !=0;nCount--);
  10. }
  11. int main(void)
  12. {
  13. printf_init();
  14. adc_init();
  15. printf("******This is a ADC test******\n");
  16.  
  17. while(1)
  18. {
  19. ADC_ConvertedValueLocal =( float) ADC_ConvertedValue/4096*3.3;
  20. printf("The current AD value =0x%04X\n",ADC_ConvertedValue);
  21. printf("The current AD value =%f V\n",ADC_ConvertedValueLocal);
  22.  
  23. Delay( 0xffffee);
  24. }
  25. return 0;
  26. }

注意ADC_ConvertedValueLocal保存了由轉換值計算出來的電壓值,計算公式是:實際電壓值=ADC轉換值 x LSB ,這裏因爲個人板子VREF+接的參考電壓爲3.3V,因此LSB=3.3/4096,STM32的ADC的精度爲12位。.net

ADC與DMA配置:adc.c3d

  1. #include "adc.h"
  2. volatile uint16_t ADC_ConvertedValue;
  3. void adc_init()
  4. {
  5. GPIO_InitTypeDef GPIO_InitStructure;
  6. ADC_InitTypeDef ADC_InitStructure;
  7. DMA_InitTypeDef DMA_InitStructure;
  8.  
  9. RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
  10. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC|RCC_APB2Periph_ADC1,ENABLE);
  11.  
  12. GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1;
  13. GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN;
  14. GPIO_Init(GPIOC,&GPIO_InitStructure);
  15. DMA_DeInit(DMA1_Channel1);
  16. DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address; //ADC地址
  17. DMA_InitStructure.DMA_MemoryBaseAddr = ( uint32_t)&ADC_ConvertedValue; //內存地址
  18. DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //方向(從外設到內存)
  19. DMA_InitStructure.DMA_BufferSize = 1; //傳輸內容的大小
  20. DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外設地址固定
  21. DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable; //內存地址固定
  22. DMA_InitStructure.DMA_PeripheralDataSize =
  23. DMA_PeripheralDataSize_HalfWord ; //外設數據單位
  24. DMA_InitStructure.DMA_MemoryDataSize =
  25. DMA_MemoryDataSize_HalfWord ; //內存數據單位
  26. DMA_InitStructure.DMA_Mode = DMA_Mode_Circular ; //DMA模式:循環傳輸
  27. DMA_InitStructure.DMA_Priority = DMA_Priority_High ; //優先級:高
  28. DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //禁止內存到內存的傳輸
  29.  
  30. DMA_Init(DMA1_Channel1, &DMA_InitStructure); //配置DMA1的4通道
  31. DMA_Cmd(DMA1_Channel1,ENABLE);
  32.  
  33. ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //獨立ADC模式
  34. ADC_InitStructure.ADC_ScanConvMode = DISABLE; //禁止掃描方式
  35. ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; //開啓連續轉換模式
  36. ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //不使用外部觸發轉換
  37. ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //採集數據右對齊
  38. ADC_InitStructure.ADC_NbrOfChannel = 1; //要轉換的通道數目
  39. ADC_Init(ADC1, &ADC_InitStructure);
  40.  
  41. RCC_ADCCLKConfig(RCC_PCLK2_Div8); //配置ADC時鐘,爲PCLK2的8分頻,即9Hz
  42. ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 1, ADC_SampleTime_55Cycles5);//配置ADC1通道11爲55.5個採樣週期
  43.  
  44. ADC_DMACmd(ADC1,ENABLE);
  45. ADC_Cmd(ADC1,ENABLE);
  46.  
  47. ADC_ResetCalibration(ADC1); //復位校準寄存器
  48. while(ADC_GetResetCalibrationStatus(ADC1));//等待校準寄存器復位完成
  49.  
  50. ADC_StartCalibration(ADC1); //ADC校準
  51. while(ADC_GetCalibrationStatus(ADC1));//等待校準完成
  52.  
  53. ADC_SoftwareStartConvCmd(ADC1, ENABLE); //因爲沒有采用外部觸發,因此使用軟件觸發ADC轉換
  54. }

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:

  1. #ifndef _adc_H
  2. #define _adc_H
  3. #include "stm32f10x.h"
  4. #include "stm32f10x_dma.h"
  5. #include "stm32f10x_adc.h"
  6. #define ADC1_DR_Address ((uint32_t)0x4001244c);
  7.  
  8. void adc_init(void);
  9.  
  10. #endif

效果圖:

因爲個人開發板沒有滑動變阻器,因此我就將電壓的輸入端接入通用IO口的3V引腳。如圖:

相關文章
相關標籤/搜索