天天看點

STM32使用ADC擷取内部溫度傳感器資料輸出(直接讀取/DMA兩種方式實作)前言一、内部溫度傳感器的使用?二、代碼操作講解總結

STM32使用ADC擷取内部溫度傳感器資料輸出(直接讀取/DMA兩種方式實作)

  • 前言
  • 一、内部溫度傳感器的使用?
  • 二、代碼操作講解
    • 1.直接讀取
    • 2.DMA處理
  • 總結

前言

STM32F1系列(本代碼基于STM32F103C8T6晶片)MCU内置了一個溫度傳感器,供ADC_1的第16通道讀取,它并非精确的溫度計量會有實際性誤差。本着對ADC功能的學習與了解,以下内容講解将使用兩種方式讀取資料(直接擷取/DMA方式兩種,具體差異後面會說明)并用序列槽列印,提供工程檔案,希望對初學者有着一定幫助。

PS:内容均為原創,轉載需擷取作者本人同意,如有侵權可聯系删除。若對内容有疑問,歡迎指正與交流,由于最近即将處于研究所學生階段有點忙碌,但盡力及時回複大家問題。

一、内部溫度傳感器的使用?

STM32晶片内部有一個溫度傳感器,已接入ADC1第16通道,它的測量範圍為-40~125度。精度比較差,為±1.5℃左右。我們接下來的目的就使用ADC讀取它的資料 ~

首先我們了解一下ADC(不是AD Carry~而是Analog-to-digital converter),大概就是将模拟信号轉化為數字信号。常用的ADC有積分型、逐次比較型、并行比較型/串并行比較型、Σ-Δ調制型、電容陣列逐次比較型、壓頻變換型等(具體不做詳細介紹,知道就好)。

我們密切關注的ADC的技術名額就兩個(其他的目前不用急):

精度:反映轉換器的實際輸出接近理想輸出的精确程度的實體量。

分辨率(Resolution): 指數字量變化一個最小量時模拟信号的變化量,定義為滿刻度與2n的比值。分辨率又稱精度,通常以數字信号的位數來表示。

一般把8位以下的A/D轉換器稱為低分辨率ADC,9~12位稱為中分辨率ADC,13位以上為高分辨率。A/D器件的位數越高,分辨率越高,量化誤差越小,能達到的精度越高。它的效果工作如下:

STM32使用ADC擷取内部溫度傳感器資料輸出(直接讀取/DMA兩種方式實作)前言一、内部溫度傳感器的使用?二、代碼操作講解總結

紅色代表電壓信号,根據一定時間周期采樣(不失真應該滿足采樣定理,這裡不做詳細介紹,具體可百度)為離散的電壓信号,這樣當間距的離散電壓時間足夠小(不準确但是可以這樣了解),那麼就足夠還原精度一定的紅色模拟信号,則會産生大量之資料。

STM32f103系列ADC為逐次逼近型,總共有3個ADC,精度為12位,其中ADC1和ADC2都有16個外部通道, 2個内部通道,ADC3一般有8個外部通道。ADC的輸入時鐘不得超過14MHz,其時鐘頻率由PCLK2分頻産生。是以我們隻關注如何配置16通道可用就好。

STM32使用ADC擷取内部溫度傳感器資料輸出(直接讀取/DMA兩種方式實作)前言一、内部溫度傳感器的使用?二、代碼操作講解總結

還有一個是我們需要關注的,就是它的轉換時間。我們可以配置它的采樣時間,轉換時間 = 采樣時間 + 12.5個周期(固定時間)。如在14MHz和采樣時間為1.5周期,則轉換時間:

TCONV = 1.5 + 12.5 = 14周期 = 14×(1 / (14 × 1000000)) = 1us
           
STM32使用ADC擷取内部溫度傳感器資料輸出(直接讀取/DMA兩種方式實作)前言一、内部溫度傳感器的使用?二、代碼操作講解總結

最後就關注它的轉化模式:

1.單次轉換模式:ADC隻執行一次轉換,然後停止。

2.連續轉換模式:目前面ADC轉換一結束,馬上啟動另一次轉換。

3.掃描模式:掃描模式用來掃描一組模拟通道。在每個組的每個通道上執行單次轉換,在每個轉換結束時,同組的下一個通道開始轉換。

在配置的時候詳細注解!

由于ADC還有注入規則通道,這裡沒有涉及就不多贅述,最後得到的資料,根據參考手冊的進行處理,大緻如下:

STM32使用ADC擷取内部溫度傳感器資料輸出(直接讀取/DMA兩種方式實作)前言一、内部溫度傳感器的使用?二、代碼操作講解總結

具體參數在代碼段裡說明!

是以,我們歸納出配置的流程如下:

ADC轉換步驟如下:

1.開啟GPIO時鐘,設定Pinx 為模拟輸入。(由于是内部的溫度傳感器,是以不用開)

2 .使能ADC1 時鐘,并設定分頻因子:要使用ADC1,第一步要使能 ADC1 的時鐘。再設定ADC1 的分頻因子。分頻因子要確定 ADC1 的時鐘(ADCCLK)不要超過14Mhz 。

3 .設定ADC1 的工作模式:設定單次轉換模式、觸發方式選擇、資料對齊方式等都在這一步實作。

4 .設定ADC1 規則序列的相關資訊:如隻有一個通道,設定規則序列中通道數為1 ,然後設定通道的采樣周期。

5 .開啟AD轉換器,并校準(必須校準否則不準确):開啟AD轉換器,執行複位校準和AD校準。

6.讀取ADC值校準完成後,ADC就算準備好了。啟動ADC轉換,在轉換結束後,讀取ADC1_DR 裡面的值。

了解了這些預備知識,下面,我們就可以開始對它進行操作了(前往不要絕對上面麻煩,不然就算實作了,也雲裡霧裡不是嘛)。

二、代碼操作講解

在講解代碼之前,有必要讓大家知道為什麼使用兩種方式來操作(畢竟要以學到東西為主嘛),我們知道,MCU與外界通信基本上為這三種方式——

1:輪詢

2:中斷

3:DMA

三者有什麼差別呢(如果懂直接跳過進入正題),在這言簡意赅但不絕對術語化的說明一下,其實學習單片機的朋友都應該熟悉前兩者,初學者卻很少使用用後者DMA,那今天就弄懂一下它吧。

輪詢咱們經常使用的呀,使用if/case等語句,加個while循環連續讀取,一直就問問CPU核心,給廚師(外設)給我做的菜做的怎麼樣了呀,給我看看咯。然後CPU就一直忙忙忙這個事情~

輪詢大概語句長這樣:

while(1){
if(成立條件1){幹成立條件1幹的事情;}
else if(成立條件2){幹成立條件2幹的事情;}
else if(成立條件3){幹成立條件3幹的事情;}
......//一直尋找适合的條件
delay_ms(100);
}
           

中斷呢就是單片機之精華所在,基本上以後工程許多案例都是中斷來通路外設。大概就像是廚師在工作,然後有個按鈴,做好了就叮~ ~ ~ 的一聲告訴CPU,這個時候CPU才可以屁颠屁颠的跑過去給我們端菜啦,不用像輪詢方式一直去問候他。

DMA其實手段跟中斷差多,不過它的存在就是給CPU分擔壓力的,當處理資料過快過多到來的時候,CPU可能比較繁忙,這個時候就可能出現資料沒有收集到或者處理太慢,那麼DMA就可以幫助CPU來收集資料,由于DMA挂靠在總線上,可以直接代替CPU與外設親密交流,這樣就可以出現這樣的情況,外面的廚師太多了,一下子就有很多的菜出鍋,DMA這個小弟先幫忙分揀,整理好,跑個腿,CPU就可以省去很多很多的事情來做其他有意義的事情,嘿嘿嘿~(當然DMA也可以給出中斷信号)

綜上呢,我們了解了,輪詢就一直幹事情簡單明了友善,但是占用cpu資源較多;中斷呢就不會占用太多資源,CPU可以該幹嘛幹嘛,需要它的時候叫叫它;DMA呢就是當上面兩種方式的資料大于CPU所能處理負載了,或者為了減輕CPU負擔而存在的功能,大大降低CPU負擔,但配置起來相對麻煩。

是以如果ADC處理多路輸入,這一龐大資料來臨時,前面兩種方法好像招架不住了,是以DMA才是真正的應對手段;而當ADC處理的資料就這一路或者很少(比如本次的溫度擷取,隻需要一個内部通道,資料少,丢了就丢了嘛),随便怎麼用都可以。

(關于他們的專業解釋可以可以百度,要是不太了解,我可以專門寫一篇文章講解他們的差別以及内在聯系)

1.直接讀取

首先配置ADC相關參數結構體:

ADC_DeInit(ADC1);//重新來配置ADC1
	ADC_TempSensorVrefintCmd(ENABLE);//傳感器這玩意必須打開,否則必定沒資料
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);   //六分頻72M/6=12M
    ADC_InitTypeDef ADCInitStruct;
	ADCInitStruct.ADC_Mode = ADC_Mode_Independent;	//設定獨立模式
	ADCInitStruct.ADC_ScanConvMode = DISABLE;		//不開掃描
    ADCInitStruct.ADC_ContinuousConvMode = DISABLE;	//不循環(其實循環不循環不重要,反正就一個通道)
    ADCInitStruct.ADC_DataAlign = ADC_DataAlign_Right;//右對齊
    ADCInitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//軟體觸發,不用硬體觸發
    ADCInitStruct.ADC_NbrOfChannel = 1;//順序轉化的規則通道數目
    ADC_Init(ADC1,&ADCInitStruct);
           

然後需要校準:

ADC_ResetCalibration(ADC1);			//初始化校準
    while(ADC_GetResetCalibrationStatus(ADC1));
    ADC_StartCalibration(ADC1);			//開始校準
    while(ADC_GetCalibrationStatus(ADC1));
	ADC_SoftwareStartConvCmd(ADC1,ENABLE);//軟體觸發開始
           

然後可以開始進行讀取資料:

通過簡單的函數ADC_GetConversionValue(ADC1)來擷取即可。

ADC_RegularChannelConfig(ADC1,ADC_Channel_16,1,ADC_SampleTime_239Cycles5);
    while(!ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC));
    return ADC_GetConversionValue(ADC1);

           

因為讀出的值會有變化,我們可以取多次的平均值,一半都是取5~20次的平均值,在這裡就直接使用20次的for循環求平均值

{
 	u8 i;
    u32 averagedata = 0;
    for(i = 0;i < 20;i++)			//利用for循環讀取20次的值
    {
        averagedata += ADC1_GetConvValue();	//這裡就是每次讀取的值
			delay_ms (5);
    }
    return averagedata/20;
}

           

擷取到數值以後,我們來進行資料處理,即得到最後的溫度結果

void GetTemperature(void)
{
    double VSense = (double)ADC1_GetAverageConvValue()*(3.3/4096.0);
    printf("VSense:%.2f; %.2f\r\n",VSense,((1.43 - VSense)/0.0043+25.0));
    //這個計算是涉及到浮點運算耗時間,可以擴大了計算更好,在這裡就這樣寫吧也沒問題也可便于了解

}
		
           

最後在主程式裡面循環它就OK,大緻如下:

while(1){
		GetTemperature();
		delay_ms(500);
	}//這是不是很像我們的輪詢方式呢~
           

最後通過序列槽看看我們的成果~

STM32使用ADC擷取内部溫度傳感器資料輸出(直接讀取/DMA兩種方式實作)前言一、内部溫度傳感器的使用?二、代碼操作講解總結

當手指按住它,則溫度升高,前面資料為保留兩位資料的ADC讀取電壓,後面則為擷取到的溫度值。

2.DMA處理

STM32使用ADC擷取内部溫度傳感器資料輸出(直接讀取/DMA兩種方式實作)前言一、内部溫度傳感器的使用?二、代碼操作講解總結

這裡我們就用到了DMA1的通道一,在表中可以檢視到,是以我們直接開始配置DMA就可。

代碼如下(示例):

void MYDMA_Config(void)
{
	
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);	//使能DMA時鐘

		DMA_DeInit(DMA1_Channel1);//重設DMA為預設值	
		DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&ADC1->DR;//外設位址
		DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&SendBuff1;//存儲器位址
		DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //外設到存儲器的傳輸模式
		DMA_InitStructure.DMA_BufferSize = 1; //資料量為1
		DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
		DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable; //
		DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord ; //16位!!!
		DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord ; //16位!!!
		DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; //循環模式
		DMA_InitStructure.DMA_Priority = DMA_Priority_High; //優先級高
		DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //(記憶體到記憶體禁止)
 
		DMA_Init(DMA_CHx,&DMA_InitStructure); //初始化
		DMA_SetCurrDataCounter(DMA1_Channel1,cndtr);//設定資料量
   		DMA_Cmd(DMA1_Channel1, ENABLE);                                       
}

           

同時,我們還需要開啟ADC的DMAcmd使能才能讓DMA接管CPU的任務。

這樣我們就可直接讀取SendBuff1的值就知道啦~

當然我們這裡也取了20次平均

u16 ADC1_GetAverageConvValue(void)
{
		u32 temp_val=0;
		u8 t;
		for(t=0;t<20;t++)
		{
				temp_val+=SendBuff1;
				delay_ms(5);
		}
		return temp_val/20;
} 	
	
           

然後在主程式中直接輸出電壓,溫度即可。

循環讀取:(因為cpu沒有其他事情做,就讓他反複讀取這個值吧)
	adcx = ADC1_GetAverageConvValue();//這個函數相比起直接擷取而言,大大降低了CPU的計算負擔,大部分的資料傳輸任務都交給了DMA
	temp=(float)adcx*((float)3.3/4096);
	printf("(DMA)temperature:%.2f; %.2f\r\n",temp,((1.43 - temp)/0.0043+25.0));
	delay_ms(500);
           

最後的結果

STM32使用ADC擷取内部溫度傳感器資料輸出(直接讀取/DMA兩種方式實作)前言一、内部溫度傳感器的使用?二、代碼操作講解總結

基本上完成任務

總結

到此為止,已經示範了兩種手段的處理思路,我們也發現了差別還是較大,使用了DMA外設(如果還不了解DMA,可以參考一下stm32的參考手冊,上面寫的相對詳盡)。

對CPU資源占用的,輪詢方式占用較大,中斷其次,DMA最優。各有各的使用手段,在不同情況下調用不同的外設是嵌入式工程師必備的能力,希望對大家有所幫助,我也将把兩份工程檔案上傳,供有需要的朋友學習。

工程檔案(DMA擷取溫度方式):https://download.csdn.net/download/qq_40249327/13944700

工程檔案(直接擷取溫度方式):

https://download.csdn.net/download/qq_40249327/13944690

碼字不易,有問題可以提出交流,希望我們能在這條路走得更遠,與君共勉!