
1. STM32 ADC 簡介
STM32 擁有 1~3 個 ADC(STM32F101/102 系列隻有 1 個 ADC),這些 ADC 可以獨立使用,也可以使用雙重模式(提高采樣率)。STM32 的 ADC 是 12 位逐次逼近型的模拟數字轉換器。它有 18 個通道,可測量 16 個外部和 2 個内部信号源。各通道的 A/D 轉換可以單次、連續、掃描或間斷模式執行。ADC 的結果可以左對齊或右對齊方式(12位)存儲在 16 位資料寄存器中。模拟看門狗特性允許應用程式檢測輸入電壓是否超出使用者定義的高/低閥值。
STM32 的 ADC 最大的轉換速率為 1Mhz,也就是轉換時間為 1us(在 ADCCLK = 14M,采樣周期為 1.5 個 ADC 時鐘下得到),不要讓 ADC 的時鐘超過 14M,否則将導緻結果準确度下降。STM32 将 ADC 的轉換分為 2 個通道組:規則通道組和注入通道組。規則通道相當于你正常運作的程式,而注入通道呢,就相當于中斷。在你程式正常執行的時候,中斷是可以打斷你的執行的。同這個類似,注入通道的轉換可以打斷規則通道的轉換, 在注入通道被轉換完成之後,規則通道才得以繼續轉換。
STM32 的 ADC 在單次轉換模式下,隻執行一次轉換,該模式可以通過 ADC_CR2 寄存器的 ADON 位(隻适用于規則通道)啟動,也可以通過外部觸發啟動(适用于規則通道和注入通道),這是 CONT 位為 0。以規則通道為例,一旦所選擇的通道轉換完成,轉換結果将被存在 ADC_DR 寄存器中,EOC(轉換結束)标志将被置位,如果設定了 EOCIE,則會産生中斷。然後 ADC 将停止,直到下次啟動。
2. 規則通道的單次轉換
2.1. 相關 ADC 寄存器
2.2.1. ADC 控制寄存器(ADC_CR1 和 ADC_CR2)
a. ADC_CR1:
ADC_CR1 的 SCAN 位,該位用于設定掃描模式,由軟體設定和清除,如果設定為 1,則使用掃描模式,如果為 0,則關閉掃描模式。在掃描模式下,由 ADC_SQRx 或 ADC_JSQRx 寄存器選中的通道被轉換。如果設定了 EOCIE 或 JEOCIE,隻在最後一個通道轉換完畢後才會産生 EOC 或 JEOC 中斷。
ADC_CR1[19:16] 用于設定 ADC 的操作模式,詳細的對應關系如下圖:
b. ADC_CR2
該寄存器的各位描述如圖:
這裡寫圖檔描述
ADON 位用于開關 AD 轉換器。CONT 位用于設定是否進行連續轉換,我們使用單次轉換,CONT 位必須為 0。CAL 和 RSTCAL 用于 AD 校準。ALIGN 用于設定資料對齊,使用右對齊,該位設定為 0。
EXTSEL[2:0] 用于選擇啟動規則轉換組轉換的外部事件
這裡使用的是軟體觸發(SWSTART),是以設定這 3 個位為 111。ADC_CR2 的 SWSTART 位用于開始規則通道的轉換,我們每次轉換(單次轉換模式下)都需要向該位寫 1。AWDEN 為用于使能溫度傳感器和 Vrefint。
2.2 ADC 采樣事件寄存器(ADC_SMPR1 和 ADC_SMPR2)
這兩個寄存器用于設定通道 0~17 的采樣時間,每個通道占用 3 個位。ADC_SMPR1 的各位描述如圖:
ADC_SMPR2 的各位描述如下圖:
對于每個要轉換的通道,采樣時間建議盡量長一點,以獲得較高的準确度,但是這樣會降低 ADC 的轉換速率。ADC 的轉換時間可以由以下公式計算:
Tcovn = 采樣時間 + 12.5 個周期
其中:Tcovn:為總轉換時間,采樣時間是根據每個通道的 SMP 位的設定來決定的。例如,當 ADCCLK = 14Mhz 的時候,并設定 1.5 個周期的采樣時間,則得到:Tcovn = 1.5 + 12.5 =14 個周期 = 1us。
2.3 ADC 規則序列寄存器(ADC_SQR1~3)
該寄存器總共有 3 個,這幾個寄存器的功能都差不多,這裡僅介紹一下 ADC_SQR1,該寄存器的各位描述如圖 :
這
L[3:0] 用于存儲規則序列的長度,我們這裡隻用了 1 個,是以設定這幾個位的值為 0。其他的 SQ13~16 則存儲了規則序列中第 13~16 通道的編号(編号範圍:0~17)。另外兩個規則序列寄存器同 ADC_SQR1 大同小異,要說明一點的是:是單次轉換,是以隻有一個通道在規則序列裡面,這個序列就是 SQ1,通過 ADC_SQR3 的最低 5位(也就是 SQ1) 設定。
2.4 ADC 規則資料寄存器(ADC_DR)
規則序列中的 AD 轉化結果都将被存在這個寄存器裡面,而注入通道的轉換結果被儲存在 ADC_JDRx 裡面。ADC_DR 的各位描述如圖 :
該寄存器的資料可以通過 ADC_CR2 的 ALIGN 位設定左對齊還是右對齊。在讀取資料的時候要注意。
2.5 ADC 狀态寄存器(ADC_SR)
該寄存器儲存了 ADC 轉換時的各種狀态。該寄存器的各位描述如圖:
這裡用到的是 EOC 位,我們通過判斷該位來決定是否此次規則通道的 AD 轉換已經完成,如果完成我們就從 ADC_DR 中讀取轉換結果,否則等待轉換完成。
3. STM32 的單次轉換模式設定步驟
1) 開啟 PA 口和 ADC1 時鐘,設定 PA1 為模拟輸入。
STM32F103RCT6 的 ADC1 通道 1 在 PA1 上,是以,我們先要使能 PORTA 的時鐘,然後設定 PA1 為模拟輸入。使能 GPIOA 和 ADC 時鐘用RCC_APB2PeriphClockCmd 函數,設定 PA1 的輸入方式,使用 GPIO_Init 函數即可。下面為 STM32 的 ADC 通道與 GPIO 對應表:
2)複位 ADC1,同時設定 ADC1 分頻因子。
開啟 ADC1 時鐘之後,我們要複位 ADC1,将 ADC1 的全部寄存器重設為預設值之後我們就可以通過 RCC_CFGR 設定 ADC1 的分頻因子。分頻因子要確定 ADC1 的時鐘(ADCCLK)不要超過 14Mhz。 這個我們設定分頻因子位 6,時鐘為 72 / 6 = 12MHz。庫函數的實作方是:
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
ADC 時鐘複位的方法是:
ADC_DeInit(ADC1);
3)初始化 ADC1 參數,設定 ADC1 的工作模式以及規則序列的相關資訊。
在設定完分頻因子之後,就可以開始 ADC1 的模式配置了,設定單次轉換模式、觸發方式選擇、資料對齊方式等都在這一步實作。同時,我們還要設定 ADC1 規則序列的相關資訊。
這裡隻有一個通道,并且是單次轉換的,是以設定規則序列中通道數為 1。這些在庫函數中是通過函數 ADC_Init 實作的,其定義:
void ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef* ADC_InitStruct);
第一個參數:指定 ADC 号。
第二個參數:跟其他外設初始化一樣,同樣是通過設定結構體成員變量的值來設定參數。
typedef struct
{uint32_t ADC_Mode;
FunctionalState ADC_ScanConvMode;
FunctionalState ADC_ContinuousConvMode;uint32_t ADC_ExternalTrigConv;uint32_t ADC_DataAlign;uint8_t ADC_NbrOfChannel;
} ADC_InitTypeDef;
參數 ADC_Mode :用來設定 ADC 的模式.ADC 的模式非常多,包括獨立模式,注入同步模式等等,這裡我們選擇獨立模式,參數為 ADC_Mode_Independent。
參數 ADC_ScanConvMode :用來設定是否開啟掃描模式,因為我們的實驗是單通道單次轉換,是以這裡我們選擇不開啟值 DISABLE 即可。
參數 ADC_ContinuousConvMode:用來設定是否開啟連續轉換模式,因為是單次轉換模式,是以我們選擇不開啟連續轉換模式, DISABLE 即可。
參數 ADC_ExternalTrigConv:是用來設定啟動規則轉換組轉換的外部事件,這裡我們選擇軟體觸發,選擇值為 ADC_ExternalTrigConv_None 即可。
參數 DataAlign:用來設定 ADC 資料對齊方式是左對齊還是右對齊,這裡選擇右對齊方式ADC_DataAlign_Right。
參數 ADC_NbrOfChannel :用來設定規則序列的長度, 我們實驗隻開啟一個通道,是以值為 1 即可。
初始化範例:
ADC_InitTypeDef ADC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_PCLK_ADC_CHL, ENABLE ); //使能ADC1通道時鐘
RCC_ADCCLKConfig(RCC_PCLK2_Div6); //設定ADC分頻因子6 72M/6=12,ADC最大時間不能超過14M
ADC_DeInit(ADC_CHL); //複位ADC1,将外設 ADC1 的全部寄存器重設為預設值
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //ADC工作模式:ADC1和ADC2工作在獨立模式
ADC_InitStructure.ADC_ScanConvMode = DISABLE; //模數轉換工作在單通道模式
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //模數轉換工作在單次轉換模式
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //轉換由軟體而不是外部觸發啟動
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //ADC資料右對齊
ADC_InitStructure.ADC_NbrOfChannel = 1; //順序進行規則轉換的ADC通道的數目
ADC_Init(ADC_CHL, &ADC_InitStructure); //根據ADC_InitStruct中指定的參數初始化外設ADCx的寄存器
5)使能 ADC 并校準。
在設定完了以上資訊後,就使能 AD 轉換器,執行複位校準和 AD 校準,注意這兩步是必須的!不校準将導緻結果很不準确。
使能指定的 ADC 的方法是:
ADC_Cmd(ADC1, ENABLE); //使能指定的 ADC1
執行複位校準的方法是:
ADC_ResetCalibration(ADC1);
執行 ADC 校準的方法是:
ADC_StartCalibration(ADC1); //開始指定 ADC1 的校準狀态
記住,每次進行校準之後要等待校準結束。這裡是通過擷取校準狀态來判斷是否校準是否結束。
複位校準和 AD 校準的等待結束方法:
while(ADC_GetResetCalibrationStatus(ADC1)); //等待複位校準結束while(ADC_GetCalibrationStatus(ADC1)); //等待校 AD 準結束
6)讀取 ADC 值。
在上面的校準完成之後, ADC 就算準備好了。接下來我們要做的就是設定規則序列 1 裡面的通道,采樣順序,以及通道的采樣周期,然後啟動 ADC 轉換。在轉換結束後,讀取 ADC 轉換結果值。
這裡設定規則序列通道以及采樣周期的函數是:
void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel,
uint8_t Rank, uint8_t ADC_SampleTime);
我們這裡是規則序列中的第 1 個轉換,同時采樣周期為 239.5,是以設定為:
ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_239Cycles5 );
軟體開啟 ADC 轉換的方法是:
ADC_SoftwareStartConvCmd(ADC1, ENABLE);//使能指定的 ADC1 的軟體轉換啟動功能
開啟轉換之後,就可以擷取轉換 ADC 轉換結果資料,方法是:
ADC_GetConversionValue(ADC1);
同時在 AD 轉換中,我們還要根據狀态寄存器的标志位來擷取 AD 轉換的各個狀态資訊。庫函數擷取 AD 轉換的狀态資訊的函數是:
FlagStatus ADC_GetFlagStatus(ADC_TypeDef* ADCx, uint8_t ADC_FLAG)
比如我們要判斷 ADC1 的轉換是否結束,方法是:
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));
通過以上幾個步驟的設定,我們就能正常的使用 STM32 的 ADC1 來執行 AD 轉換操作了。
注:說明一下 ADC 的參考電壓, MiniSTM32 開發闆使用的是STM32F103RCT6,該晶片沒有外部參考電壓引腳, ADC 的參考電壓直接取自 VDDA,也就是 3.3V,即輸入電壓範圍為:0~3.3V 。
4. 例程分析
宏定義:
//GPIO 部分#define RCC_PCLK_ADC_GPIO RCC_APB2Periph_GPIOA#define PORT_ADC_IN GPIOA#define ADC_IN_PIN GPIO_Pin_1//ADC 部分#define RCC_PCLK_ADC_CHL RCC_APB2Periph_ADC1#define ADC_CHL ADC1
例程實作:
//---------------------------------------------------------------------------------------------------------------------------------------------// 函 數 名: Adc_GpioConfig// 功能說明: ADC輸入采集引腳配置// 形 參: 無// 返 回 值: 無// 日 期: 2020-03-11// 備 注: // 作 者: 霁風AI//---------------------------------------------------------------------------------------------------------------------------------------------void Adc_GpioConfig(void){
GPIO_InitTypeDef gpio_init;
RCC_APB2PeriphClockCmd(RCC_PCLK_ADC_GPIO, ENABLE ); //使能ADC1通道時鐘//PA1 作為模拟通道輸入引腳
gpio_init.GPIO_Pin = ADC_IN_PIN; //ADC輸入引腳
gpio_init.GPIO_Mode = GPIO_Mode_AIN; //模拟輸入引腳
GPIO_Init(PORT_ADC_IN, &gpio_init);
}//---------------------------------------------------------------------------------------------------------------------------------------------// 函 數 名: Adc_Config// 功能說明: ADC輸入采集功能配置// 形 參: 無// 返 回 值: 無// 備 注: // 日 期: 2020-03-11// 作 者: 霁風AI//---------------------------------------------------------------------------------------------------------------------------------------------void Adc_Config(void){
ADC_InitTypeDef ADC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_PCLK_ADC_CHL, ENABLE ); //使能ADC1通道時鐘
RCC_ADCCLKConfig(RCC_PCLK2_Div6); //設定ADC分頻因子6 72M/6=12,ADC最大時間不能超過14M
ADC_DeInit(ADC_CHL); //複位ADC1,将外設 ADC1 的全部寄存器重設為預設值
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //ADC工作模式:ADC1和ADC2工作在獨立模式
ADC_InitStructure.ADC_ScanConvMode = DISABLE; //模數轉換工作在單通道模式
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //模數轉換工作在單次轉換模式
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //轉換由軟體而不是外部觸發啟動
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //ADC資料右對齊
ADC_InitStructure.ADC_NbrOfChannel = 1; //順序進行規則轉換的ADC通道的數目
ADC_Init(ADC_CHL, &ADC_InitStructure); //根據ADC_InitStruct中指定的參數初始化外設ADCx的寄存器
ADC_Cmd(ADC_CHL, ENABLE); //使能指定的ADC1
ADC_ResetCalibration(ADC_CHL); //使能複位校準 while(ADC_GetResetCalibrationStatus(ADC_CHL)); //等待複位校準結束
ADC_StartCalibration(ADC_CHL); //開啟AD校準while(ADC_GetCalibrationStatus(ADC_CHL)); //等待校準結束// ADC_SoftwareStartConvCmd(ADC1, ENABLE); //使能指定的ADC1的軟體轉換啟動功能
}//---------------------------------------------------------------------------------------------------------------------------------------------// 函 數 名: Adc_Init// 功能說明: ADC初始化// 形 參: 無// 返 回 值: 無// 備 注: // 日 期: 2020-03-11// 作 者: 霁風AI//---------------------------------------------------------------------------------------------------------------------------------------------void Adc_Init(void){
Adc_GpioConfig(); //IO初始化
Adc_Config(); //ADC功能配置
}//---------------------------------------------------------------------------------------------------------------------------------------------// 函 數 名: Get_AdcConvertVal// 功能說明: 輸出ADC轉換結果// 形 參: _ucChl:ADC 采集通道// 返 回 值: 采集資料結果// 備 注: // 日 期: 2020-03-11// 作 者: 霁風AI//---------------------------------------------------------------------------------------------------------------------------------------------uint16_t Get_AdcConvertVal(uint8_t _ucChl)
{//設定指定ADC的規則組通道,一個序列,采樣時間
ADC_RegularChannelConfig(ADC_CHL, _ucChl, 1, ADC_SampleTime_239Cycles5); //ADC1,ADC通道,采樣時間為239.5周期
ADC_SoftwareStartConvCmd(ADC_CHL, ENABLE); //使能指定的ADC1的軟體轉換啟動功能 while(!ADC_GetFlagStatus(ADC_CHL, ADC_FLAG_EOC)); //等待轉換結束return ADC_GetConversionValue(ADC_CHL); //傳回最近一次ADC1規則組的轉換結果
}//---------------------------------------------------------------------------------------------------------------------------------------------// 函 數 名: Get_Adc_Average// 功能說明: 轉換出 ADC 采集資料// 形 參: _ucChl:ADC 通道号// _ucTimes:采集次數// 返 回 值: 轉換資料// 備 注: // 日 期: 2020-03-11// 作 者: 霁風AI//---------------------------------------------------------------------------------------------------------------------------------------------uint16_t Get_AdcAverage(uint8_t _ucChl, uint8_t _ucTimes)
{uint32_t ulTmpVal = 0;uint8_t i = 0;for(i = 0; i {
ulTmpVal += Get_AdcConvertVal(_ucChl);
delay_ms(5);
}return ulTmpVal / _ucTimes;
}
結果測試:
//---------------------------------------------------------------------------------------------------------------------------------------------// 函 數 名: App_AdcTest// 功能說明: ADC單通道采樣測試// 形 參: 無// 返 回 值: 無// 備 注: // 日 期: 2020-03-11// 作 者: 霁風AI//---------------------------------------------------------------------------------------------------------------------------------------------void App_AdcTest(void){uint16_t usAdcVal = 0;float fTmpVal = 0.0;
usAdcVal = Get_AdcAverage(ADC_Channel_1, 10);//INPUT VOLTAGE = (ADC Value / ADC Resolution) * Reference Voltage
fTmpVal = (float)usAdcVal * (3.3 / 4096); //12位ADCprintf("voltage is %04fv.\r\n", fTmpVal);
delay_ms(250);
}
ADC 結果:
改變輸入電壓,ADC 采集的電壓對應發生改變。