Tuesday, July 5, 2011

Balancing robot with Kalman filter














จากครั้งที่แล้วหามุมการเอียงของหุ่นย์โดยใช้วิธี complementary filter พบว่าเมื่อหุ่นยนต์ run ไปนานๆแล้วมุมที่อ่านได้จาก sensor มีความผิดพลาดเกิดขึ้นเนื่องจากสัญญาณที่ได้เกิดการ drift เกิดขึ้นที่ gyro sensor ดังนั้นผมเลยเปลี่ยนวิธีในการหามุมเอียงใหม่โดยใช้วิธี sensor fusion kalman filter โดยมีขั้นตอนดังนี้ เริ่มจากการเขียน state-space ของ Predict model เสียก่อน  เรานิยามว่ามุมที่ได้จาก gyro ก็คือผลของการ Integrate สัญญาณจาก gyro นั้นเองและเรายังสามารถนิยาม bias ของสัญญาณขึ้นมาอีกตัวหนึ่งเพื่อที่จะทำการ tracking ค่า bias ให้มีค่าคงที่

หาสมการ  Kalman Predict

Kalman predict equation

x_dot=Ax+Bu
P=APA'+Q;
//***********************************************************************************//

theta_dot=(gyro-bias)    ...........................................................................(1)
bias_dot=0

จากสมการ (1) เรานิยาม state variable ดังนี้ x=[theta;bias]
จากนั้นให้เราหา jacobian matrix ก็คือการทำ partial derivative สมการ (1) เทียบกับ [theta;bias] จะได้ Matrix A,B ดังนี้

A=[0 -1;0 0],  B=[1;0]

เมื่อนำมาเขียนให้อยู่ในรูปของ state-space เราจะได้ดังนี้ จาก
x_dot=A*x+B*u

x_dot=[0 -1;0 0]*x+[1;0]*gyro   ...............................(2)


จะเห็นได้ว่า (2) ยังเป็นสมการที่อยู่ในรูปของระบบแบบ continuous  อยู่แต่ในทางปฎิบัติจริงส่วนมากแล้วเราจะใช้พวก MCU ดังนั้น เราจึง Transform ไปสู่ Discrete model เราจะได้
x_dot=(x(n+1)-x(n))/dt=[0 -1;0 0]*x+[1;0]*gyro;


x1_(n+1)=x1(n)+dt*(gyro-x2(n)) .............................................(3)
x2(n+1)=x2(n)


Ad=[1 -dt;0 1];
Bd=[dt;0];


gyro ก็คือค่า ที่อ่านจาก sensor
x1 คือ theta
x2 คือ bias
dt คือ sampling time

จากนั้นจะคำนวนหา Covariance matrix P จากสมการ
P(n+1)=Ad*P(n)*Ad'+Q    ..........................................(4)

โดยที่
Q=[Q_accelerometer 0;0 Q_gyro];

เมื่อคูณ  Matrix แล้วได้
P(n+1)=[P00 - dt P10 - (P01 - dt P11) dt + Q_accelerometer , P01 - dt P11;
               P10 -dt P11,  P11 + Q_gyro];

P00(n+1)=P00 - dt P10 - (P01 - dt P11) dt + Q_accelerometer ;   .......................(5)
P01(n+1)=P01 - dt P11;
P10(n+1)=P10 -dt P11;
P11(n+1)= P11 + Q_gyro;



จากนั้นพิจรณา Measurement Update
Measurement Update equation:
K=PH'inv(HPH'+R).........................................(6)
P=(I-KH)P......................................................(7)
x=x+K(y-Hx)...................................................(8)

H คือ measure output matrix
y คือ measure sensor
R measurement noise matrix

H=[1 0]; เนื่องจากเราวัดมุมได้อย่างเดียวนั้นคือ accelerometer
P=[P00 P01;P10 P11]

คูณ (6) ได้


K1=P00/(P00+R);
K2=P10/(P00+R);

คูณ (8) ได้


x1=x1+K1*(y-x1);
x2=x2+K2*(y-x2);

คูณ (7) ได้


P00=(1-K1)*P00;
P01=(1-K1)*P01;
P10=-K2*P00+P10;
P11=-K2*P01+P11;

เป็นอันเรียบร้อยในการทำ Kalman filter sensor fusion

จากนั้นเราก็จะนำสมการด้านบนทั้งหมดมาเขียนโปรมแกรมและโหลดลงใน Micro-conroller (CCS Compiler)

***********************************  CODE ****************************************
#include <18f458.h>
#device adc=10
#fuses H4, NOWDT, NOPROTECT, BROWNOUT, PUT, NOLVP
#use delay(clock=40000000)
#define  TxD         PIN_C6
#define  RxD         PIN_C7
#use rs232(baud=115200, xmit=TxD,rcv=RxD)
#include
#include
#include
#include


#use rtos(timer=0,minor_cycle=10ms)




float x[2][1]={{0.47},{0.0}};
float u[1][1];
float P[2][2]={{.01*.01*0.00000135 0 },{0}};
float K[2][1];
float x1=0.4656;



 float Q_roll=0.00000135,Q_roll_bias=.183;
const float R=0.028;
float dt=0.010;
 float fc=1.765;


float roll();
float qm();



float gyro;
float qm(){

set_ADC_channel(0);
delay_cycles(60);
gyro=(float)read_adc()*1/1023;
return(gyro);
}



float ax;
float roll(){

set_ADC_channel(2);
delay_cycles(60);
ax=atan((float)read_adc()*1/1023);
return(ax);
}





// ****************************** LPF ****************************************//
void LPF(){

x1+=dt*(-2*pi*fc*x1+2*pi*fc*ax);




}

float fhc=0.01;
float gyro1_old=0.34,tou,a,y=0;

void HPF(){
tou=1/(2*pi*fhc);
a=tou/(tou+dt);
y=a*y+a*(gyro-gyro1_old);



gyro1_old=gyro;

}

// ************************* KALMAN PREDICT **********************************//
void Kalman_Predict(float qm){
HPF();
   u[0][0]=y;
 
   x[0][0]=x[0][0]+dt*u[0][0]-dt*x[1][0];
   x[1][0]=x[1][0];
 
/* P=A*P*P'+Q */
 
P[0][0]=P[0][0]-dt*P[1][0]-(P[0][1]-dt*P[1][1])*dt+Q_roll;
P[0][1]=P[0][1]-dt*P[1][1];
P[1][0]=P[1][0]-dt*P[1][1];
P[1][1]=P[1][1]+Q_roll_bias;
 
}

//************************ KALMAN MEASURE ************************************//

void Kalman_Measurement(float roll){
LPF();

K[0][0]=P[0][0]/(P[0][0]+R);
K[1][0]=P[1][0]/(P[0][0]+R);

x[0][0]+=K[0][0]*(x1-x[0][0]);
x[1][0]+=K[1][0]*(x1-x[0][0]);

P[0][0]=(1-K[0][0])*P[0][0];
P[0][1]=(1-K[0][0])*P[0][1];
P[1][0]=-K[1][0]*P[0][0]+P[1][0];
P[1][1]=-K[1][0]*P[0][1]+P[1][1];

}



//****************************************************************************//

#task(rate=10ms,max=10ms)
void MAIN_LOOP(){
 
   Kalman_Predict(qm());
   Kalman_measurement(roll());


            }

#task(rate=10ms,max=10ms)
void print(){

printf("%.4f %.4f %.4f %.4f\r\n",ax,x[0][0],K[0][0],K[1][0]);
}


void main(){
    setup_port_a(0x07);
    setup_adc(adc_clock_internal);
    enable_interrupts(GLOBAL);

while(TRUE)

    {
           rtos_run();

     }

}


สนใจติดต่อ : sarayuthassa@gmail.com
                  083-4391356







5 comments:

golf said...

อยากขอคำปรึกษาเกี่ยวกับ kalman ไม่ทราบว่าขอเบอร์ติดต่อได้มั้ยครับ
หรือโทรมาได้ 08 999 11 798 ครับ รบกวนด้วยนะครับ

ME KMUTT said...

mail:sarayut_mec@hotmail.com คุยกันทางนี้ก็ได้นะครับ

golf said...

ส่งเมล์ไปหาแล้วนะครับ

Anonymous said...

คือผมขอถามอะไรหน่อยนะครับ ทำไมต้องทำ low pass กับ acc และ high pass กับ gyro อ่าครับ แล้วถ้าไม่ใช้จะเกิดอะไรขึ้นอ่าครับ

ME KMUTT said...

เนื่องจากเราทราบว่า accelerometer นั้นมันมีการตอบสนองต่อสัญญาณที่มีความถี่สูง(เพราะเราไม่ได้ต้องการสัญญาณความถี่สูง)ซึ่งอาจจะเกิดขึ้นจากระบบดังนั้นนเราจึงกรองเอาความถึี่สูงๆออกไปเสียก่อนตั้งแต่แรก และในส่วนของ gyro เราก็ทราบว่าเมื่อเวลาผ่านไปนานๆสัญญาณจะมีการ diff เกิดขึ้นเมื่อขณะที่ sensor ไม่มีการเคลื่อนไหว ดังนั้นผมก็เลยใส่ High-pass เข้าไปเพราะเรารู้ว่า high pass มันเป็น DC- coupling ตัวหนึ่ง และจากการทดลองผลก็ออกมาน่าพอใจครับ

Post a Comment