?導(dǎo)讀:《藍(lán)橋杯嵌入式組》專(zhuān)欄文章是博主2019年參加藍(lán)橋杯的嵌入式組比賽所做的學(xué)習(xí)筆記,在當(dāng)年的比賽中,由于忙于準(zhǔn)備考研及保研相關(guān)工作,博主僅僅參加了當(dāng)年的省賽,并獲得了省賽一等獎(jiǎng)的成績(jī)。成績(jī)雖談不上最好,但至少問(wèn)心無(wú)愧。如今2021年回頭再看該系列文章,仍然感觸頗多。為了能更好地幫助到單片機(jī)初學(xué)者,今年特地抽出時(shí)間對(duì)當(dāng)年的文章邏輯和結(jié)構(gòu)進(jìn)行重構(gòu),以達(dá)到初學(xué)者快速上手的目的。需要指出的是,由于本人水平有限,如有錯(cuò)誤還請(qǐng)讀者指出,非常感謝。那么,接下來(lái)讓我們一起開(kāi)始愉快的學(xué)習(xí)吧。
“一葉遮目,不見(jiàn)泰山”。不論何事,只有把握事情的總體趨勢(shì),才能做到心中有數(shù)。
1個(gè)留待補(bǔ)充的程序:
“為了確保不產(chǎn)生丟失數(shù)據(jù)的情況,開(kāi)啟 DMA 傳輸模式,將數(shù)據(jù)傳輸?shù)絻?nèi)存中”嘗試編程
一、ADC入門(mén)
1、基礎(chǔ)知識(shí)
模數(shù)轉(zhuǎn)換器(Analog To Digital Converter)簡(jiǎn)稱(chēng) ADC(也可以寫(xiě)成 A/D),是指將連續(xù)變化的模擬信號(hào)轉(zhuǎn)換為離散的數(shù)字信號(hào)的器件。 ADC 分為積分型、逐次逼近型、并行/串行比較型、Σ-Δ型等多種類(lèi)型,STM32F103 自帶的 ADC 屬于逐次逼近型。
逐次逼近型 ADC 與天平稱(chēng)物重非常相似,從高位到低位逐位比較。首先從最重的砝碼開(kāi)始試放,與被稱(chēng)物體行進(jìn)比較,若物體重于砝碼,則該砝碼保留,否則移去,然后用次重砝碼繼續(xù)比較,照此一直到最小一個(gè)砝碼為止,將所有留下的砝碼重量相加,就得此物體的重量。逐次逼近型 A/D 轉(zhuǎn)換器,就是將輸入模擬信號(hào)與不同的參考電壓作多次比較,使轉(zhuǎn)換所得的數(shù)字量在數(shù)值上逐次逼近輸入模擬量對(duì)應(yīng)值。
2、STM32F103RBT6的ADC時(shí)鐘及轉(zhuǎn)換時(shí)間
藍(lán)橋板載STM32F103RBT6擁有2路12位(0~4096)ADC,ADC掛載在A(yíng)PB2總線(xiàn)上,且ADC最大時(shí)鐘不超過(guò)14MHz。所以當(dāng)APB2總線(xiàn)設(shè)置為72M,有必要對(duì)其分頻再用于A(yíng)DC,分頻用到的庫(kù)函數(shù)是:void ADC_ADCCLKConfig(u32 RCC_ADCCLKSource);
ADC的轉(zhuǎn)換時(shí)間 = 采樣時(shí)間 + 12.5周期
采樣時(shí)間和實(shí)際電路有著莫大的關(guān)系,但是對(duì)于藍(lán)橋的板子而言,對(duì)于轉(zhuǎn)換速度沒(méi)有太大要求我們一般設(shè)置為:ADC_SampleTime_239Cycles5
239.5個(gè)周期即可。
3、ADC輸入通道
注意到了兩個(gè)ADC模塊,共用了一些通道,不同ADC應(yīng)用不同通道時(shí),可以同時(shí)進(jìn)行采樣和轉(zhuǎn)換,但不可以對(duì)相同通道同時(shí)采樣。
板子對(duì)應(yīng)ADC部分原理圖
PB0管腳可復(fù)用為ADC_IN8
ADC1和ADC2兩個(gè)通道的都?xì)W克,所以本博客選用了ADC1。
4、ADC的注入組和規(guī)則組
在使用 ADC 外部通道時(shí),可以設(shè)定為規(guī)則組和注入組。規(guī)則組就是設(shè)定好轉(zhuǎn)換順序后,按照規(guī)則正常轉(zhuǎn)換;注入組類(lèi)似于中斷,可以插隊(duì),當(dāng)觸發(fā)信號(hào)觸發(fā)注入組通道時(shí),優(yōu)先轉(zhuǎn)換注入組,轉(zhuǎn)換完后再繼續(xù)轉(zhuǎn)換規(guī)則組;如果正在轉(zhuǎn)換規(guī)則通道期間,注入通道被觸發(fā),當(dāng)前規(guī)則組轉(zhuǎn)換被復(fù)位,注入通道序列被以單次掃描方式轉(zhuǎn)換,完成轉(zhuǎn)換后恢復(fù)上次被中斷的規(guī)則通道轉(zhuǎn)換。規(guī)則組最多可以使用 16 個(gè)通道,注入組最多可以使用 4 個(gè)通道。
藍(lán)橋板子也沒(méi)有用到多么復(fù)雜,我們就直接使用規(guī)則組就好了。
5、ADC的單通道和多通道
當(dāng)“轉(zhuǎn)換組”只有一個(gè)通道轉(zhuǎn)換時(shí)稱(chēng)之為單通道模式,當(dāng)有多個(gè)通道按順序轉(zhuǎn)換時(shí)稱(chēng)之為多通道模式或者掃描模式。
藍(lán)橋板子,使用單通道即可。
6、ADC的單次轉(zhuǎn)換和連續(xù)轉(zhuǎn)換
當(dāng)規(guī)則組或注入組的通道按照設(shè)定順序執(zhí)行一次采轉(zhuǎn)換后即停止工作,這種模式稱(chēng)之為單次轉(zhuǎn)換模式;如果執(zhí)行完一次轉(zhuǎn)換后,ADC 沒(méi)有停止,而是立即啟動(dòng)新一輪轉(zhuǎn)換,這種模式稱(chēng)之為連續(xù)轉(zhuǎn)換模式。
藍(lán)橋板子,使用單次轉(zhuǎn)換即可。
7、觸發(fā)源
觸發(fā)啟動(dòng)轉(zhuǎn)換:觸發(fā)啟動(dòng)轉(zhuǎn)換分為兩種方式,分別是軟件觸發(fā)和外部事件觸發(fā)(外部是相對(duì)于 ADC 外設(shè)來(lái)講),其中外部事件觸發(fā)又分為定時(shí)器觸發(fā)和外部觸發(fā)(這里的外部指的是芯片外部信號(hào))。
藍(lán)橋板子,使用軟件觸發(fā)即可
8、轉(zhuǎn)換后的數(shù)據(jù)儲(chǔ)存
①、規(guī)則組存儲(chǔ)
在獨(dú)立 ADC 模式下,規(guī)則組通道轉(zhuǎn)換完成后,轉(zhuǎn)換后的數(shù)據(jù)被存儲(chǔ)在對(duì)應(yīng)的 ADC 規(guī)則數(shù)據(jù)寄存器 ADC_DR 的低 16 位。
雙 ADC 模式是 ADC1 和 ADC2 同時(shí)采集某些參數(shù),比如要獲取瞬時(shí)功率需要同時(shí)采集電壓和電流參數(shù)才能準(zhǔn)確計(jì)算結(jié)果,這種場(chǎng)合就必須使用雙 ADC 模式。這種模式下 ADC1所對(duì)應(yīng)的 ADC_DR 的高 16 位存儲(chǔ) ADC2 的規(guī)則數(shù)據(jù),低 16 位存儲(chǔ) ADC1 的規(guī)則數(shù)據(jù)。
由于 ADC 的精度是 12 位,因此在將 ADC 的 12 位數(shù)據(jù)存入 16 位的數(shù)據(jù)寄存器中時(shí),可以通過(guò) ADC_CR2 寄存器的 ALIGN 位來(lái)選擇數(shù)據(jù)是左對(duì)齊還是右對(duì)齊。
規(guī)則通道最多有 16 個(gè)通道,但規(guī)則數(shù)據(jù)寄存器只有一個(gè),因此當(dāng)使用多通道轉(zhuǎn)換時(shí),前一個(gè)轉(zhuǎn)換的通道數(shù)據(jù),會(huì)被后一個(gè)通道轉(zhuǎn)換的數(shù)據(jù)覆蓋掉,因此理論上必須在后一個(gè)通道轉(zhuǎn)換完畢之前就把數(shù)據(jù)取走。
為了確保不產(chǎn)生丟失數(shù)據(jù)的情況,開(kāi)啟 DMA 傳輸模式,將數(shù)據(jù)傳輸?shù)絻?nèi)存中是一個(gè)好辦法,這個(gè)到時(shí)候作為擴(kuò)展例程寫(xiě)。
②、注入組存儲(chǔ)
ADC 注入組最多有 4 個(gè)通道,每個(gè)通道都有對(duì)應(yīng)的數(shù)據(jù)寄存器 ADC_JDRx(x=1..4),ADC_JDRx 是 32 位的,高 16 位保留,低 16 位用來(lái)保存數(shù)據(jù),選擇數(shù)據(jù)是左對(duì)齊還是右對(duì)齊都由 ALIGN 決定。
9、中斷處理
如果打開(kāi)相應(yīng)的中斷,有三種情況可以進(jìn)入中斷。 (1)規(guī)則通道轉(zhuǎn)換完成中斷
- 轉(zhuǎn)換數(shù)據(jù)被存儲(chǔ)在 16 位的 ADC_DR 寄存器中。
- EOC(轉(zhuǎn)換結(jié)束)標(biāo)志被置位。
- 如果設(shè)置了 EOCIE 位,則產(chǎn)生中斷。
(2)注入通道轉(zhuǎn)換完成中斷
- 轉(zhuǎn)換數(shù)據(jù)被存儲(chǔ)在 16 位的 ADC_DRJx 寄存器中。
- JEOC(注入轉(zhuǎn)換結(jié)束)標(biāo)志被置位。
- 如果設(shè)置了 JEOCIE 位,則產(chǎn)生中斷。
(3)模擬看門(mén)狗中斷 如果開(kāi)啟了模擬看門(mén)狗中斷,并且設(shè)置ADC低閾值A(chǔ)DC_LTR和高閾值A(chǔ)DC_HTR, 當(dāng)采集到的電壓高于高閾值或者低于低閾值時(shí),就會(huì)產(chǎn)生模擬看門(mén)狗中斷。
關(guān)于A(yíng)DC中斷這部分,這里也沒(méi)有具體涉及到,等日后做題做到了再補(bǔ)充吧。目前也只用到了查詢(xún)的方式。
10、ADC校準(zhǔn)
ADC 有一個(gè)內(nèi)置自校準(zhǔn)模式,可以大幅減小由于內(nèi)部電容的變化而造成的精準(zhǔn)度誤差。通過(guò)設(shè)置 ADC_CR2 寄存器的 CAL 位啟動(dòng)校準(zhǔn),一旦校準(zhǔn)結(jié)束,CAL 位被硬件復(fù)位。建議在每次上電時(shí)執(zhí)行一次 ADC 校準(zhǔn),啟動(dòng)校準(zhǔn)前,ADC 必須處于上電狀態(tài)(ADON=‘1’)(言外之意必須先使能ADC),至少超過(guò)兩個(gè) ADC 時(shí)鐘周期。
校準(zhǔn)代碼是固定的,參考下面的代碼即可。
11、 使能通道觸發(fā)轉(zhuǎn)換
規(guī)則組和注入組觸發(fā)方式方式分為軟件觸發(fā)和外部觸發(fā),其中外部觸發(fā)包括定時(shí)器觸發(fā)和外部信號(hào)觸發(fā)。如果設(shè)置了規(guī)則組或注入組觸發(fā)方式,還需要使能相應(yīng)觸發(fā),保證在觸發(fā)到來(lái)時(shí)啟動(dòng)轉(zhuǎn)換。
規(guī)則組軟件觸發(fā)相應(yīng)函數(shù)為void ADC_SoftwareStartConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
12、轉(zhuǎn)換結(jié)束標(biāo)志位
看手冊(cè)介紹
可以由軟件清除或者讀取數(shù)據(jù)寄存器清除!
13、總結(jié)配置流程
1、初始化 ADC1 通道引腳(內(nèi)部溫度傳感器和參照電壓通道不需要進(jìn)行引腳初始化) 2、配置 ADC1 中斷優(yōu)先級(jí)(如果使用 ADC 中斷,本節(jié)未使用) 3、使能 ADC1 外設(shè)時(shí)鐘 4、設(shè)置 ADC1 預(yù)分頻系數(shù) 5、復(fù)位 ADC1 6、配置 ADC1 初始化結(jié)構(gòu)體 7、設(shè)置規(guī)則組轉(zhuǎn)換順序和采樣時(shí)間 8、使能 ADC1 中斷(本節(jié)未使用) 9、使能 ADC1 外設(shè) 10、校準(zhǔn) ADC1 11、使能 ADC1 軟件觸發(fā)或外部觸發(fā)轉(zhuǎn)換 12、編寫(xiě)中斷服務(wù)函數(shù)(如果使用 ADC 中斷,本節(jié)未使用)
二、主要代碼
main.c
/*******************************************************************************
* 文件名:main.c
* 描 述:
* 作 者:CLAY
* 版本號(hào):v1.0.0
* 日 期: 2019年1月27日
* 備 注:ADC轉(zhuǎn)換實(shí)現(xiàn)電壓測(cè)量
*
*******************************************************************************
*/
#include "config.h"
#include "led.h"
#include "key.h"
#include "timer.h"
#include "beep.h"
#include "lcd.h"
#include "stdio.h"
#include "rtc.h"
#include "adc.h"
u8 ADC_Flag = 0;
u8 string[20];
float adc_val;
int main(void)
{
STM3210B_LCD_Init();
LCD_Clear(Blue);
LCD_SetBackColor(Blue);
LCD_SetTextColor(White);
LEDInit();
KeyInit();
BeepInit();
TIM2Init(2000, 72);//定時(shí)2ms
ADC1Init();
while(1)
{
KeyDriver();
if(ADC_Flag )
{
ADC_Flag = 0;
adc_val = Get_ADC(8) * 3.3 / 4096;
sprintf((char*)string,"ADC_VAL : %.2f ",adc_val);
LCD_DisplayStringLine(Line2, string);
}
}
}
void KeyAction(int code)
{
if(code == 1)//按下B1,切換燈狀態(tài),蜂鳴器鳴叫0.1s
{
GPIOC->ODR ^= (1<<8);//PC8不斷取反
GPIOD->ODR |= (1<<2);//PD2置1,使能573鎖存器
GPIOD->ODR &= ~(1<<2);//PD2清0,關(guān)閉573鎖存器
Beep(100);
}
else if(code == 2)
{
Beep(-1);
}
else if(code == 3)
{
Beep(0);
}
else if(code == 4)
{
}
}
adc.c
#include "adc.h"
void ADC1_IOInit(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);//使能PB口時(shí)鐘
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;//選中PB0引腳
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;//模擬輸入
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化IO
}
void ADC_Cali(void)
{
ADC_ResetCalibration(ADC1);//使能復(fù)位校準(zhǔn)
while(ADC_GetResetCalibrationStatus( ADC1));//等待復(fù)位校準(zhǔn)結(jié)束
ADC_StartCalibration( ADC1);//開(kāi)啟AD校準(zhǔn)
while(ADC_GetCalibrationStatus(ADC1));//等待校準(zhǔn)結(jié)束
}
void ADC1Init(void)
{
ADC_InitTypeDef ADC_InitStructure;
ADC1_IOInit();//ADC的GPIO配置
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);//使能ADC時(shí)鐘,APB2總線(xiàn)
RCC_ADCCLKConfig(RCC_PCLK2_Div6);//ADC最大時(shí)鐘不超過(guò)14M,這里盡心了對(duì)APB2時(shí)鐘進(jìn)行6分頻=12M
ADC_DeInit(ADC1);//復(fù)位ADC
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;//ADC獨(dú)立模式
ADC_InitStructure.ADC_ScanConvMode = DISABLE;//通道模式選擇單通道
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;//轉(zhuǎn)換模式選擇單次轉(zhuǎn)換模式
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//規(guī)則組觸發(fā)轉(zhuǎn)換方式選擇轉(zhuǎn)換由軟件觸發(fā)啟動(dòng)
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//數(shù)據(jù)格式選擇右對(duì)齊
ADC_InitStructure.ADC_NbrOfChannel = 1;//通道數(shù)目為1
ADC_Init(ADC1,&ADC_InitStructure);//初始化結(jié)構(gòu)體
ADC_Cmd(ADC1, ENABLE);//使能ADC
ADC_Cali();//ADC校準(zhǔn)
}
u16 Get_ADC(u8 channel)
{
u16 temp;
ADC_RegularChannelConfig(ADC1, channel, 1, ADC_SampleTime_239Cycles5);//ADC采樣時(shí)間239.5周期 總轉(zhuǎn)換時(shí)間=采樣時(shí)間+12.5周期
ADC_SoftwareStartConvCmd( ADC1,ENABLE);//使能ADC1軟件觸發(fā)轉(zhuǎn)換,觸發(fā)一次ADC轉(zhuǎn)換
while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC) == 0);//等待ADC轉(zhuǎn)換完畢
temp = ADC_GetConversionValue( ADC1);//獲取轉(zhuǎn)換結(jié)果,注意此時(shí)已經(jīng)自動(dòng)清除EOC位
ADC_SoftwareStartConvCmd( ADC1,DISABLE);//失能ADC轉(zhuǎn)換
return temp;
}
adc.h
#ifndef _ADC_H
#define _ADC_H
#include "config.h"
void ADC1Init(void);
u16 Get_ADC(u8 channel);
#endif
stm32f10x_it.c
extern u8 ADC_Flag;
void TIM2_IRQHandler(void)
{
static u16 tmr500ms = 0;
if(TIM_GetITStatus(TIM2, TIM_FLAG_Update))
{
TIM_ClearITPendingBit(TIM2, TIM_FLAG_Update);
tmr500ms++;
KeyScan();
BeepScan(2);//2ms掃描
if(tmr500ms >= 250)
{
tmr500ms = 0;
ADC_Flag = 1;
}
}
}
三、注意事項(xiàng)
定時(shí)器設(shè)定500ms讀取一次AD值
結(jié)語(yǔ):以上就是本篇文章的全部?jī)?nèi)容啦,希望大家可以多多支持我的原創(chuàng)文章。如有錯(cuò)誤,請(qǐng)及時(shí)指正,非常感謝。