本文代码已上传开源仓库:ESP32-S3教学资产:包括了一些在ESP32-S3上的简单工程,基于ESP-IDF框架 - AtomGit | GitCode

现在,我们来讲解I2C与MPU6050相关内容。

一、I2C是什么

I2C是一种芯片间的通信协议,只需两根线,就能让多个设备互相传数据。

  • SDA (Serial Data)数据线 → 真正传数据的线
  • SCL (Serial Clock)时钟线 → 控制什么时候传、传多快

I2C属于半双工通信,同一时间,只能发送或者接收,不能同时收发。每个设备都有自己的地址,比如MPU6050默认地址:

  • 0x68(AD0 接地)
  • 0x69(AD0 接 3.3V)

二、MPU6050

MPU6050 是 InvenSense 公司推出的六轴运动处理传感器,集成了三轴加速度计 + 三轴陀螺仪 + 内置温度传感器,还自带 I2C 接口和数字运动处理(DMP)引擎,是低成本、小体积的姿态检测核心,广泛用于平衡小车、无人机、手环、体感设备等场景。

三、硬件接线

四、新建工程

新建一个名为MPU6050的工程,如下图所示:

五、模块化编程

现在,我们来引入一个模块化编程的概念。就像C语言可以自定义.h文件一样,我们的ESP32-S3的开发,也可以使用类似的操作,将部分代码封装在main.c以外,main.c中只保留少量调用代码,增加可读性,也方便后续的随时移植。

现在,我们就将MPU6050的驱动代码封装一下。

5.1 完善CMakeList.txt文件

idf_component_register(SRCS "mpu6050.c" "main.c"
                    REQUIRES driver freertos
                    INCLUDE_DIRS ".")

SRCS:告诉编译器要编译哪些.c源文件,我们写的两个.c都要加进来。

5.2 完善MPU6050.h(先定接口,再写实现)

头文件是整个驱动的「功能说明书」,必须先写。它的核心作用是:对外暴露可调用的接口,隐藏内部实现细节,同时防止头文件重复包含。

#ifndef MPU6050_H
#define MPU6050_H

#include "esp_err.h"
#include <stdint.h>

#ifdef __cplusplus
extern "C" {
#endif

// I2C 配置
#define I2C_MASTER_NUM          I2C_NUM_0
#define I2C_MASTER_SDA_IO       8
#define I2C_MASTER_SCL_IO       9
#define I2C_MASTER_FREQ_HZ      400000

// MPU6050 设备地址
#define MPU6050_ADDR            0x68

// 加速度计量程
#define ACCEL_FS_2G             0
#define ACCEL_FS_4G             1
#define ACCEL_FS_8G             2
#define ACCEL_FS_16G            3

// 陀螺仪量程
#define GYRO_FS_250DPS          0
#define GYRO_FS_500DPS          1
#define GYRO_FS_1000DPS         2
#define GYRO_FS_2000DPS         3

// 初始化 I2C 总线
esp_err_t mpu6050_i2c_init(void);

// 初始化 MPU6050
esp_err_t mpu6050_init(void);

// 读取加速度计数据
esp_err_t mpu6050_read_accel(float *ax, float *ay, float *az);

// 读取陀螺仪数据
esp_err_t mpu6050_read_gyro(float *gx, float *gy, float *gz);

// 读取温度数据
esp_err_t mpu6050_read_temp(float *temp);

// 反初始化 I2C 总线
esp_err_t mpu6050_i2c_deinit(void);

#ifdef __cplusplus
}
#endif

#endif // MPU6050_H

5.2.1 头文件的基础框架

// 头文件保护宏:防止这个头文件被重复包含,格式是#ifndef 宏名 #define 宏名 ... #endif
#ifndef MPU6050_H
#define MPU6050_H

// 这里放后续的内容

#endif // MPU6050_H

5.2.2 引入依赖的头文件

判断要引入什么头文件的标准:我们在这个头文件里用到的类型 / 函数,来自哪个头文件,就引入哪个

  • 我们的函数返回值是esp_err_t(ESP-IDF 的错误类型),来自esp_err.h
  • 我们用到了uint8_t等标准整型,来自stdint.h
  • 兼容 C++ 编译,需要加extern "C"包裹
#ifndef MPU6050_H
#define MPU6050_H

// 引入依赖头文件
#include "esp_err.h"
#include <stdint.h>

// 兼容C++编译
#ifdef __cplusplus
extern "C" {
#endif

// 这里放宏定义和函数声明

#ifdef __cplusplus
}
#endif

#endif // MPU6050_H

5.2.3 编写宏定义

宏定义的原则:所有硬件相关、可修改、有明确含义的参数,都用大写宏定义,方便后续修改和维护。我们按照功能模块,分批次写宏,每一个宏的值都有文档依据:

// -------------------------- 1. I2C硬件配置宏(来自你的硬件接线) --------------------------
// 使用的I2C总线号,ESP32-S3有2个I2C总线,选I2C_NUM_0
#define I2C_MASTER_NUM          I2C_NUM_0
// SDA引脚,对应你接的GPIO8
#define I2C_MASTER_SDA_IO       8
// SCL引脚,对应你接的GPIO9
#define I2C_MASTER_SCL_IO       9
// I2C时钟频率,MPU6050最大支持400kHz,来自datasheet
#define I2C_MASTER_FREQ_HZ      400000

// -------------------------- 2. MPU6050设备地址(来自MPU6050 datasheet) --------------------------
// AD0接GND时,7位I2C地址是0x68;接3.3V是0x69
#define MPU6050_ADDR            0x68

// -------------------------- 3. 加速度计量程宏(来自MPU6050寄存器手册) --------------------------
// ACCEL_CONFIG寄存器的BIT4-BIT3,对应4个量程
#define ACCEL_FS_2G             0  // ±2g(默认,灵敏度最高)
#define ACCEL_FS_4G             1  // ±4g
#define ACCEL_FS_8G             2  // ±8g
#define ACCEL_FS_16G            3  // ±16g

// -------------------------- 4. 陀螺仪量程宏(来自MPU6050寄存器手册) --------------------------
// GYRO_CONFIG寄存器的BIT4-BIT3,对应4个量程
#define GYRO_FS_250DPS          0  // ±250°/s(默认,灵敏度最高)
#define GYRO_FS_500DPS          1  // ±500°/s
#define GYRO_FS_1000DPS         2  // ±1000°/s
#define GYRO_FS_2000DPS         3  // ±2000°/s

5.2.4 编写对外函数接口声明

函数接口设计的原则:见名知意、单一职责、输入输出清晰、统一错误返回值。我们按照功能流程,设计 6 个核心接口,对应我们拆解的子功能:

// 1. 初始化I2C总线:无入参,返回ESP_OK代表成功,其他代表错误
esp_err_t mpu6050_i2c_init(void);

// 2. 初始化MPU6050传感器:无入参,返回ESP_OK代表成功
esp_err_t mpu6050_init(void);

// 3. 读取加速度数据:入参是3个float类型的指针,用来存放X/Y/Z轴的结果
esp_err_t mpu6050_read_accel(float *ax, float *ay, float *az);

// 4. 读取陀螺仪数据:入参是3个float类型的指针,用来存放X/Y/Z轴的结果
esp_err_t mpu6050_read_gyro(float *gx, float *gy, float *gz);

// 5. 读取温度数据:入参是float指针,存放温度结果
esp_err_t mpu6050_read_temp(float *temp);

// 6. 反初始化I2C总线:释放资源,可选但规范
esp_err_t mpu6050_i2c_deinit(void);

到这里,mpu6050.h就写完了。你已经完成了驱动的「功能设计」,接下来只需要在.c 文件里,把这些接口的功能实现出来即可。

5.3 完善MPU6050.c

#include "mpu6050.h"
#include "esp_log.h"
#include "driver/i2c_master.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

#define TAG "MPU6050"

// MPU6050 寄存器地址
#define MPU6050_REG_PWR_MGMT_1  0x6B
#define MPU6050_REG_SMPLRT_DIV  0x19
#define MPU6050_REG_CONFIG      0x1A
#define MPU6050_REG_GYRO_CONFIG 0x1B
#define MPU6050_REG_ACCEL_CONFIG 0x1C
#define MPU6050_REG_ACCEL_XOUT_H 0x3B
#define MPU6050_REG_TEMP_OUT_H   0x41
#define MPU6050_REG_GYRO_XOUT_H  0x43

// 灵敏度系数
#define ACCEL_SENSITIVITY_2G    16384.0f
#define ACCEL_SENSITIVITY_4G    8192.0f
#define ACCEL_SENSITIVITY_8G    4096.0f
#define ACCEL_SENSITIVITY_16G   2048.0f

#define GYRO_SENSITIVITY_250    131.0f
#define GYRO_SENSITIVITY_500    65.5f
#define GYRO_SENSITIVITY_1000   32.8f
#define GYRO_SENSITIVITY_2000   16.4f

static i2c_master_bus_handle_t i2c_bus_handle = NULL;
static i2c_master_dev_handle_t mpu6050_handle = NULL;

// 写入单个寄存器
static esp_err_t mpu6050_write_reg(uint8_t reg, uint8_t data)
{
    uint8_t write_buf[2] = {reg, data};
    return i2c_master_transmit(mpu6050_handle, write_buf, 2, -1);
}

// 读取多个寄存器
static esp_err_t mpu6050_read_reg(uint8_t reg, uint8_t *data, size_t len)
{
    return i2c_master_transmit_receive(mpu6050_handle, &reg, 1, data, len, -1);
}

esp_err_t mpu6050_i2c_init(void)
{
    i2c_master_bus_config_t bus_config = {
        .i2c_port = I2C_MASTER_NUM,
        .sda_io_num = I2C_MASTER_SDA_IO,
        .scl_io_num = I2C_MASTER_SCL_IO,
        .clk_source = I2C_CLK_SRC_DEFAULT,
        .glitch_ignore_cnt = 7,
        .flags.enable_internal_pullup = true,
    };

    esp_err_t ret = i2c_new_master_bus(&bus_config, &i2c_bus_handle);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to create I2C master bus");
        return ret;
    }

    i2c_device_config_t dev_config = {
        .dev_addr_length = I2C_ADDR_BIT_LEN_7,
        .device_address = MPU6050_ADDR,
        .scl_speed_hz = I2C_MASTER_FREQ_HZ,
    };

    ret = i2c_master_bus_add_device(i2c_bus_handle, &dev_config, &mpu6050_handle);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to add MPU6050 device to I2C bus");
        return ret;
    }

    ESP_LOGI(TAG, "I2C master initialized successfully");
    return ESP_OK;
}

esp_err_t mpu6050_i2c_deinit(void)
{
    esp_err_t ret = ESP_OK;

    if (mpu6050_handle != NULL) {
        ret = i2c_master_bus_rm_device(mpu6050_handle);
        if (ret != ESP_OK) {
            ESP_LOGE(TAG, "Failed to remove MPU6050 device from I2C bus");
            return ret;
        }
        mpu6050_handle = NULL;
    }

    if (i2c_bus_handle != NULL) {
        ret = i2c_del_master_bus(i2c_bus_handle);
        if (ret != ESP_OK) {
            ESP_LOGE(TAG, "Failed to delete I2C master bus");
            return ret;
        }
        i2c_bus_handle = NULL;
    }

    ESP_LOGI(TAG, "I2C master deinitialized successfully");
    return ESP_OK;
}

esp_err_t mpu6050_init(void)
{
    esp_err_t ret;

    // 唤醒 MPU6050 (清除睡眠位)
    ret = mpu6050_write_reg(MPU6050_REG_PWR_MGMT_1, 0x00);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to wake up MPU6050");
        return ret;
    }
    vTaskDelay(pdMS_TO_TICKS(10));

    // 设置采样率分频器 (1kHz / (1 + 0) = 1kHz)
    ret = mpu6050_write_reg(MPU6050_REG_SMPLRT_DIV, 0x00);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to set sample rate");
        return ret;
    }

    // 设置低通滤波器
    ret = mpu6050_write_reg(MPU6050_REG_CONFIG, 0x03);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to set config");
        return ret;
    }

    // 设置陀螺仪量程为 ±250dps
    ret = mpu6050_write_reg(MPU6050_REG_GYRO_CONFIG, GYRO_FS_250DPS << 3);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to set gyro config");
        return ret;
    }

    // 设置加速度计量程为 ±2g
    ret = mpu6050_write_reg(MPU6050_REG_ACCEL_CONFIG, ACCEL_FS_2G << 3);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to set accel config");
        return ret;
    }

    ESP_LOGI(TAG, "MPU6050 initialized successfully");
    return ESP_OK;
}

esp_err_t mpu6050_read_accel(float *ax, float *ay, float *az)
{
    uint8_t data[6];
    esp_err_t ret = mpu6050_read_reg(MPU6050_REG_ACCEL_XOUT_H, data, 6);
    if (ret != ESP_OK) {
        return ret;
    }

    int16_t raw_x = (int16_t)((data[0] << 8) | data[1]);
    int16_t raw_y = (int16_t)((data[2] << 8) | data[3]);
    int16_t raw_z = (int16_t)((data[4] << 8) | data[5]);

    *ax = raw_x / ACCEL_SENSITIVITY_2G;
    *ay = raw_y / ACCEL_SENSITIVITY_2G;
    *az = raw_z / ACCEL_SENSITIVITY_2G;

    return ESP_OK;
}

esp_err_t mpu6050_read_gyro(float *gx, float *gy, float *gz)
{
    uint8_t data[6];
    esp_err_t ret = mpu6050_read_reg(MPU6050_REG_GYRO_XOUT_H, data, 6);
    if (ret != ESP_OK) {
        return ret;
    }

    int16_t raw_x = (int16_t)((data[0] << 8) | data[1]);
    int16_t raw_y = (int16_t)((data[2] << 8) | data[3]);
    int16_t raw_z = (int16_t)((data[4] << 8) | data[5]);

    *gx = raw_x / GYRO_SENSITIVITY_250;
    *gy = raw_y / GYRO_SENSITIVITY_250;
    *gz = raw_z / GYRO_SENSITIVITY_250;

    return ESP_OK;
}

esp_err_t mpu6050_read_temp(float *temp)
{
    uint8_t data[2];
    esp_err_t ret = mpu6050_read_reg(MPU6050_REG_TEMP_OUT_H, data, 2);
    if (ret != ESP_OK) {
        return ret;
    }

    int16_t raw_temp = (int16_t)((data[0] << 8) | data[1]);
    *temp = (raw_temp / 340.0f) + 36.53f;

    return ESP_OK;
}

5.3.1 基础依赖与全局配置

// 第一步:引入我们自己的头文件,必须放在第一个
#include "mpu6050.h"

// 第二步:引入依赖的ESP-IDF头文件,用到什么功能就引入什么
#include "esp_log.h"          // 日志打印,调试必备
#include "driver/i2c_master.h"// I2C主机驱动,来自ESP-IDF driver组件
#include "freertos/FreeRTOS.h"// FreeRTOS核心库
#include "freertos/task.h"    // FreeRTOS延时函数

// 第三步:定义日志标签,方便串口调试时区分日志来源
#define TAG "MPU6050_DRIVER"

5.3.2 定义核心寄存器地址与转换系数

这部分是和传感器通信的核心,每一个寄存器地址、转换系数,都必须从官方手册里找,不能瞎写。

// -------------------------- MPU6050核心寄存器地址(来自寄存器手册) --------------------------
// 电源管理寄存器1:用来唤醒传感器,地址0x6B
#define MPU6050_REG_PWR_MGMT_1    0x6B
// 采样率分频寄存器:配置数据采样率,地址0x19
#define MPU6050_REG_SMPLRT_DIV    0x19
// 配置寄存器:配置低通滤波,地址0x1A
#define MPU6050_REG_CONFIG        0x1A
// 陀螺仪配置寄存器:配置量程,地址0x1B
#define MPU6050_REG_GYRO_CONFIG   0x1B
// 加速度计配置寄存器:配置量程,地址0x1C
#define MPU6050_REG_ACCEL_CONFIG  0x1C
// 加速度计X轴高字节寄存器:加速度数据的起始地址,地址0x3B
#define MPU6050_REG_ACCEL_XOUT_H  0x3B
// 温度高字节寄存器:温度数据的起始地址,地址0x41
#define MPU6050_REG_TEMP_OUT_H    0x41
// 陀螺仪X轴高字节寄存器:陀螺仪数据的起始地址,地址0x43
#define MPU6050_REG_GYRO_XOUT_H   0x43

// -------------------------- 物理值转换系数(来自datasheet) --------------------------
// 加速度灵敏度:不同量程下,1g对应的数字量(LSB/g)
#define ACCEL_SENS_2G    16384.0f
#define ACCEL_SENS_4G    8192.0f
#define ACCEL_SENS_8G    4096.0f
#define ACCEL_SENS_16G   2048.0f

// 陀螺仪灵敏度:不同量程下,1°/s对应的数字量(LSB/(°/s))
#define GYRO_SENS_250    131.0f
#define GYRO_SENS_500    65.5f
#define GYRO_SENS_1000   32.8f
#define GYRO_SENS_2000   16.4f

5.3.3 定义静态句柄

I2C 总线和设备的句柄,是后续所有操作的核心,我们把它定义为静态全局变量,作用域仅限本文件,外部无法访问,符合封装原则:

// I2C总线句柄
static i2c_master_bus_handle_t i2c_bus_handle = NULL;
// MPU6050设备句柄
static i2c_master_dev_handle_t mpu6050_dev_handle = NULL;

5.3.4 封装底层寄存器读写函数

MPU6050 的所有操作,本质都是「通过 I2C 写寄存器」和「通过 I2C 读寄存器」。我们把这两个操作封装成静态函数,上层代码不用关心 I2C 的底层细节,只需要调用这两个函数即可。

这是 I2C 驱动的通用写法,所有 I2C 传感器都可以用这个思路封装:

/**
 * @brief  静态函数:向MPU6050的单个寄存器写入1个字节数据
 * @param  reg  要写入的寄存器地址
 * @param  data 要写入的数据
 * @return esp_err_t  ESP_OK=成功,其他=失败
 */
static esp_err_t mpu6050_write_reg(uint8_t reg, uint8_t data)
{
    // I2C写寄存器的格式:先发送寄存器地址,再发送要写入的数据
    uint8_t write_buf[2] = {reg, data};
    // 调用ESP-IDF的I2C发送函数,参数:设备句柄、发送缓冲区、长度、超时时间(-1=无限等待)
    return i2c_master_transmit(mpu6050_dev_handle, write_buf, 2, -1);
}

/**
 * @brief  静态函数:从MPU6050的寄存器连续读取多个字节数据
 * @param  reg  要读取的起始寄存器地址
 * @param  data 存放读取结果的缓冲区
 * @param  len  要读取的字节数
 * @return esp_err_t  ESP_OK=成功,其他=失败
 */
static esp_err_t mpu6050_read_reg(uint8_t reg, uint8_t *data, size_t len)
{
    // I2C读寄存器的格式:先发送要读取的寄存器地址,再接收对应长度的数据
    return i2c_master_transmit_receive(mpu6050_dev_handle, &reg, 1, data, len, -1);
}

5.3.5 I2C 初始化与反初始化函数

这一步是实现硬件通信的基础,我们严格按照 ESP-IDF 官方 I2C API 的流程来写:

// 实现头文件里的I2C初始化函数
esp_err_t mpu6050_i2c_init(void)
{
    // 第一步:配置I2C总线参数,结构体来自ESP-IDF的i2c_master.h
    i2c_master_bus_config_t bus_config = {
        .i2c_port = I2C_MASTER_NUM,        // 我们在头文件里定义的I2C总线号
        .sda_io_num = I2C_MASTER_SDA_IO,   // SDA引脚号
        .scl_io_num = I2C_MASTER_SCL_IO,   // SCL引脚号
        .clk_source = I2C_CLK_SRC_DEFAULT, // 默认时钟源,不用改
        .glitch_ignore_cnt = 7,             // 时钟毛刺过滤,默认值,增强稳定性
        .flags.enable_internal_pullup = true, // 开启GPIO内部上拉,硬件没有外部上拉电阻必须开
    };

    // 第二步:创建I2C主机总线,获取总线句柄
    esp_err_t ret = i2c_new_master_bus(&bus_config, &i2c_bus_handle);
    // 错误处理:如果创建失败,打印错误日志,直接返回错误
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "I2C总线创建失败,错误码:%d", ret);
        return ret;
    }

    // 第三步:配置MPU6050设备参数
    i2c_device_config_t dev_config = {
        .dev_addr_length = I2C_ADDR_BIT_LEN_7, // MPU6050用7位I2C地址,固定
        .device_address = MPU6050_ADDR,        // 头文件里定义的设备地址
        .scl_speed_hz = I2C_MASTER_FREQ_HZ,    // I2C时钟频率
    };

    // 第四步:把MPU6050设备添加到I2C总线上,获取设备句柄
    ret = i2c_master_bus_add_device(i2c_bus_handle, &dev_config, &mpu6050_dev_handle);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "MPU6050设备添加失败,错误码:%d", ret);
        return ret;
    }

    // 到这里,I2C初始化成功,打印日志
    ESP_LOGI(TAG, "I2C总线初始化成功");
    return ESP_OK;
}

// 实现头文件里的I2C反初始化函数
esp_err_t mpu6050_i2c_deinit(void)
{
    esp_err_t ret = ESP_OK;

    // 先移除设备,再删除总线,顺序不能反
    if (mpu6050_dev_handle != NULL) {
        ret = i2c_master_bus_rm_device(mpu6050_dev_handle);
        if (ret != ESP_OK) {
            ESP_LOGE(TAG, "MPU6050设备移除失败,错误码:%d", ret);
            return ret;
        }
        mpu6050_dev_handle = NULL; // 清空句柄,避免野指针
    }

    if (i2c_bus_handle != NULL) {
        ret = i2c_del_master_bus(i2c_bus_handle);
        if (ret != ESP_OK) {
            ESP_LOGE(TAG, "I2C总线删除失败,错误码:%d", ret);
            return ret;
        }
        i2c_bus_handle = NULL;
    }

    ESP_LOGI(TAG, "I2C总线反初始化成功");
    return ESP_OK;
}

5.3.6 MPU6050初始化函数

初始化函数的核心,是按照 MPU6050 手册里的上电流程,通过我们封装的mpu6050_write_reg函数,给对应的寄存器写入配置值。

// 实现头文件里的MPU6050初始化函数
esp_err_t mpu6050_init(void)
{
    esp_err_t ret;
    ESP_LOGI(TAG, "开始初始化MPU6050传感器");

    // 第一步:唤醒MPU6050(重点!MPU6050上电默认进入睡眠模式)
    // 手册说明:PWR_MGMT_1寄存器的BIT6是Sleep位,写0=唤醒,写1=睡眠
    ret = mpu6050_write_reg(MPU6050_REG_PWR_MGMT_1, 0x00);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "MPU6050唤醒失败");
        return ret;
    }
    // 延时10ms,等待传感器唤醒稳定
    vTaskDelay(pdMS_TO_TICKS(10));

    // 第二步:配置采样率分频器
    // 手册公式:采样率 = 陀螺仪输出频率 / (1 + SMPLRT_DIV)
    // 陀螺仪默认输出频率1kHz,写入0x00 → 采样率=1kHz
    ret = mpu6050_write_reg(MPU6050_REG_SMPLRT_DIV, 0x00);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "采样率配置失败");
        return ret;
    }

    // 第三步:配置低通滤波器(DLPF)
    // 写入0x03 → 低通滤波带宽44Hz,降低噪声,平衡响应速度
    ret = mpu6050_write_reg(MPU6050_REG_CONFIG, 0x03);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "低通滤波配置失败");
        return ret;
    }

    // 第四步:配置陀螺仪量程
    // 手册说明:GYRO_CONFIG寄存器的BIT4-BIT3是量程位,需要左移3位写入
    // 我们用默认的±250°/s,对应宏GYRO_FS_250DPS=0
    ret = mpu6050_write_reg(MPU6050_REG_GYRO_CONFIG, GYRO_FS_250DPS << 3);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "陀螺仪量程配置失败");
        return ret;
    }

    // 第五步:配置加速度计量程
    // 手册说明:ACCEL_CONFIG寄存器的BIT4-BIT3是量程位,左移3位写入
    // 我们用默认的±2g,对应宏ACCEL_FS_2G=0
    ret = mpu6050_write_reg(MPU6050_REG_ACCEL_CONFIG, ACCEL_FS_2G << 3);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "加速度计量程配置失败");
        return ret;
    }

    // 所有配置完成,初始化成功
    ESP_LOGI(TAG, "MPU6050传感器初始化完成");
    return ESP_OK;
}

5.3.7 数据读取与转换函数

这部分的核心逻辑是:通过mpu6050_read_reg读取连续的寄存器数据→拼接高低字节为 16 位有符号整数→通过转换系数,把原始数字量转换成有物理意义的数值。

// 实现加速度读取函数
esp_err_t mpu6050_read_accel(float *ax, float *ay, float *az)
{
    // 加速度数据共6个字节:X轴高+低字节,Y轴高+低字节,Z轴高+低字节
    uint8_t raw_data[6] = {0};
    // 从加速度起始寄存器,连续读取6个字节
    esp_err_t ret = mpu6050_read_reg(MPU6050_REG_ACCEL_XOUT_H, raw_data, 6);
    if (ret != ESP_OK) {
        return ret;
    }

    // 拼接高低字节:MPU6050是大端模式,高字节在前,低字节在后
    // 数据是16位有符号整数(int16_t),因为加速度有正负
    int16_t raw_ax = (int16_t)((raw_data[0] << 8) | raw_data[1]);
    int16_t raw_ay = (int16_t)((raw_data[2] << 8) | raw_data[3]);
    int16_t raw_az = (int16_t)((raw_data[4] << 8) | raw_data[5]);

    // 转换为物理值(单位:g):原始值 / 对应量程的灵敏度系数
    *ax = raw_ax / ACCEL_SENS_2G;
    *ay = raw_ay / ACCEL_SENS_2G;
    *az = raw_az / ACCEL_SENS_2G;

    return ESP_OK;
}

// 实现陀螺仪读取函数
esp_err_t mpu6050_read_gyro(float *gx, float *gy, float *gz)
{
    // 陀螺仪数据共6个字节:X/Y/Z轴各2字节
    uint8_t raw_data[6] = {0};
    // 从陀螺仪起始寄存器,连续读取6个字节
    esp_err_t ret = mpu6050_read_reg(MPU6050_REG_GYRO_XOUT_H, raw_data, 6);
    if (ret != ESP_OK) {
        return ret;
    }

    // 拼接高低字节
    int16_t raw_gx = (int16_t)((raw_data[0] << 8) | raw_data[1]);
    int16_t raw_gy = (int16_t)((raw_data[2] << 8) | raw_data[3]);
    int16_t raw_gz = (int16_t)((raw_data[4] << 8) | raw_data[5]);

    // 转换为物理值(单位:°/s)
    *gx = raw_gx / GYRO_SENS_250;
    *gy = raw_gy / GYRO_SENS_250;
    *gz = raw_gz / GYRO_SENS_250;

    return ESP_OK;
}

// 实现温度读取函数
esp_err_t mpu6050_read_temp(float *temp)
{
    // 温度数据共2个字节
    uint8_t raw_data[2] = {0};
    // 从温度起始寄存器,连续读取2个字节
    esp_err_t ret = mpu6050_read_reg(MPU6050_REG_TEMP_OUT_H, raw_data, 2);
    if (ret != ESP_OK) {
        return ret;
    }

    // 拼接高低字节
    int16_t raw_temp = (int16_t)((raw_data[0] << 8) | raw_data[1]);
    // 转换为摄氏温度:公式来自MPU6050官方datasheet
    *temp = (raw_temp / 340.0f) + 36.53f;

    return ESP_OK;
}

到这里,mpu6050.c的所有核心功能就全部实现了。

六、完善main.c

主函数是 ESP32 程序的入口,核心逻辑是:按顺序初始化→循环调用驱动接口读取数据→打印输出。

// 引入依赖头文件
#include <stdio.h>
#include "esp_log.h"
#include "mpu6050.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

// 主函数日志标签
#define TAG "MPU6050_MAIN"

// ESP32程序的唯一入口,相当于单片机的main函数
void app_main(void)
{
    ESP_LOGI(TAG, "===== ESP32-S3 MPU6050 驱动测试开始 =====");

    // 第一步:初始化I2C总线
    // ESP_ERROR_CHECK:如果函数返回不是ESP_OK,直接终止程序,打印错误
    ESP_ERROR_CHECK(mpu6050_i2c_init());

    // 第二步:初始化MPU6050传感器
    ESP_ERROR_CHECK(mpu6050_init());

    ESP_LOGI(TAG, "传感器初始化完成,开始循环读取数据");

    // 定义变量,存放读取到的传感器数据
    float ax, ay, az;  // 加速度,单位g
    float gx, gy, gz;  // 陀螺仪,单位°/s
    float temp;        // 温度,单位℃

    // 第三步:无限循环,定时读取数据
    while (1) {
        // 读取加速度数据
        if (mpu6050_read_accel(&ax, &ay, &az) == ESP_OK) {
            ESP_LOGI(TAG, "加速度 | X: %.3f g | Y: %.3f g | Z: %.3f g", ax, ay, az);
        } else {
            ESP_LOGE(TAG, "加速度数据读取失败");
        }

        // 读取陀螺仪数据
        if (mpu6050_read_gyro(&gx, &gy, &gz) == ESP_OK) {
            ESP_LOGI(TAG, "陀螺仪 | X: %.3f dps | Y: %.3f dps | Z: %.3f dps", gx, gy, gz);
        } else {
            ESP_LOGE(TAG, "陀螺仪数据读取失败");
        }

        // 读取温度数据
        if (mpu6050_read_temp(&temp) == ESP_OK) {
            ESP_LOGI(TAG, "芯片温度: %.2f ℃", temp);
        } else {
            ESP_LOGE(TAG, "温度数据读取失败");
        }

        // 打印分隔线,方便查看日志
        ESP_LOGI(TAG, "----------------------------------------");

        // 延时1000ms,也就是1秒读取一次
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

七、编译烧录测试

编译烧录成功后,打开串口监视器,可以看到每秒都有数据的输出:

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐