原创出处:STM32系列(HAL库)——F103C8T6通过MPU6050+DMP姿态解算读取角度及温度_mpu6050 读取温度-CSDN博客
本帖纯属记录自己的学习心得体会,首先先列出原创文章,本文章内容都是基于使用这个代码的时候遇到的一些问题,非常感谢这位大佬,我觉得他的代码,对我这制作平衡小车起到了非常大的帮助
近期学习计划是小项目与FreeRTOS并行学习,在近段时间,我做出来了属于自己的平衡小车,虽然平衡性能不像其他大佬那样一动不动,代码也是一步一步得跟着例程来,但是我是个飞舞,即使是这样感觉做出了也很不容易了,而且我自己的也可以做到平衡了,已经很满足了(大佬勿喷,这纯属是菜鸟专属的自娱自乐)
问题1:dmp库是什么?
dmp库最开始我都没有听说过,都不知道这是个啥玩意,最开始使用MPU6050,我学习的都是如何根据IIC协议读出三轴的加速度与角速度,而不是我们更需要的俯仰角(pitch),偏航角(yaw),横滚角(roll)
dmp库是官方自己写出来的一些库函数,可以通过使用dmp库读出三轴角速度,加速度,以及俯仰角(pitch),偏航角(yaw),横滚角(roll),虽然yaw,roll,pitch可以通过根据三轴的角速度,三轴的加速度经过一系列数学运算得出来,但是误差很大,所以一般都是通过dmp库读出yaw,roll,pitch这三个角
问题2:如何使用dmp库?
两种方法:一种是直接去官方下载,但是官方的dmp库并不是基于STM32的,而是MSP430的,还需要经过一系列的修改才能使用,在我自己看来,这种方法非常耗时,网上的方法百花齐放,却没有找到一个我觉得OK的
另一种就是去找现成的,最顶头的那篇原创文章正是我需要的,所以如果需要的,可以直接去原创处下载,我觉得非常好用,只需要根据实际情况的MPU6050连接方式,修改对应的宏定义就好,甚至还免去了我们STM32的IIC通信接口,也不需要我们手写IIC通信协议,因为原创的dmp库是基于软件IIC的,只需要随便找两个IO口就可以使用
#define GPIO_PORT_IIC GPIOB /* GPIO端口 */
#define RCC_IIC_ENABLE __HAL_RCC_GPIOB_CLK_ENABLE() /* GPIO端口时钟 */
#define IIC_SCL_PIN GPIO_PIN_4 /* 连接到SCL时钟线的GPIO */
#define IIC_SDA_PIN GPIO_PIN_3 /* 连接到SDA数据线的GPIO */
问题3:我自己遇到的一些问题以及解决方法
1.程序卡死,一般都是卡死在mpu_dmp_init()这里
我第一次使用是完全用不了的,就是无报错,但是运行起来啥现象也没有,怀疑是卡死在HardWare了,然后自己通过之前学习的Keil的调试方法,一步一步找,找到了卡死的原因:卡在了HAL_Delay这里,发现处了IIC底层通信协议的延时函数是自己写的,其他的(总共有两处)都是调用了HAL_Delay,至于为什么在这里会卡死,目前我自己还不明白,于是自己修改成了之前在学习51单片机的时候,跟着江科大黄老师写的万能延时函数,用此来代替HAL_Delay,然后自己又重新试了一下,发现可以使用了,当时非常开心!!
两处修改,一处是在MPU6050.h文件,另一个是在inv_mpu_dmp_motion_driver.h文件
#define delay_ms My_Delay
2. MPU6050的dmp加载可以通过,但是读出的数据都是0或者无法持续读(只读一次数据,后面就没反应了)
uint8_t mpu_dmp_init(void)
{
uint8_t res=0;
MPU_IIC_Init(); //初始化IIC总线
if(mpu_init()==0) //初始化MPU6050
{
res=mpu_set_sensors(INV_XYZ_GYRO|INV_XYZ_ACCEL);//设置所需要的传感器
if(res)return 1;
res=mpu_configure_fifo(INV_XYZ_GYRO|INV_XYZ_ACCEL);//设置FIFO
if(res)return 2;
res=mpu_set_sample_rate(DEFAULT_MPU_HZ); //设置采样率
if(res)return 3;
res=dmp_load_motion_driver_firmware(); //加载dmp固件
if(res)return 4;
res=dmp_set_orientation(inv_orientation_matrix_to_scalar(gyro_orientation));//设置陀螺仪方向
if(res)return 5;
res=dmp_enable_feature(DMP_FEATURE_6X_LP_QUAT|DMP_FEATURE_TAP| //设置dmp功能
DMP_FEATURE_ANDROID_ORIENT|DMP_FEATURE_SEND_RAW_ACCEL|DMP_FEATURE_SEND_CAL_GYRO|
DMP_FEATURE_GYRO_CAL);
if(res)return 6;
res=dmp_set_fifo_rate(DEFAULT_MPU_HZ); //设置DMP输出速率(最大不超过200Hz)
if(res)return 7;
res=run_self_test(); //自检
if(res)return 8;
res=mpu_set_dmp_state(1); //使能DMP
if(res)return 9;
} else return 10;
return 0;
}
上面的就是我印象最深刻的地方,可以发现,dmp库的加载只要有一步出错了,后面的都不会加载,而是直接推出了,我们可以通过查看该函数的返回值,查看究竟是哪一步出错了,从而进一步排查错误
MPUSingle = mpu_dmp_init();
if(MPUSingle == 0) OLED_ShowString(0, 0, "MPU_OK:", 12, 0);
else OLED_ShowString(0, 0, "MPU_Err:", 12, 0);
而且我们进一步观察函数,发现只有当它返回值是0的时候,dmp库才会被正确加载,只要返回值不是0,就说明dmp加载必然出错了
可能性1:暂时不明
我自己出现的问题是一直返回10,就说明连最基础的mpu_init()都出错了。mpu_init的作用是向MPU6050写一系列数据,除非是地址出错了,否则就是通信协议时序有问题
最后发现问题出在这里,连续写数据的时候出错了,写数据的时候SDA应答有问题
if(MPU_IIC_Wait_Ack()) //等待ACK
{
MPU_IIC_Stop();
return 1;
}
但是到最后我发现,源码的通信协议时序是完全没问题的,这就说明,有可能是其他硬件与其发送了冲突吧,到最后我发现是自己的HC-SR04超声波模块的初始化有问题
void HC_SR04_Start(void)
{
HAL_GPIO_WritePin(HCSR04_TrigPort, HCSR04_TrigPin, GPIO_PIN_SET);
Delay_us(13);
HAL_GPIO_WritePin(HCSR04_TrigPort, HCSR04_TrigPin, GPIO_PIN_RESET);
}
去掉了超声波模块的初始化,就没问题了,至于是为什么,我现在还没有弄明白,不过超声波模块本就是附加品,对平衡小车功能影响不大,去掉也无所谓
可能性2:MPU6050初始化的时候没有水平放置
这个我自己是注意到的,不过看到很多人都会被这个问题困扰着,所以在这里再提一嘴
总结:
最好在dmp初始化函数加上调试函数,这样可以帮助我们更快锁定错误源头
PID:
PID我自己学的不怎么样,经过这个项目后,只能说是略有心得,而且PID有很多大佬讲的都非常不错,我这就不提了,这里想提醒的就是,PID的运算最好放在MPU6050的INT中断函数里面,并且PID的执行间隔一定要快(从而映射到MPU6050的采样率),最好是10ms或者5ms进行一次PID运算,也就是10ms或者5msMPU6050采样一次,同时我也学到了较好地写法,如下:
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(mpu_dmp_get_data(&Pitch, &Roll, &Yaw) == 0)
{
if(GPIO_Pin == GPIO_PIN_5)
{
PID_Control();
}
}
}
在运行PID之前先来个if判断,判断是否采样成功,如果采样成功,马上进行一次PID运算