L3G4200D + ADXL345 卡尔曼滤波
小车下面就是 L3G4200D + ADXL345 两个模块,加速度模块没固定好,板子太小了没地方打孔;
加速度模块角度计算:
如果要换算成具体角度: angle = atan2(y, z) * (180/3.14)
如: actan(1,sqrt(3)) * 180 / PI = 30°
陀螺仪角度计算:
网上找的kalman滤波,具体代码如下 :
1 static const float dt = 0.02; 2 3 static float P[2][2] = {{ 1, 0 }, { 0, 1 }}; 4 5 float angle; 6 float q_bias; 7 float rate; 8 9 static const float R_angle = 0.5 ; 10 static const float Q_angle = 0.001; 11 static const float Q_gyro = 0.003; 12 13 float stateUpdate(const float gyro_m){ 14 15 float q; 16 float Pdot[4]; 17 q = gyro_m - q_bias; 18 Pdot[0] = Q_angle - P[0][1] - P[1][0]; /* 0,0 */ 19 Pdot[1] = - P[1][1]; /* 0,1 */ 20 Pdot[2] = - P[1][1]; /* 1,0 */ 21 Pdot[3] = Q_gyro; /* 1,1 */ 22 23 rate = q; 24 25 26 angle += q * dt; 27 28 P[0][0] += Pdot[0] * dt; 29 P[0][1] += Pdot[1] * dt; 30 P[1][0] += Pdot[2] * dt; 31 P[1][1] += Pdot[3] * dt; 32 33 return angle; 34 } 35 36 float kalmanUpdate(const float incAngle) 37 { 38 39 float angle_m = incAngle; 40 float angle_err = angle_m - angle; 41 42 43 float h_0 = 1; 44 45 const float PHt_0 = h_0*P[0][0]; /* + h_1*P[0][1] = 0*/ 46 const float PHt_1 = h_0*P[1][0]; /* + h_1*P[1][1] = 0*/ 47 48 float E = R_angle +(h_0 * PHt_0); 49 50 float K_0 = PHt_0 / E; 51 float K_1 = PHt_1 / E; 52 53 float Y_0 = PHt_0; /*h_0 * P[0][0]*/ 54 float Y_1 = h_0 * P[0][1]; 55 56 P[0][0] -= K_0 * Y_0; 57 P[0][1] -= K_0 * Y_1; 58 P[1][0] -= K_1 * Y_0; 59 P[1][1] -= K_1 * Y_1; 60 61 62 angle += K_0 * angle_err; 63 q_bias += K_1 * angle_err; 64 65 return angle;
http://www.cnblogs.com/relax/archive/2012/01/14/2322290.html
制作之前我们需要对陀螺仪 + 加速度计 进行测试,看我们获取的角度数据是否满足要求。网上常用的方法是使用卡尔曼滤波将陀螺仪和加速度计的数据进行融合而得到一个相对稳定正确的角度值,具体方法在我前面的文章中提到过:L3G4200D + ADXL345 卡尔曼滤波
获取到角度以后需要找到小车的平衡点,也就是无外力作用的时候小车能够立在地面上的角度: 角度差 = 小车角度 - 平衡点角度。
用小车角度数据结合当前的倾斜目标值,通过PID运算,得出电机PWM脉宽数据,指挥电机运行即可。
PID算法相对比较简单,而且arduino有现成的PID libraries : http://arduino.cc/playground/Code/PIDLibrary
PID::PID(double* Input, double* Output, double* Setpoint, double Kp, double Ki, double Kd, int ControllerDirection) { PID::SetOutputLimits(0, 255); //default output limit corresponds to //the arduino pwm limits SampleTime = 100; //default Controller Sample Time is 0.1 seconds PID::SetControllerDirection(ControllerDirection); PID::SetTunings(Kp, Ki, Kd); lastTime = millis()-SampleTime; inAuto = false; myOutput = Output; myInput = Input; mySetpoint = Setpoint; }
PID LIB的参数分别是这样的:
Input 输入值(这里输入卡尔曼融合获取的角度值)
Output PID计算的结果,供电机驱动的PWM使用
Setpoint 期望值(这里输入小车平衡点的角度值)
Kp、Ki、Kd 这是KPI的三个重要参数
这三个参数的详细说明我从网上摘录了一段:
在微分控制中,控制器的输出与输入误差信号的微分(即误差的变化率)成正比关系。自动控制系统在克服误差的调节过程中可能会出现振荡甚至失稳。其原因是由于存在有较大惯性组件(环节)或有滞后(delay)组件,具有抑制误差的作用,其变化总是落后于误差的变化。解决的办法是使抑制误差的作用的变化“超前”,即在误差接近零时,抑制误差的作用就应该是零。这就是说,在控制器中仅引入 “比例”项往往是不够的,比例项的作用仅是放大误差的幅值,而目前需要增加的是“微分项”,它能预测误差变化的趋势,这样,具有比例+微分的控制器,就能够提前使抑制误差的控制作用等于零,甚至为负值,从而避免了被控量的严重超调。所以对有较大惯性或滞后的被控对象,比例+微分(PD)控制器能改善系统在调节过程中的动态特性。
1 PID myPID(&Input, &Output, &Setpoint,2,5,1, DIRECT); //PID对象声明 2 3 4 setupPID(); //PID初始化 5 6 .... 7 Kalman_Filter(Adxl_angle, Gyro_sensor); //卡尔曼融合获取angle 8 Input = angle; 9 myPID.Compute(); //PID计算获取 Output 10 Drive(Output); //根据Output驱动电机 11 12 void setupPID(){ 13 Input = 0; 14 Setpoint = 17; //我的小车自平衡角度为17 15 myPID.SetSampleTime(100); //控制器的采样时间100ms 16 //myPID.SetOutputLimits(0, 2000); 17 myPID.SetMode(AUTOMATIC); 18 }
如果你做完这些小车也能成功站起来了,我的小车抖动得比较厉害,是因为我的直流电机减速太多了(减速比1:220的单轴电机),而且PID的kp,ki,kd三个参数没调整好。等有时间换个电机再仔细调整一下参数,最好能做成可以控制前景、后退、转弯的小车。 弄个体积大点的就能骑着上下班了,哈。
http://www.cnblogs.com/relax/archive/2012/01/06/2313935.html
#define DEBUG 0 // set to 1 to print to serial monitor, 0 to disable #include <Servo.h> Servo headservo; // 头部舵机对象 // Constants const int EchoPin = 2; //超声波信号输入 const int TrigPin = 3; //超声波控制信号输出 const int leftmotorpin1 = 4; // 直流电机信号输出 const int leftmotorpin2 = 5; const int rightmotorpin1 = 6; const int rightmotorpin2 = 7; const int HeadServopin = 9; // 舵机信号输出 只有9或10接口可利用 const int Sharppin = 11; // 红外输入 const int maxStart = 800; //run dec time // Variables int isStart = maxStart; //启动 int currDist = 0; // 距离 boolean running = false; void setup() { Serial.begin(9600); // 开始串行监测 //信号输入接口 pinMode(EchoPin, INPUT); pinMode(Sharppin, INPUT); //信号输出接口 for (int pinindex = 3; pinindex < 11; pinindex++) { pinMode(pinindex, OUTPUT); // set pins 3 to 10 as outputs } //舵机接口 headservo.attach(HeadServopin); //启动缓冲活动头部 headservo.write(70); delay(2000); headservo.write(20); delay(2000); } void loop() { if(DEBUG){ Serial.print("running:"); if(running){ Serial.println("true"); } else{ Serial.println("false"); } } if (isStart <= 0) { if(running){ totalhalt(); // stop! } int findsomebody = digitalRead(Sharppin); if(DEBUG){ Serial.print("findsomebody:"); Serial.println(findsomebody); } if(findsomebody > 0) { isStart = maxStart; } delay(4000); return; } isStart--; delay(100); if(DEBUG){ Serial.print("isStart: "); Serial.println(isStart); } currDist = MeasuringDistance(); //读取前端距离 if(DEBUG){ Serial.print("Current Distance: "); Serial.println(currDist); } if(currDist > 30) { nodanger(); } else if(currDist < 15){ backup(); randTrun(); } else { //whichway(); randTrun(); } } //测量距离 单位厘米 long MeasuringDistance() { long duration; //pinMode(TrigPin, OUTPUT); digitalWrite(TrigPin, LOW); delayMicroseconds(2); digitalWrite(TrigPin, HIGH); delayMicroseconds(5); digitalWrite(TrigPin, LOW); //pinMode(EchoPin, INPUT); duration = pulseIn(EchoPin, HIGH); return duration / 29 / 2; } // 前进 void nodanger() { running = true; digitalWrite(leftmotorpin1, LOW); digitalWrite(leftmotorpin2, HIGH); digitalWrite(rightmotorpin1, LOW); digitalWrite(rightmotorpin2, HIGH); return; } //后退 void backup() { running = true; digitalWrite(leftmotorpin1, HIGH); digitalWrite(leftmotorpin2, LOW); digitalWrite(rightmotorpin1, HIGH); digitalWrite(rightmotorpin2, LOW); delay(1000); } //选择路线 void whichway() { running = true; totalhalt(); // first stop! headservo.write(20); delay(1000); int lDist = MeasuringDistance(); // check left distance totalhalt(); // 恢复探测头 headservo.write(120); // turn the servo right delay(1000); int rDist = MeasuringDistance(); // check right distance totalhalt(); // 恢复探测头 if(lDist < rDist) { body_lturn(); } else{ body_rturn(); } return; } //重新机械调整到初始状态 void totalhalt() { digitalWrite(leftmotorpin1, HIGH); digitalWrite(leftmotorpin2, HIGH); digitalWrite(rightmotorpin1, HIGH); digitalWrite(rightmotorpin2, HIGH); headservo.write(70); // set servo to face forward running = false; return; delay(1000); } //左转 void body_lturn() { running = true; digitalWrite(leftmotorpin1, LOW); digitalWrite(leftmotorpin2, HIGH); digitalWrite(rightmotorpin1, HIGH); digitalWrite(rightmotorpin2, LOW); delay(1500); totalhalt(); } //右转 void body_rturn() { running = true; digitalWrite(leftmotorpin1, HIGH); digitalWrite(leftmotorpin2, LOW); digitalWrite(rightmotorpin1, LOW); digitalWrite(rightmotorpin2, HIGH); delay(1500); totalhalt(); } void randTrun(){ long randNumber; randomSeed(analogRead(0)); randNumber = random(0, 10); if(randNumber > 5) { body_rturn(); } else { body_lturn(); } }