草庐IT

STM32移植MPU6050/9250的DMP官方库(motion_driver_6.12)修改移植 DMP简单使用教程

随波逐流_Huffer 2024-07-02 原文

前言

为STM32F1/F4移植的Motion Driver 6.12库 俗称DMP库。

官方的库从初始化硬件到获取数据一条龙服务,关键是假如想要用MPU的DMP单元,对于一般人来说那就只能用官方库了,因为官方库包含一个最核心的闭源静态库

工程已经发布在Github:https://github.com/Huffer342-WSH/MPU6050_I2C

蓝奏云:https://wwz.lanzouo.com/iV0SQ004pn8b 密码:1n4j

该项目源自野火的例程,但野火MPU6050的例程对DMP的功能浅尝辄止,视频也讲的比较乱,我对原代码进行了一些删减,去掉了一些没有意义的部分,同时对略微的修改了官方库更方便移植,工程默认状态是在串口输出数据,想要匿名上位机的程序修改预编译指令即可。

除了移植程序外,在README文件中对使用DMP库接口获取数据的方法有详细解说。

结果展示


开发环境

STM32CubeIDE Version: 1.8.0

也可以使用VScode + GNU Arm Embedded Toolchain + OpenOCD
vscode的配置文件都在工程里,修改一下路径就能用,编译调试都有,gcc和openocd我都是MSYS2一键安装的。

使用的是HAL库

Includ path

假如不设置Includ path,那#include “…”都要改成相对路径,否则编译器找不到头文件

   Core/Inc Drivers/STM32F1xx_HAL_Driver/Inc   
   Drivers/STM32F1xx_HAL_Driver/Inc/Legacy   
   Drivers/CMSIS/Device/ST/STM32F1xx/Include Drivers/CMSIS/Include   
   Middlewares/Third_Party/FreeRTOS/Source/include   
   Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2   
   Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM3   
   Middlewares/MPU6050_Motion_Driver/driver/eMPL   
   Middlewares/MPU6050_Motion_Driver/driver/include   
   Middlewares/MPU6050_Motion_Driver/eMPL-hal   
   Middlewares/MPU6050_Motion_Driver/driver/stm32L   
   Middlewares/MPU6050_Motion_Driver/mllite   
   Middlewares/MPU6050_Motion_Driver/mpl   
   Middlewares/MPU6050_Motion_Driver/porting
macro

DEBUG
USE_HAL_DRIVER
STM32F103xE
//下面的是DMP相关的
EMPL
MPL_LOG_NDEBUG=0
MPU6050
USE_DMP
REMOVE_LOGGING
EMPL_TARGET_STM32F1//EMPL_TARGET_STM32F4,唯一的区别是用的头文件不一样
静态库

在工程的Middlewares\MPU6050_Motion_Driver.zip位置有官方的库,内部包含msp430和stm32f4两个例子,官方python上位机,说明文档和各个版本的静态库

STM32属于arm架构,使用\motion_driver_6.12\mpl libraries\arm目录下的文件

MSP430使用\motion_driver_6.12\mpl libraries\msp430目录下的文件

比如我是用 GNU Arm Embedded Toolchain工具链的,芯片是STM32F1系列,属于Cortex-M3架构,那选择的就是

\motion_driver_6.12\mpl libraries\arm\gcc4.9.3\liblibmplmpu_m3.zip

压缩包的库,使用用Keil和IAR同理

静态库放到MPU6050_Motion_Driver\mpl\liblibmplmpu.a
gcc要用库名的时候去掉开头lib和后缀.a
比如此处liblibmplmpu.a填写的是libmplmpu

源位置

*MDK的优化真的太糟心了,虽然代码量少,但总是bug,不如gcc稳定,工程使用stm32cubeide(eclipse)建立的,写代码编译都在vscode,代码补全真的好用,eclipse主要用来调试。

  • 之后中断会改成FreeRTOS的,对读数据的函数进一步封装

下面直接贴github上的readme,就不改了。

Motion Driver 6.12 移植

基于官方库文件略作修改,可以直接用于STM32F1系列,使用gcc编译器 hal库*(Middlewares\MPU6050_Motion_Driver\mpl\liblibmplmpu.a为官方静态库,此处使用的适用于GCC-CM3,假如使用其他编译器替换该静态库并设置链接命令即可)*

STM32F4、MSP430部分未作修改

文章目录

目录

├── driver
    ├── eMPL
         ├── inv_mpu.c
         ├── inv_mpu_dmp_motion_driver.c
         ├── ...
    ├── include...
    ├── stm32L...
├── eMPL-hal...
├── mllite...
├── porting
         ├── STM32F1_porting.c
         ├── STM32F1_porting.h
         ├──mpu6050_SL.c
         └──mpu6050_SL.h
├── README.md
 

porting文件夹存放移植需要的函数,其余四个文件夹均为Motion Driver 6.12原有的文件夹

其中STM32F1_porting.c为移植必须的内容,

​ mpu6050_SL.c是我自己使用时封装的一部分函数,可有可无。

修改

  • 基于原官方STM32F4部分略作修改,使之适用于STM32F1,编译器符号也应当从EMPL_TARGET_STM32F4改为EMPL_TARGET_STM32F1
  • /driver/eMPL/inv_mpu_dmp_motion_driver.c:627
#ifdef EMPL_TARGET_STM32F1
    __NOP();
#else
    __no_operation();
#endif
    // __no_operation();

__no_operation();是MSP的函数

  • 原文件中eMPL_outputs和hal_outputs在注册用于处理数据的回调函数时采用了相同的优先级,导致两个函数不能同时注册,导致eMPL_outputs.c和hal_outputs.c中的函数不能同时使用。所以修改了eMPL_outputs的优先级#define INV_PRIORITY_EMPL_OUTPUTS 899,经测试不影响结果,假如使用发现问题了,那就改回来吧。

    #define INV_PRIORITY_MOTION_NO_MOTION          100
    #define INV_PRIORITY_GYRO_TC                   150
    #define INV_PRIORITY_QUATERNION_GYRO_ACCEL     200
    #define INV_PRIORITY_QUATERNION_NO_GYRO        250
    #define INV_PRIORITY_MAGNETIC_DISTURBANCE      300
    #define INV_PRIORITY_HEADING_FROM_GYRO         350
    #define INV_PRIORITY_COMPASS_BIAS_W_GYRO       375
    #define INV_PRIORITY_COMPASS_VECTOR_CAL        400
    #define INV_PRIORITY_COMPASS_ADV_BIAS          500
    #define INV_PRIORITY_9_AXIS_FUSION             600
    #define INV_PRIORITY_QUATERNION_ADJUST_9_AXIS  700
    #define INV_PRIORITY_QUATERNION_ACCURACY       750
    #define INV_PRIORITY_RESULTS_HOLDER            800
    #define INV_PRIORITY_INUSE_AUTO_CALIBRATION    850
    #define INV_PRIORITY_EMPL_OUTPUTS              899
    #define INV_PRIORITY_HAL_OUTPUTS               900//原文件中均使用此优先级
    #define INV_PRIORITY_GLYPH                     950
    #define INV_PRIORITY_SHAKE                     975
    #define INV_PRIORITY_SM                        1000
    
    
    inv_register_data_cb(inv_generate_hal_outputs, INV_PRIORITY_HAL_OUTPUTS, INV_GYRO_NEW | INV_ACCEL_NEW | INV_MAG_NEW);
    inv_register_data_cb(inv_generate_eMPL_outputs, INV_PRIORITY_EMPL_OUTPUTS,INV_GYRO_NEW | INV_ACCEL_NEW | INV_MAG_NEW);
    

移植

inv_mpu.cinv_mpu_dmp_motion_driver.c中有宏定义以供移植,原代码注释如下

inv_mpu_dmp_motion_driver.c

/* The following functions must be defined for this platform:

** i2c_write(unsigned char slave_addr, unsigned char reg_addr,*

** unsigned char length, unsigned char const *data)*

** i2c_read(unsigned char slave_addr, unsigned char reg_addr,*

** unsigned char length, unsigned char *data)*

** delay_ms(unsigned long num_ms)*

** get_ms(unsigned long *count)*

*/

inv_mpu.c

/* The following functions must be defined for this platform:

** i2c_write(unsigned char slave_addr, unsigned char reg_addr,*

** unsigned char length, unsigned char const *data)*

** i2c_read(unsigned char slave_addr, unsigned char reg_addr,*

** unsigned char length, unsigned char *data)*

** delay_ms(unsigned long num_ms)*

** get_ms(unsigned long *count)*

** reg_int_cb(void (*cb)(void), unsigned char port, unsigned char pin)*

** labs(long x)*

** fabsf(float x)*

** min(int a, int b)*

*/

实际上需要提供的只有

  • i2c_write(unsigned char slave_addr, unsigned char reg_addr,unsigned char length, unsigned char const *data)

  • i2c_read(unsigned char slave_addr, unsigned char reg_addr,unsigned char length, unsigned char *data)

  • delay_ms(unsigned long num_ms)

  • get_ms(unsigned long *count)

    该部分具体代码如下

    #define i2c_write Sensors_I2C_WriteRegister
    
    #define i2c_read Sensors_I2C_ReadRegister
    
    #define delay_ms HAL_Delay
    
    #define get_ms get_ms_user
    

    具体实现在porting文件夹中

引脚

包含一个外部上升沿中断引脚和I2C的SDA、SCL引脚,共三个引脚

想要高频率的获取数据就用中断,不需要使用时关闭中断,等要数据时打开中断,高频率获取数据再滤波。

编译

编译器设置符号:

EMPL_TARGET_STM32F1

EMPL

MPL_LOG_NDEBUG = 0/1 没有用官方python上位机的话设置为0

MPU6050你使用的型号

USE_DMP

REMOVE_LOGGING

使用

请先完成移植

步骤为:

  • 硬件初始化(MPU初始化)
  • MPL初始化(MPL 库是 InvenSense Motion Apps 专有算法的核⼼,由 Mllite 和 mpl ⽬录组成。)
  • 设置MPL与DMP
  • 调用函数获取数据

注意:DMP传感器融合仅适用于+ -2000dps和Accel + -2G的陀螺仪(官方文档:DMP sensor fusion works only with gyro at ±2000dps and accel ±2G),大部分情况下我们都需要使用DMP(要不然也不用官方库了),所以设置并没有太多选择,照抄就完事了。除非你需要很大的量程,否则没有必要关闭DMP。

前三步基本都是按照官方给的例子,封装成三个函数:

uint8_t MPU6050_mpu_init(void);
uint8_t MPU6050_mpl_init(void);
uint8_t MPU6050_config(void);
在mpu6050_SL.c中

重点是使用库中给出的函数,获取数据

官方的库中有三个.c文件提供了获取数据的接口,分别是mllite/results_holder.cmllite/hal_outputs.ceMPL-hal/eMPL_outputs.c。这三个文件获取都通过data_builder.c中的函数获取数据,所以也可以从data_builder.c中的函数获取原始数据自己处理~~(有现成的为什么要自己做呢)~~

官方库处理数据的思路是将处理数据的函数以回调函数的方式存储起来,并在inv_error_t inv_execute_on_data(void)中统一调用,调用的顺序由注册时传入的优先级决定(本质就是拿一个数组把函数指针存起来,然后都调用一遍)。 官方库的步骤是先注册启动函数,在调用启动函数来注册数据处理函数,再统一调用。

例子如下(展示一下官方库处理数据的步骤,看一眼就行,不涉及使用):

inv_error_t inv_enable_hal_outputs(void)
{
    inv_error_t result;
    inv_init_hal_outputs();
    result = inv_register_mpl_start_notification(inv_start_hal_outputs);//注册启动函数,
    return result;
}

inv_error_t inv_register_mpl_start_notification(inv_error_t (*start_cb)(void))
{
    if (inv_start_cb.num_cb >= INV_MAX_START_CB)
        return INV_ERROR_INVALID_PARAMETER;

    inv_start_cb.start_cb[inv_start_cb.num_cb] = start_cb;//将函数指针存起来
    inv_start_cb.num_cb++;
    return INV_SUCCESS;
}

inv_error_t inv_start_mpl(void)
{
    INV_ERROR_CHECK(inv_execute_mpl_start_notification());//执行启动函数
    return INV_SUCCESS;
}

inv_error_t inv_start_hal_outputs(void)
{
    inv_error_t result;
    result =inv_register_data_cb(inv_generate_hal_outputs, INV_PRIORITY_HAL_OUTPUTS,
                             INV_GYRO_NEW | INV_ACCEL_NEW | INV_MAG_NEW);//注册数据处理函数,同样将函数指针存起来,此处INV_PRIORITY_HAL_OUTPUTS是优先级,数组中函数指针按优先级排序
    return result;
}



inv_error_t inv_execute_on_data(void)
{
    inv_error_t result, first_error;
    int kk;
    int mode;

#ifdef INV_PLAYBACK_DBG
    if (inv_data_builder.debug_mode == RD_RECORD)
    {
        int type = PLAYBACK_DBG_TYPE_EXECUTE;
        fwrite(&type, sizeof(type), 1, inv_data_builder.file);
    }
#endif
    // Determine what new data we have
    mode = 0;
    if (sensors.gyro.status & INV_NEW_DATA)
        mode |= INV_GYRO_NEW;
    if (sensors.accel.status & INV_NEW_DATA)
        mode |= INV_ACCEL_NEW;
    if (sensors.compass.status & INV_NEW_DATA)
        mode |= INV_MAG_NEW;
    if (sensors.temp.status & INV_NEW_DATA)
        mode |= INV_TEMP_NEW;
    if (sensors.quat.status & INV_NEW_DATA)
        mode |= INV_QUAT_NEW;

    first_error = INV_SUCCESS;

    for (kk = 0; kk < inv_data_builder.num_cb; ++kk)
    {
        if (mode & inv_data_builder.process[kk].data_required)
        {
            result = inv_data_builder.process[kk].func(&sensors);//使用存起来的数据处理函数
            if (result && !first_error)
            {
                first_error = result;
            }
        }
    }

    inv_set_contiguous();

    return first_error;
}

再上文提到过优先级冲突的事情,官方的相关部分代码如下:

 for (kk = 0; kk < inv_data_builder.num_cb; ++kk)
    {
        if ((inv_data_builder.process[kk].func == func) || (inv_data_builder.process[kk].priority == priority))
        {
            return INV_ERROR_INVALID_PARAMETER; // fixme give a warning
        }
    }

可以看到它不允许相同函数或相同优先级的函数多次注册,所以就有了上文改优先级的事情。

那么接下来是具体使用方法

  1. inv_error_t inv_init_mpl(void);初始化mpl

  2. 首先调用文件中的enable函数,完成启动函数的注册

    1. inv_error_t inv_enable_results_holder(void);
    2. inv_error_t inv_enable_hal_outputs(void);
    3. inv_error_t inv_enable_eMPL_outputs(void);
  3. 然后调用inv_error_t inv_start_mpl(void);调用启动函数来注册数据处理函数

  4. 获取原始数据int dmp_read_fifo(short *gyro, short *accel, long *quat, unsigned long *timestamp, short *sensors, unsigned char *more)

  5. 然后inv_build后调用inv_error_t inv_execute_on_data(void);处理数据(可以在接收到MPU的INT引脚发送过来的上升沿处理完成中断后使用)

    inv_error_t inv_build_gyro(const short *gyro, inv_time_t timestamp);
    inv_error_t inv_build_compass(const long *compass, int status, inv_time_t timestamp);
    inv_error_t inv_build_accel(const long *accel, int status, inv_time_t timestamp);
    inv_error_t inv_build_temp(const long temp, inv_time_t timestamp);
    inv_error_t inv_build_quat(const long *quat, int status, inv_time_t timestamp);
    inv_error_t inv_execute_on_data(void);//先调上面5个至少成功一个,再调用此函数
    
  6. 接下来就可以随意调用获取数据的函数了

    results_holder.c中的函数可以获取原始的角速度,加速度等数据(没什么用,)

    hal_outputs.c可以获取矫正的方位角(与航空中使用的偏航,俯仰和横滚不同)、四元数 以及加速度角速度什么的,并且都是可以直接使用的浮点数 都可以直接代公式。

    eMPL_outputs.c中的函数可以获取欧拉角以及…,不过欧拉角获取的是32位的q16.16定点数,整形消耗计算资源少,可以自己写定点数的计算函数,当然也可以转化为32位的单精度浮点数
    f l o a t = q 16 ∗ 1.0 / ( 1 < < 16 ) ; float=q16* 1.0 / (1 << 16); float=q161.0/(1<<16);
    具体是干什么的,返回什么数据官方库都在.c 里有英文注释,还是比较易懂的。

上文还提了一嘴data_builder.c,这文件的内容就是set、build、get,get的都是原始数据,和dmp_read_fifo读出来的寄存器数据没什么差别,重点是set和build。既然用了官方的那自然是要用DMP,所以就不解释这个文件了。

其他

听说STM32F1的硬件I2C有bug,但我目前没有遇到,此处都是基于HAL库的硬件I2C实现。

小技巧,看一大堆文件不知道干什么,可以debug看看调用顺序(只能打8个断点可太累了)

有关STM32移植MPU6050/9250的DMP官方库(motion_driver_6.12)修改移植 DMP简单使用教程的更多相关文章

  1. ruby - 简单获取法拉第超时 - 2

    有没有办法在这个简单的get方法中添加超时选项?我正在使用法拉第3.3。Faraday.get(url)四处寻找,我只能先发起连接后应用超时选项,然后应用超时选项。或者有什么简单的方法?这就是我现在正在做的:conn=Faraday.newresponse=conn.getdo|req|req.urlurlreq.options.timeout=2#2secondsend 最佳答案 试试这个:conn=Faraday.newdo|conn|conn.options.timeout=20endresponse=conn.get(url

  2. ruby - 用 Ruby 编写一个简单的网络服务器 - 2

    我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b

  3. ruby-on-rails - 简单的 Ruby on Rails 问题——如何将评论附加到用户和文章? - 2

    我意识到这可能是一个非常基本的问题,但我现在已经花了几天时间回过头来解决这个问题,但出于某种原因,Google就是没有帮助我。(我认为部分问题在于我是一个初学者,我不知道该问什么......)我也看过O'Reilly的RubyCookbook和RailsAPI,但我仍然停留在这个问题上.我找到了一些关于多态关系的信息,但它似乎不是我需要的(尽管如果我错了请告诉我)。我正在尝试调整MichaelHartl'stutorial创建一个包含用户、文章和评论的博客应用程序(不使用脚手架)。我希望评论既属于用户又属于文章。我的主要问题是:我不知道如何将当前文章的ID放入评论Controller。

  4. ruby-on-rails - Capybara-webkit 引发 Capybara::Driver::Webkit::WebkitInvalidResponseError - 2

    我在rspec中收到来自webkit驱动程序的以下消息:Capybara::Driver::Webkit::WebkitInvalidResponseError:UnabletoloadURL:http://127.0.0.1:44923/posts几天前它成功了。问题出在save_page方法上。有什么问题吗? 最佳答案 当我的页面出现错误时,我收到过类似的错误消息。您应该通过在测试模式下启动服务器(railss-etest)并自行访问页面来手动检查情况是否如此。 关于ruby-on-

  5. ruby - 使用 Ruby 通过 Outlook 发送消息的最简单方法是什么? - 2

    我的工作要求我为某些测试自动生成电子邮件。我一直在四处寻找,但未能找到可以快速实现的合理解决方案。它需要在outlook而不是其他邮件服务器中,因为我们有一些奇怪的身份验证规则,我们需要保存草稿而不是仅仅发送邮件的选项。显然win32ole可以做到这一点,但我找不到任何相当简单的例子。 最佳答案 假设存储了Outlook凭据并且您设置为自动登录到Outlook,WIN32OLE可以很好地完成此操作:require'win32ole'outlook=WIN32OLE.new('Outlook.Application')message=

  6. postman接口测试工具-基础使用教程 - 2

    1.postman介绍Postman一款非常流行的API调试工具。其实,开发人员用的更多。因为测试人员做接口测试会有更多选择,例如Jmeter、soapUI等。不过,对于开发过程中去调试接口,Postman确实足够的简单方便,而且功能强大。2.下载安装官网地址:https://www.postman.com/下载完成后双击安装吧,安装过程极其简单,无需任何操作3.使用教程这里以百度为例,工具使用简单,填写URL地址即可发送请求,在下方查看响应结果和响应状态码常用方法都有支持请求方法:getpostputdeleteGet、Post、Put与Delete的作用get:请求方法一般是用于数据查询,

  7. postman——集合——执行集合——测试脚本——pm对象简单示例02 - 2

    //1.验证返回状态码是否是200pm.test("Statuscodeis200",function(){pm.response.to.have.status(200);});//2.验证返回body内是否含有某个值pm.test("Bodymatchesstring",function(){pm.expect(pm.response.text()).to.include("string_you_want_to_search");});//3.验证某个返回值是否是100pm.test("Yourtestname",function(){varjsonData=pm.response.json

  8. Qt Designer的简单使用 - 2

    在前面两节的例子中,主界面窗口的尺寸和标签控件显示的矩形区域等,都是用C++代码编写的。窗口和控件的尺寸都是预估的,控件如果多起来,那就不好估计每个控件合适的位置和大小了。用C++代码编写图形界面的问题就是不直观,因此Qt项目开发了专门的可视化图形界面编辑器——QtDesigner(Qt设计师)。通过QtDesigner就可以很方便地创建图形界面文件*.ui,然后将ui文件应用到源代码里面,做到“所见即所得”,大大方便了图形界面的设计。本节就演示一下QtDesigner的简单使用,学习拖拽控件和设置控件属性,并将ui文件应用到Qt程序代码里。使用QtDesigner设计界面在开始菜单中找到「Q

  9. STM32读取串口传感器数据(颗粒物传感器,主动上传) - 2

    文章目录1.开发板选择*用到的资源2.串口通信(个人理解)3.代码分析(注释比较详细)1.主函数2.串口1配置3.串口2配置以及中断函数4.注意问题5.源码链接1.开发板选择我用的是STM32F103RCT6的板子,不过代码大概在F103系列的板子上都可以运行,我试过在野火103的霸道板上也可以,主要看一下串口对应的引脚一不一样就行了,不一样的就更改一下。*用到的资源keil5软件这里用到了两个串口资源,采集数据一个,串口通信一个,板子对应引脚如下:串口1,TX:PA9,RX:PA10串口2,TX:PA2,RX:PA32.串口通信(个人理解)我就从串口采集传感器数据这个过程说一下我自己的理解,

  10. 在VMware16虚拟机安装Ubuntu详细教程 - 2

    在VMware16.2.4安装Ubuntu一、安装VMware1.打开VMwareWorkstationPro官网,点击即可进入。2.进入后向下滑动找到Workstation16ProforWindows,点击立即下载。3.下载完成,文件大小615MB,如下图:4.鼠标右击,以管理员身份运行。5.点击下一步6.勾选条款,点击下一步7.先勾选,再点击下一步8.去掉勾选,点击下一步9.点击下一步10.点击安装11.点击许可证12.在百度上搜索VM16许可证,复制填入,然后点击输入即可,亲测有效。13.点击完成14.重启系统,点击是15.双击VMwareWorkstationPro图标,进入虚拟机主

随机推荐