你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

基于STM32CubeIDE+MPU6050做的动量轮平衡自行车(一)

[复制链接]
STMCU小助手 发布时间:2023-2-9 17:00


N1UI`V7SWCGTLM$D1GQ54.png


背景

        本文用于记录平衡自行车的制作过程,及制作中遇到的问题;总体方案如下:采用STM32F103C8T6作为主控单元、MPU6050作为位姿采集单元、无刷电机带动动量轮调节小车平衡、1S锂电池配合5V和12V升压模块作为电源、蓝牙模块用于和微信小程序进行无线遥控及PID调试、舵机用于控制行驶方向和支撑小车站立。

MPU-6050简介
    MPU-6050集成了 3 轴陀螺仪、3 轴加速度计及温度传感器,并且预留一个IIC 接口,可用于连接外部磁力传感器,并利用自带的数字运动处理器(DMP: Digital Motion Processor)硬件加速引擎,通过主 IIC 接口,向应用端输出完整的 9 轴融合演算数据。

    MPU-6050 对陀螺仪和加速度计分别用了三个16 位的ADC(0~65535),将其测量的模拟量转化为可输出的数字量。且传感器的测量范围都是可以根据需求设定的,陀螺仪可测范围为±250,±500,±1000,±2000°/秒(dps),加速度计可测范围为±2,±4,±8,±16g。


Q(D3Y}_OBQFJ0FA501Q1PWI.png

MPU-6050姿态获取与处理(惭愧,我也一知半解)
    理论上只需要对3轴陀螺仪的角度进行积分,就可以得到MPU-6050的姿态数据。实际上由于陀螺仪受噪声的影响,只对陀螺仪积分并不能得到完全准确的姿态,所以需要用加速度计进行辅助矫正,常用的方法有三种:互补滤波、卡尔曼滤波、硬件DMP解算四元数。

3M4ZAPN6Y61O]`Q(`F`%P%T.png
互补滤波、卡尔曼滤波计算位姿示例

LTC%Z6L{1J6%~U}3VE@]A$P.png

互补滤波、卡尔曼滤波计算位姿示例

    除了使用加速度计计算的噪声较大,卡尔曼滤波,一阶互补滤波,二阶互补滤波看着差不多,O(∩_∩)O哈哈~(四元数计算的示例就不演示了,有兴趣的可以自己研究一下。)
    1)一阶互补滤波:因为加速度计有高频噪声,陀螺仪有低频噪声,需要互补滤波融合得到较可靠的角度值。
    2)卡尔曼滤波:利用线性系统状态方程,通过系统输入输出观测数据,对系统状态进行最优估计的算法。由于观测数据中包括系统中的噪声和干扰的影响,所以最优估计也可看作是滤波过程(来源于百度词条,我也看不懂这说的啥)。
    3)硬件DMP解算四元数:DMP将原始数据直接转换成四元数输出,运用欧拉角转换算法,从而得到Yaw、Roll和Pitch(使用四元数计算位姿,好像会将初始化的位姿设定为零位)。

~3FY21~}NEG2BK(M@DI]NBR.png


一、一阶互补滤波算法

    MPU-6050 的加速度计和陀螺仪各有优缺点,三轴的加速度值没有累积误差,通过简单的计算即可得到倾角,但是它包含的噪声太多(因为待测物运动时会产生加速度,电机运行时振动会产生加速度等),不能直接使用;陀螺仪对外界振动影响小,精度高,通过对角速度积分可以得到倾角,但是会产生累积误差。所以不能单独使用MPU-6050的加速度计或陀螺仪来得到倾角,需要二者进行互补。一阶互补算法的思想就是给加速度和陀螺仪不同的权值,把它们结合到一起进行修正,通过加速度和角速度就可以计算 Pitch 和 Roll 角(单靠 MPU6050 无法准确得到 Yaw 角,需要和地磁传感器结合使用)。


一阶互补算法如下:

  1. //一阶互补滤波
  2. float K1 =0.1;         // 对加速度计取值的权重
  3. float FirstOrder_Pitch;

  4. float FirstOrder(float newAngle, float newRate, float dt)//采集后计算的角度和角加速度
  5. {
  6.   FirstOrder_Pitch = K1 * newAngle + (1-K1) * (angle + newRate * dt);
  7.   return FirstOrder_Pitch;
  8. }
复制代码

二阶互补算法如下:
  1. //二阶互补滤波
  2. float K2 =0.2; // 对加速度计取值的权重
  3. float x1,x2,y1;
  4. float SecondOrder_Pitch;

  5. float SecondOrder(float newAngle, float newRate, float dt)//采集后计算的角度和角加速度
  6. {
  7. x1=(newAngle-SecondOrder_Pitch)*(1-K2)*(1-K2);
  8. y1=y1+x1*dt;
  9. x2=y1+2*(1-K2)*(newAngle-SecondOrder_Pitch)+newRate;
  10. SecondOrder_Pitch=SecondOrder_Pitch+ x2*dt;
  11. return SecondOrder_Pitch;
  12. }
复制代码

二、卡尔曼滤波
  1. /* Kalman filter variables */
  2.         float Q_angle = 0.001; // Process noise variance for the accelerometer
  3.         float Q_bias = 0.003; // Process noise variance for the gyro bias
  4.         float R_measure = 0.03; // Measurement noise variance - this is actually the variance of the measurement noise

  5.         float angle = 0.0; // The angle calculated by the Kalman filter - part of the 2x1 state vector
  6.         float bias = 0.0; // The gyro bias calculated by the Kalman filter - part of the 2x1 state vector
  7.         float rate; // Unbiased rate calculated from the rate and the calculated bias - you have to call getAngle to update the rate

  8.         float P[2][2]={0.0,0.0,0.0,0.0}; // Error covariance matrix - This is a 2x2 matrix


  9. // The angle should be in degrees and the rate should be in degrees per second and the delta time in seconds
  10. float Kalman_filter(float newAngle, float newRate, float dt) {
  11.     // KasBot V2  -  Kalman filter module - http://www.x-firm.com/?page_id=145
  12.     // Modified by Kristian Lauszus
  13.     // See my blog post for more information: http://blog.tkjelectronics.dk/2012/09/a-practical-approach-to-kalman-filter-and-how-to-implement-it

  14.     // Discrete Kalman filter time update equations - Time Update ("Predict")
  15.     // Update xhat - Project the state ahead
  16.     /* Step 1 */
  17.     rate = newRate - bias;
  18.     angle += dt * rate;

  19.     // Update estimation error covariance - Project the error covariance ahead
  20.     /* Step 2 */
  21.     P[0][0] += dt * (dt*P[1][1] - P[0][1] - P[1][0] + Q_angle);
  22.     P[0][1] -= dt * P[1][1];
  23.     P[1][0] -= dt * P[1][1];
  24.     P[1][1] += Q_bias * dt;

  25.     // Discrete Kalman filter measurement update equations - Measurement Update ("Correct")
  26.     // Calculate Kalman gain - Compute the Kalman gain
  27.     /* Step 4 */
  28.     float S = P[0][0] + R_measure; // Estimate error
  29.     /* Step 5 */
  30.     float K[2]; // Kalman gain - This is a 2x1 vector
  31.     K[0] = P[0][0] / S;
  32.     K[1] = P[1][0] / S;

  33.     // Calculate angle and bias - Update estimate with measurement zk (newAngle)
  34.     /* Step 3 */
  35.     float y = newAngle - angle; // Angle difference
  36.     /* Step 6 */
  37.     angle += K[0] * y;
  38.     bias += K[1] * y;

  39.     // Calculate estimation error covariance - Update the error covariance
  40.     /* Step 7 */
  41.     float P00_temp = P[0][0];
  42.     float P01_temp = P[0][1];

  43.     P[0][0] -= K[0] * P00_temp;
  44.     P[0][1] -= K[0] * P01_temp;
  45.     P[1][0] -= K[1] * P00_temp;
  46.     P[1][1] -= K[1] * P01_temp;

  47.     return angle;
  48. };
复制代码

三、四元数法

     MPU-6050 自带了数字运动处理器,即 DMP,并且InvenSense公司 提供了一个 MPU-6050 的嵌入式运动驱动库,结合 MPU-6050 的 DMP,可以将我们的原始数据直接转换成四元数输出,而得到四元数之后,可以很方便的计算出欧拉角,从而得到 Yaw、Roll 和Pitch。

       使用内置的 DMP,大大简化了代码设计,且 MCU 不用进行姿态解算过程,大大降低了 MCU 的负担,从而有更多的时间去处理其他事件,提高系统实时性。
  1. void Read_DMP(float *Pitch,float *Roll,float *Yaw)
  2. {
  3.         unsigned long sensor_timestamp;
  4.         unsigned char more;
  5.         long quat[4];

  6.         dmp_read_fifo(gyro, accel, quat, &sensor_timestamp, &sensors, &more);
  7.         if (sensors & INV_WXYZ_QUAT )
  8.         {
  9.                 q0=quat[0] / q30;
  10.                 q1=quat[1] / q30;
  11.                 q2=quat[2] / q30;
  12.                 q3=quat[3] / q30;
  13.                 *Pitch = asin(-2 * q1 * q3 + 2 * q0* q2)* 57.3;
  14.                 *Roll = atan2(2 * q2 * q3 + 2 * q0 * q1, -2 * q1 * q1 - 2 * q2* q2 + 1)* 57.3; // roll
  15.                  *Yaw = atan2(2*(q1*q2 + q0*q3),q0*q0+q1*q1-q2*q2-q3*q3) * 57.3;        //yaw
  16.         }

  17. }
复制代码

    以下是我测试一阶互补滤波和卡尔曼滤波项目的搭建过程,老鸟请忽略。

    固件开发采用STM32CubeIDE开发,对于我这种菜鸡来说还是很友好的。

    我使用的是STM32CubeIDE1.9.0,打开软件后,依次点击 File-->New-->Stm32 Project,弹出如下界面,输入MCU型号-->选择封装形式-->Next

6Z1)SWD0)NGR@]O%J3~_A`G.png

输入工程名字,点击Next

KU9167BQ{6[RN@@Z{Q}@K7B.png

设置完成后,点击Finish

)W%A{MBY6WZ9DC`3B@F1KM8.png

进入MCU配置界面

LG2T6_XWK%N3B66H$BJS(D0.png

设置I2C与MPU-6050通讯。

CO(V62(7YX0CZ4}@DZ)$Q.png

设置串口与上位机通讯,实时显示MPU-6050位姿。

ZJQU$P3UR1R4)729_G][%{S.png

设置系统时钟及调试模式。

(~J`34%%TXA1IXKWT(T@WN7.png

设置时钟源。

~VCI0F0AO}W_{IQM08)~J44.png

全部设置好后,芯片引脚的分配情况。

SL6GUUTZ2Q)$R5{_){T9V@T.png

在时钟配置里设置时钟最大频率。

3_0F%FN3W27QQvC$O5S$I.png

在工程管理——>代码生成器中进行设置

2ET9LUB057L`M4NNOJ@@HRK.png

点击此处,生成代码

NB$AP684`5LM@ZCOK85OHM6.png

在工程文件夹中创建iCode文件。

~3XUPXUKJ3X311X9L%MRRSX.png

将MPU-6050、卡尔曼滤波库文件复制到iCode文件中。

XFBF%G7M)$$FIFDRGQDM6W1.png

点击Debug,刚刚添加的驱动文件全部在工程文件中显示出来。

Y%XW13SG7SJ%JBPZA1JIEAH.png

按照下列方式设置MPU-6050、卡尔曼滤波驱动文件的编译路径。

DJZL)$_$FQL20QWJM(8)A.png

WQUT]3V991@5_$%1Q%8XS`S.png

然后再main.c文件中添加如下代码,用于重定义printf函数,用于串口输出;获取系统时间,用于滤波。
  1. /* USER CODE END Header */
  2. /* Includes ------------------------------------------------------------------*/
  3. #include "main.h"
  4. #include "i2c.h"
  5. #include "usart.h"
  6. #include "gpio.h"

  7. /* Private includes ----------------------------------------------------------*/
  8. /* USER CODE BEGIN Includes */
  9. #include "mpu6050.h"
  10. #include <stdio.h>
  11. /* USER CODE END Includes */

  12. /* Private typedef -----------------------------------------------------------*/
  13. /* USER CODE BEGIN PTD */

  14. /* USER CODE END PTD */

  15. /* Private define ------------------------------------------------------------*/
  16. /* USER CODE BEGIN PD */
  17. /* USER CODE END PD */

  18. /* Private macro -------------------------------------------------------------*/
  19. /* USER CODE BEGIN PM */

  20. /* USER CODE END PM */

  21. /* Private variables ---------------------------------------------------------*/

  22. /* USER CODE BEGIN PV */

  23. //重定义printf函数,用于串口输出
  24. #ifdef __GNUC__
  25. #define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
  26. #else
  27. #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
  28. #endif

  29. PUTCHAR_PROTOTYPE
  30. {
  31.         HAL_UART_Transmit(&huart1, (uint8_t*)&ch,1,HAL_MAX_DELAY);
  32.     return ch;
  33. }
  34. /* ---------------------------------------------------------------------------*/

  35. //获取系统时间,用于卡尔曼滤波
  36. __STATIC_INLINE uint32_t GXT_SYSTICK_IsActiveCounterFlag(void)
  37. {
  38.   return ((SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk) == (SysTick_CTRL_COUNTFLAG_Msk));
  39. }
  40. static uint32_t getCurrentMicros(void)
  41. {
  42.   /* Ensure COUNTFLAG is reset by reading SysTick control and status register */
  43.   GXT_SYSTICK_IsActiveCounterFlag();
  44.   uint32_t m = HAL_GetTick();
  45.   const uint32_t tms = SysTick->LOAD + 1;
  46.   __IO uint32_t u = tms - SysTick->VAL;
  47.   if (GXT_SYSTICK_IsActiveCounterFlag()) {
  48.     m = HAL_GetTick();
  49.     u = tms - SysTick->VAL;
  50.   }
  51.   return (m * 1000 + (u * 1000) / tms);
  52. }
  53. //获取系统时间,单位us
  54. uint32_t micros(void)
  55. {
  56.   return getCurrentMicros();
  57. }


  58. /* USER CODE END PV */
复制代码

使用printf()函数,还需进行如下设置:点击菜单栏 Project-->properties,弹出如下界面

[I](O`R0}YFTDA{2SX~2%)J.png

在main()函数中添加MPU6050初始化代码和发送位姿数据代码
  1. int main(void)
  2. {
  3.   /* USER CODE BEGIN 1 */

  4.   /* USER CODE END 1 */

  5.   /* MCU Configuration--------------------------------------------------------*/

  6.   /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  7.   HAL_Init();

  8.   /* USER CODE BEGIN Init */

  9.   /* USER CODE END Init */

  10.   /* Configure the system clock */
  11.   SystemClock_Config();

  12.   /* USER CODE BEGIN SysInit */

  13.   /* USER CODE END SysInit */

  14.   /* Initialize all configured peripherals */
  15.   MX_GPIO_Init();
  16.   MX_I2C1_Init();
  17.   MX_USART1_UART_Init();
  18.   /* USER CODE BEGIN 2 */

  19.   while (MPU_Init()){                //如果初始化失败,继续初始化
  20.           printf("MPU_Init_Fail...");
  21.   }

  22.   /* USER CODE END 2 */

  23.   /* Infinite loop */
  24.   /* USER CODE BEGIN WHILE */
  25.   while (1)
  26.   {
  27.           GetAngle();
  28.           GetPitch();
  29.           HAL_Delay(10);
  30.     /* USER CODE END WHILE */

  31.     /* USER CODE BEGIN 3 */
  32.   }
  33.   /* USER CODE END 3 */
  34. }
复制代码

点击,下载程序

{90_LT8S2ACLQ4(5~ZX943B.png

程序烧录好后就会通过串口通讯向上位机发送位姿数据(加速度计、卡尔曼、一阶二阶互补滤波计算的位姿),上位机我用的是Arduino编译器来接受并显示数据。设置好波特率后即可显示数据。

H5PKUP6Y02TULI6(N)Y_NZ5.png

作者:DIY攻城狮


收藏 评论1 发布时间:2023-2-9 17:00

举报

1个回答
比奇堡成功人士 回答时间:2024-10-23 18:15:57

MPU-6050、卡尔曼滤波库在哪里呀

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版