ESP32上手笔记 | 05 - 获取MPU6050数据进行姿态解算和展示(I2Cdev+MPU6050+Processing)_processing姿态可视化程序-程序员宅基地

技术标签: esp32  mpu6050  ESP32-C3F  嵌入式硬件  单片机  

一、MPU6050陀螺仪加速度计传感器

1. 介绍

MPU6050是一个带有3轴加速度计和3轴陀螺仪的传感器,也称之为惯性测量单元(IMU)传感器:

陀螺仪测量回转的速度(rad/s),是在X、Y、Z三个轴的角位置变化,分别称为roll、pitch、yaw,这可以使我们判断物体的朝向:

加速度计用来测量加速度,也就是物体速度的变化率。

2. 模块引脚说明

  • VCC:3.3V
  • GND
  • SCL:用于I2C通信
  • SDA:用于I2C通信
  • XDA:用来连接其它的I2C传感器到MPU6050
  • XCL:用来连接其它的I2C传感器到MPU6050
  • AD0:用来设置I2C从机地址
  • INT:中断引脚,用来表示有新的测量数据可用

3. I2C通信协议

MPU6050的I2C从机地址是110100X,7bit长度,最低位X由AD0引脚来控制。

MPU6050支持的最大I2C速度为400kHz。

二、i2cdevlib

I2C Device Library(i2cdevlib)是一组基本统一且文档良好的类的集合,为I2C设备提供简单直观的接口。

1. 安装库

Github仓库地址:https://github.com/jrowberg/i2cdevlib

拉取到之后,将其中Arduino下的I2Cdev文件夹和MPU6050文件夹复制到platformIO工程的lib路径中。

2. 使用库

包含头文件:

#include "I2Cdev.h"
#include "MPU6050.h"

2.1. 创建MPU6050对象

MPU6050_Base(uint8_t address=MPU6050_DEFAULT_ADDRESS, void *wireObj=0);

构造函数中address参数是指MPU6050的从机地址,

默认是0x68(AD0引脚为低电平),如果AD0引脚接为高电平,可以指定地址为0x69。

2.2. 初始化

/** Power on and prepare for general usage.
 * This will activate the device and take it out of sleep mode (which must be done
 * after start-up). This function also sets both the accelerometer and the gyroscope
 * to their most sensitive settings, namely +/- 2g and +/- 250 degrees/sec, and sets
 * the clock source to use the X Gyro for reference, which is slightly better than
 * the default internal clock source.
 */
void MPU6050_Base::initialize();

2.3. 测试通信是否正常

/** Verify the I2C connection.
 * Make sure the device is connected and responds as expected.
 * @return True if connection is valid, false otherwise
 */
bool MPU6050_Base::testConnection() {
    
    return getDeviceID() == 0x34;
}

2.4. 获取六轴数据

/** Get raw 6-axis motion sensor readings (accel/gyro).
 * Retrieves all currently available motion sensor values.
 * @param ax 16-bit signed integer container for accelerometer X-axis value
 * @param ay 16-bit signed integer container for accelerometer Y-axis value
 * @param az 16-bit signed integer container for accelerometer Z-axis value
 * @param gx 16-bit signed integer container for gyroscope X-axis value
 * @param gy 16-bit signed integer container for gyroscope Y-axis value
 * @param gz 16-bit signed integer container for gyroscope Z-axis value
 * @see getAcceleration()
 * @see getRotation()
 * @see MPU6050_RA_ACCEL_XOUT_H
 */
void MPU6050_Base::getMotion6(int16_t* ax, int16_t* ay, int16_t* az, int16_t* gx, int16_t* gy, int16_t* gz);

三、获取MPU6050原始数据

1. 硬件连接

2. 代码编写

#include <Arduino.h>
#include "I2Cdev.h"
#include "MPU6050.h"
#include "Wire.h"

class IMU {
    
    private:
        MPU6050 imu;
        int16_t ax, ay, az;
        int16_t gx, gy, gz;
        int16_t temperature;
    public:
        int init();
        void update();

        int16_t getAccelX();
        int16_t getAccelY();
        int16_t getAccelZ();

        int16_t getGyroX();
        int16_t getGyroY();
        int16_t getGyroZ();

        int16_t getTemperature();
};

IMU imu;

void setup() {
    
    Serial.begin(115200);
    imu.init();
}

void loop() {
    
    imu.update();

    // display tab-separated accel/gyro x/y/z values
    Serial.print("a/g/t:\t");
    Serial.print(imu.getAccelX()); Serial.print("\t");
    Serial.print(imu.getAccelY()); Serial.print("\t");
    Serial.print(imu.getAccelZ()); Serial.print("\t");
    Serial.print(imu.getGyroX()); Serial.print("\t");
    Serial.print(imu.getGyroY()); Serial.print("\t");
    Serial.print(imu.getGyroZ()); Serial.print("\t");
    Serial.println(imu.getTemperature());

    delay(100);
}

int IMU::init()
{
    
    // initialize i2c
    Wire.begin();
    Wire.setClock(400000);

    // initialize device
    Serial.println("Initializing I2C devices...");
    imu.initialize();

    // verify connection
    Serial.println("Testing device connections...");
    if (imu.testConnection()) {
    
        Serial.println("MPU6050 connection successful");
        return 0;
    } else {
    
        Serial.println("MPU6050 connection failed");
        return -1;
    }
}

void IMU::update()
{
    
    // read raw accel/gyro measurements from device
    imu.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);

    // read temperature
    temperature = imu.getTemperature();
}

int16_t IMU::getAccelX()
{
    
    return ax;
}

int16_t IMU::getAccelY()
{
    
    return ay;
}

int16_t IMU::getAccelZ()
{
    
    return az;
}

int16_t IMU::getGyroX()
{
    
    return gx;
}

int16_t IMU::getGyroY()
{
    
    return gy;
}

int16_t IMU::getGyroZ()
{
    
    return gz;
}

int16_t IMU::getTemperature()
{
    
    return temperature;
}

3. 测试结果

四、获取MPU6050 DMP姿态解算数据

1. 姿态解算

2. 硬件连接

添加 引脚GPIO16用来连接MPU6050中断引脚:

3. 代码编写

#include <Arduino.h>
#include "I2Cdev.h"
#include "MPU6050_6Axis_MotionApps20.h"
#include "Wire.h"

#define INTERRUPT_PIN   16

class IMU {
    
    private:
        MPU6050 imu;
        float euler[3];         // [psi, theta, phi]    Euler angle container
        float ypr[3];           // [yaw, pitch, roll]   yaw/pitch/roll container and gravity vector
        int16_t temperature;

        // MPU control/status vars
        bool dmpReady = false;  // set true if DMP init was successful
        
    public:
        int init(uint8_t pin);
        void update();

        float getYaw();
        float getPitch();
        float getRoll();

        int16_t getTemperature();
};

IMU imu;

volatile bool mpuInterrupt = false;     // indicates whether MPU interrupt pin has gone high
void dmpDataReady() {
    
    mpuInterrupt = true;
}

void setup() {
    
    Serial.begin(115200);
    imu.init(INTERRUPT_PIN);
}

void loop() {
    
    imu.update();

    Serial.print("ypr\t");
    Serial.print(imu.getYaw());
    Serial.print("\t");
    Serial.print(imu.getPitch());
    Serial.print("\t");
    Serial.println(imu.getRoll());

    delay(100);
}

int IMU::init(uint8_t pin)
{
    
    uint8_t mpuIntStatus;   // holds actual interrupt status byte from MPU
    uint8_t devStatus;      // return status after each device operation (0 = success, !0 = error)
    uint16_t packetSize;    // expected DMP packet size (default is 42 bytes)

    // initialize i2c
    Wire.begin();
    Wire.setClock(400000);

    // initialize device
    Serial.println("Initializing I2C devices...");
    imu.initialize();

    // verify connection
    Serial.println("Testing device connections...");
    if (imu.testConnection()) {
    
        Serial.println("MPU6050 connection successful");
    } else {
    
        Serial.println("MPU6050 connection failed");
        return -1;
    }

    pinMode(pin, INPUT);

    // load and configure the DMP
    devStatus = imu.dmpInitialize();

    // supply your own gyro offsets here, scaled for min sensitivity
    imu.setXGyroOffset(220);
    imu.setYGyroOffset(76);
    imu.setZGyroOffset(-85);
    imu.setZAccelOffset(1788); // 1688 factory default for my test chip

    // make sure it worked (returns 0 if so)
    if (devStatus == 0) {
    
        // Calibration Time: generate offsets and calibrate our MPU6050
        imu.CalibrateAccel(6);
        imu.CalibrateGyro(6);
        imu.PrintActiveOffsets();
        // turn on the DMP, now that it's ready
        Serial.println(F("Enabling DMP..."));
        imu.setDMPEnabled(true);

        // enable Arduino interrupt detection
        Serial.print(F("Enabling interrupt detection (Arduino external interrupt "));
        Serial.print(digitalPinToInterrupt(INTERRUPT_PIN));
        Serial.println(F(")..."));
        attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN), dmpDataReady, RISING);
        mpuIntStatus = imu.getIntStatus();

        // set our DMP Ready flag so the main loop() function knows it's okay to use it
        Serial.println(F("DMP ready! Waiting for first interrupt..."));
        dmpReady = true;

        // get expected DMP packet size for later comparison
        packetSize = imu.dmpGetFIFOPacketSize();
    } else {
    
        // ERROR!
        // 1 = initial memory load failed
        // 2 = DMP configuration updates failed
        // (if it's going to break, usually the code will be 1)
        Serial.print(F("DMP Initialization failed (code "));
        Serial.print(devStatus);
        Serial.println(F(")"));
    }
}

void IMU::update()
{
    
    // orientation/motion vars
    Quaternion q;           // [w, x, y, z]         quaternion container
    VectorInt16 aa;         // [x, y, z]            accel sensor measurements
    VectorInt16 aaReal;     // [x, y, z]            gravity-free accel sensor measurements
    VectorInt16 aaWorld;    // [x, y, z]            world-frame accel sensor measurements
    VectorFloat gravity;    // [x, y, z]            gravity vector

    // MPU control/status vars
    uint8_t fifoBuffer[64]; // FIFO storage buffer

    // if programming failed, don't try to do anything
    if (!dmpReady) return;

    // read a packet from FIFO
    if (imu.dmpGetCurrentFIFOPacket(fifoBuffer)) {
     // Get the Latest packet 
         // display Euler angles in degrees
            imu.dmpGetQuaternion(&q, fifoBuffer);
            imu.dmpGetGravity(&gravity, &q);
            imu.dmpGetYawPitchRoll(ypr, &q, &gravity);
    }

    // read temperature
    temperature = imu.getTemperature();
}

float IMU::getYaw()
{
    
    return ypr[0] * 180/M_PI;
}

float IMU::getPitch()
{
    
    return ypr[1] * 180/M_PI;
}

float IMU::getRoll()
{
    
    return ypr[2] * 180/M_PI;
}

int16_t IMU::getTemperature()
{
    
    return temperature;
}

4. 测试结果

五、使用Processing进行姿态可视化

参考:如何用Processing对MPU 6050的值进行3D建模

1. 安装Processing

下载地址:https://processing.org/download

2. 安装toxiclibs库

3. 修改数据打印格式

添加格式定义:

// packet structure for InvenSense teapot demo
uint8_t teapotPacket[14] = {
     '$', 0x02, 0,0, 0,0, 0,0, 0,0, 0x00, 0x00, '\r', '\n' };

删除之前的打印格式:

Serial.print("ypr\t");
Serial.print(imu.getYaw());
Serial.print("\t");
Serial.print(imu.getPitch());
Serial.print("\t");
Serial.println(imu.getRoll());

新增一个IMU类的发送数据函数:

void IMU::sendDataToProcessing()
{
    
    // display quaternion values in InvenSense Teapot demo format:
    teapotPacket[2] = fifoBuffer[0];
    teapotPacket[3] = fifoBuffer[1];
    teapotPacket[4] = fifoBuffer[4];
    teapotPacket[5] = fifoBuffer[5];
    teapotPacket[6] = fifoBuffer[8];
    teapotPacket[7] = fifoBuffer[9];
    teapotPacket[8] = fifoBuffer[12];
    teapotPacket[9] = fifoBuffer[13];
    Serial.write(teapotPacket, 14);
    teapotPacket[11]++; // packetCount, loops at 0xFF on purpose
}

在update函数调用之后,调用该函数发送数据到上位机。

修改完毕,烧录代码。

4. 运行processing上位机

上位机为lib\MPU6050\examples\MPU6050_DMP6\Processing\MPUTeapot\MPUTeapot.pde,使用processing打开。

修改连接ESP32的串口:

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Mculover666/article/details/123541279

智能推荐

sed命令详解_sed '/^$/d-程序员宅基地

文章浏览阅读1.7w次,点赞20次,收藏310次。主要参考:(1)使用sed输出文件的指定行(2)sed 字符串替换(3)sed之添加空行仅供自己学习使用,如有侵权,请联系删除获取指定的行(1) 获取test.txt的第二行,输入到屏幕sed -n 2p test.txt(1) 获取test.txt的第一行至第二行sed -n 1,2p test.txt# 加不加引号均可 sed -n ‘1,2p’ test.txt(2)获取test.txt的第二行至最后一行sed -n '2,$p' test.txt# 必须加单引号(3_sed '/^$/d

音乐推荐数据集Million Song Dataset-程序员宅基地

文章浏览阅读1.4w次,点赞5次,收藏35次。音乐推荐数据集_million song dataset

开箱即用的微服务框架 Go-zero(进阶篇)_gozero 验证参数-程序员宅基地

文章浏览阅读7.1k次,点赞7次,收藏28次。之前我们简单介绍过 Go-zero 详见《Go-zero:开箱即用的微服务框架》。这次我们从动手实现一个 Blog 项目的用户模块出发,详细讲述 Go-zero 的使用。特别说明本文涉及的所有资料都已上传 Github 仓库 “kougazhang/go-zero-demo”, 感兴趣的同学可以自行下载。Go-zero 实战项目:blog本文以 blog 的网站后台为例,着重介绍一下如何使用 Go-zero 开发 blog 的用户模块。用户模块是后台管理系统常见的模块,它的功能大家也非常熟悉。管理用_gozero 验证参数

文件的打开_ctx文档怎么打开 site:blog.csdn.net-程序员宅基地

文章浏览阅读337次。系统调用open打开文件,函数声明如下:#include#include#includeint open(coust char *pathname,int flags);int open(const char *pathname,int flags,made_t mode);pathname 文件名称mode 文件权限调用成功时,返回值为所打开文件的文件描述符,反_ctx文档怎么打开 site:blog.csdn.net

关于SetCapture() 和 ReleaseCapture()的用法_c# releasecapture-程序员宅基地

文章浏览阅读3.2k次。http://blog.csdn.net/lanyzh0909/article/details/5543399查MSND,对SetCapture()函数的说明为:“该函数在属于当前线程的指定窗口里设置鼠标捕获。一旦窗口捕获了鼠标,所有鼠标输入都针对该窗口,无论光标是否在窗口的边界内。同一时刻只能有一个窗口捕获鼠标。如果鼠标光标在另一个线程创建的窗口上,只有当鼠标键按下时系统才将鼠标输入指向_c# releasecapture

NLTK学习(一)_maternal affection on the side of the former-程序员宅基地

文章浏览阅读442次。Python简单入门 交互式开发环境(IDLE)Windows下在开始菜单应用程序那里可以找到,而Unix在shell下输入idle即可运行。我选择的是Sublime Text的代码编辑器,下载安装插件SublimeREPL,下载完之后,在【Tool】【SublimeREPL】【Python】【Python】启动交互式开发环境>>> 1+5*2-38>>> 1/30>>> 1.0/30._maternal affection on the side of the former

随便推点

<Linux内核源码>文件系统VFS内核4.0.4版本基本概念源码_linux kernel 文件系统源码-程序员宅基地

文章浏览阅读703次。文件系统VFS内核4.0.4版本基本概念源码题外话:Linux内核从2.x和3.x到现在最新的4.x变化非常大,最直观的表现就是很多书上的内核代码已经无法直接继续使用,所以看看新的源码是非常有意义的! (下文中的内核源码都来自于 kernel 4.0.4 版本,本人都验证过正确,正文假设读者对 linux系统下mount命令有操作经验。另外,linux内核源码中关于文件操作的代码量比内_linux kernel 文件系统源码

二维字符数组_c语言用二维数组存一个hello world在列里-程序员宅基地

文章浏览阅读1.7w次,点赞6次,收藏24次。字符(串)指针char *p ="hello world!"; //相当于:char *p; p="hello world!"; p是字符串"hello world!"的首地址。字符串"hello world"存放在数据段。p[0]='a'; //错误,不能修改。printf("%s",p); //打印出字..._c语言用二维数组存一个hello world在列里

#Java教程:InputStream、FileinputStream #Java字符输入流 @FDDLC_fileinputstream和inputstream-程序员宅基地

文章浏览阅读1.3k次。一、InputStream..._fileinputstream和inputstream

[转载]在RHEL系统上使用“subscription-manager”注册和激活“subscription”_[root@localhost spdk]# sudo subscription-manager r-程序员宅基地

文章浏览阅读1.3w次。https://nanxiao.me/use-subscription-manager-register-on-rhel/在RHEL系统中注册和使用subscription是两个过程:NOTE: With Red Hat Subscription-Manager, registration and utilization of a subscription is actually a t..._[root@localhost spdk]# sudo subscription-manager register registering to: su

go 与java netty 之间的通信实现_golang可以连netty-程序员宅基地

文章浏览阅读2k次。前言: 笔记上一篇介绍了,go语言如何使用protobuf及生成go的protobuf文件,具体内容请见上一篇:go 与 protobuf 安装和使用1.protobuf文件定义及注意事项// [开始声明]syntax = "proto3"; //定义protobuf的包名称空间package message;// [结束声明]// [开始 java 选项配置]option java_package = "xxxx.core.message";opt..._golang可以连netty

neutron中的安全组和防火墙_安全组的防护对象是?-程序员宅基地

文章浏览阅读3k次。文章来自作者维护的社区微信公众号【虚拟化云计算】)(目前有两个微信群《kvm虚拟化》和《openstack》,扫描二维码点击“云-交流”,进群交流提问)一。防火墙与安全组区别防火墙一般放在网关上,用来隔离子网之间的访问。因此,防火墙即服务也是在网络节点上(具体说来是在路由器命名空间中)来实现。防火墙可以在安全组之前隔离外部过来的恶意流量,但是对于同个子网内部不同虚拟网卡间的通..._安全组的防护对象是?

推荐文章

热门文章

相关标签