赞
踩
先上运行效果:
Motion Driver是传感器驱动层的嵌入式软件堆栈,可轻松配置和利用Invense运动跟踪解决方案的许多功能。支持的运动设备为MPU6050/MPU6500/MPU9150/MPU9250。硬件和板载数字运动处理器(DMP)的许多功能都封装在模块化API中,可供使用和参考。
DMP有很多功能,并且可以在运行过程中动态配置。除计步器外,所有DMP数据均输出至FIFO。DMP还可以编程,通过手势或数据准备就绪生成中断。
DMP的功能:
Motion Driver 6.12 的功能:
运行 Motion Driver 6.12 的 MCU 的硬件要求
MD6.12 驱动层(Driver Layer)包含的文件:
我们在移植的时候,需要基于自己的平台提供下面的API给MD6.12使用:
#define i2c_write esp32_i2c_write
#define i2c_read esp32_i2c_read
#define delay_ms esp32_delay_ms
#define get_ms esp32_get_clock_ms
i2c_write/i2c_read:
需要输入以下4个参数:
对于I2C读写函数,ESP32 IDF平台调用官方的 i2c 基本读写命令即可:
int esp32_i2c_write(unsigned char slave_addr, unsigned char reg_addr, unsigned char length, unsigned char const *data) { i2c_cmd_handle_t cmd = i2c_cmd_link_create(); i2c_master_start(cmd); i2c_master_write_byte(cmd, (slave_addr << 1) | WRITE_BIT, ACK_CHECK_EN); i2c_master_write_byte(cmd, reg_addr, ACK_CHECK_EN); i2c_master_write(cmd, data, length, ACK_CHECK_EN); i2c_master_stop(cmd); esp_err_t ret = i2c_master_cmd_begin(0, cmd, 1000 / portTICK_RATE_MS); i2c_cmd_link_delete(cmd); // printf("esp32_i2c_write\n"); return ret; } int esp32_i2c_read(unsigned char slave_addr, unsigned char reg_addr, unsigned char length, unsigned char *data) { if (length == 0) { return ESP_OK; } i2c_cmd_handle_t cmd = i2c_cmd_link_create(); i2c_master_start(cmd); i2c_master_write_byte(cmd, (slave_addr << 1) | WRITE_BIT, ACK_CHECK_EN); i2c_master_write_byte(cmd, reg_addr, ACK_CHECK_EN); i2c_master_start(cmd); i2c_master_write_byte(cmd, (slave_addr << 1) | READ_BIT, ACK_CHECK_EN); if (length > 1) { i2c_master_read(cmd, data, length - 1, ACK_VAL); } i2c_master_read_byte(cmd, data + length - 1, NACK_VAL); i2c_master_stop(cmd); esp_err_t ret = i2c_master_cmd_begin(0, cmd, 1000 / portTICK_RATE_MS); i2c_cmd_link_delete(cmd); return ret; }
delay_ms:延时ms
延时也采用官方的延时函数:
int esp32_delay_ms(unsigned long num_ms)
{
vTaskDelay(num_ms / portTICK_RATE_MS);
return 0;
}
get_ms:获取时间
int esp32_get_clock_ms(unsigned long *count)
{
*count = (unsigned long)esp_timer_get_time();
return 0;
}
log_i/log_e: 打印信息和错误
直接用printf就行。就不区分信息还是错误了。
#define log_i printf
#define log_e printf
MPL库是专有Invense运动应用程序算法的核心,由Mllite和MPL目录组成。MPL不需要移植。您可能需要包含特定于系统的头文件,以支持mllite包中的memcpy、memset等函数调用。
包含从MPL库获取各种数据的API。
移植的过程我是参考小马哥STM32课程系列直播-第十一讲(MPU6050 官方DMP库的移植),小马哥是移植MPL到STM32,因为基本的过程是差不多的,只是一些细节略有差异,现在将移植的过程记录如下:
1 首先需要选择器件
因为这个驱动是支持多种MCU(MSP430、UC3L0) 和 MPU(MPU9150、MPU6050、MPU9250等) 的,所以文件中有很多条件编译。我们需要选择在某一个器件的基础上进行移植。
在“ inv_mpu.h ”添加宏定义即可:
c #define MOTION_DRIVER_TARGET_MSP430 #define MPU6050
2 将msp430相关的头文件注释掉,由于IDF平台是在make时会自己索引平台驱动,所以这里我们只需要注释掉就行。
// #include "msp430.h"
// #include "msp430_i2c.h"
// #include "msp430_clock.h"
// #include "msp430_interrupt.h"
···
首先修改上述基于平台的功能函数宏定义,改为
```c
#include "empl_driver.h"
#define i2c_write esp32_i2c_write
#define i2c_read esp32_i2c_read
#define delay_ms esp32_delay_ms
#define get_ms esp32_get_clock_ms
我的处理是将上诉函数全部打包在一个名为 empl_driver.c 的文件中,在 inv_mpu.c 包含 empl_driver.h 即可。具体的函数实现见 1.3.1 MD6.12 驱动层(Driver Layer)。
将 结构体 gyro_reg_s 的 accel_cfg2 、 lp_accel_odr 与 accel_intel成员注释掉。
mpu_init mpu初始化函数:
struct int_param_s *int_param是用于中断API的平台特定参数。为了简化可以不传入此参数,所以需要将函数定义改为:
int mpu_init(void);
同时返回此参数的部分需要注释掉
// if (int_param)
// reg_int_cb(int_param);
1 选择器件
相同的,在 inv_mpu_dmp_motion_driver.h 中加上宏定义:
#define MOTION_DRIVER_TARGET_MSP430
2 与 inv_mpu.c 的移植一样,需要注释掉与 MSP430 相关的头文件包含。
// #include "msp430.h"
// #include "msp430_clock.h"
并将相关的功能函数改成 ESP32 平台的
#define delay_ms esp32_delay_ms
#define get_ms esp32_get_clock_ms
#define log_i printf
#define log_e printf
记得加上我们的库函数文件包含:
#include "empl_driver.h"
需要注释掉其中一个空载函数 __no_operation();
为了方便的使用,将MD5.1.3版本下的 simple_apps\msp430\motion_driver_test.c 相关功能函数封装一下。
这一部分与小马哥的视频基本上一致,只在头文件与宏定义上需要根据自己的平台修改。
修改后将名字改为 mpu_dmp_driver.c 并添加对应的头文件 mpu_dmp_driver.h 方便后续包含使用。
同样注释掉之前与平台相关的头文件,包含ESP32平台头文件
#include <math.h> // #include "USB_eMPL/descriptors.h" // #include "USB_API/USB_Common/device.h" // #include "USB_API/USB_Common/types.h" // #include "USB_API/USB_Common/usb.h" // #include "F5xx_F6xx_Core_Lib/HAL_UCS.h" // #include "F5xx_F6xx_Core_Lib/HAL_PMM.h" // #include "F5xx_F6xx_Core_Lib/HAL_FLASH.h" // #include "USB_API/USB_CDC_API/UsbCdc.h" // #include "usbConstructs.h" // #include "msp430.h" // #include "msp430_clock.h" // #include "msp430_i2c.h" // #include "msp430_interrupt.h" #include "esp_system.h" #include "mpu_dmp_driver.h" #include "empl_driver.h"
math 用于姿态角的计算。
值得注意的是,MPU6050数据率在DEFAULT_MPU_HZ宏定义中设置:
#define DEFAULT_MPU_HZ (100) // 设置MPU6050的输出数据率为100Hz
官方示例程序是有与之对应的上位机的,是有Python实现,基于pygame。不过我跑了一下跑不通,因为我的Python水平很差,所以也懒得去研究,自己写了一个简单的显示姿态的上位机用于测试。比研究别人的实现更省时间。
motion_driver_test.c中很大一部分代码用于与上位机通信,我们直接将相关的部分注释掉就行。在数据定义部分,需要注释的有:
// 通信数据包定义
// enum packet_type_e {
// PACKET_TYPE_ACCEL,
// PACKET_TYPE_GYRO,
// PACKET_TYPE_QUAT,
// PACKET_TYPE_TAP,
// PACKET_TYPE_ANDROID_ORIENT,
// PACKET_TYPE_PEDO,
// PACKET_TYPE_MISC
// };
1 删
注释掉与通信相关的函数:
// 发包函数
// void send_packet(char packet_type, void *data)
// 收包函数
// static void handle_input(void)
用不到的函数:
// 开关传感器
// static void setup_gyro(void)
// 手势回调函数
// static void tap_cb(unsigned char direction, unsigned char count)
// static void android_orient_cb(unsigned char orientation)
// MSP430 平台初始化
// static inline void platform_init(void)
// app 主函数
// void main(void)
2 改
重写复位函数:
// static inline void msp430_reset(void)
// {
// PMMCTL0 |= PMMSWPOR;
// }
// 重启系统
void system_reset(void)
{
esp_restart();
}
dmp初始化函数 mpu_dmp_init:
在其中加入对应 I2C 初始化函数 mpu_init_i2c,并注释掉 MSP430 初始化的部分。
给每个函数执行结果增加错误检测,方便调试知道初始化执行到哪一步,因为 ESP32 不能在线调试。
3 增
加上 mpu_init_i2c 函数 用于初始化 MPU 使用的 I2C 接口。
uint8_t mpu_init_i2c(void) { esp_err_t esp_err; i2c_config_t conf = { .mode = I2C_MODE_MASTER, .sda_io_num = MPU_I2C_SDA, // select GPIO specific to your project .sda_pullup_en = GPIO_PULLUP_ENABLE, .scl_io_num = MPU_I2C_SCL, // select GPIO specific to your project .scl_pullup_en = GPIO_PULLUP_ENABLE, .master.clk_speed = 200000, // select frequency specific to your project // .clk_flags = 0, /*!< Optional, you can use I2C_SCLK_SRC_FLAG_* flags to choose i2c source clock here. */ }; esp_err = i2c_param_config(0, &conf); printf("i2c_param_config: %d \n", esp_err); esp_err = i2c_driver_install(0, I2C_MODE_MASTER, 0, 0, 0); printf("i2c_driver_install: %d \n", esp_err); return esp_err; }
自检函数,因为我们只用了6轴,所以自检的结果是0x3 而不是 0x7。
加上姿态角的计算部分:
这部分套用公式:
if (sensors & INV_WXYZ_QUAT) { q0 = quat[0] / q30; q1 = quat[1] / q30; q2 = quat[2] / q30; q3 = quat[3] / q30; pitch = asin(-2 * q1 * q3 + 2 * q0 * q2) * 57.3; roll = atan2(2 * q2 * q3 + 2 * q0 * q1, -2 * q1 * q1 - 2 * q2 * q2 + 1)*57.3; yaw = atan2(2 * (q1 * q2 + q0 * q3), q0 * q0 + q1 * q1 - q2 * q2 -q3 * q3)*57.3; // printf("pitch: %f\t", pitch); // printf("roll: %f\t", roll); // printf("yaw: %f\n", yaw); }
< 20220316补充 >
值得注意的是 DMP 是通过中断通知 MPU 姿态角数据准备好了,所以需要将 MPU6050 模块的 INT 引脚接到 ESP32 的GPIO上,比如我这边将 INT 引脚接到 GPIO4。
主函数为:
void app_main(void)
{
oled_init();
mpu_dmp_init();
gpio_init();
gpio_evt_queue = xQueueCreate(10, sizeof(uint32_t));
xTaskCreate(gpio_task, "gpio_task", 2048, NULL, 10, NULL);
gpio_install_isr_service(0);
gpio_isr_handler_add(GPIO_MPU_INTR, gpio_intr_handle, (void*) GPIO_MPU_INTR);
}
基本的处理流程为:
基于PyQt5 和 OpenGL 写了一个简单的MPU6050测试用的上位机,主要的需求:
结合上面的需求,我设计的程序框架设计为:
printf("pitch:%f,roll:%f,yaw:%f\n",pitch, roll, yaw);
,所以我在上位机中分别直接去匹配关键词pitch:
、roll:
和yaw:
后面的数据来获得姿态角,这样就不需要额外再定通信协议了。UI:
使用:
文件夹包含了:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。