第30章 ADC—電壓采集
全套200集視頻教程和1000頁PDF教程請到秉火論壇下載:www.firebbs.cn
野火視頻教程優酷觀看網址:http://i.youku.com/firege
本章參考資料:《STM32F4xx中文參考手冊》ADC章節。
學習本章時,配合《STM32F4xx中文參考手冊》ADC章節一起閱讀,效果會更佳,特別是涉及到寄存器說明的部分。
30.1 ADC簡介
STM32F429IGT6有3個ADC,每個ADC有12位、10位、8位和6位可選,每個ADC有16個外部通道。另外還有兩個內部ADC源和VBAT通道掛在ADC1上。ADC具有獨立模式、雙重模式和三重模式,對於不同AD轉換要求幾乎都有合適的模式可選。ADC功能非常強大,具體的我們在功能框圖中分析每個部分的功能。
30.2 ADC功能框圖剖析

圖 01 單個ADC功能框圖
掌握了ADC的功能框圖,就可以對ADC有一個整體的把握,在編程的時候可以做到了然如胸,不會一知半解。框圖講解采用從左到右的方式,跟ADC采集數據,轉換數據,傳輸數據的方向大概一致。
1. ①電壓輸入范圍
ADC輸入范圍為:VREF- ≤ VIN ≤ VREF+。由VREF-、VREF+ 、VDDA 、VSSA、這四個外部引腳決定。
我們在設計原理圖的時候一般把VSSA和VREF-接地,把VREF+和VDDA 接3V3,得到ADC的輸入電壓范圍為:0~3.3V。
如果我們想讓輸入的電壓范圍變寬,去到可以測試負電壓或者更高的正電壓,我們可以在外部加一個電壓調理電路,把需要轉換的電壓抬升或者降壓到0~3.3V,這樣ADC就可以測量了。
2. ②輸入通道
我們確定好ADC輸入電壓之后,那么電壓怎么輸入到ADC?這里我們引入通道的概念,STM32的ADC多達19個通道,其中外部的16個通道就是框圖中的ADCx_IN0、ADCx_IN1...ADCx_IN5。這16個通道對應着不同的IO口,具體是哪一個IO口可以從手冊查詢到。其中ADC1/2/3還有內部通道: ADC1的通道ADC1_IN16連接到內部的VSS,通道ADC1_IN17連接到了內部參考電壓VREFINT 連接,通道ADC1_IN18連接到了芯片內部的溫度傳感器或者備用電源VBAT。ADC2和ADC3的通道16、17、18全部連接到了內部的VSS。

圖 02 STM32F429IGT6 ADC 通道
外部的16個通道在轉換的時候又分為規則通道和注入通道,其中規則通道最多有16路,注入通道最多有4路。那這兩個通道有什么區別?在什么時候使用?
規則通道
規則通道:顧名思意,規則通道就是很規矩的意思,我們平時一般使用的就是這個通道,或者應該說我們用到的都是這個通道,沒有什么特別要注意的可講。
注入通道
注入,可以理解為插入,插隊的意思,是一種不安分的通道。它是一種在規則通道轉換的時候強行插入要轉換的一種。如果在規則通道轉換過程中,有注入通道插隊,那么就要先轉換完注入通道,等注入通道轉換完成后,再回到規則通道的轉換流程。這點跟中斷程序很像,都是不安分的主。所以,注入通道只有在規則通道存在時才會出現。
3. ③轉換順序
規則序列
規則序列寄存器有3個,分別為SQR3、SQR2、SQR1。SQR3控制着規則序列中的第一個到第六個轉換,對應的位為:SQ1[4:0]~SQ6[4:0],第一次轉換的是位4:0 SQ1[4:0],如果通道16想第一次轉換,那么在SQ1[4:0]寫16即可。SQR2控制着規則序列中的第7到第12個轉換,對應的位為:SQ7[4:0]~SQ12[4:0],如果通道1想第8個轉換,則SQ8[4:0]寫1即可。SQR1控制着規則序列中的第13到第16個轉換,對應位為:SQ13[4:0]~SQ16[4:0],如果通道6想第10個轉換,則SQ10[4:0]寫6即可。具體使用多少個通道,由SQR1的位L[3:0]決定,最多16個通道。

圖 03 規則序列寄存器
注入序列
注入序列寄存器JSQR只有一個,最多支持4個通道,具體多少個由JSQR的JL[2:0]決定。如果JL的值小於4的話,則JSQR跟SQR決定轉換順序的設置不一樣,第一次轉換的不是JSQR1[4:0],而是JCQRx[4:0] ,x = (4-JL),跟SQR剛好相反。如果JL=00(1個轉換),那么轉換的順序是從JSQR4[4:0]開始,而不是從JSQR1[4:0]開始,這個要注意,編程的時候不要搞錯。當JL等於4時,跟SQR一樣。

圖 04 注入序列寄存器
4. ④觸發源
通道選好了,轉換的順序也設置好了,那接下來就該開始轉換了。ADC轉換可以由ADC控制寄存器2: ADC_CR2的ADON這個位來控制,寫1的時候開始轉換,寫0的時候停止轉換,這個是最簡單也是最好理解的開啟ADC轉換的控制方式,理解起來沒啥技術含量。
除了這種庶民式的控制方法,ADC還支持外部事件觸發轉換,這個觸發包括內部定時器觸發和外部IO觸發。觸發源有很多,具體選擇哪一種觸發源,由ADC控制寄存器2:ADC_CR2的EXTSEL[2:0]和JEXTSEL[2:0]位來控制。EXTSEL[2:0]用於選擇規則通道的觸發源,JEXTSEL[2:0]用於選擇注入通道的觸發源。選定好觸發源之后,觸發源是否要激活,則由ADC控制寄存器2:ADC_CR2的EXTTRIG和JEXTTRIG這兩位來激活。
如果使能了外部觸發事件,我們還可以通過設置ADC控制寄存器2:ADC_CR2的EXTEN[1:0]和JEXTEN[1:0]來控制觸發極性,可以有4種狀態,分別是:禁止觸發檢測、上升沿檢測、下降沿檢測以及上升沿和下降沿均檢測。
5. ⑤轉換時間
ADC時鍾
ADC輸入時鍾ADC_CLK由PCLK2經過分頻產生,最大值是36MHz,典型值為30MHz,分頻因子由ADC通用控制寄存器ADC_CCR的ADCPRE[1:0]設置,可設置的分頻系數有2、4、6和8,注意這里沒有1分頻。對於STM32F429IGT6我們一般設置PCLK2=HCLK/2=90MHz。所以程序一般使用4分頻或者6分頻。
采樣時間
ADC需要若干個ADC_CLK周期完成對輸入的電壓進行采樣,采樣的周期數可通過ADC 采樣時間寄存器ADC_SMPR1和ADC_SMPR2中的SMP[2:0]位設置,ADC_SMPR2控制的是通道0~9,ADC_SMPR1控制的是通道10~17。每個通道可以分別用不同的時間采樣。其中采樣周期最小是3個,即如果我們要達到最快的采樣,那么應該設置采樣周期為3個周期,這里說的周期就是1/ADC_CLK。
ADC的總轉換時間跟ADC的輸入時鍾和采樣時間有關,公式為:
Tconv = 采樣時間 + 12個周期
當ADCCLK = 30MHz,即PCLK2為60MHz,ADC時鍾為2分頻,采樣時間設置為3個周期,那么總的轉換時為:Tconv = 3 + 12 = 15個周期 =0.5us。
一般我們設置PCLK2=90MHz,經過ADC預分頻器能分頻到最大的時鍾只能是22.5M,采樣周期設置為3個周期,算出最短的轉換時間為0.6667us,這個才是最常用的。
6. ⑥數據寄存器
一切准備就緒后,ADC轉換后的數據根據轉換組的不同,規則組的數據放在ADC_DR寄存器,注入組的數據放在JDRx。如果是使用雙重或者三重模式那規矩組的數據是存放在通用規矩寄存器ADC_CDR內的。
規則數據寄存器ADC_DR
ADC規則組數據寄存器ADC_DR只有一個,是一個32位的寄存器,只有低16位有效並且只是用於獨立模式存放轉換完成數據。因為ADC的最大精度是12位,ADC_DR是16位有效,這樣允許ADC存放數據時候選擇左對齊或者右對齊,具體是以哪一種方式存放,由ADC_CR2的11位ALIGN設置。假如設置ADC精度為12位,如果設置數據為左對齊,那AD轉換完成數據存放在ADC_DR寄存器的[4:15]位內;如果為右對齊,則存放在ADC_DR寄存器的[0:11]位內。
規則通道可以有16個這么多,可規則數據寄存器只有一個,如果使用多通道轉換,那轉換的數據就全部都擠在了DR里面,前一個時間點轉換的通道數據,就會被下一個時間點的另外一個通道轉換的數據覆蓋掉,所以當通道轉換完成后就應該把數據取走,或者開啟DMA模式,把數據傳輸到內存里面,不然就會造成數據的覆蓋。最常用的做法就是開啟DMA傳輸。
如果沒有使用DMA傳輸,我們一般都需要使用ADC狀態寄存器ADC_SR獲取當前ADC轉換的進度狀態,進而進行程序控制。
注入數據寄存器ADC_JDRx
ADC注入組最多有4個通道,剛好注入數據寄存器也有4個,每個通道對應着自己的寄存器,不會跟規則寄存器那樣產生數據覆蓋的問題。ADC_JDRx是32位的,低16位有效,高16位保留,數據同樣分為左對齊和右對齊,具體是以哪一種方式存放,由ADC_CR2的11位ALIGN設置。
通用規則數據寄存器ADC_CDR
規則數據寄存器ADC_DR是僅適用於獨立模式的,而通用規則數據寄存器ADC_CDR是適用於雙重和三重模式的。獨立模式就是僅僅適用三個ADC的其中一個,雙重模式就是同時使用ADC1和ADC2,而三重模式就是三個ADC同時使用。在雙重或者三重模式下一般需要配合DMA數據傳輸使用。
7. ⑦中斷
轉換結束中斷
數據轉換結束后,可以產生中斷,中斷分為四種:規則通道轉換結束中斷,注入轉換通道轉換結束中斷,模擬看門狗中斷和溢出中斷。其中轉換結束中斷很好理解,跟我們平時接觸的中斷一樣,有相應的中斷標志位和中斷使能位,我們還可以根據中斷類型寫相應配套的中斷服務程序。
模擬看門狗中斷
當被ADC轉換的模擬電壓低於低閾值或者高於高閾值時,就會產生中斷,前提是我們開啟了模擬看門狗中斷,其中低閾值和高閾值由ADC_LTR和ADC_HTR設置。例如我們設置高閾值是2.5V,那么模擬電壓超過2.5V的時候,就會產生模擬看門狗中斷,反之低閾值也一樣。
溢出中斷
如果發生DMA傳輸數據丟失,會置位ADC狀態寄存器ADC_SR的OVR位,如果同時使能了溢出中斷,那在轉換結束后會產生一個溢出中斷。
DMA請求
規則和注入通道轉換結束后,除了產生中斷外,還可以產生DMA請求,把轉換好的數據直接存儲在內存里面。對於獨立模式的多通道AD轉換使用DMA傳輸非常有必須要,程序編程簡化了很多。對於雙重或三重模式使用DMA傳輸幾乎可以說是必要的。有關DMA請求需要配合《STM32F4xx中文參考手冊》DMA控制器這一章節來學習。一般我們在使用ADC的時候都會開啟DMA傳輸。
8. ⑧電壓轉換
模擬電壓經過ADC轉換后,是一個相對精度的數字值,如果通過串口以16進制打印出來的話,可讀性比較差,那么有時候我們就需要把數字電壓轉換成模擬電壓,也可以跟實際的模擬電壓(用萬用表測)對比,看看轉換是否准確。
我們一般在設計原理圖的時候會把ADC的輸入電壓范圍設定在:0~3.3v,如果設置ADC為12位的,那么12位滿量程對應的就是3.3V,12位滿量程對應的數字值是:2^12。數值0對應的就是0V。如果轉換后的數值為 X ,X對應的模擬電壓為Y,那么會有這么一個等式成立: 2^12 / 3.3 = X / Y,=> Y = (3.3 * X ) / 2^12。
30.3 ADC初始化結構體詳解
標准庫函數對每個外設都建立了一個初始化結構體xxx_InitTypeDef(xxx為外設名稱),結構體成員用於設置外設工作參數,並由標准庫函數xxx_Init()調用這些設定參數進入設置外設相應的寄存器,達到配置外設工作環境的目的。
結構體xxx_InitTypeDef和庫函數xxx_Init配合使用是標准庫精髓所在,理解了結構體xxx_InitTypeDef每個成員意義基本上就可以對該外設運用自如了。結構體xxx_InitTypeDef定義在stm32f4xx_xxx.h文件中,庫函數xxx_Init定義在stm32f4xx_xxx.c文件中,編程時我們可以結合這兩個文件內注釋使用。
ADC_InitTypeDef結構體
ADC_InitTypeDef結構體定義在stm32f4xx_adc.h文件內,具體定義如下:
1 typedef struct {
2 uint32_t ADC_Resolution; //ADC分辨率選擇
3 FunctionalState ADC_ScanConvMode; //ADC掃描選擇
4 FunctionalState ADC_ContinuousConvMode; //ADC連續轉換模式選擇
5 uint32_t ADC_ExternalTrigConvEdge; //ADC外部觸發極性
6 uint32_t ADC_ExternalTrigConv; //ADC外部觸發選擇
7 uint32_t ADC_DataAlign; //輸出數據對齊方式
8 uint8_t ADC_NbrOfChannel; //轉換通道數目
9 } ADC_InitTypeDef;
ADC_Resolution:配置ADC的分辨率,可選的分辨率有12位、10位、8位和6位。分辨率越高,AD轉換數據精度越高,轉換時間也越長;分辨率越低,AD轉換數據精度越低,轉換時間也越短。
ScanConvMode:可選參數為ENABLE和DISABLE,配置是否使用掃描。如果是單通道AD轉換使用DISABLE,如果是多通道AD轉換使用ENABLE。
ADC_ContinuousConvMode:可選參數為ENABLE和DISABLE,配置是啟動自動連續轉換還是單次轉換。使用ENABLE配置為使能自動連續轉換;使用DISABLE配置為單次轉換,轉換一次后停止需要手動控制才重新啟動轉換。
ADC_ExternalTrigConvEdge:外部觸發極性選擇,如果使用外部觸發,可以選擇觸發的極性,可選有禁止觸發檢測、上升沿觸發檢測、下降沿觸發檢測以及上升沿和下降沿均可觸發檢測。
ADC_ExternalTrigConv:外部觸發選擇,圖 01中列舉了很多外部觸發條件,可根據項目需求配置觸發來源。實際上,我們一般使用軟件自動觸發。
ADC_DataAlign:轉換結果數據對齊模式,可選右對齊ADC_DataAlign_Right或者左對齊ADC_DataAlign_Left。一般我們選擇右對齊模式。
ADC_NbrOfChannel:AD轉換通道數目。
ADC_CommonInitTypeDef結構體
ADC除了有ADC_InitTypeDef初始化結構體外,還有一個ADC_CommonInitTypeDef通用初始化結構體。ADC_CommonInitTypeDef結構體內容決定三個ADC共用的工作環境,比如模式選擇、ADC時鍾等等。
ADC_CommonInitTypeDef結構體也是定義在stm32_f4xx.h文件中,具體定義如下:
1 typedef struct {
2 uint32_t ADC_Mode; //ADC模式選擇
3 uint32_t ADC_Prescaler; //ADC分頻系數
4 uint32_t ADC_DMAAccessMode; //DMA模式配置
5 uint32_t ADC_TwoSamplingDelay; //采樣延遲
6 } ADC_InitTypeDef;
ADC_Mode:ADC工作模式選擇,有獨立模式、雙重模式以及三重模式。
ADC_Prescaler:ADC時鍾分頻系數選擇,ADC時鍾是有PCLK2分頻而來,分頻系數決定ADC時鍾頻率,可選的分頻系數為2、4、6和8。ADC最大時鍾配置為36MHz。
ADC_DMAAccessMode:DMA模式設置,只有在雙重或者三重模式才需要設置,可以設置三種模式,具體可參考參考手冊說明。
ADC_TwoSamplingDelay:2個采樣階段之前的延遲,僅適用於雙重或三重交錯模式。
30.4 獨立模式單通道采集實驗
STM32的ADC功能繁多,我們設計三個實驗盡量完整的展示ADC的功能。首先是比較基礎實用的單通道采集,實現開發板上電位器的動觸點輸出引腳電壓的采集並通過串口打印至PC端串口調試助手。單通道采集適用AD轉換完成中斷,在中斷服務函數中讀取數據,不使用DMA傳輸,在多通道采集時才使用DMA傳輸。
30.4.1 硬件設計
開發板板載一個貼片滑動變阻器,電路設計見圖 05。

圖 05 開發板電位器部分原理圖
貼片滑動變阻器的動觸點通過連接至STM32芯片的ADC通道引腳。當我們使用旋轉滑動變阻器調節旋鈕時,其動觸點電壓也會隨之改變,電壓變化范圍為0~3.3V,亦是開發板默認的ADC電壓采集范圍。
30.4.2 軟件設計
這里只講解核心的部分代碼,有些變量的設置,頭文件的包含等並沒有涉及到,完整的代碼請參考本章配套的工程。
我們編寫兩個ADC驅動文件,bsp_adc.h 和 bsp_adc.c,用來存放ADC所用IO引腳的初始化函數以及ADC配置相關函數。
1. 編程要點
1) 初始化配置ADC目標引腳為模擬輸入模式;
2) 使能ADC時鍾;
3) 配置通用ADC為獨立模式,采樣4分頻;
4) 設置目標ADC為12位分辨率,1通道的連續轉換,不需要外部觸發;
5) 設置ADC轉換通道順序及采樣時間;
6) 配置使能ADC轉換完成中斷,在中斷內讀取轉換完數據;
7) 啟動ADC轉換;
8) 使能軟件觸發ADC轉換。
ADC轉換結果數據使用中斷方式讀取,這里沒有使用DMA進行數據傳輸。
2. 代碼分析
ADC宏定義
代碼清單 01 ADC宏定義
1 #define Rheostat_ADC_IRQ ADC_IRQn
2 #define Rheostat_ADC_INT_FUNCTION ADC_IRQHandler
3
4 #define RHEOSTAT_ADC_GPIO_PORT GPIOC
5 #define RHEOSTAT_ADC_GPIO_PIN GPIO_Pin_3
6 #define RHEOSTAT_ADC_GPIO_CLK RCC_AHB1Periph_GPIOC
7
8 #define RHEOSTAT_ADC ADC1
9 #define RHEOSTAT_ADC_CLK RCC_APB2Periph_ADC1
10 #define RHEOSTAT_ADC_CHANNEL ADC_Channel_13
使用宏定義引腳信息方便硬件電路改動時程序移植。
ADC GPIO初始化函數
代碼清單 02 ADC GPIO初始化
1 static void Rheostat_ADC_GPIO_Config(void)
2 {
3 GPIO_InitTypeDef GPIO_InitStructure;
4
5 // 使能 GPIO 時鍾
6 RCC_AHB1PeriphClockCmd(RHEOSTAT_ADC_GPIO_CLK, ENABLE);
7
8 // 配置 IO
9 GPIO_InitStructure.GPIO_Pin = RHEOSTAT_ADC_GPIO_PIN;
10 // 配置為模擬輸入
11 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
12 // 不上拉不下拉
13 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;
14 GPIO_Init(RHEOSTAT_ADC_GPIO_PORT, &GPIO_InitStructure);
15 }
使用到GPIO時候都必須開啟對應的GPIO時鍾,GPIO用於AD轉換功能必須配置為模擬輸入模式。
配置ADC工作模式
代碼清單 03 ADC工作模式配置
1 static void Rheostat_ADC_Mode_Config(void)
2 {
3 ADC_InitTypeDef ADC_InitStructure;
4 ADC_CommonInitTypeDef ADC_CommonInitStructure;
5
6 // 開啟ADC時鍾
7 RCC_APB2PeriphClockCmd(RHEOSTAT_ADC_CLK , ENABLE);
8
9 // -------------------ADC Common 結構體參數初始化--------------------
10 // 獨立ADC模式
11 ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;
12 // 時鍾為fpclk x分頻
13 ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4;
14 // 禁止DMA直接訪問模式
15 ADC_CommonInitStructure.ADC_DMAAccessMode=ADC_DMAAccessMode_Disabled;
16 // 采樣時間間隔
17 ADC_CommonInitStructure.ADC_TwoSamplingDelay=
18 ADC_TwoSamplingDelay_10Cycles;
19 ADC_CommonInit(&ADC_CommonInitStructure);
20
21 // -------------------ADC Init 結構體參數初始化---------------------
22 // ADC 分辨率
23 ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
24 // 禁止掃描模式,多通道采集才需要
25 ADC_InitStructure.ADC_ScanConvMode = DISABLE;
26 // 連續轉換
27 ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
28 //禁止外部邊沿觸發
29 ADC_InitStructure.ADC_ExternalTrigConvEdge =
30 ADC_ExternalTrigConvEdge_None;
31 //使用軟件觸發,外部觸發不用配置,注釋掉即可
32 //ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_T1_CC1;
33 //數據右對齊
34 ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
35 //轉換通道 1個
36 ADC_InitStructure.ADC_NbrOfConversion = 1;
37 ADC_Init(RHEOSTAT_ADC, &ADC_InitStructure);
38 //------------------------------------------------------------------
39 // 配置 ADC 通道轉換順序為1,第一個轉換,采樣時間為56個時鍾周期
40 ADC_RegularChannelConfig(RHEOSTAT_ADC, RHEOSTAT_ADC_CHANNEL,
41 1, ADC_SampleTime_56Cycles);
42
43 // ADC 轉換結束產生中斷,在中斷服務程序中讀取轉換值
44 ADC_ITConfig(RHEOSTAT_ADC, ADC_IT_EOC, ENABLE);
45 // 使能ADC
46 ADC_Cmd(RHEOSTAT_ADC, ENABLE);
47 //開始adc轉換,軟件觸發
48 ADC_SoftwareStartConv(RHEOSTAT_ADC);
49 }
首先,使用ADC_InitTypeDef和ADC_CommonInitTypeDef結構體分別定義一個ADC初始化和ADC通用類型變量,這兩個結構體我們之前已經有詳細講解。
我們調用RCC_APB2PeriphClockCmd()開啟ADC時鍾。
接下來我們使用ADC_CommonInitTypeDef結構體變量ADC_CommonInitStructure來配置ADC為獨立模式、分頻系數為4、不需要設置DMA模式、20個周期的采樣延遲,並調用ADC_CommonInit函數完成ADC通用工作環境配置。
我們使用ADC_InitTypeDef結構體變量ADC_InitStructure來配置ADC1為12位分辨率、單通道采集不需要掃描、啟動連續轉換、使用內部軟件觸發無需外部觸發事件、使用右對齊數據格式、轉換通道為1,並調用ADC_Init函數完成ADC1工作環境配置。
ADC_RegularChannelConfig函數用來綁定ADC通道轉換順序和時間。它接收4個形參,第一個形參選擇ADC外設,可為ADC1、ADC2或ADC3;第二個形參通道選擇,總共可選18個通道;第三個形參為轉換順序,可選為1到16;第四個形參為采樣周期選擇,采樣周期越短,ADC轉換數據輸出周期就越短但數據精度也越低,采樣周期越長,ADC轉換數據輸出周期就越長同時數據精度越高。PC3對應ADC通道ADC_Channel_13,這里我們選擇ADC_SampleTime_56Cycles即56周期的采樣時間。
利用ADC轉換完成中斷可以非常方便的保證我們讀取到的數據是轉換完成后的數據而不用擔心該數據可能是ADC正在轉換時"不穩定"的數據。我們使用ADC_ITConfig函數使能ADC轉換完成中斷,並在中斷服務函數中讀取轉換結果數據。
ADC_Cmd函數控制ADC轉換啟動和停止。
最后,如果使用軟件觸發需要調用ADC_SoftwareStartConvCmd函數進行使能配置。
ADC中斷配置
代碼清單 04 ADC中斷配置
1 static void Rheostat_ADC_NVIC_Config(void)
2 {
3 NVIC_InitTypeDef NVIC_InitStructure;
4
5 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
6
7 NVIC_InitStructure.NVIC_IRQChannel = Rheostat_ADC_IRQ;
8 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
9 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
10 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
11
12 NVIC_Init(&NVIC_InitStructure);
13 }
在Rheostat_ADC_NVIC_Config函數中我們配置了ADC轉換完成中斷,使用中斷同時需要配置中斷源和中斷優先級。
ADC中斷服務函數
代碼清單 05 ADC中斷服務函數
1 void ADC_IRQHandler(void)
2 {
3 if (ADC_GetITStatus(RHEOSTAT_ADC,ADC_IT_EOC)==SET) {
4 // 讀取ADC的轉換值
5 ADC_ConvertedValue = ADC_GetConversionValue(RHEOSTAT_ADC);
6
7 }
8 ADC_ClearITPendingBit(RHEOSTAT_ADC,ADC_IT_EOC);
9
10 }
中斷服務函數一般定義在stm32f4xx_it.c文件內,我們只使能了ADC轉換完成中斷,在ADC轉換完成后就會進入中斷服務函數,我們在中斷服務函數內直接讀取ADC轉換結果保存在變量ADC_ConvertedValue(在main.c中定義)中。
ADC_GetConversionValue函數是獲取ADC轉換結果值的庫函數,只有一個形參為ADC外設,可選為ADC1、ADC2或ADC3,該函數還返回一個16位的ADC轉換結果值。
主函數
代碼清單 06 主函數
1 int main(void)
2 {
3 /*初始化USART1*/
4 Debug_USART_Config();
5
6 /* 初始化滑動變阻器用到的DAC,ADC采集完成會產生ADC中斷,
7 在stm32f4xx_it.c文件中的中斷服務函數更新ADC_ConvertedValue的值 */
8 Rheostat_Init();
9
10 printf("\r\n ----這是一個ADC實驗(NO DMA傳輸)----\r\n");
11
12
13 while (1) {
14 Delay(0xffffee);
15 printf("\r\n The current AD value = 0x%04X \r\n",
16 ADC_ConvertedValue);
17 printf("\r\n The current AD value = %f V \r\n",ADC_Vol);
18
19 ADC_Vol =(float)(ADC_ConvertedValue*3.3/4096); // 讀取轉換的AD值
20
21 }
22 }
主函數先調用USARTx_Config函數配置調試串口相關參數,函數定義在bsp_debug_usart.c文件中。
接下來調用Rheostat _Init函數進行ADC初始化配置並啟動ADC。Rheostat _Init函數是定義在bsp_adc.c文件中,它只是簡單的分別調用Rheostat_ADC_GPIO_Config ()、Rheostat_ADC_Mode_Config ()和Rheostat_ADC_NVIC_Config()。
Delay函數只是一個簡單的延時函數。
在ADC中斷服務函數中我們把AD轉換結果保存在變量ADC_ConvertedValue中,根據我們之前的分析可以非常清楚的計算出對應的電位器動觸點的電壓值。
最后就是把相關數據打印至串口調試助手。
30.4.3 下載驗證
用USB線連接開發板"USB TO UART"接口跟電腦,在電腦端打開串口調試助手,把編譯好的程序下載到開發板。在串口調試助手可看到不斷有數據從開發板傳輸過來,此時我們旋轉電位器改變其電阻值,那么對應的數據也會有變化。
30.5 獨立模式多通道采集實驗
30.5.1 硬件設計
開發板已通過排針接口把部分ADC通道引腳引出,我們可以根據需要選擇使用。實際使用時候必須注意保存ADC引腳是單獨使用的,不可能與其他模塊電路共用同一引腳。
30.5.2 軟件設計
這里只講解核心的部分代碼,有些變量的設置,頭文件的包含等並沒有涉及到,完整的代碼請參考本章配套的工程。
跟單通道例程一樣,我們編寫兩個ADC驅動文件,bsp_adc.h 和 bsp_adc.c,用來存放ADC所用IO引腳的初始化函數以及ADC配置相關函數,實際上這兩個文件跟單通道實驗的文件是非常相似的。
1. 編程要點
1) 初始化配置ADC目標引腳為模擬輸入模式;
2) 使能ADC時鍾和DMA時鍾;
3) 配置DMA從ADC規矩數據寄存器傳輸數據到我們指定的存儲區;
4) 配置通用ADC為獨立模式,采樣4分頻;
5) 設置ADC為12位分辨率,啟動掃描,連續轉換,不需要外部觸發;
6) 設置ADC轉換通道順序及采樣時間;
7) 使能DMA請求,DMA在AD轉換完自動傳輸數據到指定的存儲區;
8) 啟動ADC轉換;
9) 使能軟件觸發ADC轉換。
ADC轉換結果數據使用DMA方式傳輸至指定的存儲區,這樣取代單通道實驗使用中斷服務的讀取方法。實際上,多通道ADC采集一般使用DMA數據傳輸方式更加高效方便。
2. 代碼分析
ADC宏定義
代碼清單 07 多通道ADC相關宏定義
1 //轉換的通道個數
2 #define RHEOSTAT_NOFCHANEL 4
3
4 #define RHEOSTAT_ADC_DR_ADDR ((u32)ADC3+0x4c)
5 #define RHEOSTAT_ADC_GPIO_PORT GPIOF
6 #define RHEOSTAT_ADC_GPIO_CLK RCC_AHB1Periph_GPIOF
7 #define RHEOSTAT_ADC ADC3
8 #define RHEOSTAT_ADC_CLK RCC_APB2Periph_ADC3
9
10 #define RHEOSTAT_ADC_GPIO_PIN1 GPIO_Pin_6
11 #define RHEOSTAT_ADC_CHANNEL1 ADC_Channel_4
12
13 #define RHEOSTAT_ADC_GPIO_PIN2 GPIO_Pin_7
14 #define RHEOSTAT_ADC_CHANNEL2 ADC_Channel_5
15
16 #define RHEOSTAT_ADC_GPIO_PIN3 GPIO_Pin_8
17 #define RHEOSTAT_ADC_CHANNEL3 ADC_Channel_6
18
19 #define RHEOSTAT_ADC_GPIO_PIN4 GPIO_Pin_9
20 #define RHEOSTAT_ADC_CHANNEL4 ADC_Channel_7
21
22 // DMA2 數據流0 通道2
23 #define RHEOSTAT_ADC_DMA_CLK RCC_AHB1Periph_DMA2
24 #define RHEOSTAT_ADC_DMA_CHANNEL DMA_Channel_2
25 #define RHEOSTAT_ADC_DMA_STREAM DMA2_Stream0
定義4個通道進行多通道ADC實驗,並且定義DMA相關配置。
ADC GPIO初始化函數
代碼清單 08 ADC GPIO初始化
1 static void Rheostat_ADC_GPIO_Config(void)
2 {
3 GPIO_InitTypeDef GPIO_InitStructure;
4
5 // 使能 GPIO 時鍾
6 RCC_AHB1PeriphClockCmd(RHEOSTAT_ADC_GPIO_CLK, ENABLE);
7
8 // 配置 IO
9 GPIO_InitStructure.GPIO_Pin = RHEOSTAT_ADC_GPIO_PIN1 |
10 RHEOSTAT_ADC_GPIO_PIN2 |
11 RHEOSTAT_ADC_GPIO_PIN3 |
12 RHEOSTAT_ADC_GPIO_PIN4;
13 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
14 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ; //不上拉不下拉
15 GPIO_Init(RHEOSTAT_ADC_GPIO_PORT, &GPIO_InitStructure);
16 }
使用到GPIO時候都必須開啟對應的GPIO時鍾,GPIO用於AD轉換功能必須配置為模擬輸入模式。
配置ADC工作模式
代碼清單 09 ADC工作模式配置
1 static void Rheostat_ADC_Mode_Config(void)
2 {
3 DMA_InitTypeDef DMA_InitStructure;
4 ADC_InitTypeDef ADC_InitStructure;
5 ADC_CommonInitTypeDef ADC_CommonInitStructure;
6
7 // 開啟ADC時鍾
8 RCC_APB2PeriphClockCmd(RHEOSTAT_ADC_CLK , ENABLE);
9 // 開啟DMA時鍾
10 RCC_AHB1PeriphClockCmd(RHEOSTAT_ADC_DMA_CLK, ENABLE);
11
12 // ------------------DMA Init 結構體參數初始化------------------------
13 // 選擇 DMA 通道,通道存在於數據流中
14 DMA_InitStructure.DMA_Channel = RHEOSTAT_ADC_DMA_CHANNEL;
15
16 // 外設基址為:ADC 數據寄存器地址
17 DMA_InitStructure.DMA_PeripheralBaseAddr = RHEOSTAT_ADC_DR_ADDR;
18 // 存儲器地址
19 DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)ADC_ConvertedValue;
20
21 // 數據傳輸方向為外設到存儲器
22 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
23 // 緩沖區大小,指一次傳輸的數據項
24 DMA_InitStructure.DMA_BufferSize = RHEOSTAT_NOFCHANEL;
25
26 // 外設寄存器只有一個,地址不用遞增
27 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
28 // 存儲器地址遞增
29 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
30 //DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;
31
32 // // 外設數據大小為半字,即兩個字節
33 DMA_InitStructure.DMA_PeripheralDataSize =
34 DMA_PeripheralDataSize_HalfWord;
35 // 存儲器數據大小也為半字,跟外設數據大小相同
36 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
37
38 // 循環傳輸模式
39 DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
40 // DMA 傳輸通道優先級為高,當使用一個DMA通道時,優先級設置不影響
41 DMA_InitStructure.DMA_Priority = DMA_Priority_High;
42
43 // 禁止DMA FIFO ,使用直連模式
44 DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
45 // FIFO 閾值大小,FIFO模式禁止時,這個不用配置
46 DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
47
48 // 存儲器突發單次傳輸
49 DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
50 // 外設突發單次傳輸
51 DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
52
53 //初始化DMA數據流,流相當於一個大的管道,管道里面有很多通道
54 DMA_Init(RHEOSTAT_ADC_DMA_STREAM, &DMA_InitStructure);
55
56 // 使能DMA數據流
57 DMA_Cmd(RHEOSTAT_ADC_DMA_STREAM, ENABLE);
58
59 // -------------------ADC Common 結構體參數初始化--------------------
60 // 獨立ADC模式
61 ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;
62 // 時鍾為fpclk x分頻
63 ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4;
64 // 禁止DMA直接訪問模式
65 ADC_CommonInitStructure.ADC_DMAAccessMode=ADC_DMAAccessMode_Disabled;
66 // 采樣時間間隔
67 ADC_CommonInitStructure.ADC_TwoSamplingDelay =
68 ADC_TwoSamplingDelay_10Cycles;
69 ADC_CommonInit(&ADC_CommonInitStructure);
70
71 // -------------------ADC Init 結構體參數初始化---------------------
72 // ADC 分辨率
73 ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
74 // 開啟掃描模式
75 ADC_InitStructure.ADC_ScanConvMode = ENABLE;
76 // 連續轉換
77 ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
78 //禁止外部邊沿觸發
79 ADC_InitStructure.ADC_ExternalTrigConvEdge =
80 ADC_ExternalTrigConvEdge_None;
81 //使用軟件觸發,外部觸發不用配置,注釋掉即可
82 //ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_T1_CC1;
83 //數據右對齊
84 ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
85 //轉換通道 x個
86 ADC_InitStructure.ADC_NbrOfConversion = RHEOSTAT_NOFCHANEL;
87 ADC_Init(RHEOSTAT_ADC, &ADC_InitStructure);
88 //------------------------------------------------------------------
89
90 // 配置 ADC 通道轉換順序和采樣時間周期
91 ADC_RegularChannelConfig(RHEOSTAT_ADC, RHEOSTAT_ADC_CHANNEL1,
92 1, ADC_SampleTime_56Cycles);
93 ADC_RegularChannelConfig(RHEOSTAT_ADC, RHEOSTAT_ADC_CHANNEL2,
94 2, ADC_SampleTime_56Cycles);
95 ADC_RegularChannelConfig(RHEOSTAT_ADC, RHEOSTAT_ADC_CHANNEL3,
96 3, ADC_SampleTime_56Cycles);
97 ADC_RegularChannelConfig(RHEOSTAT_ADC, RHEOSTAT_ADC_CHANNEL4,
98 4, ADC_SampleTime_56Cycles);
99 // 使能DMA請求 after last transfer (Single-ADC mode)
100 ADC_DMARequestAfterLastTransferCmd(RHEOSTAT_ADC, ENABLE);
101 // 使能ADC DMA
102 ADC_DMACmd(RHEOSTAT_ADC, ENABLE);
103 // 使能ADC
104 ADC_Cmd(RHEOSTAT_ADC, ENABLE);
105 //開始adc轉換,軟件觸發
106 ADC_SoftwareStartConv(RHEOSTAT_ADC);
107 }
首先,我們使用了DMA_InitTypeDef定義了一個DMA初始化類型變量,該結構體內容我們在DMA篇已經做了非常詳細的講解;另外還使用ADC_InitTypeDef和ADC_CommonInitTypeDef結構體分別定義一個ADC初始化和ADC通用類型變量,這兩個結構體我們之前已經有詳細講解。
調用RCC_APB2PeriphClockCmd()開啟ADC時鍾以及RCC_AHB1PeriphClockCmd()開啟DMA時鍾。
我們需要對DMA進行必要的配置。首先設置外設基地址就是ADC的規則數據寄存器地址;存儲器的地址就是我們指定的數據存儲區空間,ADC_ConvertedValue是我們定義的一個全局數組名,它是一個無符號16位含有4個元素的整數數組;ADC規則轉換對應只有一個數據寄存器所以地址不能遞增,而我們定義的存儲區是專門用來存放不同通道數據的,所以需要自動地址遞增。ADC的規則數據寄存器只有低16位有效,實際存放的數據只有12位而已,所以設置數據大小為半字大小。ADC配置為連續轉換模式DMA也設置為循環傳輸模式。設置好DMA相關參數后就使能DMA的ADC通道。
接下來我們使用ADC_CommonInitTypeDef結構體變量ADC_CommonInitStructure來配置ADC為獨立模式、分頻系數為4、不需要設置DMA模式、20個周期的采樣延遲,並調用ADC_CommonInit函數完成ADC通用工作環境配置。
我們使用ADC_InitTypeDef結構體變量ADC_InitStructure來配置ADC1為12位分辨率、使能掃描模式、啟動連續轉換、使用內部軟件觸發無需外部觸發事件、使用右對齊數據格式、轉換通道為4,並調用ADC_Init函數完成ADC3工作環境配置。
ADC_RegularChannelConfig函數用來綁定ADC通道轉換順序和采樣時間。分別綁定四個ADC通道引腳並設置相應的轉換順序。
ADC_DMARequestAfterLastTransferCmd函數控制是否使能ADC的DMA請求,如果使能請求,並調用ADC_DMACmd函數使能DMA,則在ADC轉換完成后就請求DMA實現數據傳輸。
ADC_Cmd函數控制ADC轉換啟動和停止。
最后,如果使用軟件觸發需要調用ADC_SoftwareStartConvCmd函數進行使能配置。
主函數
代碼清單 010 主函數
1 int main(void)
2 {
3 /*初始化USART1*/
4 Debug_USART_Config();
5
6 /* 初始化滑動變阻器用到的DAC
7 ,ADC數據采集完成后直接由DMA運輸數據到ADC_ConvertedValue變量
8 DMA直接改變ADC_ConvertedValue的值*/
9 Rheostat_Init();
10
11 printf("\r\n ----這是一個ADC實驗(多通道采集)----\r\n");
12
13 while (1) {
14 Delay(0xffffff);
15 ADC_ConvertedValueLocal[0]=(float)(ADC_ConvertedValue[0]*3.3/4096);
16 ADC_ConvertedValueLocal[1]=(float)(ADC_ConvertedValue[1]*3.3/4096);
17 ADC_ConvertedValueLocal[2]=(float)(ADC_ConvertedValue[2]*3.3/4096);
18 ADC_ConvertedValueLocal[3]=(float)(ADC_ConvertedValue[3]*3.3/4096);
19
20 printf("\r\n CH1_PF6 value = %fV\r\n",ADC_ConvertedValueLocal[0]);
21 printf("\r\n CH2_PF7 value = %fV\r\n",ADC_ConvertedValueLocal[1]);
22 printf("\r\n CH3_PF8 value = %fV\r\n",ADC_ConvertedValueLocal[2]);
23 printf("\r\n CH4_PF9 value = %fV\r\n",ADC_ConvertedValueLocal[3]);
24
25 printf("\r\n");
26 }
27 }
主函數先調用USARTx_Config函數配置調試串口相關參數,函數定義在bsp_debug_usart.c文件中。
接下來調用Rheostat_Init函數進行ADC初始化配置並啟動ADC。Rheostat_Init函數是定義在bsp_adc.c文件中,它只是簡單的分別調用Rheostat_ADC_GPIO_Config()和Rheostat_ADC_Mode_Config ()。
Delay函數只是一個簡單的延時函數。
我們配置了DMA數據傳輸所以它會自動把ADC轉換完成后數據保存到數組ADC_ConvertedValue內,我們只要直接使用數組就可以了。經過簡單地計算就可以得到每個通道對應的實際電壓。
最后就是把相關數據打印至串口調試助手。
30.5.3 下載驗證
將待測電壓通過杜邦線接在對應引腳上,用USB線連接開發板"USB TO UART"接口跟電腦,在電腦端打開串口調試助手,把編譯好的程序下載到開發板。在串口調試助手可看到不斷有數據從開發板傳輸過來,此時我們改變輸入電壓值,那么對應的數據也會有變化。
30.6 三重ADC交替模式采集實驗
AD轉換包括采樣階段和轉換階段,在采樣階段才對通道數據進行采集;而在轉換階段只是將采集到的數據進行轉換為數字量輸出,此刻通道數據變化不會改變轉換結果。獨立模式的ADC采集需要在一個通道采集並且轉換完成后才會進行下一個通道的采集。雙重或者三重ADC的機制使用兩個或以上ADC同時采樣兩個或以上不同通道的數據或者使用兩個或以上ADC交叉采集同一通道的數據。雙重或者三重ADC模式較獨立模式一個最大的優勢就是轉換速度快。
我們這里只介紹三重ADC交替模式,關於雙重或者三重ADC的其他模式與之類似,可以參考三重ADC交替模式使用。三重ADC交替模式是針對同一通道的使用三個ADC交叉采集,就是在ADC1采樣完等幾個時鍾周期后ADC2開始采樣,此時ADC1處在轉換階段,當ADC2采樣完成再等幾個時鍾周期后ADC3就進行采樣此時ADC1和ADC2處在轉換階段,如果ADC3采樣完成並且ADC1已經轉換完成那么就可以准備下一輪的循環,這樣充分利用轉換階段時間達到增快采樣速度的效果。AD轉換過程見圖 06,利用ADC的轉換階段時間另外一個ADC進行采樣,而不用像獨立模式必須等待采樣和轉換結束后才進行下一次采樣及轉換。

圖 06 三重ADC交叉模式
30.6.1 硬件設計
三重ADC交叉模式是針對同一個通道的ADC采集模式,這種情況跟00小節的單通道實驗非常類似,只是同時使用三個ADC對同一通道進行采集,所以電路設計與之相同即可,具體可參考圖 05。
30.6.2 軟件設計
這里只講解核心的部分代碼,有些變量的設置,頭文件的包含等並沒有涉及到,完整的代碼請參考本章配套的工程。
跟單通道例程一樣,我們編寫兩個ADC驅動文件,bsp_adc.h 和 bsp_adc.c,用來存放ADC所用IO引腳的初始化函數以及ADC配置相關函數,實際上這兩個文件跟單通道實驗的文件非常相似。
1. 編程要點
1) 初始化配置ADC目標引腳為模擬輸入模式;
2) 使能ADC1、ADC2、ADC3以及DMA時鍾;
3) 配置DMA控制將ADC通用規矩數據寄存器數據轉存到指定存儲區;
4) 配置通用ADC為三重ADC交替模式,采樣4分頻,使用DMA模式2;
5) 設置ADC1、ADC2和ADC3為12位分辨率,禁用掃描,連續轉換,不需要外部觸發;
6) 設置ADC1、ADC2和ADC3轉換通道順序及采樣時間;
7) 使能ADC1的 DMA請求,在ADC轉換完后自動請求DMA進行數據傳輸;
8) 啟動ADC1、ADC2和ADC3轉換;
9) 使能軟件觸發ADC轉換。
ADC轉換結果數據使用DMA方式傳輸至指定的存儲區,這樣取代單通道實驗使用中斷服務的讀取方法。
2. 代碼分析
ADC宏定義
代碼清單 011 多通道ADC相關宏定義
1 #define RHEOSTAT_ADC_CDR_ADDR ((uint32_t)0x40012308)
2
3 #define RHEOSTAT_ADC_GPIO_PORT GPIOC
4 #define RHEOSTAT_ADC_GPIO_PIN GPIO_Pin_3
5 #define RHEOSTAT_ADC_GPIO_CLK RCC_AHB1Periph_GPIOC
6
7 #define RHEOSTAT_ADC1 ADC1
8 #define RHEOSTAT_ADC1_CLK RCC_APB2Periph_ADC1
9 #define RHEOSTAT_ADC2 ADC2
10 #define RHEOSTAT_ADC2_CLK RCC_APB2Periph_ADC2
11 #define RHEOSTAT_ADC3 ADC3
12 #define RHEOSTAT_ADC3_CLK RCC_APB2Periph_ADC3
13 #define RHEOSTAT_ADC_CHANNEL ADC_Channel_13
14
15 #define RHEOSTAT_ADC_DMA_CLK RCC_AHB1Periph_DMA2
16 #define RHEOSTAT_ADC_DMA_CHANNEL DMA_Channel_0
17 #define RHEOSTAT_ADC_DMA_STREAM DMA2_Stream0
雙重或者三重ADC需要使用通用規則數據寄存器ADC_CDR,這點跟獨立模式不同。定義電位器動觸點引腳作為三重ADC的模擬輸入。
ADC GPIO初始化函數
代碼清單 012 ADC GPIO初始化
1 static void Rheostat_ADC_GPIO_Config(void)
2 {
3 GPIO_InitTypeDef GPIO_InitStructure;
4
5 // 使能 GPIO 時鍾
6 RCC_AHB1PeriphClockCmd(RHEOSTAT_ADC_GPIO_CLK, ENABLE);
7
8 // 配置 IO
9 GPIO_InitStructure.GPIO_Pin = RHEOSTAT_ADC_GPIO_PIN;
10 // 配置為模擬輸入
11 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
12 // 不上拉不下拉
13 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;
14 GPIO_Init(RHEOSTAT_ADC_GPIO_PORT, &GPIO_InitStructure);
15 }
使用到GPIO時候都必須開啟對應的GPIO時鍾,GPIO用於AD轉換功能必須配置為模擬輸入模式。
配置三重ADC交替模式
代碼清單 013 三重ADC交替模式配置
1 static void Rheostat_ADC_Mode_Config(void)
2 {
3 DMA_InitTypeDef DMA_InitStructure;
4 ADC_InitTypeDef ADC_InitStructure;
5 ADC_CommonInitTypeDef ADC_CommonInitStructure;
6
7 // 開啟ADC時鍾
8 RCC_APB2PeriphClockCmd(RHEOSTAT_ADC1_CLK , ENABLE);
9 RCC_APB2PeriphClockCmd(RHEOSTAT_ADC2_CLK , ENABLE);
10 RCC_APB2PeriphClockCmd(RHEOSTAT_ADC3_CLK , ENABLE);
11
12 // ------------------DMA Init 結構體參數初始化-----------------------
13 // ADC1使用DMA2,數據流0,通道0,這個是手冊固定死的
14 // 開啟DMA時鍾
15 RCC_AHB1PeriphClockCmd(RHEOSTAT_ADC_DMA_CLK, ENABLE);
16 // 外設基址為:ADC 數據寄存器地址
17 DMA_InitStructure.DMA_PeripheralBaseAddr = RHEOSTAT_ADC_CDR_ADDR;
18 // 存儲器地址,實際上就是一個內部SRAM的變量
19 DMA_InitStructure.DMA_Memory0BaseAddr = (u32)ADC_ConvertedValue;
20 // 數據傳輸方向為外設到存儲器
21 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
22 // 緩沖區大小為3,緩沖區的大小應該等於存儲器的大小
23 DMA_InitStructure.DMA_BufferSize = 3;
24 // 外設寄存器只有一個,地址不用遞增
25 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
26 // 存儲器地址自動遞增
27 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
28 // // 外設數據大小為字,即四個字節
29 DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_Word;
30 // 存儲器數據大小也為字,跟外設數據大小相同
31 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
32 // 循環傳輸模式
33 DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
34 // DMA 傳輸通道優先級為高,當使用一個DMA通道時,優先級設置不影響
35 DMA_InitStructure.DMA_Priority = DMA_Priority_High;
36 // 禁止DMA FIFO ,使用直連模式
37 DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
38 // FIFO 大小,FIFO模式禁止時,這個不用配置
39 DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
40 DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
41 DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
42 // 選擇 DMA 通道,通道存在於流中
43 DMA_InitStructure.DMA_Channel = RHEOSTAT_ADC_DMA_CHANNEL;
44 //初始化DMA流,流相當於一個大的管道,管道里面有很多通道
45 DMA_Init(RHEOSTAT_ADC_DMA_STREAM, &DMA_InitStructure);
46 // 使能DMA流
47 DMA_Cmd(RHEOSTAT_ADC_DMA_STREAM, ENABLE);
48
49 // -------------------ADC Common 結構體參數初始化--------------------
50 // 三重ADC交替模式
51 ADC_CommonInitStructure.ADC_Mode = ADC_TripleMode_Interl;
52 // 時鍾為fpclk2 4分頻
53 ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4;
54 // 禁止DMA直接訪問模式
55 ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_2;
56 // 采樣時間間隔
57 ADC_CommonInitStructure.ADC_TwoSamplingDelay =
58 ADC_TwoSamplingDelay_10Cycles;
59 ADC_CommonInit(&ADC_CommonInitStructure);
60
61 // -------------------ADC Init 結構體參數初始化----------------------
62 // ADC 分辨率
63 ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
64 // 禁止掃描模式,多通道采集才需要
65 ADC_InitStructure.ADC_ScanConvMode = DISABLE;
66 // 連續轉換
67 ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
68 //禁止外部邊沿觸發
69 ADC_InitStructure.ADC_ExternalTrigConvEdge =
70 ADC_ExternalTrigConvEdge_None;
71 //使用軟件觸發,外部觸發不用配置,注釋掉即可
72 //ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_T1_CC1;
73 //數據右對齊
74 ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
75 //轉換通道 1個
76 ADC_InitStructure.ADC_NbrOfConversion = 1;
77 ADC_Init(RHEOSTAT_ADC1, &ADC_InitStructure);
78 // 配置 ADC 通道轉換順序為1,第一個轉換,采樣時間為3個時鍾周期
79 ADC_RegularChannelConfig(RHEOSTAT_ADC1, RHEOSTAT_ADC_CHANNEL,
80 1, ADC_SampleTime_3Cycles);
81 //------------------------------------------------------------------
82 ADC_Init(RHEOSTAT_ADC2, &ADC_InitStructure);
83 // 配置 ADC 通道轉換順序為1,第一個轉換,采樣時間為3個時鍾周期
84 ADC_RegularChannelConfig(RHEOSTAT_ADC2, RHEOSTAT_ADC_CHANNEL,
85 1, ADC_SampleTime_3Cycles);
86 //------------------------------------------------------------------
87 ADC_Init(RHEOSTAT_ADC3, &ADC_InitStructure);
88 // 配置 ADC 通道轉換順序為1,第一個轉換,采樣時間為3個時鍾周期
89 ADC_RegularChannelConfig(RHEOSTAT_ADC3, RHEOSTAT_ADC_CHANNEL,
90 1, ADC_SampleTime_3Cycles);
91
92 // 使能DMA請求 after last transfer (multi-ADC mode)
93 ADC_MultiModeDMARequestAfterLastTransferCmd(ENABLE);
94 // 使能ADC DMA
95 ADC_DMACmd(RHEOSTAT_ADC1, ENABLE);
96 // 使能ADC
97 ADC_Cmd(RHEOSTAT_ADC1, ENABLE);
98 ADC_Cmd(RHEOSTAT_ADC2, ENABLE);
99 ADC_Cmd(RHEOSTAT_ADC3, ENABLE);
100
101 //開始adc轉換,軟件觸發
102 ADC_SoftwareStartConv(RHEOSTAT_ADC1);
103 ADC_SoftwareStartConv(RHEOSTAT_ADC2);
104 ADC_SoftwareStartConv(RHEOSTAT_ADC3);
105 }
首先,我們使用了DMA_InitTypeDef定義了一個DMA初始化類型變量,該結構體內容我們在DMA篇已經做了非常詳細的講解;另外還使用ADC_InitTypeDef和ADC_CommonInitTypeDef結構體分別定義一個ADC初始化和ADC通用類型變量,這兩個結構體我們之前已經有詳細講解。
調用RCC_APB2PeriphClockCmd()開啟ADC時鍾以及RCC_AHB1PeriphClockCmd()開啟DMA時鍾。
我們需要對DMA進行必要的配置。首先設置外設基地址就是ADC的通用規則數據寄存器地址;存儲器的地址就是我們指定的數據存儲區空間,ADC_ConvertedValue是我們定義的一個全局數組名,它是一個無符號32位有三個元素的整數數字;ADC規則轉換對應只有一個數據寄存器所以地址不能遞增,我們指定的存儲區也需要遞增地址。ADC的通用規則數據寄存器是32位有效,我們配置ADC為DMA模式2,設置數據大小為字大小。ADC配置為連續轉換模式DMA也設置為循環傳輸模式。設置好DMA相關參數后就使能DMA的ADC通道。
接下來我們使用ADC_CommonInitTypeDef結構體變量ADC_CommonInitStructure來配置ADC為三重ADC交替模式、分頻系數為4、需要設置DMA模式2、10個周期的采樣延遲,並調用ADC_CommonInit函數完成ADC通用工作環境配置。
我們使用ADC_InitTypeDef結構體變量ADC_InitStructure來配置ADC1為12位分辨率、不使用掃描模式、啟動連續轉換、使用內部軟件觸發無需外部觸發事件、使用右對齊數據格式、轉換通道為1,並調用ADC_Init函數完成ADC1工作環境配置。ADC2和ADC3使用與ADC1相同配置即可。
ADC_RegularChannelConfig函數用來綁定ADC通道轉換順序和采樣時間。綁定ADC通道引腳並設置相應的轉換順序。
ADC_MultiModeDMARequestAfterLastTransferCmd函數控制是否使能ADC的DMA請求,如果使能請求,並調用ADC_DMACmd函數使能DMA,則在ADC轉換完成后就請求DMA實現數據傳輸。三重模式只需使能ADC1的DMA通道。
ADC_Cmd函數控制ADC轉換啟動和停止。
最后,如果使用軟件觸發需要調用ADC_SoftwareStartConvCmd函數進行使能配置。
主函數
代碼清單 014 主函數
1 int main(void)
2 {
3 /*初始化USART1*/
4 Debug_USART_Config();
5
6 /*
7 初始化滑動變阻器用到的DAC,ADC數據采集完成后直接由DMA運輸數據到
8 ADC_ConvertedValue變量DMA直接改變ADC_ConvertedValue的值*/
9 Rheostat_Init();
10
11 printf("\r\n ----這是一個ADC實驗(DMA傳輸)----\r\n");
12
13 while (1) {
14 Delay(0xffffee);
15 ADC_ConvertedValueLocal[0] =
16 (float)((uint16_t)ADC_ConvertedValue[0]*3.3/4096);
17 ADC_ConvertedValueLocal[1] =
18 (float)((uint16_t)ADC_ConvertedValue[2]*3.3/4096);
19 ADC_ConvertedValueLocal[2] =
20 (float)((uint16_t)ADC_ConvertedValue[1]*3.3/4096);
21 printf("\r\n The current AD value = 0x%08X \r\n",
22 ADC_ConvertedValue[0]);
23 printf("\r\n The current AD value = 0x%08X \r\n",
24 ADC_ConvertedValue[1]);
25 printf("\r\n The current AD value = 0x%08X \r\n",
26 ADC_ConvertedValue[2]);
27 printf("\r\n The current ADC1 value = %f V \r\n",
28 ADC_ConvertedValueLocal[0]);
29 printf("\r\n The current ADC2 value = %f V \r\n",
30 ADC_ConvertedValueLocal[1]);
31 printf("\r\n The current ADC3 value = %f V \r\n",
32 ADC_ConvertedValueLocal[2]);
33 }
34 }
主函數先調用USARTx_Config函數配置調試串口相關參數,函數定義在bsp_debug_usart.c文件中。
接下來調用Rheostat_Init函數進行ADC初始化配置並啟動ADC。Rheostat_Init函數是定義在bsp_adc.c文件中,它只是簡單的分別調用Rheostat_ADC_GPIO_Config()和Rheostat_ADC_Mode_Config ()。
Delay函數只是一個簡單的延時函數。
我們配置了DMA數據傳輸所以它會自動把ADC轉換完成后數據保存到數組變量ADC_ConvertedValue內,根據DMA模式2的數據存放規則,ADC_ConvertedValue[0]的低16位存放ADC1數據、高16位存放ADC2數據,ADC_ConvertedValue[1]的低16位存放ADC3數據、高16位存放ADC1數據,ADC_ConvertedValue[2]的低16位存放ADC2數據、高16位存放ADC3數據,我們可以根據需要提取出對應ADC的轉換結果數據。經過簡單地計算就可以得到每個ADC對應的實際電壓。
最后就是把相關數據打印至串口調試助手。
30.6.3 下載驗證
保證開發板相關硬件連接正確,用USB線連接開發板"USB TO UART"接口跟電腦,在電腦端打開串口調試助手,把編譯好的程序下載到開發板。在串口調試助手可看到不斷有數據從開發板傳輸過來,此時我們旋轉電位器改變其電阻值,那么對應的數據也會有變化。
30.7 每課一問
1、如果設置ADC分辨率為6、8或者10位時,輸入電壓值如何計算?
2、根據獨立模式單通道采集實驗設計實現相同功能,要求使用外部硬件觸發轉換方式。
3、編寫一個雙重ADC交替模式程序,實現類似三重ADC交替模式實驗功能,要求使用DMA模式2實現程序功能。
