STM32 ADC結(jié)合DMA數(shù)據(jù)采樣與軟件濾波處理
ADC接口上的其它邏輯功能包括:
●同步的采樣和保持
●交叉的采樣和保持
●單次采樣
模擬看門狗功能允許非常精準(zhǔn)地監(jiān)視一路、多路或所有選中的通道,當(dāng)被監(jiān)視的信號(hào)超出預(yù)置的閥值時(shí),將產(chǎn)生中斷。
由標(biāo)準(zhǔn)定時(shí)器(TIMx)和高級(jí)控制定時(shí)器(TIM1和TIM8)產(chǎn)生的事件,可以分別內(nèi)部級(jí)聯(lián)到ADC的開始觸發(fā)和注入觸發(fā),應(yīng)用程序能使AD轉(zhuǎn)換與時(shí)鐘同步。
12位ADC是一種逐次逼近型模擬數(shù)字?jǐn)?shù)字轉(zhuǎn)換器。它有多達(dá)18個(gè)通道,可測(cè)量16個(gè)外部和2個(gè)內(nèi)部信號(hào)源。
ADC的輸入時(shí)鐘不得超過14MHZ,它是由PCLK2經(jīng)分頻產(chǎn)生。
如果被ADC轉(zhuǎn)換的模擬電壓低于低閥值或高于高閥值,AWD模擬看門狗狀態(tài)位被設(shè)置。
關(guān)于ADC采樣與DMA關(guān)系,引用網(wǎng)上一段解釋:
STM32 的優(yōu)點(diǎn)在哪里?
除去宣傳環(huán)節(jié),細(xì)細(xì)分析。
STM32 時(shí)鐘不算快,72MHZ,
也不能擴(kuò)展大容量的RAM FLASH,
同樣沒有 DSP 那樣強(qiáng)大的指令集。
它的優(yōu)勢(shì)在哪里呢?
---就在快速采集數(shù)據(jù),快速處理上。
ARM 的特點(diǎn)就是方便。
這個(gè)快速采集,高性能的ADC 就是一個(gè)很好的體現(xiàn),
12 位精度,最快1uS 的轉(zhuǎn)換速度,通常具備2 個(gè)以上獨(dú)立的ADC 控制器,
這意味著,
STM32 可以同時(shí)對(duì)多個(gè)模擬量進(jìn)行快速采集,
這個(gè)特性不是一般的MCU具有的。
以上高性能的 ADC,配合相對(duì)比較塊的指令集和一些特色的算法支持,
就構(gòu)成了STM32 在電機(jī)控制上的強(qiáng)大特性。
好了,正題,怎末做一個(gè)簡(jiǎn)單的ADC,注意是簡(jiǎn)單的,
ADC 是個(gè)復(fù)雜的問題,涉及硬件設(shè)計(jì),電源質(zhì)量,參考電壓,信號(hào)預(yù)處理等等問題。
我們只就如何在MCU內(nèi)完成一次ADC 作討論。
談到 ADC,我們還要第一次引入另外一個(gè)重要的設(shè)備DMA.
DMA是什么東西呢。
通常在 8 位單片機(jī)時(shí)代,很少有這個(gè)概念。
在外置資源越來越多以后,
我們把一個(gè)MCU內(nèi)部分為主處理器和 外設(shè)兩個(gè)部分。
主處理器當(dāng)然是執(zhí)行我們指令的主要部分,
外設(shè)則是串口 I2C ADC 等等用來實(shí)現(xiàn)特定功能的設(shè)備
回憶一下,8 位時(shí)代,我們的主處理器最常干的事情是什么?
邏輯判斷?不是。那才幾個(gè)指令
計(jì)算算法?不是。大部分時(shí)候算法都很簡(jiǎn)單。
事實(shí)上,主處理器就是作個(gè)搬運(yùn)工,
把 USART 的數(shù)據(jù)接收下來,存起來
把 ADC 的數(shù)據(jù)接收下來,存起來
把要發(fā)送的數(shù)據(jù),存起來,一個(gè)個(gè)的往USART 里放。
…………
為了解決這個(gè)矛盾,
人們想到一個(gè)辦法,讓外設(shè)和內(nèi)存間建立一個(gè)通道,
在主處理器允許下,
讓外設(shè)和內(nèi)存直接讀寫,這樣就釋放了主處理器,
這個(gè)東西就是DMA。
打個(gè)比方:
一個(gè)MCU是個(gè)公司。
老板就是主處理器
員工是外設(shè)
倉(cāng)庫(kù)就是內(nèi)存
從前 倉(cāng)庫(kù)的東西都是老板管的。
員工需要原料工作,就一個(gè)個(gè)報(bào)給老板,老板去倉(cāng)庫(kù)里一個(gè)一個(gè)拿。
員工作好的東西,一個(gè)個(gè)給老板,老板一個(gè)個(gè)放進(jìn)倉(cāng)庫(kù)里。
老板很累,雖然老板是超人,也受不了越來越多的員工和單子。
最后老板雇了一個(gè)倉(cāng)庫(kù)保管員,它就是DMA
他專門負(fù)責(zé)入庫(kù)和出庫(kù),
只需要把出庫(kù)和入庫(kù)計(jì)劃給老板過目
老板說 OK,就不管了。
后面的入庫(kù)和出庫(kù)過程,
員工只需要和這個(gè)倉(cāng)庫(kù)保管員打交道就可以了。
--------閑話,馬七時(shí)常想,讓設(shè)備與設(shè)備之間開DMA,豈不更牛X
比喻完成。
ADC 是個(gè)高速設(shè)備,前面提到。
而且 ADC 采集到的數(shù)據(jù)是不能直接用的。即使你再小心的設(shè)計(jì)外圍電路,測(cè)的離譜的數(shù)據(jù)總會(huì)出現(xiàn)。
那么通常來說,是采集一批數(shù)據(jù),然后進(jìn)行處理,這個(gè)過程就是軟件濾波。
DMA用到這里就很合適。讓ADC 高速采集,把數(shù)據(jù)填充到RAM 中,填充一定數(shù)量,比如32 個(gè),64 個(gè)MCU再來使用。
-----多一句,也可以說,單次ADC 毫無(wú)意義。
下面我們來具體介紹,如何使用DMA來進(jìn)行ADC 操作。
初始化函數(shù)包括兩部分,DMA 初始化和ADC 初始化
我們有多個(gè)管理員--DMA
一個(gè)管理員當(dāng)然不止管一個(gè)DMA 操作。所以DMA有多個(gè)Channel
以下是程序分析:
程序基于STM32F103VET6,庫(kù)函數(shù)實(shí)現(xiàn)
RCC部分:(忽略系統(tǒng)時(shí)鐘配置)
//啟動(dòng)DMA時(shí)鐘
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
//啟動(dòng)ADC1時(shí)鐘
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
GPIO部分:(ADC引腳參見上表)
//ADC_CH10--> PC0
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;//模擬輸入
GPIO_Init(GPIOC, &GPIO_InitStructure);
// PC2
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOC, &GPIO_InitStructure);
ADC1配置:(兩外部輸入,另采樣內(nèi)部溫度傳感器)
void ADC1_Configuration(void)
{
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //轉(zhuǎn)換模式為獨(dú)立,還有交叉等非常多樣的選擇
ADC_InitStructure.ADC_ScanConvMode = ENABLE;
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;//連續(xù)轉(zhuǎn)換開啟
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = 3; //設(shè)置轉(zhuǎn)換序列長(zhǎng)度為3,三通道
ADC_Init(ADC1, &ADC_InitStructure);
//ADC內(nèi)置溫度傳感器使能(要使用片內(nèi)溫度傳感器,切忌要開啟它)
ADC_TempSensorVrefintCmd(ENABLE);
//常規(guī)轉(zhuǎn)換序列1:通道10
ADC_RegularChannelConfig(ADC1, ADC_Channel_10, 1, ADC_SampleTime_239Cycles5);
//常規(guī)轉(zhuǎn)換序列2:通道16(內(nèi)部溫度傳感器),采樣時(shí)間>2.2us,(239cycles)
ADC_RegularChannelConfig(ADC1, ADC_Channel_16, 2, ADC_SampleTime_239Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 3, ADC_SampleTime_239Cycles5);
//輸入?yún)?shù):ADC外設(shè),ADC通道,轉(zhuǎn)換序列順序,采樣時(shí)間
// Enable ADC1
ADC_Cmd(ADC1, ENABLE);
// 開啟ADC的DMA支持(要實(shí)現(xiàn)DMA功能,還需獨(dú)立配置DMA通道等參數(shù))
ADC_DMACmd(ADC1, ENABLE);
// 下面是ADC自動(dòng)校準(zhǔn),開機(jī)后需執(zhí)行一次,保證精度
// Enable ADC1 reset calibaration register
ADC_ResetCalibration(ADC1);
// Check the end of ADC1 reset calibration register
while(ADC_GetResetCalibrationStatus(ADC1));
// Start ADC1 calibaration
ADC_StartCalibration(ADC1);
// Check the end of ADC1 calibration
while(ADC_GetCalibrationStatus(ADC1));
// ADC自動(dòng)校準(zhǔn)結(jié)束---------------
ADC_SoftwareStartConvCmd(ADC1, ENABLE); //ADC啟動(dòng)
}
DMA配置:(無(wú)軟件濾波)
void DMA_Configuration(void)
{
DMA_InitTypeDef DMA_InitStructure;
DMA_DeInit(DMA1_Channel1);
DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address; //DMA外設(shè)地址,在頭部定義
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&AD_Value; //內(nèi)存地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //外設(shè)至內(nèi)存模式
//BufferSize=2,因?yàn)锳DC轉(zhuǎn)換序列有2個(gè)通道
//如此設(shè)置,使序列1結(jié)果放在AD_Value[0],序列2結(jié)果放在AD_Value[1]
DMA_InitStructure.DMA_BufferSize = 3; //一次轉(zhuǎn)換三個(gè)
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //接受一次后,設(shè)備地址不后移
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //接受一次后,內(nèi)存地址后移
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //每次傳輸半字
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
//循環(huán)模式開啟,Buffer寫滿后,自動(dòng)回到初始地址開始傳輸
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
//配置完成后,啟動(dòng)DMA通道
DMA_Cmd(DMA1_Channel1, ENABLE);
}
此DMA例程用于單次ADC轉(zhuǎn)換,配合軟件濾波可做如下改動(dòng):
全局聲明:
vu16 AD_Value[30][3]; //AD采樣值
vu16 After_filter[3]; //AD濾波后
DMA部分:(帶中斷濾波)
void DMA_Configuration(void)
{
DMA_InitTypeDef DMA_InitStructure;
DMA_DeInit(DMA1_Channel1);
DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address;
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&AD_Value;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
//BufferSize=2,因?yàn)锳DC轉(zhuǎn)換序列有2個(gè)通道
//如此設(shè)置,使序列1結(jié)果放在AD_Value[0],序列2結(jié)果放在AD_Value[1]
DMA_InitStructure.DMA_BufferSize = 90;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
//循環(huán)模式開啟,Buffer寫滿后,自動(dòng)回到初始地址開始傳輸
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
//配置完成后,啟動(dòng)DMA通道
DMA_Cmd(DMA1_Channel1, ENABLE);
DMA_ITConfig(DMA1_Channel1, DMA_IT_TC, ENABLE); //使能DMA傳輸完成中斷
}
NVIC部分:
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel1_IRQChannel;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure); // Enable the DMA Interrupt
stm32f10x_it.c文件:
void DMA1_Channel1_IRQHandler(void)
{
if(DMA_GetITStatus(DMA1_IT_TC1) != RESET)
{
filter();
DMA_ClearITPendingBit(DMA1_IT_TC1);
}
}
濾波部分:(均值濾波)
#define N 30
void filter(void)
{
intsum = 0;
u8 count,i;
for(i=0;i<2;i++)
{
for ( count=0;count
sum += AD_Value[count][i];
}
After_filter[i]=sum/N;
sum=0;
}
}
采樣數(shù)據(jù)與實(shí)際電壓/溫度轉(zhuǎn)換:
u16 GetTemp(u16 advalue)
{
u32 Vtemp_sensor;
s32 Current_Temp;
// ADC轉(zhuǎn)換結(jié)束以后,讀取ADC_DR寄存器中的結(jié)果,轉(zhuǎn)換溫度值計(jì)算公式如下:
// V25 - VSENSE
//T(℃) = ------------+ 25
// Avg_Slope
// V25:溫度傳感器在25℃時(shí) 的輸出電壓,典型值1.43 V。
//VSENSE:溫度傳感器的當(dāng)前輸出電壓,與ADC_DR 寄存器中的結(jié)果ADC_ConvertedValue之間的轉(zhuǎn)換關(guān)系為:
// ADC_ConvertedValue * Vdd
//VSENSE = --------------------------
// Vdd_convert_value(0xFFF)
//Avg_Slope:溫度傳感器輸出電壓和溫度的關(guān)聯(lián)參數(shù),典型值4.3 mV/℃。
Vtemp_sensor = advalue * 330 / 4096;
Current_Temp = (s32)(143 - Vtemp_sensor)*10000/43 + 2500;
return (s16)Current_Temp;
}
u16 GetVolt(u16 advalue)
{
return (u16)(advalue * 330 / 4096);
}
濾波部分思路為:ADC正常連續(xù)采樣三個(gè)通道,由DMA進(jìn)行搬運(yùn),一次搬運(yùn)90個(gè)數(shù)據(jù),即為1-2-3-1-2-3循環(huán),每個(gè)通道各30次,存在 AD_Value[30][3]中,30為每通道30個(gè)數(shù)據(jù),3為三個(gè)通道,根據(jù)二維數(shù)組存儲(chǔ)方式此過程自動(dòng)完成。而每當(dāng)一次DMA過程結(jié)束后,觸發(fā) DMA完成中斷,進(jìn)入濾波函數(shù)將30個(gè)數(shù)據(jù)均值成一個(gè), 存入After_filter[3]。整個(gè)過程濾波計(jì)算需要CPU參與,而在程序中采樣結(jié)果值隨時(shí)均為最新,盡力解決程序復(fù)雜性和CPU負(fù)載。 x=GetVolt(After_filter[0]);即可得到即時(shí)電壓值。
評(píng)論