卡尔曼滤波+PID 源代码-Arduino中文社区 - Powered by Discuz!

Arduino中文社区

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 9810|回复: 5

[未解决] 卡尔曼滤波+PID 源代码

[复制链接]
发表于 2017-3-25 09:02 | 显示全部楼层 |阅读模式
先前研究arduino +MPU6050用卡尔曼滤波和PID处理,现在得到了初步成果
感谢一下坛友的文章:YWD19890423的  形象解释PID算法+PID算法源代码
博友ling3ye    的  http://blog.csdn.net/ling3ye/article/details/51360568
极客工坊中  johnsonzzd  前辈的   http://www.geek-workshop.com/thread-2471-1-1.html






另,极客工坊是一个很好的网站,大家如果在本社区内无法找到的东西可以去那里看看






#include <MPU6050.h>


#include <I2Cdev.h>

#include <MPU6050.h>







#include "Wire.h"



typedef struct PID_Value
{
  double liEkVal[3];   //差值保存,给定和反馈的差值
  char Flag[3];
  double KP = 1; //比例系数
  double KI = 1; //积分常数
  double KD = 1; //微分常数
  double PriVal;
  double CurVal;
  double SetVal = 0;
} PID_ValueStr;

PID_ValueStr PID;//此处缺少让pid隔一段时间运行一次的程序

double input;
int pinId = 1; //定义接口
int pinIc = 2;
int pinIb = 3;
int pinIa = 4;
int ENA = 6;
int ENB = 5;

double PID_Operation(double input)
{
  PID.CurVal = input;
  double Temp[3] = {0};   //中间临时变量
  double PostSum = 0;     //正数和
  double NegSum = 0;      //负数和
  if (PID.SetVal > PID.CurVal)               //设定值大于实际值否?
  {
    if (PID.SetVal - PID.CurVal > 10)     //偏差大于10否?
      PID.PriVal = 100;                  //偏差大于10为上限幅值输出(全速加热)
    else                                    //否则慢慢来
    {
      Temp[0] = PID.SetVal - PID.CurVal;    //偏差<=10,计算E(k)
      PID.Flag[1] = 0;                     //E(k)为正数,因为设定值大于实际值
      //数值进行移位,注意顺序,否则会覆盖掉前面的数值
      PID.liEkVal[2] = PID.liEkVal[1];
      PID.liEkVal[1] = PID.liEkVal[0];
      PID.liEkVal[0] = Temp[0];
      // ===================================================================
      if (PID.liEkVal[0] > PID.liEkVal[1])             //E(k)>E(k-1)否?
      {
        Temp[0] = PID.liEkVal[0] - PID.liEkVal[1];  //E(k)>E(k-1)
        PID.Flag[0] = 0;                         //E(k)-E(k-1)为正数
      }
      else
      {
        Temp[0] = PID.liEkVal[1] - PID.liEkVal[0];  //E(k)<E(k-1)
        PID.Flag[0] = 1;                         //E(k)-E(k-1)为负数
      }
      // ===================================================================
      Temp[2] = PID.liEkVal[1] * 2;                   //2E(k-1)
      if ((PID.liEkVal[0] + PID.liEkVal[2]) > Temp[2]) //E(k-2)+E(k)>2E(k-1)否?
      {
        Temp[2] = (PID.liEkVal[0] + PID.liEkVal[2]) - Temp[2];
        PID.Flag[2] = 0;                         //E(k-2)+E(k)-2E(k-1)为正数
      }
      else                                            //E(k-2)+E(k)<2E(k-1)
      {
        Temp[2] = Temp[2] - (PID.liEkVal[0] + PID.liEkVal[2]);
        PID.Flag[2] = 1;                         //E(k-2)+E(k)-2E(k-1)为负数
      }
      // ===================================================================
      Temp[0] = PID.KP * Temp[0];        //KP*[E(k)-E(k-1)]
      Temp[1] = PID.KI * PID.liEkVal[0]; //KI*E(k)
      Temp[2] = PID.KD * Temp[2];        //KD*[E(k-2)+E(k)-2E(k-1)]
      // 以下部分代码是讲所有的正数项叠加,负数项叠加
      // ========= 计算KP*[E(k)-E(k-1)]的值 =========
      if (PID.Flag[0] == 0)
        PostSum += Temp[0];                         //正数和
      else
        NegSum += Temp[0];                          //负数和
      // ========= 计算KI*E(k)的值 =========
      if (PID.Flag[1] == 0)
        PostSum += Temp[1];                         //正数和
      else
        ;   /* 空操作。就是因为PID.iSetVal > PID.iCurVal(即E(K)>0)才进入if的,
                    那么就没可能为负,所以打个转回去就是了 */
      // ========= 计算KD*[E(k-2)+E(k)-2E(k-1)]的值 =========

      if (PID.Flag[2] == 0)
        PostSum += Temp[2];             //正数和
      else
        NegSum += Temp[2];              //负数和
      /* ========= 计算U(k) ========= */
      PostSum += PID.PriVal;
      if (PostSum > NegSum)                //是否控制量为正数
      {
        Temp[0] = PostSum - NegSum;
        if (Temp[0] < 100 )              //小于上限幅值则为计算值输出
          PID.PriVal = Temp[0];
        else PID.PriVal = 100;         //否则为上限幅值输出
      }
      else                                //控制量输出为负数,则输出0(下限幅值输出)
        PID.PriVal = 0;
    }
  }
  else PID.PriVal = 0;
  double output = PID.PriVal;
  return output;    //同上,嘿嘿
}







/*Constructor (...)*********************************************************
      The parameters specified here are those for for which we can't set up
      reliable defaults, so we need to have the user set them.
***************************************************************************/










MPU6050 accelgyro;

unsigned long now, lastTime = 0;
float dt;                                   //微分时间

int16_t ax, ay, az, gx, gy, gz;             //加速度计陀螺仪原始数据
float aax = 0, aay = 0, aaz = 0, agx = 0, agy = 0, agz = 0; //角度变量
long axo = 0, ayo = 0, azo = 0;             //加速度计偏移量
long gxo = 0, gyo = 0, gzo = 0;             //陀螺仪偏移量

float pi = 3.1415926;
float AcceRatio = 16384.0;                  //加速度计比例系数
float GyroRatio = 131.0;                    //陀螺仪比例系数

uint8_t n_sample = 8;                       //加速度计滤波算法采样个数
float aaxs[8] = {0}, aays[8] = {0}, aazs[8] = {0};         //x,y轴采样队列
long aax_sum, aay_sum, aaz_sum;                     //x,y轴采样和

float a_x[10] = {0}, a_y[10] = {0}, a_z[10] = {0} , g_x[10] = {0} , g_y[10] = {0}, g_z[10] = {0}; //加速度计协方差计算队列
float Px = 1, Rx, Kx, Sx, Vx, Qx;           //x轴卡尔曼变量
float Py = 1, Ry, Ky, Sy, Vy, Qy;           //y轴卡尔曼变量
float Pz = 1, Rz, Kz, Sz, Vz, Qz;           //z轴卡尔曼变量





static int dianji = 0;
static int SampleTime;
static double Input;
static double Drive;
static double Output;
static double Setpoint;








void setup()
{

  Wire.begin();
  Serial.begin(115200);

  accelgyro.initialize();                 //初始化

  unsigned short times = 200;             //采样次数
  for (int i = 0; i < times; i++)
  {
    accelgyro.getMotion6(&ax, &ay, &az, &gx, &gy, &gz); //读取六轴原始数值
    axo += ax; ayo += ay; azo += az;      //采样和
    gxo += gx; gyo += gy; gzo += gz;

  }

  axo /= times; ayo /= times; azo /= times; //计算加速度计偏移
  gxo /= times; gyo /= times; gzo /= times; //计算陀螺仪偏移
  pinMode(pinIa, OUTPUT);
  pinMode(pinIb, OUTPUT);
  pinMode(pinIc, OUTPUT);
  pinMode(pinId, OUTPUT);
}

void loop()
{
  unsigned long now = millis();             //当前时间(ms)
  dt = (now - lastTime) / 1000.0;           //微分时间(s)
  lastTime = now;                           //上一次采样时间(ms)

  accelgyro.getMotion6(&ax, &ay, &az, &gx, &gy, &gz); //读取六轴原始数值

  float accx = ax / AcceRatio;              //x轴加速度
  float accy = ay / AcceRatio;              //y轴加速度
  float accz = az / AcceRatio;              //z轴加速度

  aax = atan(accy / accz) * (-180) / pi;    //y轴对于z轴的夹角
  aay = atan(accx / accz) * 180 / pi;       //x轴对于z轴的夹角
  aaz = atan(accz / accy) * 180 / pi;       //z轴对于y轴的夹角

  aax_sum = 0;                              // 对于加速度计原始数据的滑动加权滤波算法
  aay_sum = 0;
  aaz_sum = 0;

  for (int i = 1; i < n_sample; i++)
  {
    aaxs[i - 1] = aaxs;
    aax_sum += aaxs * i;
    aays[i - 1] = aays;
    aay_sum += aays * i;
    aazs[i - 1] = aazs;
    aaz_sum += aazs * i;

  }

  aaxs[n_sample - 1] = aax;
  aax_sum += aax * n_sample;
  aax = (aax_sum / (11 * n_sample / 2.0)) * 9 / 7.0; //角度调幅至0-90°
  aays[n_sample - 1] = aay;                      //此处应用实验法取得合适的系数
  aay_sum += aay * n_sample;                     //本例系数为9/7
  aay = (aay_sum / (11 * n_sample / 2.0)) * 9 / 7.0;
  aazs[n_sample - 1] = aaz;
  aaz_sum += aaz * n_sample;
  aaz = (aaz_sum / (11 * n_sample / 2.0)) * 9 / 7.0;

  float gyrox = - (gx - gxo) / GyroRatio * dt; //x轴角速度
  float gyroy = - (gy - gyo) / GyroRatio * dt; //y轴角速度
  float gyroz = - (gz - gzo) / GyroRatio * dt; //z轴角速度
  agx += gyrox;                             //x轴角速度积分
  agy += gyroy;                             //x轴角速度积分
  agz += gyroz;

  /* kalman start */
  Sx = 0; Rx = 0;
  Sy = 0; Ry = 0;
  Sz = 0; Rz = 0;

  for (int i = 1; i < 10; i++)
  { //测量值平均值运算
    a_x[i - 1] = a_x;                    //即加速度平均值
    Sx += a_x;
    a_y[i - 1] = a_y;
    Sy += a_y;
    a_z[i - 1] = a_z;
    Sz += a_z;

  }

  a_x[9] = aax;
  Sx += aax;
  Sx /= 10;                                 //x轴加速度平均值
  a_y[9] = aay;
  Sy += aay;
  Sy /= 10;                                 //y轴加速度平均值
  a_z[9] = aaz;
  Sz += aaz;
  Sz /= 10;

  for (int i = 0; i < 10; i++)
  {
    Rx += sq(a_x - Sx);
    Ry += sq(a_y - Sy);
    Rz += sq(a_z - Sz);

  }

  Rx = Rx / 9;                              //得到方差
  Ry = Ry / 9;
  Rz = Rz / 9;

  Px = Px + 0.0025;                         // 0.0025在下面有说明...
  Kx = Px / (Px + Rx);                      //计算卡尔曼增益
  agx = agx + Kx * (aax - agx);             //陀螺仪角度与加速度计速度叠加
  Px = (1 - Kx) * Px;                       //更新p值

  Py = Py + 0.0025;
  Ky = Py / (Py + Ry);
  agy = agy + Ky * (aay - agy);
  Py = (1 - Ky) * Py;

  Pz = Pz + 0.0025;
  Kz = Pz / (Pz + Rz);
  agz = agz + Kz * (aaz - agz);
  Pz = (1 - Kz) * Pz;





  input = agx;





  Serial.println (PID_Operation(input));









  if (agx > 5)
  {
    dianji = dianji - 1;
    delay(20);
  }
  if (agx < -5)
  {
    dianji = dianji + 1;
    delay(20);
  }
  if (agx > -5 && agx < 5)
  {
    dianji = 90;
  }


  /* kalman end */
  Serial.print(agx); Serial.print(",");
  Serial.print(agy); Serial.print(",");
  Serial.print(agz); Serial.println();

}






这个版本还比较粗糙,之后会在帖子后面再发最后的完成稿
 楼主| 发表于 2017-3-25 09:07 | 显示全部楼层
另,我先前看到好几个人问为什么自己没有办法加载i2cdevlib“库”,这里给出我的解答
首先:i2cdevlib不是一个库!
它是一个库的集合
其次:github上下载的i2cdevlib中打开会有这个界面
1.png
 楼主| 发表于 2017-3-25 09:08 | 显示全部楼层
打开arduino那个文件夹
2.png
 楼主| 发表于 2017-3-25 09:09 | 显示全部楼层
里面会有一个“I2Cdev”文件夹
这才是I2Cdev库
发表于 2019-8-4 21:48 | 显示全部楼层
大佬有没有完善的代码链接?还有想问一下如何同时写控制4个电机的pid代码,就是2015年风力摆代码用arduino写,如果有空,跪求大佬写一下。
发表于 2022-7-27 09:29 | 显示全部楼层
那个卡尔曼滤波+pid有没有最新的程序呀
跪求
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|Archiver|手机版|Arduino中文社区

GMT+8, 2024-12-29 23:43 , Processed in 0.088542 second(s), 19 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表