学习记录:自平衡莱洛三角形v1(原理,代码)学习记录(三)(完)

 来源:

v1版本::【开源】百元内可自制的自平衡莱洛三角形_哔哩哔哩_bilibili

v2版本: 【开源】自平衡莱洛三角形V2 | 我加了RGB性能翻倍_哔哩哔哩_bilibili

原作者开源链接:https://gitee.com/coll45/foc

说明:第一次接触Arduino和ESP32

参考文件:

灯哥开源FOC:【发布】不足百元!双路无刷电机驱动器 -灯哥双路无刷FOC驱动板正式开源!支持四足机器人!_哔哩哔哩_bilibili

simpleFOC库

KalmanFilter

FOC算法入门:FOC算法入门_梦如南伐的博客-CSDN博客_foc

爱转的光凌 CH32读取MPU6050姿态数据(卡尔曼滤波法)_哔哩哔哩_bilibili

分析loop函数问题

        由几个部分组成,读数据、算数据、写数据、写FOC控制、写wifi参数。

        理解起来不是很难,主要和硬件电路结合来说就这么几个功能。软件实现比较简单,参考开源FOC和基于灯哥的开源FOC控制的倒立摆项目就可以理解FOC的控制。kalman基本上就是给mpu6050处理数据用的,这一点可参考CH32读取MPU6050姿态数据(卡尔曼滤波法)_哔哩哔哩_bilibili

motor.loopFOC();
  if (1)
  {
    //读MPU6050的数据吧
    //============================================================================== 
    while (i2cRead(0x3B, i2cData, 14));      
    //读14位正好读完所需要值
        /*
         * mpu6050寄存器地址
         
       0X3B  //加速度值,X轴高8位寄存器
       0X3C  //加速度值,X轴低8位寄存器
       0X3D  //加速度值,Y轴高8位寄存器
       0X3E  //加速度值,Y轴低8位寄存器
       0X3F  //加速度值,Z轴高8位寄存器
       0X40  //加速度值,Z轴低8位寄存器
    
       0X41  //温度值高八位寄存器
       0X42  //温度值低8位寄存器
    
       0X43  //陀螺仪值,X轴高8位寄存器
       0X44  //陀螺仪值,X轴低8位寄存器
       0X45  //陀螺仪值,Y轴高8位寄存器
       0X46  //陀螺仪值,Y轴低8位寄存器
       0X47  //陀螺仪值,Z轴高8位寄存器
       0X48  //陀螺仪值,Z轴低8位寄存器
           
        */
    accX = (int16_t)((i2cData[0] << 8) | i2cData[1]);
    accY = (int16_t)((i2cData[2] << 8) | i2cData[3]);
    accZ = (int16_t)((i2cData[4] << 8) | i2cData[5]);
    tempRaw = (int16_t)((i2cData[6] << 8) | i2cData[7]);
    gyroX = (int16_t)((i2cData[8] << 8) | i2cData[9]);
    gyroY = (int16_t)((i2cData[10] << 8) | i2cData[11]);
    gyroZ = (int16_t)((i2cData[12] << 8) | i2cData[13]);
    
    double dt = (double)(micros() - timer) / 1000000; //计算增量时间,微秒算秒
    timer = micros();
    
    double pitch = acc2rotation(accX, accY);    // mpu6050加速度转换为角度函数
    double gyroZrate = gyroZ / 131.0;           //   转换为速度
    
    if(abs(pitch-last_pitch)>100)     //abs():绝对值函数
      kalmanZ.setAngle(pitch);      //用于设置角度,应将其设置为起始角度
 
    kalAngleZ = kalmanZ.getAngle(pitch, gyroZrate + gyroZ_OFF, dt);
                        //角度应以度为单位,速率应以度/秒为单位,增量时间应以秒为单位 
    
    last_pitch = pitch;
    
    gyroZangle += (gyroZrate + gyroZ_OFF) * dt;       //使用陀螺仪计算角度
    compAngleZ = 0.93 * (compAngleZ + (gyroZrate + gyroZ_OFF) * dt) + 0.07 * pitch;                 
                                                    //使用互补滤波器计算角度 

                //当陀螺仪角度漂移过大时,重置陀螺仪角度
    if (gyroZangle < -180 || gyroZangle > 180)
      gyroZangle = kalAngleZ;     //kalAngleZ:用卡尔曼滤波器计算角度
      
    float pendulum_angle = constrainAngle(fmod(kalAngleZ,120)-target_angle);
                                    // 将角度限制在-60~60之间的函数
    //============================================================================== 
    //控制部分
    //==============================================================================
            //pendulum_angle当前角度与期望角度差值,在差值大的时候进行摇摆,
            //差值小的时候LQR控制电机保持平衡
    if(test_flag == 0)//正常控制
    {
      if (abs(pendulum_angle) < swing_up_angle) 
                        // if angle small enough stabilize 0.5~30°,1.5~90°
      {
        target_velocity = controllerLQR(pendulum_angle, gyroZrate,         
                                            motor.shaft_velocity);   //目标速度
        if (abs(target_velocity) > 140)
          target_velocity = _sign(target_velocity) * 140;   //目标速度
          
        //设置要使用的运动控制循环 
        motor.controller = MotionControlType::velocity;

        //运动控制功能 
        motor.move(target_velocity);
      }
      else // else do swing-up
      {    
        //设置电机的上摆电压,以便上摆
        motor.controller = MotionControlType::torque;
        target_voltage = -_sign(gyroZrate) * swing_up_voltage;  //目标速度
        motor.move(target_voltage);
      }
    }
    else if(test_flag == 1)
    {
       motor.controller = MotionControlType::torque;
       motor.move(target_voltage);
    }
    else
    {
       motor.controller = MotionControlType::velocity;
       motor.move(target_velocity);
    }
     //==============================================================================
//串口输出数据部分,不需要的情况可以改为0
#if 1

Serial.print(pitch);Serial.print("\t");
Serial.print(kalAngleZ);Serial.print("\t");
Serial.print(target_voltage);Serial.print("\t");
Serial.print(motor.shaft_velocity);Serial.print("\t");
Serial.print(motor.voltage.q);Serial.print("\t");
Serial.print(target_angle);Serial.print("\t");
Serial.print(pendulum_angle);Serial.print("\t");
Serial.print(gyroZrate);Serial.print("\t");
Serial.print("\r\n");
#endif

     //wifi传输数据
     //==============================================================================
    //可以使用该方法wifi发送udp信息
    if(wifi_flag)
    {
      //语言初始化函数,将buf中的当前位置后strlen(buf)个字节用0替代并返回buf
      memset(buf, 0, strlen(buf));  
      //调用wifi_print()函数
      wifi_print("v", motor.shaft_velocity);//旋转方向
      wifi_print("vq",motor.voltage.q);     //当前其中一项的期望电压
      wifi_print("p",pendulum_angle);       //当前期望角度与真实角度差值
      wifi_print("t",target_angle);         //期望角度,应该是固定不变的
      wifi_print("k",kalAngleZ);            //卡尔曼滤波后的当前角度
      wifi_print("g",gyroZrate);            //当前左右加速度
      
      //广播数据
      udp.writeTo((const unsigned char*)buf, strlen(buf), IPAddress(192,168,4,2), 
                                                            localUdpPort); 
      }
  }
}

倾斜角度计算

        MPU6050加速度转换为角度,MPU6050可以计算两个方向的角度,但在本项目中,只需要一个方向的角度即可。参考有关MPU6050的例程来看,有两个函数只需用其中一个函数。

/* mpu6050加速度转换为角度
            两个方向的角度:
            acc2rotation(ax, ay)
            acc2rotation(az, ay) */
//只需要一个方向角度
double acc2rotation(double x, double y)
{
  double tmp_kalAngleZ = (atan(x / y) / 1.570796 * 90);   //atan():反正切函数
  if (y < 0)
  {
    return (tmp_kalAngleZ + 180);
  }
  else if (x < 0)
  {
    //将当前值与前值比较,当前差值大于100则认为异常
    if (!isnan(kalAngleZ) && (tmp_kalAngleZ + 360 - kalAngleZ) > 100) 
    {
      //Serial.print("X<0"); Serial.print("\t");
      //Serial.print(tmp_kalAngleZ); Serial.print("\t");
      //Serial.print(kalAngleZ); Serial.print("\t");
      //Serial.print("\r\n");
      if (tmp_kalAngleZ < 0 && kalAngleZ < 0) //按键右边角
        return tmp_kalAngleZ;
      else  //按键边异常处理
        return tmp_kalAngleZ;
    } 
    else
      return (tmp_kalAngleZ + 360);
  }
  else
  {
    return tmp_kalAngleZ;
  }
}

限制角度-60~60之间函数

// 将角度限制在-60~60之间的函数 
float constrainAngle(float x)
{
  float a = 0;
  if(x < 0)
  {
    a = 120+x;
    if(a<abs(x))    //abs():求整数的绝对值
      return a;
  }
  return x;
}

LQR稳定控制器

这一部分LQR控制不太明白。没学过现代控制相关的问题。等学学相关知识再更新吧!!!

//LQR稳定控制器功能
//计算稳定摆锤所需的电机电压 
float controllerLQR(float p_angle, float p_vel, float m_vel)
{
  if (abs(p_angle) > 5) //摆角大于5则进入非稳态,记录非稳态时间
  {
    last_unstable_time = millis();
    if (stable) //如果是稳态进入非稳态则调整为目标角度
    {
      //target_angle = EEPROM.readFloat(0) - p_angle;
      target_angle = EEPROM.readFloat(0);
      stable = 0;
    }
  }
  if ((millis() - last_unstable_time) > 1000 && !stable)  
                    //非稳态进入稳态超过500ms检测,更新目标角为目标角+摆角,假设进入稳态
  {
    //target_angle  -= _sign(target_velocity) * 0.4;
    target_angle = target_angle+p_angle;
    stable = 1;
  }

  if ((millis() - last_stable_time) > 2500 && stable) 
  { //稳态超过2000ms检测,更新目标角
    if (abs(target_velocity) > 5 ) 
    { 
      //稳态速度偏大校正
      last_stable_time = millis();
      target_angle  -= _sign(target_velocity) * 0.2;
    }
  }

  //Serial.println(stable);
  float u;

  if (!stable)  //非稳态计算
  {
    motor.PID_velocity.P = v_p_1;
    motor.PID_velocity.I = v_i_1;
    u = LQR_K3_1 * p_angle + LQR_K3_2 * p_vel + LQR_K3_3 * m_vel;
  }
  else
  {
    motor.PID_velocity.P = v_p_2;
    motor.PID_velocity.I = v_i_2;
    u = LQR_K4_1 * p_angle + LQR_K4_2 * p_vel + LQR_K4_3 * m_vel;
  }
  return u;
}

wifi输出函数

        在loop函数中调用的输出函数!!



void wifi_print(char * s,double num)
{
  char str[255];
  char n[255];
  sprintf(n, "%.2f",num);     //用于将输出的字符存储到n中,格式化输出函数
  strcpy(str,s);      //复制字符串
  strcat(str, n);     //附加字符串
  strcat(buf+strlen(buf), str);   //strlen()取字符串长度
  strcat(buf, ",\0");     //附加字符串

}

        至此,分析完了我接触的第一个Arduino相关的ESP32的项目,也是第一次接触C++和Arduino。希望能够多看、多分析、多思考一些项目的实现方法。本项目中,值得学习的知识点有无线调参函数。这一点和正点原子的STM32的串口调试代码有类似之处,值得在以后项目中应用。以及项目中的FOC、Kalman、LQR等都需要找些资料继续学习一下。OK软件部分都已经分析到这个程度啦~~!以后就用CSDN来记录研究项目的笔记吧,省的以后找不到了。哈哈哈~开心!

硬件电路分析

 项目地址:

自平衡的莱洛三角_esp32_可充电_10*10版本 - 立创EDA开源硬件平台 

        作者分的很清楚,硬件电路非常简单。

上位机软件

Python的GUI是SimpleFOC的SimpleFOCStudio。

SimpleFOCStudio的下载可参考:SimpleFOCStudio安装使用说明及PID调试_loop222的博客-CSDN博客_pid调试软件

最后

        基本上本项目通过参考SimpleFOC、SimpleFOCStudio以及MPU6050等关键部分就可以理解原理。

        继续学习呀~~

        加油学习~

  • 10
    点赞
  • 73
    收藏
    觉得还不错? 一键收藏
  • 17
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值