LED數碼管或者LCD12864等顯示器在顯示溫度采樣值時,需要的是穩定的能反映溫度實際變化的顯示效果。ADC采樣頻率快,直接采樣輸出的值往往有不小的跳變,這時候需要對其數據做一些數字濾波處理。常用的方法有:平均值法、多次采樣用冒泡法取中間一段數值法、平滑平均線法。冒泡法處理時間太長,這里主要用了平均值和平滑平均線兩種方法一起處理。
一、算法部分
平滑平均線法可解決一般均值法的欠靈敏和滯后性,同時濾除毛刺。用此法對ADC采集的數據進行ADC濾波后,在LCD12864上顯示穩定。
具體做法如下:
- 在一個ADC通道上采集多次,獲得采樣平均值a,用a初始化含有假定100個數據的數組b[100]中所有的數據(這樣b[]數組的平均值就是a);
- 后續每次運行一次ADC采樣都會得到一個新的采樣平均值A,同時把數組b的數據左移(b[0]=b[1]~~~b[98]=b[99]),然后把A的值賦值給b[99];
- 如此每次運行一次采樣就會平滑的更新一次數組b的平均值B,也就得到了數字濾波后的平滑平均值B,平均值B就可以拿去做顯示,效果穩定;
- 實際程序處理中是:第一次得到的采樣平均值A0賦值給b[0],如此順序,第100次采樣得到的平均值A99賦值給b[99],之后又重新依次更新b[0]到b[99]的值,如此循環,這樣做可以減少運算量,同時達到左移的效果
平滑平均值代碼
/**
* @brief ADC采樣所得到的溫度
* @param None
* @retval : None
*/
void ADC1_Tempera(void)
{
u32 i,k,m,n;
static u32 ValueCount;
for(i=0;i<ADC_ChannelCount;i++)
{
/*
*@平均值濾波
*/
ValueCount=0;
for(k=0;k<ADC1_Count;k++)//取中間的100次值(ADC1_Count)
{
ValueCount+=ADGetValue[k][i];
ADC_Filter[i]=(ValueCount/ADC1_Count);//求取一個ADC通道的平均值
}
if(Tflag==0)
{
for(n=0;n<SmoothNum;n++)
{
ADC_SmoothFilter[i][n]=ADC_Filter[i];//獲得第一次平滑值
}
ADC_SmoothCount[i]=ADC_Filter[i];//獲取第一次平滑平均值線
}
/*
*@平滑濾波
*/
if(Tflag==1)
{
ADC_SmoothFilter[i][Smoth_Num[i]]=ADC_Filter[i];//把數值循環放置
Smoth_Num[i]++;
if(Smoth_Num[i]==SmoothNum)
Smoth_Num[i]=0;
for(n=0;n<SmoothNum;n++)//平滑濾波
{
ADC_SmoothCount[i] += ADC_SmoothFilter[i][n];
}
ADC_SmoothCount[i]=(ADC_SmoothCount[i]/SmoothNum);//獲取平滑平均值線
}
}
for(m=0;m<3;m++)
{
TMonitor[m]=(((2361*ADC_SmoothCount[2*m])/(ADC_SmoothCount[2*m+1])-2597)+TemperaSet[m+4]);//已經擴大10倍的值轉化為溫度數據,低溫,高溫,環境溫度 TemperaSet[m+4]為溫度補償系數
if(Tflag==0)
TMonitorDis[m]=TMonitor[m];
/*
*@顯示處理緩慢變化
*/
if(TMonitorDis[m]<(TMonitor[m]-1))
TMonitorDis[m] += 1;
if(TMonitorDis[m]>(TMonitor[m]+1))
TMonitorDis[m] -= 1;
}
Tflag=1;
}
代碼注解
- ADC_ChannelCount為采樣通道數,其值為6,表示ADC1有6個采樣通道。
- ADC1_Count為每一個通道在執行一次采樣函數中需要采樣的次數,此值為100。
- ADGetValue[ADC1_Count][ADC_ChannelCount]數組內存中存儲的是6個通道中每個通道的100個數據,即每執行一次函數void ADC1_Tempera(void)就更新一次這段內存的所有數據。
- 批量采樣工作過程是:定時器觸發一次后依次采樣通道(1-2-3-4-5-6),第二次定時器觸發后依次采樣通道(1-2-3-4-5-6),如此連續轉換,循環掃描;所以要想每個通道都采樣100次,那么就需要觸發轉換100次,這樣定義一段內存存儲數據的數組就應該是ADGetValue[100][6],這樣在i=0~99情況下100個數據ADGetValue[i][0]表示為通道1在采樣了100次后所得到的數據。
ADC通道配置
DMA模式配置要點
- STM32的ADC1作為DMA的外設源,其地址為16位,如果變量值不是16位那么所得到的數據會混亂,但是DMA配置里是32位,所以需要強制轉換為32位。
vu16 ADGetValue[][];//AD采樣值,ADC_ChannelCount個通道,每個通道采樣ADC1_Count次
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&ADGetValue;//設置DMA內存地址,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_T1_CC1; //外部定時器觸發轉換
ADC配置代碼
/**
* @brief 配置采樣通道端口 使能GPIO時鐘 設置ADC采樣PA0端口信號
* @param None
* @retval : None
*/
void ADC1_GPIO_Config(void)
{
GPIO_InitTypeDef ADC_PORT;
RCC_APB2PeriphClockCmd(ADC_ClK, ENABLE);
ADC_PORT.GPIO_Pin = ADC_Pin; //ADC引腳選擇,6個通道
ADC_PORT.GPIO_Mode = GPIO_Mode_AIN; //GPIO設置為模擬輸入
GPIO_Init(ADC_CONTROL, &ADC_PORT);
}
/**
* @brief 配置ADC1的工作模式為DMA模式
* @param None
* @retval : None
*/
void ADC1_Mode_Config(void)
{
ADC_InitTypeDef ADC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); //使能ADC1時鐘
/* ADC1 configuration */
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //使用獨立模式,掃描模式
ADC_InitStructure.ADC_ScanConvMode = ENABLE; //循環掃描模式
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; //連續轉換模式
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1; //外部定時器觸發轉換
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//數據右對齊
ADC_InitStructure.ADC_NbrOfChannel = ADC_ChannelCount; //有ADC_ChannelCount個轉換通道
ADC_Init(ADC1, &ADC_InitStructure);
/* 以下為配置通道數總共6路,分別是AD10,AD11,AD12,AD13,AD14,AD15 */
/* ADC1 regular channel10 configuration */
ADC_RegularChannelConfig(ADC1, ADC_Channel_10, 1, ADC_SampleTime_55Cycles5); //通道10,作為第1個采樣目標,采樣周期239.5個時鐘周期
/* ADC1 regular channel11 configuration */
ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 2, ADC_SampleTime_55Cycles5); //通道11,作為第2個采樣目標,采樣周期55.5個時鐘周期
/* ADC1 regular channel12 configuration */
ADC_RegularChannelConfig(ADC1, ADC_Channel_12, 3, ADC_SampleTime_55Cycles5); //通道12,作為第3個采樣目標,采樣周期55.5個時鐘周期
/* ADC1 regular channel13 configuration */
ADC_RegularChannelConfig(ADC1, ADC_Channel_13, 4, ADC_SampleTime_55Cycles5); //通道13,作為第4個采樣目標,采樣周期55.5個時鐘周期
/* ADC1 regular channel14 configuration */
ADC_RegularChannelConfig(ADC1, ADC_Channel_14, 5, ADC_SampleTime_55Cycles5); //通道14,作為第5個采樣目標,采樣周期55.5個時鐘周期
/* ADC1 regular channel15 configuration */
ADC_RegularChannelConfig(ADC1, ADC_Channel_15, 6, ADC_SampleTime_55Cycles5); //通道15,作為第6個采樣目標,采樣周期55.5個時鐘周期
/* Enable ADC1 DMA */
ADC_DMACmd(ADC1, ENABLE); //使能ADC的DMA
/*使能ADC1外部觸發*/
ADC_ExternalTrigConvCmd(ADC1,ENABLE);
/* Enable ADC1 */
ADC_Cmd(ADC1, ENABLE); //使能ADC1
/* 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));
/* Start ADC1 Software Conversion */
ADC_SoftwareStartConvCmd(ADC1, ENABLE); //開始轉換
}
/**
* @brief 配置DMA
* @param None
* @retval : None
*/
void DMA_Config(void)
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //使能DMA1時鐘
/* DMA1 channel1 configuration */
DMA_DeInit(DMA1_Channel1); //指定DMA1通道1
DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address;//設置DMA外設地址
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&ADGetValue;//設置DMA內存地址,ADC轉換結果直接放入該地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //外設設置為數據傳輸的來源
DMA_InitStructure.DMA_BufferSize = (ADC_ChannelCount* ADC1_Count); //DMA緩沖區設置為ADC_ChannelCount* ADC1_Count,有6個通道;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外設地址不增加
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//內存地址增加
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//外設源的數據寬度半字,ADC1_DR的值16位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
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);
/* Enable DMA channel1 */
DMA_Cmd(DMA1_Channel1, ENABLE); //使能DMA通道
//DMA_ITConfig(DMA1_Channel1, DMA_IT_TC, ENABLE); //使能DMA傳輸完成中斷
}
/**
* @brief 初始化ADC1
* @param None
* @retval : None
*/
void ADC1_Init(void)
{
ADC1_GPIO_Config();
ADC1_Mode_Config();
DMA_Config();
TIM1_Mode_Config();//開啟定時器1,產生PWN1外部觸發源
}