【STM32F103筆記】8、數據採集之ADC——做個數字電壓表吧

咳咳,這一篇來玩一下STM32的ADC(Analog to Digital Converter),也就是可以把輸入的模擬量轉換爲數字量,這樣就可以做個電壓表了,再加上一些輔助電路,就能夠自己做一個萬用表了,非常完美。(嗯,這篇我們只做數字電壓表~就是這麼懶)

從這一篇開始,對STM32內部結構和寄存器的介紹會更加詳細一點,要開始深入瞭解了,感興趣的朋友還可以對照前幾篇自個兒深入瞭解一下,嘿嘿~ . ~

ADC介紹

打開STM32F103C8T6的數據手冊,第一頁就對其擁有的外設進行了簡單的列舉介紹,找到ADC的相關介紹:
在這裏插入圖片描述
從介紹中可以知道:

  • STM32F103C8T6有2個12位的A/D轉換器,支持16個通道,轉換時間最小可達1us;
  • A/D轉換電壓範圍是0到3.6V,這個非常重要,不要把超過範圍的電壓接入電壓採集引腳,輕則導致轉換結果超出範圍,讀數不準,嚴重可能會燒壞引腳;因此在使用ADC進行電壓轉換的時候,要根據採集的電壓範圍進行相應的分壓設計;
  • 2個ADC可以同步採集,並且可由定時器控制,還具有內部溫度傳感器;
  • 其它說明等用到的時候再看。

ADC內部結構

在STM32手冊中,找到ADC章節,其內部結構圖如下(看不清的話請點擊放大):
在這裏插入圖片描述
有幾個需要關注的部分:

  • 正中間就是STM32的A/D模塊的核心部分——ADC,模數轉換器;
  • 左側上面的紅框中:
    • VREF+與VREF-是ADC的參考電壓,這個電壓的要求是精度高且穩定,由於筆者的最小系統板用的是STM32F103C8T6,引腳數是48,而64引腳及以下的芯片VREF在內部連接到VDDA,僅有100腳以上的芯片纔會將VREF引到外部引腳;
    • VDDA和VSSA是ADC的供電電源,在筆者的最小系統板上通過電感電容組成LC濾波電路,與供電VDD連接,提供一個較爲穩定的3.3V電源:
      在這裏插入圖片描述
  • 左側下面的紅框中就是外部電壓的輸入引腳了(常規的輸入引腳),而VDD爲3.3V供電電壓,因此VREF電壓爲3.3V,即AD採樣轉換的電壓範圍爲0~3.3V,而ADC爲12位,故採樣結果 0 - 4095 對應電壓 0 - 3.3V;
  • 在右側的紅框中是ADC所使用的時鐘,在進入ADC之前有一個預分頻器,而在第二篇中的時鐘分析裏可知,ADC掛載在APB2總線上,因此APB2總線時鐘信號(72MHz)經過這個預分頻器最後進入ADC,提供時鐘信號,這裏注意進入ADC的時鐘不能超過14MHz,因此在72MHz的APB2總線時鐘下,預分頻係數只能設置爲8分頻或6分頻;
  • 在圖中還可以看出,ADC還可以用定時器進行控制,並可以觸發中斷,在這裏暫時不用這些功能。

ADC寄存器

瞭解了ADC的內部結構之後,就要開始對其寄存器進行分析,那麼就會問了,我們用的是庫開發,爲什麼還要去看寄存器呢;這裏簡單說明一下,庫開發是爲了方便程序的開發,也是建立在寄存器控制之上的,如果不瞭解相關的寄存器的話,可能會不知道用哪些庫函數,並且作爲單片機開發,瞭解其寄存器對後續程序移植等等都有很大的好處。當然,在剛開始看寄存器的時候難免會暈,熟悉了就好了(づ ̄ v ̄)づ

建議大家在開發單片機程序時,用這麼個流程:確定相關外設後,先看板子的電路圖,確定硬件電路的關係,然後在手冊中找對應的外設章節進行了解,然後查看相關寄存器的配置說明,並結合手冊中的功能說明進行分析,最後確定程序設計思路,然後就是寫程序,最後就到了最重要的Debug,也就是改bug……

1、ADC status register (ADC_SR)

ADC_SR爲ADC的狀態標誌寄存器,提供AD轉換中的一些標誌:
在這裏插入圖片描述

  • Bit 31:15:保留位,不需要進行設置,但是必須保持清0;
  • Bit 4 STRT:普通通道AD轉換開始標誌位,轉換開始後由硬件置1,結束後由軟件清0;
  • Bit 3 JSTRT:注入通道AD轉換開始標誌位,同上;
  • Bit 2 JEOC:注入通道AD轉換完成標誌位,轉換完成時硬件置1,由軟件清0;
  • Bit 1 EOC:通道AD轉換完成標誌位,轉換完成時硬件置1,由軟件清0;
  • Bit 0 AWD:模擬看門狗功能,當AD轉換值超過設定的電壓範圍時置1,由軟件清0;
    所謂由軟件清0就是在程序中要向該位寫入0。

這裏Bit 1 EOC可以用於判斷AD轉換是否完成,若完成就可以讀取AD轉換的數據了。

2、ADC control register 1 (ADC_CR1)

ADC_CR1是ADC的控制寄存器1,用於對ADC進行設置:
在這裏插入圖片描述

  • Bit 31:24:保留位,不需要進行設置,但是必須保持清0;
  • Bit 23 AWDEN:普通通道模擬看門狗使能,置1使能,清0禁用;
  • ……
  • Bit 19:16 DUALMOD[3:0]:設置2個ADC的使用方式:
    • 0000 - 獨立模式,2個ADC不同時工作;
  • ……
  • Bit 11 DISCEN:普通通道非連續模式設置,置1使能非連續模式,清0禁用;
  • Bit 8 SCAN:當需要採集多路電壓數據時,可以使用SCAN模式,掃描模式,置1使能,清0禁用,這裏我們不需要使用禁用就可以;
  • ……
  • Bit 5 EOCIE:轉換完成觸發中斷使能位,置1使能,清0禁用,這裏我們不需要使用禁用就可以;
  • ……

由於我們只進行一路ADC的採集,只需要用到一個ADC,因此設置成獨立模式即可。

3、ADC control register 2 (ADC_CR2)

ADC_CR2是ADC的控制寄存器2,用於對ADC進行設置:
在這裏插入圖片描述

  • Bit 31:24:保留位,不需要進行設置,但是必須保持清0;
  • Bit 23 TSVREFE:溫度傳感器使能;
  • Bit 22 SWSTART:普通通道轉換開始觸發位,置1則ADC開始轉換;
  • ……
  • Bit 11 ALIGN:數據對齊方式,置1左對齊,清0右對齊,爲了計算方便,這裏清0選擇右對齊;
  • Bit 3 RSTCAL:復位校準,軟件置1開始復位,硬件清0表示復位完成;
  • Bit 2 CAL:AD校準,軟件置1開始校準,硬件清0表示校準完成;
  • Bit 1 CONT:連續轉換模式,置1設置連續轉換,清0設置單次轉換;
  • Bit 0 ADON:置1使能並啓動AD轉換;

STM32的ADC模塊自帶內部校準功能,手冊建議是每次上電之後都進行一次校準,以保證AD結果的準確;並且設置AD數據右對齊(因此ADC爲12位,但ADC結果數據寄存器爲16位),這樣直接讀取就是AD轉換結果(不需要進行移位),方便計算;設置ADC爲連續採樣模式,這樣不需要每次都觸發AD轉換;

4、ADC sample time register 1 (ADC_SMPR1)

ADC_SMPR1是ADC的採樣週期設置寄存器:
在這裏插入圖片描述

  • Bit 23:0 SMPx[2:0]:通道X的採樣週期設置:

    • 000:1.5個週期
    • 001:7.5個週期
    • ……具體設置可以參閱手冊。

    總的採樣時間計算爲:
    Tconv=+12.5 T_{conv}=採樣週期+12.5個週期
    這裏我們可以根據需要進行設置。

5、ADC sample time register 2 (ADC_SMPR2)

同上

6、ADC regular sequence register 1 (ADC_SQR1)

ADC_SQR1與ADC_SQR2、ADC_SQR3都是用於在多通道採集時,設置通道採集順序的寄存器。這裏我們設置成1就好,因爲沒有多通道,不涉及順序。

7、ADC regular data register (ADC_DR)

ADC普通通道採樣數據寄存器:
在這裏插入圖片描述
可以看到,一個ADC功能雖然提供了很多寄存器(14個,上面沒涉及的沒有列出)進行設置,但是如果只考慮相關功能的寄存器,其實還是不多的,有了寄存器的基礎,再使用庫函數開發就更快了(當然也可能就拋棄庫開發轉向寄存器開發了,嘻嘻嘻)

程序設計

綜合上述寄存器說明,結合STM32F103C8T6引腳圖,可以選擇PA2(ADC_IN2)作爲ADC1採樣電壓輸入引腳:

  • 配置ADC1採樣的引腳;
  • 設置ADC1預分頻;
  • 設置ADC1爲獨立轉換模式,不使用掃描功能,使能連續轉換功能,並且設置轉換數據爲右對齊;
  • 使能ADC1,並且進行校準;
  • 最後開始轉換,並讀取AD採樣數據,通過串口進行顯示。

硬件連接

這裏筆者用了一個很久很久之前買的按鍵鍵盤作爲ADC電壓輸入,將電壓信號接到PA2引腳上:
在這裏插入圖片描述
這個小鍵盤通過電阻對供電電壓進行分壓,當按下不同按鍵時,其輸出端口OUT的電壓不同,筆者正好趁這個機會,把每個按鍵按下的輸出電壓測一下,後續可以用這個鍵盤配合其他小模塊寫更有意思的程序。

ADC初始化程序

根據上面寄存器分析記過,在官方庫中查找相應的庫函數,編寫ADC初始化程序,具體程序說明請看註釋:

void ADC1_PA2_Config(void)
{
	GPIO_InitTypeDef PA2InitStruct;
	// 同樣,官方庫爲ADC初始化提供了對應的結構體,可以先在幫助手冊中看看結構體的內容
	ADC_InitTypeDef ADC1InitStruct;
	
	// ADC1、GPIOA都掛載在APB2總線上,開啓它們的時鐘
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE);
	
	// 設置GPIOA2爲模擬輸入
	PA2InitStruct.GPIO_Pin = GPIO_Pin_2;
	PA2InitStruct.GPIO_Mode = GPIO_Mode_AIN;
	GPIO_Init(GPIOA, &PA2InitStruct);
	
	// ADC1設置爲獨立模式
	ADC1InitStruct.ADC_Mode = ADC_Mode_Independent;
	// 不使用掃描模式(就一個通道)
	ADC1InitStruct.ADC_ScanConvMode = DISABLE;
	// 設置爲連續轉換模式
	ADC1InitStruct.ADC_ContinuousConvMode = ENABLE;
	// 不使用外部觸發ADC採樣轉換
	ADC1InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
	// 設置AD轉換數據右對齊
	ADC1InitStruct.ADC_DataAlign = ADC_DataAlign_Right;
	// 一個通道-PA2對應ADC_IN2
	ADC1InitStruct.ADC_NbrOfChannel = 1;
	// 調用ADC_Init函數進行初始化,這裏其實主要設置的是ADC_CR1和ADC_CR2寄存器,可以去函數定義裏看看,體會一下
	ADC_Init(ADC1, &ADC1InitStruct);
	
	// 設置ADC1的預分頻係數,APB2時鐘信號爲72MHz,6分頻,即12MHz
	// 這裏是設置RCC的RCC_CFGR寄存器的Bit 15:14,即ADC預分頻器
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);
	
	// 設置ADC1的通道2(對應PA2),轉換順序設置爲1(就一個通道)
	// 採樣週期設置爲41.5個,這樣總的轉換時間就是54個時鐘週期,爲4.5us
	ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 1, ADC_SampleTime_41Cycles5);
	
	// 使能ADC1,即設置ADC_CR2寄存器Bit0 ADON爲1
	ADC_Cmd(ADC1, ENABLE);
	
	// 復位校準,即將ADC_CR2寄存器Bit3 RSTCAL置1
	ADC_ResetCalibration(ADC1);
	// 等待復位校準完成,即不斷讀取ADC_CR2寄存器Bit3 RSTCAL,若爲0則表示已完成
	while(ADC_GetResetCalibrationStatus(ADC1));
	// 開始校準,即將ADC_CR2寄存器Bit2 CAL置1
	ADC_StartCalibration(ADC1);
	// 同樣等待校準完成,即不斷讀取ADC_CR2寄存器Bit2 CAL,若爲0則表示已完成
	while(ADC_GetResetCalibrationStatus(ADC1));
	
	// 由於沒有設置ADC外部觸發,所以這裏需要軟件觸發一次,這樣在連續模式下,ADC就會一直採用轉換了
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}

其他程序說明

這裏還使用了前幾篇的delay與usart相關程序,筆者推薦將其放在單獨的文件夾中,幷包含進工程,這樣不同外設的程序比較整齊,也方便後續擴展:
在這裏插入圖片描述

完整程序

這裏只給出main.c中程序,其它程序請在博客的其它文章中參考:

#include "stm32f10x.h"
#include "usart.h"
#include "delay.h"

void ADC1_PA2_Config(void);

int main(void)
{
	uint16_t adcResult;
	double vol;
	
	USART1Config();
	ADC1_PA2_Config();
	
	printf("Hello\r\n");
	printf("Getting ADC1 ch2:\r\n");
	
	while(1)
	{
		while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);
		adcResult = ADC_GetConversionValue(ADC1);
		vol = (double)adcResult / 4096.0 * 3.3;
		printf("%d - %lf \r\n", adcResult, vol);
		delay_ms(500);
	}
	
}

void ADC1_PA2_Config(void)
{
	GPIO_InitTypeDef PA2InitStruct;
	ADC_InitTypeDef ADC1InitStruct;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE);
	
	PA2InitStruct.GPIO_Pin = GPIO_Pin_2;
	PA2InitStruct.GPIO_Mode = GPIO_Mode_AIN;
	GPIO_Init(GPIOA, &PA2InitStruct);
	
	ADC1InitStruct.ADC_Mode = ADC_Mode_Independent;
	ADC1InitStruct.ADC_ScanConvMode = DISABLE;
	ADC1InitStruct.ADC_ContinuousConvMode = ENABLE;
	ADC1InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
	ADC1InitStruct.ADC_DataAlign = ADC_DataAlign_Right;
	ADC1InitStruct.ADC_NbrOfChannel = 1;
	ADC_Init(ADC1, &ADC1InitStruct);
	
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);
	
	ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 1, ADC_SampleTime_41Cycles5);
	
	ADC_Cmd(ADC1, ENABLE);
	
	ADC_ResetCalibration(ADC1);
	while(ADC_GetResetCalibrationStatus(ADC1));
	ADC_StartCalibration(ADC1);
	while(ADC_GetResetCalibrationStatus(ADC1));
	
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}

運行結果

將程序編譯下載運行:
在這裏插入圖片描述

完結撒花✿✿ヽ(°▽°)ノ✿

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章