1. 程式人生 > >STM32之ADC_2(DMA例項)

STM32之ADC_2(DMA例項)

例項分析:(採用DMA模式)

mian函式:
extern __IO u16 ADC_ConvertedValue;//ADC轉換的電壓值,是在ADC1_Init()所屬的文字中定義的
float ADC_ConvertedValueLocal;//用來儲存轉換計算後的電壓值
int main(void)
{
USART1_Config(); //串列埠配置
ADC1_Init(); //使能ADC1以及配置ADC1為DMA模式

while (1)
{
    ADC_ConvertedValueLocal =(float) ADC_ConvertedValue/4096*3.3; // 讀取轉換的 AD 值

    printf("\r\n The current AD value = 0x%04X \r\n", ADC_ConvertedValue); // ADC_ConvertedValue為全域性變數,SRAM讀取出來的ADC1轉換的值

    printf("\r\n The current AD value = %f V \r\n",ADC_ConvertedValueLocal);//區域性變數,用來儲存轉換計算後的電壓值

    Delay(0xffffee); // 延時
}

}

main函式的功能就是向串列埠傳送當前ADC1轉換的電壓值。(串列埠配置在前面的部落格有說,這裡就不分析了)

PS:
ADC_ConvertedValue的值通過DMA獲取的,但是在使用DMA時,由於不是核心執行的指令,所以修改變數值時不會出現賦值語句的。

ADC的初始化函式:
void ADC1_Init()
{
ADC1_GPIO_Config();
ADC1_Mode_Config();
}
ADC1_Init()呼叫了ADC1_GPIO_Config()和ADC1_Mode_Config().這兩個函式的作用分別是配置好ADC1所用的I/O埠,配置ADC1初始化及DMA模式。

GPIO配置:
static ADC1_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //使能DMA1的時鐘

RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOC,ENABLE); //使能GPIOC並且使能複用功能ADC1

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN; //模擬輸入
GPIO_Init(GPIOC,&GPIO_InitStructure);  //屬於PC1,輸入是不需要設定頻率的

}
GPIO的配置很簡單,首先是使能了DMA1,GPIOC,ADC1的時鐘,然後把ADC1的通道11使用的GPIO引腳PC1配置成模擬輸入模式.(在使用ADC的輸入時,必須使用模擬輸入,詳細的可以檢視之前的部落格,GPIO工作方式)

PS: ADC1通道11
每個ADC通道都對應著一個GPIO引腳埠,GPIO的引腳在設定為模擬輸入模式後可用於模擬電壓的輸入端,STM32F103VET6是有三個ADC的,這三個ADC共用了16個外部通道。
這裡寫圖片描述
(ADC通道引腳表,表中寫的ADC12_INx x:表示4~9或者14~15,ADC12:表示可以使用ADC1_IN或者ADC2_IN.這裡用到PC1在表中對應的是ADC123_IN11,所以可以使用ADC1的通道11,ADC2的通道11或者ADC3的通道11來採集PC1上模擬電壓資料)

DMA以及ADC的配置
static ADC1_Mode_Config(void)
{
DMA_InitTypeDef DMA_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
/這裡是DMA的配置/
DMA_DeInit(DMA1_Channel1);

DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address;//ADC1的地址    
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&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_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //關閉記憶體到記憶體的傳輸
DMA_Init(DMA1_Channel1, &DMA_InitStructure); //填充DMA,配置為DMA1的通道11

DMA_Cmd(DMA1_Channel1, ENABLE); //使能通道11

/*這裡是ADC的配置*/
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;//獨立的ADC模式
ADC_InitStructure.ADC_ScanConvMode = DISABLE ;//關閉掃描模式(掃描一般用於多通道採集)
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;//開啟連續轉換模式(就是不停的進行ADC轉換)
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //不使用外部觸發轉換
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//採集的資料右對齊
ADC_InitStructure.ADC_NbrOfChannel = 1; //要轉換的通道數量是1個
ADC_Init(ADC1, &ADC_InitStructure);//填充ADC1
RCC_ADCCLKConfig(RCC_PCLK2_Div8); //設定ADC時鐘的分頻,為PCLK2的8分頻,就是9HZ 72/8 = 9
ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 1, ADC_SampleTime_55Cycles5);//配置ADC1的通道11為55.5個取樣週期

/*這裡是ADC的復位校準操作*/
ADC_DMACmd(ADC1, ENABLE);//使能ADC1的DMA
ADC_Cmd(ADC1, ENABLE);   //使能ADC1

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

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

ADC_SoftwareStartConvCmd(ADC1, ENABLE);//使用軟體觸發ADC轉換,因為沒有采用外部觸發

}
ADC的DMA配置:(詳細的可以檢視之前的部落格STM32之DMA的配置程式碼分析)
使用DMA1的通道1,資料從ADC外設的資料暫存器(ADC1_DR_Address)轉移到記憶體(ADC_ConvertedValue 變數),記憶體、外設地址都固定,每次傳輸的資料大小為半字(16位),使用 DMA 迴圈傳輸模式。
(其中ADC1外設的DMA請求通道為DMA1的通道1,初始化時要注意.)
DMA傳輸的外設地址ADC1_DR_Address是一個自定義巨集#define ADC1_DR_Address ((u32)0x40012400+0x4c),ADC_DR資料暫存器儲存了ADC轉換後的數值,以它作為DMA的傳輸源地址,它的地址是ADC1外設的基地址(0X4001 2400)加上ADC資料暫存器(ADC_DR)的地址偏移(0x4c)計算得到的.
(ADC起始地址說明表以及ADC_DR暫存器描述可以在STM32參考手冊找到)
起始地址表大概如下:
起始地址 外設
0x4001 2800~0x4001 2BFF ADC2
0x4001 2400~0x4001 27FF ADC1

ADC_DR暫存器描述及其地址偏移
這裡寫圖片描述

ADC配置:
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
STM32具有多個ADC,而不同的ADC又有共用通道,當兩個ADC採集一個通道的先後順序,時間間隔不同,就會出現了各種各樣的模式,例如:同步注入模式,同步規則模式等10種.(這裡因為用於測量電阻分壓後的電壓值,所以要求不高,只使用一個ADC就可以滿足要求了,因此ADC_Mode被賦值成ADC_Mode_Independent.(獨立模式))

ADC_InitStructure.ADC_ScanConvMode = DISABLE;
如果有多個通道需要採集訊號,可以把ADC配置成按一定的順序來對各個通道進行掃描轉換(就是輪流採集各個通道的值).如果是多個通道採集,就必須開啟這個模式.(因為這裡只用了1個通道,所以禁止使用掃描模式)

ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
連續轉換模式,ADC轉換模式有兩個,一個是連續轉換一個是單次轉換.
單次轉換:ADC只採集一次資料就停止.
連續轉換:上一次ADC轉換完成後,立即開啟下一次轉換.
這裡需要不斷採集電壓值,所以使能連續轉換模式.

ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC是需要接收到觸發訊號才開始進行模數轉換,這些觸發訊號可以是外部中斷觸發(EXTI線),定時器觸發.這兩個都是外部觸發訊號,如果不使用外部觸發訊號可以使用軟體控制觸發(這裡只用了軟體控制觸發,所以禁止外部觸發)

ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
資料的對齊方式.ADC在轉換後的數值是被儲存到資料暫存器(ADC_DR)的0~15位或者16~32位,資料的寬度為16位,而ADC轉換精度為12位.所以把12位的資料儲存到16位的區域,就涉及到資料對齊.(這裡的左右對齊其實和word文件中的文字左,右對齊是一個意思).
左對齊:ADC轉換的數值最高位D12與儲存區域的最高位Bit 15對齊,也就是說儲存區域的低4位無意義.
右對齊:ADC轉換的數值最低位D0與儲存區域的最低位Bit 0對齊,也就是說儲存區域的高4位無意義.
(這裡右對齊比較方便,所以這裡用了右對齊)

ADC_InitStructure.ADC_NbrOfChannel = 1;
儲存了進行ADC資料轉換的通道數量,可以是1~16(一共16個通道),這裡只採集了PC1這個通道,所以賦值為1.

RCC_ADCCLKConfig(RCC_PCLK2_Div8);
填充完結構體後,就呼叫了外設初始化函式ADC_Init(ADC1, &ADC_InitStructure);進行初始化了.在使能DMA和ADC1時,還需要設定ADC的時鐘(ADCCLK),ADC的頻率越高,轉換的速率也就越快,但是ADC時鐘有上限值,不能超過14MHz.
這裡寫圖片描述

ADC的時鐘(ADCCLK)為ADC預分頻器的輸出,而ADC預分頻器的輸入則為高速外設時鐘(PCLK2).使用RCC_ADCCLKConfig()庫函式實質就是設定ADC的預分頻值,可以設定為PCLK2的2,4,6,8分頻.
PS:
PCLK2的常用時鐘頻率為72MHz,而ADCCLK的頻率必須低14MHz,所以ADCCLK最高頻率為PCLK2的8分頻(ADDCLK=9MHz),如果希望使ADC以最高頻率14MHz執行,可以把PCLK2配置為56MHz,然後用4分頻得到ADCCLK(14MHz).

ADC_RegularChannelConfig(ADC1,ADC_Channel_11,1,ADC_SampleTime_55Cycles5);
設定ADC取樣週期,ADC的轉換時間不僅與ADC的時鐘有關,還與取樣週期有關.(每個不用的ADC通道,都可以配置為不同的取樣週期)
這個庫函式的原形是:
voidADC_RegularChannelConfig(ADCTypeDef*ADCx,uint8_t ADC_Channel,uint8_t Rank,uint8_t ADC_SampleTime)
ADCx:選用的那個ADC 如ADC1,ADC2,ADC2

ADC_Channel:選擇要配置的ADC通道(其中16,17是內部通道 16是連線至晶片的溫度感測器,17是連線至內部電源模組)

Rank:配置為多通道掃描時,此通道的取樣順序,例如通道1,4,7的Rank值分別配置成3,2,1.那麼ADC掃描時,掃描的順序就是通道7->通道4->通道1

ADC_SampleTime:配置本通道的取樣週期,最短可配置為1.5個取樣週期(這裡的週期是ADCCLK的時鐘週期)
這裡面是把ADC1通道11配置為55.5個取樣週期,因為前面ADCCLK在前面已經配置為9MHz,根據STM32的ADC取樣時間計算公式:
TCONV = (取樣週期+12.5個週期)/ADCCLK
所以這裡的TCONV = (55.5+12.5)/9 大約等於7.56us.

ADC的自校準:
在開始ADC轉換之前,需要啟動ADC的自校準.ADC有一個內建自校準模式(校準可以大幅減小因內部電容器組的變化而造成的準精度誤差),在校準期間,在每個電容器上都會計算出一個誤差修正碼(數字值).這個碼是用於消除在隨後的轉換中每個電容器上產生的誤差.
程式碼:
ADC_ResetCalibration(ADC1);//復位ADC1校準暫存器
while(ADC_GetResetCalibrationStatus(ADC1)); //等待校準暫存器復位成功

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

呼叫了復位校準函式ADC_ResetCalibration()以及開始校準函式ADC_StartCalibration(),必須檢查標誌位等待校準完成,確保完成後才開始ADC轉換.(建議是每次上電後都校準一次咯)

ADC_SoftwareStartConvCmd(ADC1, ENABLE);
配置ADC1的模式為軟體觸發方式.

呼叫這個函式之後,ADC就開始進行轉換了,每次轉換完成後,由DMA控制器把轉換從ADC資料暫存器(ADC_DR)中轉移到變數ADC_ConvertedValue中,當DMA傳輸完成後,在main函式中使用 ADC_ConvertedValue的內容就是ADC的轉換值了.

計算電壓值:
在main函式中,ADC_ConvertedValueLoca是一個float型別變數,它儲存了有轉換值計算出來的電壓值,計算的公式是ADC通用的

實際電壓 = ADC轉換值*LSB
LSB為Vref+接的參考電壓/ADC的精度( LSB =3.3/2的12次方)

PS:
這裡面ADC_ConvertedValue是用volatile修飾的,用 volatile 宣告的型別變量表示可以被某些編譯器未知的因素更改,比如:作業系統、硬體或者其它執行緒等。因為 ADC_ConvertedValue 這個變數值隨時都是會被 DMA 控制器改變的,所以用 volatile 來修飾它,確保每次讀取到的都是實時的 ADC 轉換值.