目錄
一、使用過程中問題
1、ADC數據會產生跳動解決辦法:
①多去幾次求平均值(平均值會將毛刺及錯誤的值加入平均計算);
②多取幾次,去除最大值與最小值,其餘求平均值(中位值平均濾波法);
A、名稱:中位值平均濾波法(又稱防脈衝干擾平均濾波法)
B、方法:
採一組隊列去掉最大值和最小值後取平均值,
相當於“中位值濾波法”+“算術平均濾波法”。
連續採樣N個數據,去掉一個最大值和一個最小值,
然後計算N-2個數據的算術平均值。
N值的選取:3-14。
C、優點:
融合了“中位值濾波法”+“算術平均濾波法”兩種濾波法的優點。
對於偶然出現的脈衝性干擾,可消除由其所引起的採樣值偏差。
對週期干擾有良好的抑制作用。
平滑度高,適於高頻振盪的系統。
D、缺點:
計算速度較慢,和算術平均濾波法一樣。
比較浪費RAM。
③採集數據進行排列,然後取中間值(中位值濾波法);
A、名稱:中位值濾波法
B、方法:
連續採樣N次(N取奇數),把N次採樣值按大小排列,
取中間值爲本次有效值。
C、優點:
能有效克服因偶然因素引起的波動干擾;
對溫度、液位的變化緩慢的被測參數有良好的濾波效果。
D、缺點:
對流量、速度等快速變化的參數不宜。
④採用濾波算法②③似乎就是。
2、使用過程中ADC採集沒有中間值,要麼最大要麼最小:
首先就要查看基準電平是否焊接上。
使用過程:
比較神奇,就算端口引腳沒有初始化模擬輸入也可以使用adc採集,本來是PC0、PC2、PC3、PC4、PC5設置成PC10、PC12、PC13、PC14、PC15,也可以使用,
另外使用中,adc不能模擬採集,可能由於基準電壓引腳沒有焊接
二、ADC電壓採集
電壓輸入範圍
ADC 輸入範圍爲: VREF- ≤ VIN ≤ VREF+。由 VREF-、 VREF+ 、 VDDA 、 VSSA、這四個外部引腳決定。
在設計原理圖的時候一般把VSSA和VREF-接地,把VREF+和VDDA 接3V3,得到ADC的輸入電壓範圍爲: 0~3.3V。
如果想讓輸入的電壓範圍變寬,去到可以測試負電壓或者更高的正電壓,我們可以在外部加一個電壓調理電路,把需要轉換的電壓擡升或者降壓到 0~3.3V,這樣 ADC 就可以測量了
輸入通道
轉換順序
觸發源
轉換時間
ADC 時鐘
ADC 輸入時鐘 ADC_CLK 由 PCLK2 經過分頻產生, 最大是 14M,分頻因子由 RCC 時鐘配置寄存器 RCC_CFGR 的位 15:14 ADCPRE[1:0]設置,可以是 2/4/6/8 分頻,注意這裏沒有 1 分頻。一般我們設置 PCLK2=HCLK=72M。
採樣時間
ADC 使用若干個 ADC_CLK 週期對輸入的電壓進行採樣,採樣的週期數可通過 ADC採樣時間寄存器 ADC_SMPR1 和 ADC_SMPR2 中的 SMP[2:0]位設置, ADC_SMPR2 控制的是通道 0~9, ADC_SMPR1 控制的是通道 10~17。每個通道可以分別用不同的時間採樣。其中採樣週期最小是 1.5 個,即如果我們要達到最快的採樣,那麼應該設置採樣週期爲 1.5 個週期,這裏說的週期就是 1/ADC_CLK
ADC 的轉換時間跟 ADC 的輸入時鐘和採樣時間有關,公式爲: Tconv = 採樣時間 +12.5 個週期。 當 ADCLK = 14MHZ (最高),採樣時間設置爲 1.5 週期(最快),那麼總的轉換時間(最短) Tconv = 1.5 週期 + 12.5 週期 = 14 週期 = 1us。
一般我們設置 PCLK2=72M,經過 ADC 預分頻器能分頻到最大的時鐘只能是 12M,採樣週期設置爲 1.5 個週期,算出最短的轉換時間爲 1.17us,這個纔是最常用的。
數據寄存器
一切準備就緒後, ADC 轉換後的數據根據轉換組的不同,規則組的數據放在 ADC_DR寄存器,注入組的數據放在 JDRx。
中斷
轉換結束中斷
數據轉換結束後,可以產生中斷,中斷分爲三種:規則通道轉換結束中斷,注入轉換通道轉換結束中斷,模擬看門狗中斷。其中轉換結束中斷很好理解,跟我們平時接觸的中斷一樣,有相應的中斷標誌位和中斷使能位,我們還可以根據中斷類型寫相應配套的中斷服務程序。
模擬看門狗中斷
當被 ADC 轉換的模擬電壓低於低閾值或者高於高閾值時,就會產生中斷,前提是我們開啓了模擬看門狗中斷,其中低閾值和高閾值由 ADC_LTR 和 ADC_HTR 設置。例如我們設置高閾值是 2.5V,那麼模擬電壓超過 2.5V的時候,就會產生模擬看門狗中斷,反之低閾值也一樣。
DMA 請求
規則和注入通道轉換結束後,除了產生中斷外,還可以產生 DMA 請求,把轉換好的數據直接存儲在內存裏面。要注意的是隻有 ADC1 和 ADC3 可以產生 DMA 請求。一般我們在使用 ADC 的時候都會開啓 DMA 傳輸。
電壓轉換
模擬電壓經過 ADC 轉換後,是一個 12 位的數字值,如果通過串口以 16 進制打印出來的話,可讀性比較差,那麼有時候我們就需要把數字電壓轉換成模擬電壓,也可以跟實際的模擬電壓(用萬用表測)對比,看看轉換是否準確。
我們一般在設計原理圖的時候會把 ADC 的輸入電壓範圍設定在: 0~3.3v,因爲 ADC是 12 位的,那麼 12 位滿量程對應的就是 3.3V, 12 位滿量程對應的數字值是: 2^12。數值0 對應的就是 0V。 如果轉換後的數值爲 X , X 對應的模擬電壓爲 Y,那麼會有這麼一個等式成立: 2^12 / 3.3 = X / Y, => Y = (3.3 * X )
三、代碼使用
1、adc值讀取
adc.c
#include "main.h"
#include "stm32f10x_dma.h"
#include "bsp_adc.h"
void ADC_IN_Init(){
ADC_InitTypeDef ADC_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC|RCC_APB2Periph_ADC1,ENABLE);//使能ADC時鐘和相關GPIO時鐘
RCC_ADCCLKConfig(RCC_PCLK2_Div6);//配置ADC時鐘72M/6=12,最大不能超過14
//GPIO配置
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10|GPIO_Pin_10|GPIO_Pin_10|GPIO_Pin_10|GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN;
GPIO_Init(GPIOC,&GPIO_InitStructure);
ADC_DeInit(ADC1);//復位ADC
ADC_InitStructure.ADC_Mode=ADC_Mode_Independent;//獨立工作模式
ADC_InitStructure.ADC_ScanConvMode=DISABLE;//啓動多通道掃描(單通道不要打開)
ADC_InitStructure.ADC_ContinuousConvMode=DISABLE;//啓動連續轉換
ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None;//軟件觸發
ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right;//右對齊
ADC_InitStructure.ADC_NbrOfChannel=1;//掃描8個通道
ADC_Init(ADC1,&ADC_InitStructure);//根據參數初始化ADC1
ADC_Cmd(ADC1,ENABLE);//使能ADC
ADC_ResetCalibration(ADC1);//使能復位校準
while(ADC_GetResetCalibrationStatus(ADC1));//等待校準完成
ADC_StartCalibration(ADC1);//使能ADC校準
while(ADC_GetCalibrationStatus(ADC1));//等待校準完成
ADC_SoftwareStartConvCmd(ADC1,ENABLE);//啓動一次ADC轉換
}
//讀取指定通道值
uint16_t read_adc_value(uint8_t ch)
{
uint16_t adc_val = 0;
ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_55Cycles5 );
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
while(ADC_GetSoftwareStartConvStatus(ADC1)){};
delay_ms(10);
adc_val = 100 - ADC_GetConversionValue(ADC1)*100/4096;
ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_55Cycles5 );
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
while(ADC_GetSoftwareStartConvStatus(ADC1)){};
delay_ms(10);
adc_val += 100 - ADC_GetConversionValue(ADC1)*100/4096;
ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_55Cycles5 );
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
while(ADC_GetSoftwareStartConvStatus(ADC1)){};
delay_ms(10);
adc_val += 100 - ADC_GetConversionValue(ADC1)*100/4096;
ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_55Cycles5 );
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
while(ADC_GetSoftwareStartConvStatus(ADC1)){};
delay_ms(10);
adc_val += 100 - ADC_GetConversionValue(ADC1)*100/4096;
return adc_val/4;
}
adc.h
#ifndef BSP_ADC_H
#define BSP_ADC_H
#include "stm32f10x_adc.h"
int getAdcValue(char channle);
void ADC_IN_Init(void);
uint16_t read_adc_value(uint8_t ch);
#endif
2、ADC的DMA多通道讀取使用方法
adc.h
#ifndef __ADC_H
#define __ADC_H
#include "stm32f10x.h"
/********************ADC1輸入通道(引腳)配置**************************/
#define ADC_APBxClock_FUN RCC_APB2PeriphClockCmd
#define ADC_CLK RCC_APB2Periph_ADC1
#define ADC_GPIO_APBxClock_FUN RCC_APB2PeriphClockCmd
#define ADC_GPIO_CLK RCC_APB2Periph_GPIOC
#define ADC_PORT GPIOC
// 轉換通道個數
#define NOFCHANEL 6
#define ADC_PIN1 GPIO_Pin_0
#define ADC_CHANNEL1 ADC_Channel_10
#define ADC_PIN2 GPIO_Pin_1
#define ADC_CHANNEL2 ADC_Channel_11
#define ADC_PIN3 GPIO_Pin_2
#define ADC_CHANNEL3 ADC_Channel_12
#define ADC_PIN4 GPIO_Pin_3
#define ADC_CHANNEL4 ADC_Channel_13
#define ADC_PIN5 GPIO_Pin_4
#define ADC_CHANNEL5 ADC_Channel_14
#define ADC_PIN6 GPIO_Pin_5
#define ADC_CHANNEL6 ADC_Channel_15
// ADC1 對應 DMA1通道1,ADC3對應DMA2通道5,ADC2沒有DMA功能
#define ADC_x ADC1
#define ADC_DMA_CHANNEL DMA1_Channel1
#define ADC_DMA_CLK RCC_AHBPeriph_DMA1
/**************************函數聲明********************************/
void ADCx_Init (void);
#endif /* __ADC_H */
adc.c
#include "bsp_adc.h"
__IO uint16_t ADC_ConvertedValue[NOFCHANEL]={0,0,0,0,0,0};
/**
* @brief ADC GPIO 初始化
* @param 無
* @retval 無
*/
static void ADCx_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// 打開 ADC IO端口時鐘
ADC_GPIO_APBxClock_FUN ( ADC_GPIO_CLK, ENABLE );
// 配置 ADC IO 引腳模式
GPIO_InitStructure.GPIO_Pin = ADC_PIN1|
ADC_PIN2|
ADC_PIN3|
ADC_PIN4|
ADC_PIN5|
ADC_PIN6;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
// 初始化 ADC IO
GPIO_Init(ADC_PORT, &GPIO_InitStructure);
}
/**
* @brief 配置ADC工作模式
* @param 無
* @retval 無
*/
static void ADCx_Mode_Config(void)
{
DMA_InitTypeDef DMA_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
// 打開DMA時鐘
RCC_AHBPeriphClockCmd(ADC_DMA_CLK, ENABLE);
// 打開ADC時鐘
ADC_APBxClock_FUN ( ADC_CLK, ENABLE );
// 復位DMA控制器
DMA_DeInit(ADC_DMA_CHANNEL);
// 配置 DMA 初始化結構體
// 外設基址爲:ADC 數據寄存器地址
DMA_InitStructure.DMA_PeripheralBaseAddr = ( u32 ) ( & ( ADC_x->DR ) );
// 存儲器地址
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)ADC_ConvertedValue;
// 數據源來自外設
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
// 緩衝區大小,應該等於數據目的地的大小
DMA_InitStructure.DMA_BufferSize = NOFCHANEL;
// 外設寄存器只有一個,地址不用遞增
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;
// 循環傳輸模式
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
// DMA 傳輸通道優先級爲高,當使用一個DMA通道時,優先級設置不影響
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
// 禁止存儲器到存儲器模式,因爲是從外設到存儲器
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
// 初始化DMA
DMA_Init(ADC_DMA_CHANNEL, &DMA_InitStructure);
// 使能 DMA 通道
DMA_Cmd(ADC_DMA_CHANNEL , ENABLE);
// ADC 模式配置
// 只使用一個ADC,屬於單模式
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
// 掃描模式
ADC_InitStructure.ADC_ScanConvMode = ENABLE ;
// 連續轉換模式
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
// 不用外部觸發轉換,軟件開啓即可
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
// 轉換結果右對齊
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
// 轉換通道個數
ADC_InitStructure.ADC_NbrOfChannel = NOFCHANEL;
// 初始化ADC
ADC_Init(ADC_x, &ADC_InitStructure);
// 配置ADC時鐘N狿CLK2的8分頻,即9MHz
RCC_ADCCLKConfig(RCC_PCLK2_Div8);
// 配置ADC 通道的轉換順序和採樣時間
ADC_RegularChannelConfig(ADC_x, ADC_CHANNEL1, 1, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC_x, ADC_CHANNEL2, 2, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC_x, ADC_CHANNEL3, 3, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC_x, ADC_CHANNEL4, 4, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC_x, ADC_CHANNEL5, 5, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC_x, ADC_CHANNEL6, 6, ADC_SampleTime_55Cycles5);
// 使能ADC DMA 請求
ADC_DMACmd(ADC_x, ENABLE);
// 開啓ADC ,並開始轉換
ADC_Cmd(ADC_x, ENABLE);
// 初始化ADC 校準寄存器
ADC_ResetCalibration(ADC_x);
// 等待校準寄存器初始化完成
while(ADC_GetResetCalibrationStatus(ADC_x));
// ADC開始校準
ADC_StartCalibration(ADC_x);
// 等待校準完成
while(ADC_GetCalibrationStatus(ADC_x));
// 由於沒有采用外部觸發,所以使用軟件觸發ADC轉換
ADC_SoftwareStartConvCmd(ADC_x, ENABLE);
}
/**
* @brief ADC初始化
* @param 無
* @retval 無
*/
void ADCx_Init(void)
{
ADCx_GPIO_Config();
ADCx_Mode_Config();
}
/*********************************************END OF FILE**********************/
數據讀取
void read_value(1)
{
ADC_ConvertedValueLocal[0] =(float) ADC_ConvertedValue[0]/4096*3.3;
ADC_ConvertedValueLocal[1] =(float) ADC_ConvertedValue[1]/4096*3.3;
ADC_ConvertedValueLocal[2] =(float) ADC_ConvertedValue[2]/4096*3.3;
ADC_ConvertedValueLocal[3] =(float) ADC_ConvertedValue[3]/4096*3.3;
ADC_ConvertedValueLocal[4] =(float) ADC_ConvertedValue[4]/4096*3.3;
ADC_ConvertedValueLocal[5] =(float) ADC_ConvertedValue[5]/4096*3.3;
Delay_s(1);
}