STM32學習筆記(七) ADC模數轉換測電平(普通和DMA模式)


  嵌入式系統在微控制領域(溫度,濕度,壓力檢測,四軸飛行器)中占據着重要地位,這些功能的實現是由微處理器cpu(如stm32)和傳感器以及控制器共同完成的,而連接他們,使它們能夠互相正常交流的正是本小節要講訴的模塊,ADC模數轉換外設。下面從最簡單的實驗說起,逐漸深入了解這個外設。

    本次ADC模數轉換設計實現並不復雜,步驟可簡化為以下三步:

  1. 接收板上電位器的輸入電壓

    2. 經過A/D轉換獲得數字量,並傳送給cpu

    3. 通過串口在PC機上輸出。

    解析上面三個步驟,分析要求,就會發現ADC、GPIO、USART以及RCC模塊就是本次實驗所需要的用到的外設,因為除ADC模塊,其它外設前面已經學習和實踐了,那么理解和學習ADC模塊,就可開始程序的設計實現了。

  根據stm32f系列微控制器手冊ADC章節

   

 

   

 

    ADC轉換的后數字量為12位(分辨率),在參考開發板用戶手冊和原理圖,可知電位器的端口為PC0,輸入電壓范圍0~3.3V,可知精度為3.3/(2^12)V.

    

    查詢stm32f107的引腳定義分配,可知PC0對應ADC12_IN10,也就是說采集電位器電壓用ADC1和ADC2都可以,但必須采用通道10。

目前來說,用庫函數操作可以避免出現漏錯,因此我還是推薦使用庫函數配置寄存器,但是了解庫函數的含義還是十分有必要的:

typedef struct {   u32 ADC_Mode;   //明確ADC1和ADC2的工作方式,獨立或其它組合
   FunctionalState ADC_ScanConvMode;     //通道工作方式,單通道還是多通道(掃描)
   FunctionalState ADC_ContinuousConvMode;  //工作在連續還是單次模式(ADC轉換工作在連續模式
   u32 ADC_ExternalTrigConv;      //A/D轉換啟動規則
   u32 ADC_DataAlign;   //判斷轉換數據的對齊方式
   u8 ADC_NbrOfChannel;   //明確規則轉換通道的具體數目1~16
 }ADC_InitTypeDef

了解上述結構體代表含義,下面就可以初始化相關寄存器實現ADC外設的配置:

GPIO_InitTypeDef GPIO_InitStructure; ADC_InitTypeDef ADC_InitStructure; ADC_DeInit(ADC1); //ADC模塊外設時鍾需在APB2時鍾基礎上設置,決定單個周期的時鍾長度(因為ADC時鍾不能大於14MHZ,注意)
RCC_ADCCLKConfig(RCC_PCLK2_Div4);
//使能ADC對應GPIO口,外設區域及復用功能時鍾 RCC_APB2PeriphClockCmd(RCC_ADC1, ENABLE); //初始化ADC模塊對應GPIO GPIO_InitStructure.GPIO_Pin = GPIO_ADC1_Pin; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; GPIO_Init(GPIO_ADC1, &GPIO_InitStructure); ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //ADC1和ADC2工作在獨立模式 ADC_InitStructure.ADC_ScanConvMode = ENABLE; //工作在掃描模式
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; //轉換工作在連續模式

ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //轉換由軟件觸發,啟動需調用ADC_Cmd程序 ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //ADC數據右對齊 ADC_InitStructure.ADC_NbrOfChannel = 1; //ADC通道數目為1 ADC_Init(ADC1, &ADC_InitStructure); //指定ADC轉換的通道和轉換周期 ADC_RegularChannelConfig(ADC1, ADC_Channel_10, 1, ADC_SampleTime_55Cycles5);
#ifdef USE_ADC1_DMA_PER ADC_DMACmd(ADC1, ENABLE);
//ADC1 DMA請求使能 #endif ADC_Cmd(ADC1, ENABLE); //ADC1使能

ADC_ResetCalibration(ADC1); //重置ADC1校准寄存器 while (ADC_GetCalibrationStatus(ADC1)) //等待ADC1校准寄存器重置完成 { } ADC_StartCalibration(ADC1); //ADC1進入校准狀態 while(ADC_GetCalibrationStatus(ADC1)) //等待ADC1校准完成 { }

ADC_SoftwareStartConvCmd(ADC1, ENABLE);
//ADC1軟件觸發方式啟動

這里有一個重要知識點:ADC通道的規則組和注入組

      在AD轉換中,規則組定義的是ADC掃描通道的順序,按照規則組配置時的采樣順序從小到大依次掃描ADC通道,而注入組的優先級高於規則組,當注入組轉換觸發時就打斷規則組的掃描而執行注入組的通道掃描,具體流程類似於中斷中的搶占。本次ADC的轉換僅僅使用到一個端口,這些不用考慮,但是在多通道AD/DA采集時,規則組和注入組要根據實際情況進行配置。

注意:配置通道的規則組和注入組是一定要在使能ADC轉換之前的。

完成了初始化后,剩下的就簡單了,只要獲得ADC處理后的數字量,在轉換成整形變量,就可以通過串口發送接收了,如下:

//直接獲得當前ADC轉換后的值,轉換並輸出,CPU參與傳送
ADValue = ADC_GetConversionValue(ADC1); Precent = (ADValue*100/0x1000); Voltage = Precent*33; printf("\r\n\n ADCConvertedValue is 0x%x, Percent is %d%%, voltage is %d.%d%dV", ADValue,Precent,Voltage/1000,(Voltage%1000)/100,(Voltage%100)/10); printf("\r\n ADC output"); ARM_DELAY(2000000);

注意:使用了printf函數作為輸入輸出時,包含頭文件#include ”stdio.h” Target下要選擇use MicroLib,否則是不會有輸出的(串口章節已經說明,重要)

如此便實現了電位記電壓的采集和輸出,不過這並不是結束,因為今天我們還要學習另一個同樣用途廣泛的外設-DMA模塊。

    首先我們要知道DMA是干什么的?DMA模塊的主要作用是將內存或者外設中的數據自由移動,而不需要cpu的參與,同時通過存儲指針的自偏移,實現大量數據的順序存儲(這一點在通訊領域具有重要意義)。和上面一樣,學習DMA,肯定首先查詢手冊了:

   

   從這上面我們可以得出,ADC1對應的傳輸通道為通道1,在了解下面的結構體后:

typedef struct {   u32 DMA_PeripheralBaseAddr; //定義的外設基地址
  u32 DMA_MemoryBaseAddr; //定義的內存基地址
  u32 DMA_DIR; //外設作為數據傳輸的來源還是目的地
  u32 DMA_BufferSize; //DMA通道的 DMA緩存的大小,單位為數據單位
  u32 DMA_PeripheralInc; //外設地址寄存器遞增或不變
  u32 DMA_MemoryInc; //內存地址寄存器遞增或不變
  u32 DMA_PeripheralDataSize; //外設數據寬度
  u32 DMA_MemoryDataSize; //內存數據寬度
  u32 DMA_Mode; //DMA緩存工作方式
  u32 DMA_Priority; //DMA工作優先級
  u32 DMA_M2M; //DMA工作是內存到內存,還是外設到內存 } DMA_InitTypeDef;

我們可以得出以下結論:

定義uint16_t  ADCConvertedValue; //接收內存地址

1.外設基地址為(uint32_t)&(ADC1->DR)

2.數據來源於ADC外設,傳送地址為內存

3.工作在循環模式,且外設和內存地址都不自增

4.數據傳輸使用DMA1的通道1

按照上面的結構體依此配置DMA_InitStructrue的各項參數,初始化如下:

DMA_InitTypeDef DMA_InitStructure;
      
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); 
DMA_DeInit(DMA1_Channel1);                                                  //復位ADC1對應DMA通道DMA1_Channel1
       
DMA_InitStructure.DMA_PeripheralBaseAddr =(uint32_t)&(ADC1->DR);            //ADC1規則組轉換值寄存器地址作為基地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&ADCConvertedValue;        //數據傳輸至內存的基地址
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; //外設數據為半字16bit
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;         //內存數據為半字16bit
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;                             //DMA工作在循環模式
DMA_InitStructure.DMA_Priority = DMA_Priority_High;                         //DMA請求優先級高     
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;                                //DMA是外設到內存傳遞
DMA_Init(DMA1_Channel1, &DMA_InitStructure);                                  
       
DMA_Cmd(DMA1_Channel1, ENABLE);                                             /*DMA使能*/

同時在main函數中添加:

//將DMA從ADC處傳送的數字量經過處理,轉換並輸出,DMA控制器用於傳送
ADCConvertedValueLocal = ADCConvertedValue;                      
Precent = (ADCConvertedValueLocal*100/0x1000);
Voltage = Precent*33;
printf("\r\n\n ADCConvertedValue is 0x%x, Percent is %d%%, voltage is %d.%d%dV",
       ADCConvertedValueLocal,Precent,Voltage/1000,(Voltage%1000)/100,(Voltage%100)/10);
printf("\r\n ADC DMA output");

如此便實現了通過ADC通過DMA的傳輸。

實驗現象如下圖:

代碼下載:http://files.cnblogs.com/files/zc110747/5.ADC-DMA.7z

以下來自外部資料及個人總結,希望對理解DMA模塊有用處:

   1.DMA傳輸將數據從一個地址空間復制到另外一個地址空間,這部分是由DMA控制器實現的,不需要依靠CPU的大量的數據采集傳送,節省cpu資源。

   2.DMA工作包含四個過程

     DMA請求-〉DMA響應-〉DMA傳輸-〉DMA結束

   3.DMA傳送方式有以下三種

     (1)停止CPU訪內存

     當外圍器件有一批數據需要傳送時,DMA給CPU發送停止信號,CPU停止訪問內存,釋放相關總線控制權,DMA獲得總線控制權后開始傳遞數據,完成后將總線控制權交給CPU。一次DMA傳送結束。

    優點:控制簡單,用於速率很高的組傳送

    缺點:內存的效能沒有發揮,一部分時間內存處於空閑狀態。這是因為DMA傳送階段有很多時間是在讀取外設的數據,總線一段時間肯定是空閑的,而這部分時間足夠CPU進行內存的訪問。

    (2)周期挪用;(ADC轉換采用的正是這種方式)

       當I/O設備沒有DMA請求時,CPU按程序要求訪問內存;一旦I/O設備有DMA請求,則由I/O設備挪用一個或幾個內存周期。

    (3)DMA與CPU交替訪內存


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM