2016年9月9日 星期五

Published 晚上7:13 by with 0 comment

[實驗] STM8與低通濾波器

進行一些控制操作的時候,
需要透過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



      edit

0 意見:

張貼留言