進行一些控制操作的時候,
需要透過ADC讀取一些外部sensor資訊。
有時候會遇到sensor狀態不穩,
也因此讀取的數值就會莫名飄動。
在這種時候會需要一些簡單的濾波器。
關於濾波器的技術,一些前輩都寫過了。
例如
以8051為基礎的軟體濾波器操作
http://chamberplus.blogspot.tw/2010/05/ad.html
濾波器的數學樣式
http://blog.xuite.net/juinghuei/twblog/99597535
而在這邊我們使用 STM8 重現軟體濾波器實驗。
首先準備個500K的可變電阻裝在麵包板上,
並且和STM8的AD腳連接起來。
為了避免一些電源的浮動什麼的影響,所以另外接了3.3v 的電源。
在我們選用的這塊核心板,AD 專用電源 VDDA 在 PB7 而 VSSA 在PB6。
基本的系統程式使用前面談過的time system,把要用的功能放入以利操作。
STM8 的ADC有個奇異的特點,就是從ADON 到真正要開始取樣資料的時候,
要再一次下達ADON的指令才會開始。
根據原廠文件 AN2658 內的時序圖
所以必須在初始化設定ADON 位元為1,然後到真正要啟動取樣的時候再設定一次。
我們這裡採用的是 Continuous conversion mode,這樣不用每次都重複設定兩次。
所以啟動ADC是寫成這樣:
添加一個接收 UART 送來的資訊並且判斷動作的程式,方便我們下指令啟動ADC開跑。
在這裡的啟動指令是 $ADC 。
在 main.c 內的主迴圈中,放入收指令判讀確定後的運作程式。
而 ADCCounter 當作取樣總數,在這裡我們總共取100筆。
原始的一階濾波方程式的數學是:
其中K是濾波截止頻率的參數,fs 是取樣頻率的一半、而 fBW 則是頻寬。
由於這是低通濾波器,因此把頻寬當作截止頻率即可。將K事先計算好直接利用。
把計算的數學式減少乘法的運算次數,稍微精簡一下。
根據式子,計算時就是把新取得的ADC值 ADC new ,減去上一次濾波計算的值 f(n-1),乘以K倍之後再加一次上次濾波的值。
但是這個K的範圍是 0 ~ 1 之間,也就是必須動用浮點數了。為了要能夠給沒有乘除法的單晶片利用,我們改為固定範圍的值,也就是進行移位乘除法。
以本次範例ADC來說,我們的取樣頻率是20,fs 就是10。截止頻率目標訂在5Hz的話,經過計算的 K 值為 0.2078。如果用10bit當分母為底,那大約就是 212/1024。
若要採用移位乘除,我們取個接近的數值也就是 256/1024 當作 K 值好了。
再反推算回去,截止頻率座落在 4.41 Hz,也能概略符合需求。
所以再把式子精簡一下:
最後的結果,就是只要右移運算兩次即可。
但因為我們的操作是無號數,所以必須先判讀ADC取值或是上次濾波值誰比較大,大的值減小的值這樣才不會造成號數問題。
副程式 LowPassFilter 則是疊代計算的濾波程式。
這是經過精簡計算後的程式。
濾波程式只需要靠移位和加減法即可完成。
在下指令開啟ADC讀取,我們旋轉可變電阻記錄下五秒的資料。
可以看到原始資料與濾波後的結果。
從結果可以看到,濾波後的(紅線)對於短的雜訊可以有抵抗效果。
完整實驗程式已放在 GitHub 。
Read More
需要透過ADC讀取一些外部sensor資訊。
有時候會遇到sensor狀態不穩,
也因此讀取的數值就會莫名飄動。
在這種時候會需要一些簡單的濾波器。
關於濾波器的技術,一些前輩都寫過了。
例如
以8051為基礎的軟體濾波器操作
http://chamberplus.blogspot.tw/2010/05/ad.html
濾波器的數學樣式
http://blog.xuite.net/juinghuei/twblog/99597535
而在這邊我們使用 STM8 重現軟體濾波器實驗。
首先準備個500K的可變電阻裝在麵包板上,
並且和STM8的AD腳連接起來。
為了避免一些電源的浮動什麼的影響,所以另外接了3.3v 的電源。
在我們選用的這塊核心板,AD 專用電源 VDDA 在 PB7 而 VSSA 在PB6。
基本的系統程式使用前面談過的time system,把要用的功能放入以利操作。
STM8 的ADC有個奇異的特點,就是從ADON 到真正要開始取樣資料的時候,
要再一次下達ADON的指令才會開始。
根據原廠文件 AN2658 內的時序圖
所以必須在初始化設定ADON 位元為1,然後到真正要啟動取樣的時候再設定一次。
我們這裡採用的是 Continuous conversion mode,這樣不用每次都重複設定兩次。
所以啟動ADC是寫成這樣:
void ADCInit(void)
{
ADC1->CR1=0x03; // fADC=fMaster/2, CONT=1, ADON=1
ADC1->CR2=0x08; // Right alignment
ADC1->CR3=0x00;
ADC1->CSR=0x00;
ADC1->TDRL = 0x01; // PB0 Schmitt trigger disable
GPIOB->DDR &= 0xFE; // PB0 output mode
GPIOB->CR1 &= 0xFE; // PB0 floating input
GPIOB->CR2 &= 0xFE; // External interrupt disable
}
對應的AD讀取腳為PB0,必須將 Schmitt trigger 關掉,並且定為 floating input 。添加一個接收 UART 送來的資訊並且判斷動作的程式,方便我們下指令啟動ADC開跑。
void RXProcess(unsigned char *p)
{
if((*p)=='$')
{
SysFlag.RXDone=1;
if((*(p+1)=='A')&&(*(p+2)=='D')&&(*(p+3)=='C'))
{
SysFlag.ADCGo=1;
}
}
else
{
SysFlag.RXDone=0;
SysFlag.ADCGo=0;
}
}
這是將收資料矩陣的指標位置傳入來做處理。當確定指令有效時更動對應旗標。在這裡的啟動指令是 $ADC 。
在 main.c 內的主迴圈中,放入收指令判讀確定後的運作程式。
if((SysFlag.RXDone)&&(SysFlag.ADCGo))
{
unsigned int ADCvalue,FiltedADCvalue;
if(ADCTimer>5)
{
ADC1->CR1 |=0x01;
while(((ADC1->CSR)&0x80)!=0x80);
ADC1->CSR &= 0x7F;
ADCvalue=(((unsigned int)ADC1->DRH)<<8) | ((unsigned int)ADC1->DRL);
FiltedADCvalue = LowPassFilter(ADCvalue);
OldFilterValue=FiltedADCvalue;
printf("%d,%d,\n",ADCvalue,FiltedADCvalue);
ADCCounter++;
ADCTimer=ADCTimer-5;
}
if(ADCCounter==100)
{
//__asm__("bres _SysFlag,#1\n");
ADC1->DRH=0;ADC1->DRL=0;OldFilterValue=0;
SysFlag.RXDone=0;
SysFlag.ADCGo=0;
RXIndex=0;
ADCCounter=0;
}
}
其中 ADCTimer 被添加到時間中斷中計時,這樣可以做成每50mS取樣一次。而 ADCCounter 當作取樣總數,在這裡我們總共取100筆。
原始的一階濾波方程式的數學是:
其中K是濾波截止頻率的參數,fs 是取樣頻率的一半、而 fBW 則是頻寬。
由於這是低通濾波器,因此把頻寬當作截止頻率即可。將K事先計算好直接利用。
把計算的數學式減少乘法的運算次數,稍微精簡一下。
根據式子,計算時就是把新取得的ADC值 ADC new ,減去上一次濾波計算的值 f(n-1),乘以K倍之後再加一次上次濾波的值。
但是這個K的範圍是 0 ~ 1 之間,也就是必須動用浮點數了。為了要能夠給沒有乘除法的單晶片利用,我們改為固定範圍的值,也就是進行移位乘除法。
以本次範例ADC來說,我們的取樣頻率是20,fs 就是10。截止頻率目標訂在5Hz的話,經過計算的 K 值為 0.2078。如果用10bit當分母為底,那大約就是 212/1024。
若要採用移位乘除,我們取個接近的數值也就是 256/1024 當作 K 值好了。
再反推算回去,截止頻率座落在 4.41 Hz,也能概略符合需求。
所以再把式子精簡一下:
最後的結果,就是只要右移運算兩次即可。
但因為我們的操作是無號數,所以必須先判讀ADC取值或是上次濾波值誰比較大,大的值減小的值這樣才不會造成號數問題。
副程式 LowPassFilter 則是疊代計算的濾波程式。
unsigned int LowPassFilter(unsigned int NewADCValue)
{
unsigned int FiltedValue;
/*
half Sampling rate=10, BW = 4.41Hz
Filter factor
= exp(-pi*BW / Fs)
= exp (-3.14*4.4/10) = 0.251
= 256/1024 = (2^8) / (2^10)
right shift two times
*/
if(NewADCValue < OldFilterValue)
{
FiltedValue=OldFilterValue-NewADCValue;
FiltedValue=FiltedValue>>2;
FiltedValue=OldFilterValue-FiltedValue;
}
else if(NewADCValue > OldFilterValue)
{
FiltedValue=NewADCValue-OldFilterValue;
FiltedValue=FiltedValue>>2;
FiltedValue=OldFilterValue+FiltedValue;
}
return FiltedValue;
}
這是經過精簡計算後的程式。
濾波程式只需要靠移位和加減法即可完成。
在下指令開啟ADC讀取,我們旋轉可變電阻記錄下五秒的資料。
可以看到原始資料與濾波後的結果。
從結果可以看到,濾波後的(紅線)對於短的雜訊可以有抵抗效果。
完整實驗程式已放在 GitHub 。