天天看點

cortex_m3_stm32嵌入式學習筆記(十六):ADC實驗(模數轉換)

之前沒學過數模電,對A/D D/A轉換一竅不通,也百度了很多資料大都深奧難懂。。算了,先自以為是一下吧,等以後學了專業課再說。。(寒假回家一定要學。。恩 就這麼決定了)看了那麼多資料,感覺 A/D轉換就是将電壓(或者是其他模拟量:如 壓力,圖像等)轉換為數字,D/A就是反過來,而ADC就是A/D轉換器,他可以采集外部電壓轉化為數字。本節實驗通過ADC采集外部電壓轉換為數字顯示在螢幕上。

STM32 擁有 1~3 個 ADC( STM32F101/102 系列隻有 1 個 ADC),這些 ADC 可以獨立使用,也可以使用雙重模式(提高采樣率)。 STM32 的 ADC 是 12 位逐次逼近型的模拟數字轉換器。它有 18 個通道,可測量 16 個外部和 2 個内部信号源。各通道的 A/D 轉換可以單次、連續、掃描或間斷模式執行。 ADC 的結果可以左對齊或右對齊方式存儲在 16 位資料寄存器中。 模拟看門狗特性允許應用程式檢測輸入電壓是否超出使用者定義的高/低閥值。

STM32 将 ADC 的轉換分為 2 個通道組:規則通道組和注入通道組。規則通道相當于你正常運作的程式,而注入通道呢,就相當于中斷。在你程式正常執行的時候,中斷是可以打斷你的執行的。同這個類似,注入通道的轉換可以打斷規則通道的轉換, 在注入通道被轉換完成之後,規則通道才得以繼續轉換。

通過一個形象的例子可以說明: 假如你在家裡的院子内放了 5 個溫度探頭,室内放了3個溫度探頭; 你需要時刻監視室外溫度即可,但偶爾你想看看室内的溫度;是以你可以使用規則通道組循環掃描室外的 5 個探頭并顯示 AD 轉換結果,當你想看室内溫度時,通過一個按鈕啟動注入轉換組(3 個室内探頭)并暫時顯示室内溫度,當你放開這個按鈕後,系統又會回到規則通道組繼續檢測室外溫度。從系統設計上,測量并顯示室内溫度的過程中斷了測量并顯示室外溫度的過程,但程式設計上可以在初始化階段分别設定好不同的轉換組,系統運作中不必再變更循環轉換的配置,進而達到兩個任務互不幹擾和快速切換的結果。可以設想一下,如果沒有規則組和注入組的劃分,當你按下按鈕後,需要從新配置 AD 循環掃描的通道,然後在釋放按鈕後需再次配置 AD 循環掃描的通道。

但本節隻用到規則通道,因為是單次轉換模式。。大概可以了解為我們現在隻測量一個地方的電壓值。。

配置ADC步驟如下:

1) 開啟 PA 口和 ADC1 時鐘,設定 PA1 為模拟輸入。

STM32F103RCT6 的 ADC 通道 1 在 PA1 上,是以,我們先要使能 PORTA 的時鐘,然後設定 PA1 為模拟輸入。 使能 GPIOA 和 ADC 時鐘用 RCC_APB2PeriphClockCmd 函數,設定 PA1的輸入方式,使用 GPIO_Init 函數即可。這裡我們列出 STM32 的 ADC 通道與 GPIO 對應表:

cortex_m3_stm32嵌入式學習筆記(十六):ADC實驗(模數轉換)

2)複位 ADC1,同時設定 ADC1 分頻因子。

3)初始化 ADC1 參數,設定 ADC1 的工作模式以及規則序列的相關資訊。

4)使能 ADC 并校準。

5)讀取 ADC 值。

配置ADC的檔案adc.c

#include "adc.h"
void Adc_Init(void)
{
	ADC_InitTypeDef ADC_ist;
	GPIO_InitTypeDef GPIO_ist;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_ADC1,ENABLE );
	//72M/6=12,ADC 最大時間不能超過 14M
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);//設定 ADC分頻因子6
	//PA1 作為模拟通道輸入引腳
	GPIO_ist.GPIO_Pin=GPIO_Pin_1;
	GPIO_ist.GPIO_Mode=GPIO_Mode_AIN;//模拟輸入
	GPIO_Init(GPIOA,&GPIO_ist);
	
	ADC_DeInit(ADC1);//複位 ADC1,将外設 ADC1 的全部寄存器重設為預設值
	ADC_ist.ADC_Mode= ADC_Mode_Independent;//ADC 獨立模式
	ADC_ist.ADC_ScanConvMode=DISABLE;//單通道模式
	ADC_ist.ADC_ContinuousConvMode=DISABLE;//單次轉換模式
	//轉換由軟體而不是外部觸發啟動
	ADC_ist.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None;
	ADC_ist.ADC_DataAlign=ADC_DataAlign_Right;//ADC 資料右對齊
	ADC_ist.ADC_NbrOfChannel=1;//順序進行規則轉換的 ADC 通道的數目
	ADC_Init(ADC1,&ADC_ist);
	
	ADC_Cmd(ADC1,ENABLE);//使能指定的 ADC1
	ADC_ResetCalibration(ADC1);//開啟複位校準
	while(ADC_GetResetCalibrationStatus(ADC1));//等待複位校準結束
	ADC_StartCalibration(ADC1);//開啟 AD 校準
	while(ADC_GetCalibrationStatus(ADC1));//等待校準結束
}
//獲得 ADC 值
//ch:通道值 0~3
u16 Get_Adc(u8 ch)
{
	//設定指定 ADC 的規則組通道設定它們的轉化順序和采樣時間
	ADC_RegularChannelConfig(ADC1,ch,1,ADC_SampleTime_239Cycles5); 
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);//使能指定的 ADC1 的軟體轉換功能
	while(!ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC));//等待轉換結束
	return ADC_GetConversionValue(ADC1);//傳回最近一次 ADC1 規則組的轉換結果
}
u16 Get_Adc_Average(u8 ch,u8 times)
{
	u32 tem_val=0;
	u8 i;
	for(i=0;i<times;i++)
	{
		tem_val+=Get_Adc(ch);
		delay_ms(5);
	}
	return tem_val/times;
}
           

adc.h

#ifndef _ADC_H
#define _ADC_H
#include "sys.h"
#include "delay.h"
void Adc_Init(void);
u16 Get_Adc(u8 ch);
u16 Get_Adc_Average(u8 ch,u8 times);
#endif
           

主函數

#include "led.h"
#include "delay.h"
#include "sys.h"
#include "usart.h"
#include "lcd.h"
#include "adc.h"
void init()
{
	delay_init();	    	 //延時函數初始化	  
	uart_init(9600);	 	//序列槽初始化為9600
	LED_Init();		  		//初始化與LED連接配接的硬體接口
 	LCD_Init();
	Adc_Init();
	POINT_COLOR=RED;//設定字型為紅色
	LCD_ShowString(60,40,200,24,24,"ADC Test ^-^");
	LCD_ShowString(60,70,200,16,16,"Medium difficulty");
	LCD_ShowString(60,90,200,16,16,"2015/1/24");
	LCD_ShowString(60,110,200,16,16,"By--Mr yh");
	//顯示提示資訊
	POINT_COLOR=BLUE;//設定字型為藍色
	LCD_ShowString(60,130,200,16,16,"ADC_CH0_VAL:"); 
	LCD_ShowString(60,150,200,16,16,"ADC_CH0_VOL:0.000V");
}
int main(void)
{
	u16 adcnum;
	float tem;
	init();
	while(1)
	{
		adcnum=Get_Adc_Average(ADC_Channel_1,10);
		LCD_ShowxNum(156,130,adcnum,4,16,0);//顯示ADC的值
		tem=(float)adcnum*(3.3/4096);
		adcnum=tem;
		LCD_ShowxNum(156,150,adcnum,1,16,0);//顯示電壓值的整數位
		tem-=adcnum;
		tem*=1000;
		LCD_ShowxNum(172,150,tem,3,16,0x80);//顯示ADC的值的小數位
		LED0=!LED0;
		delay_ms(250);
	}
}
           

獲得了ADC的值之後。。 再轉換成電壓值的公式就看不懂了。。orz

不過有一個地方需要注意 LCD_ShowxNum()的用法

再次翻出它的源碼

//顯示數字,高位為0,還是顯示
//x,y:起點坐标
//num:數值(0~999999999);	 
//len:長度(即要顯示的位數)
//size:字型大小
//mode:
//[7]:0,不填充;1,填充0.
//[6:1]:保留
//[0]:0,非疊加顯示;1,疊加顯示.
void LCD_ShowxNum(u16 x,u16 y,u32 num,u8 len,u8 size,u8 mode)
{  
	u8 t,temp;
	u8 enshow=0;						   
	for(t=0;t<len;t++)
	{
		temp=(num/LCD_Pow(10,len-t-1))%10;
		if(enshow==0&&t<(len-1))
		{
			if(temp==0)
			{
				if(mode&0X80)LCD_ShowChar(x+(size/2)*t,y,'0',size,mode&0X01);  
				else LCD_ShowChar(x+(size/2)*t,y,' ',size,mode&0X01);  
 				continue;
			}else enshow=1; 
		 	 
		}
	 	LCD_ShowChar(x+(size/2)*t,y,temp+'0',size,mode&0X01); 
	}
} 
           

看到最後一個參數的說明, mode 是一個8位的變量,第7位為0代表不填充,1代表填充。

一開始對填充這個概念沒什麼了解,于是将兩種結果(填充和不填充)燒進去看了一下,發現顯示0.001 的時候,如果選不填充,它會顯示0.  1(點和1之間有2個空格),如果是填充就會顯示0.001 (正常顯示)是以我對填充的了解是:假如一個數6, 你想顯示006,那麼需要設定數的長度為3,填充模式