toy-Kapi_Rtc/QMI8658替换方案_Github驱动.md
2026-01-20 16:55:17 +08:00

19 KiB
Raw Blame History

QMI8658传感器驱动替换方案

一、概述

本文档提供了使用qmi8658-master目录下的C驱动替换现有QMI8658AC++类的完整方案,同时解决当前驱动读取数值不准确的问题。

二、驱动对比分析

1. 当前使用的QMI8658A

  • 基于C++实现的面向对象设计
  • 继承自I2cDevice
  • 提供丰富的功能校准、FIFO、缓冲区管理、中断处理等
  • 接口复杂但完善
  • 存在数值读取不准确的问题

2. qmi8658-master中的C驱动

  • 基于C语言实现的函数式设计
  • 实现了基本的传感器功能:初始化、配置、数据读取等
  • 包含FIFO、计步器、运动检测等功能
  • 提供校准功能
  • 代码简洁明了

3. 替换优势

  • qmi8658-master驱动经过完整验证与README文档描述一致
  • 包含适当的校准功能,有助于解决数值不准确问题
  • 接口简洁,易于集成和维护
  • 支持与现有ImuSensorThing类兼容的功能

三、替换方案实现

1. 创建C++包装类

创建一个名为QMI8658Wrapper的C++类,它将使用qmi8658-master中的C驱动函数但提供与现有QMI8658A类兼容的接口。

// qmi8658_wrapper.h
#ifndef QMI8658_WRAPPER_H
#define QMI8658_WRAPPER_H

#include "driver/i2c_master.h"
#include "boards/movecall-moji-esp32s3/qmi8658-master/qmi8658.h"
#include <cstdint>
#include <cstring>
#include <mutex>

// 与QMI8658A类兼容的数据结构
typedef struct {
    union {
        struct {
            float acc_x;
            float acc_y;
            float acc_z;
        };
        float accel[3];
    };
    union {
        struct {
            float gyro_x;
            float gyro_y;
            float gyro_z;
        };
        float gyro[3];
    };
    float temperature;
    uint64_t timestamp;
    bool valid;
} qmi8658a_data_t;

// 与QMI8658A类兼容的错误代码
typedef enum {
    QMI8658A_OK = 0,
    QMI8658A_ERROR_INVALID_PARAM = -1,
    QMI8658A_ERROR_I2C_COMM = -2,
    QMI8658A_ERROR_CHIP_ID = -3,
    QMI8658A_ERROR_INIT_FAILED = -4,
    QMI8658A_ERROR_CONFIG_FAILED = -5,
    QMI8658A_ERROR_DATA_NOT_READY = -6,
    QMI8658A_ERROR_TIMEOUT = -7
} qmi8658a_error_t;

// 与QMI8658A类兼容的状态定义
typedef enum {
    QMI8658A_STATE_UNINITIALIZED = 0,
    QMI8658A_STATE_INITIALIZING,
    QMI8658A_STATE_READY,
    QMI8658A_STATE_ERROR
} qmi8658a_state_t;

class QMI8658Wrapper {
private:
    qmi8658a_state_t state_;
    qmi8658a_error_t last_error_;
    bool is_initialized_;
    std::mutex mutex_;
    float acc_offset_[3];
    float gyro_offset_[3];
    bool calibration_applied_;

public:
    QMI8658Wrapper(i2c_master_bus_handle_t i2c_bus, uint8_t addr = 0x6A);
    ~QMI8658Wrapper();

    // 初始化函数
    qmi8658a_error_t Initialize(void* config = nullptr);
    
    // 数据读取函数
    qmi8658a_error_t ReadSensorData(qmi8658a_data_t* data);
    qmi8658a_error_t ReadAccelData(float* acc_x, float* acc_y, float* acc_z);
    qmi8658a_error_t ReadGyroData(float* gyro_x, float* gyro_y, float* gyro_z);
    qmi8658a_error_t ReadTemperature(float* temperature);
    
    // 校准相关函数
    qmi8658a_error_t StartCalibration(uint32_t duration_ms = 5000);
    qmi8658a_error_t ApplyCalibration();
    qmi8658a_error_t SaveCalibrationToNVS();
    qmi8658a_error_t LoadCalibrationFromNVS();
    
    // 状态查询函数
    qmi8658a_state_t GetState() const { return state_; }
    qmi8658a_error_t GetLastError() const { return last_error_; }
    bool IsDataReady();
    
    // 芯片信息函数
    uint8_t GetChipId();
    
    // 重置函数
    qmi8658a_error_t SoftReset();
};

#endif // QMI8658_WRAPPER_H
// qmi8658_wrapper.cc
#include "qmi8658_wrapper.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "nvs_flash.h"
#include "nvs.h"
#include <cmath>

#define TAG "QMI8658Wrapper"
#define CALIBRATION_NAMESPACE "qmi8658_cal"
#define ACC_OFFSET_KEY "acc_offset"
#define GYRO_OFFSET_KEY "gyro_offset"
#define CALIBRATION_FLAG_KEY "calibrated"

// 实现与qmi8658-master驱动的接口函数
extern "C" {
    // 如果qmi8658-master驱动需要这些函数的自定义实现
    // 可以在这里提供
}

QMI8658Wrapper::QMI8658Wrapper(i2c_master_bus_handle_t i2c_bus, uint8_t addr) {
    state_ = QMI8658A_STATE_UNINITIALIZED;
    last_error_ = QMI8658A_OK;
    is_initialized_ = false;
    memset(acc_offset_, 0, sizeof(acc_offset_));
    memset(gyro_offset_, 0, sizeof(gyro_offset_));
    calibration_applied_ = false;
}

QMI8658Wrapper::~QMI8658Wrapper() {
    // 清理资源
}

qmi8658a_error_t QMI8658Wrapper::Initialize(void* config) {
    std::lock_guard<std::mutex> lock(mutex_);
    
    state_ = QMI8658A_STATE_INITIALIZING;
    
    // 初始化传感器
    if (qmi8658_init() != 1) {
        ESP_LOGE(TAG, "Sensor initialization failed");
        last_error_ = QMI8658A_ERROR_INIT_FAILED;
        state_ = QMI8658A_STATE_ERROR;
        return last_error_;
    }
    
    // 尝试加载保存的校准数据
    LoadCalibrationFromNVS();
    
    state_ = QMI8658A_STATE_READY;
    is_initialized_ = true;
    return QMI8658A_OK;
}

qmi8658a_error_t QMI8658Wrapper::ReadSensorData(qmi8658a_data_t* data) {
    std::lock_guard<std::mutex> lock(mutex_);
    
    if (!is_initialized_ || state_ != QMI8658A_STATE_READY) {
        last_error_ = QMI8658A_ERROR_INIT_FAILED;
        return last_error_;
    }
    
    float acc[3] = {0};
    float gyro[3] = {0};
    
    // 读取传感器数据
    qmi8658_read_sensor_data(acc, gyro);
    
    // 应用校准偏移
    if (calibration_applied_) {
        for (int i = 0; i < 3; i++) {
            acc[i] -= acc_offset_[i];
            gyro[i] -= gyro_offset_[i];
        }
    }
    
    // 填充数据结构
    data->acc_x = acc[0];
    data->acc_y = acc[1];
    data->acc_z = acc[2];
    data->gyro_x = gyro[0];
    data->gyro_y = gyro[1];
    data->gyro_z = gyro[2];
    data->temperature = qmi8658_readTemp();
    data->timestamp = esp_timer_get_time();
    data->valid = true;
    
    return QMI8658A_OK;
}

qmi8658a_error_t QMI8658Wrapper::ReadAccelData(float* acc_x, float* acc_y, float* acc_z) {
    std::lock_guard<std::mutex> lock(mutex_);
    
    if (!is_initialized_ || state_ != QMI8658A_STATE_READY) {
        last_error_ = QMI8658A_ERROR_INIT_FAILED;
        return last_error_;
    }
    
    float acc[3] = {0};
    float gyro[3] = {0};
    
    qmi8658_read_sensor_data(acc, gyro);
    
    // 应用校准偏移
    if (calibration_applied_) {
        for (int i = 0; i < 3; i++) {
            acc[i] -= acc_offset_[i];
        }
    }
    
    *acc_x = acc[0];
    *acc_y = acc[1];
    *acc_z = acc[2];
    
    return QMI8658A_OK;
}

qmi8658a_error_t QMI8658Wrapper::ReadGyroData(float* gyro_x, float* gyro_y, float* gyro_z) {
    std::lock_guard<std::mutex> lock(mutex_);
    
    if (!is_initialized_ || state_ != QMI8658A_STATE_READY) {
        last_error_ = QMI8658A_ERROR_INIT_FAILED;
        return last_error_;
    }
    
    float acc[3] = {0};
    float gyro[3] = {0};
    
    qmi8658_read_sensor_data(acc, gyro);
    
    // 应用校准偏移
    if (calibration_applied_) {
        for (int i = 0; i < 3; i++) {
            gyro[i] -= gyro_offset_[i];
        }
    }
    
    *gyro_x = gyro[0];
    *gyro_y = gyro[1];
    *gyro_z = gyro[2];
    
    return QMI8658A_OK;
}

qmi8658a_error_t QMI8658Wrapper::ReadTemperature(float* temperature) {
    std::lock_guard<std::mutex> lock(mutex_);
    
    if (!is_initialized_ || state_ != QMI8658A_STATE_READY) {
        last_error_ = QMI8658A_ERROR_INIT_FAILED;
        return last_error_;
    }
    
    *temperature = qmi8658_readTemp();
    return QMI8658A_OK;
}

qmi8658a_error_t QMI8658Wrapper::StartCalibration(uint32_t duration_ms) {
    std::lock_guard<std::mutex> lock(mutex_);
    
    if (!is_initialized_ || state_ != QMI8658A_STATE_READY) {
        last_error_ = QMI8658A_ERROR_INIT_FAILED;
        return last_error_;
    }
    
    ESP_LOGI(TAG, "Starting calibration for %lu ms", duration_ms);
    
    // 使用qmi8658驱动内置的校准功能
    qmi8658_send_ctl9cmd(qmi8658_Ctrl9_Cmd_On_Demand_Cali);
    
    // 等待校准完成
    vTaskDelay(duration_ms / portTICK_PERIOD_MS);
    
    // 应用校准
    return ApplyCalibration();
}

qmi8658a_error_t QMI8658Wrapper::ApplyCalibration() {
    std::lock_guard<std::mutex> lock(mutex_);
    
    // 在这里可以实现更复杂的校准逻辑
    // 目前使用默认的偏移值或从NVS加载的偏移值
    calibration_applied_ = true;
    ESP_LOGI(TAG, "Calibration applied");
    
    return QMI8658A_OK;
}

qmi8658a_error_t QMI8658Wrapper::SaveCalibrationToNVS() {
    std::lock_guard<std::mutex> lock(mutex_);
    
    nvs_handle_t nvs_handle;
    esp_err_t err = nvs_open(CALIBRATION_NAMESPACE, NVS_READWRITE, &nvs_handle);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "Failed to open NVS namespace: %s", esp_err_to_name(err));
        return QMI8658A_ERROR_I2C_COMM;
    }
    
    // 保存加速度计偏移
    err = nvs_set_blob(nvs_handle, ACC_OFFSET_KEY, acc_offset_, sizeof(acc_offset_));
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "Failed to save accelerometer offset: %s", esp_err_to_name(err));
        nvs_close(nvs_handle);
        return QMI8658A_ERROR_I2C_COMM;
    }
    
    // 保存陀螺仪偏移
    err = nvs_set_blob(nvs_handle, GYRO_OFFSET_KEY, gyro_offset_, sizeof(gyro_offset_));
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "Failed to save gyroscope offset: %s", esp_err_to_name(err));
        nvs_close(nvs_handle);
        return QMI8658A_ERROR_I2C_COMM;
    }
    
    // 保存校准标志
    err = nvs_set_u8(nvs_handle, CALIBRATION_FLAG_KEY, 1);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "Failed to save calibration flag: %s", esp_err_to_name(err));
        nvs_close(nvs_handle);
        return QMI8658A_ERROR_I2C_COMM;
    }
    
    err = nvs_commit(nvs_handle);
    nvs_close(nvs_handle);
    
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "Failed to commit NVS: %s", esp_err_to_name(err));
        return QMI8658A_ERROR_I2C_COMM;
    }
    
    ESP_LOGI(TAG, "Calibration data saved to NVS");
    return QMI8658A_OK;
}

qmi8658a_error_t QMI8658Wrapper::LoadCalibrationFromNVS() {
    std::lock_guard<std::mutex> lock(mutex_);
    
    nvs_handle_t nvs_handle;
    esp_err_t err = nvs_open(CALIBRATION_NAMESPACE, NVS_READONLY, &nvs_handle);
    if (err != ESP_OK) {
        ESP_LOGW(TAG, "Failed to open NVS namespace: %s", esp_err_to_name(err));
        return QMI8658A_ERROR_I2C_COMM;
    }
    
    // 检查是否有校准数据
    uint8_t calibrated = 0;
    err = nvs_get_u8(nvs_handle, CALIBRATION_FLAG_KEY, &calibrated);
    if (err != ESP_OK || calibrated != 1) {
        ESP_LOGW(TAG, "No calibration data found in NVS");
        nvs_close(nvs_handle);
        return QMI8658A_ERROR_I2C_COMM;
    }
    
    // 加载加速度计偏移
    size_t size = sizeof(acc_offset_);
    err = nvs_get_blob(nvs_handle, ACC_OFFSET_KEY, acc_offset_, &size);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "Failed to load accelerometer offset: %s", esp_err_to_name(err));
        nvs_close(nvs_handle);
        return QMI8658A_ERROR_I2C_COMM;
    }
    
    // 加载陀螺仪偏移
    size = sizeof(gyro_offset_);
    err = nvs_get_blob(nvs_handle, GYRO_OFFSET_KEY, gyro_offset_, &size);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "Failed to load gyroscope offset: %s", esp_err_to_name(err));
        nvs_close(nvs_handle);
        return QMI8658A_ERROR_I2C_COMM;
    }
    
    nvs_close(nvs_handle);
    calibration_applied_ = true;
    
    ESP_LOGI(TAG, "Calibration data loaded from NVS");
    return QMI8658A_OK;
}

bool QMI8658Wrapper::IsDataReady() {
    std::lock_guard<std::mutex> lock(mutex_);
    
    if (!is_initialized_ || state_ != QMI8658A_STATE_READY) {
        return false;
    }
    
    // 检查传感器数据是否就绪
    unsigned char status = qmi8658_readStatus0();
    return (status & 0x03) == 0x03; // 假设位0和位1表示加速度计和陀螺仪数据就绪
}

uint8_t QMI8658Wrapper::GetChipId() {
    std::lock_guard<std::mutex> lock(mutex_);
    
    return qmi8658_get_id();
}

qmi8658a_error_t QMI8658Wrapper::SoftReset() {
    std::lock_guard<std::mutex> lock(mutex_);
    
    // 发送重置命令
    qmi8658_send_ctl9cmd(qmi8658_Ctrl9_Cmd_EnableExtReset);
    vTaskDelay(100 / portTICK_PERIOD_MS); // 等待重置完成
    
    // 重新初始化
    return Initialize(nullptr);
}

2. 修改ImuSensorThing类

修改ImuSensorThing类的头文件和实现文件,使其可以接受QMI8658Wrapper类的实例。

// imu_sensor_thing.h 修改后的版本
#ifndef IMU_SENSOR_THING_H
#define IMU_SENSOR_THING_H

#include "iot/thing.h"
#include "qmi8658_wrapper.h" // 使用新的包装类

namespace iot {

class ImuSensorThing : public Thing {
private:
    QMI8658Wrapper* imu_sensor_;
    qmi8658a_data_t latest_data_;
    bool motion_detected_;
    float motion_threshold_;

public:
    ImuSensorThing(QMI8658Wrapper* sensor);
    virtual ~ImuSensorThing() = default;

    void UpdateData(const qmi8658a_data_t& data);
    void SetMotionDetected(bool detected);
};

} // namespace iot

#endif // IMU_SENSOR_THING_H
// imu_sensor_thing.cc 基本保持不变,但构造函数参数类型改变
#include "imu_sensor_thing.h"
#include "esp_log.h"
#include <cmath>
#include <cstring>

#define TAG "ImuSensorThing"

namespace iot {

ImuSensorThing::ImuSensorThing(QMI8658Wrapper* sensor) 
    : Thing("ImuSensor", "姿态传感器"), 
      imu_sensor_(sensor),
      motion_detected_(false),
      motion_threshold_(1.5f) {
    
    // 初始化数据
    memset(&latest_data_, 0, sizeof(latest_data_));
    
    // 其他代码保持不变...
}

// 其他方法保持不变...

} // namespace iot

3. 创建测试模式初始化代码

// test_mode_init.cc
#include "esp_log.h"
#include "driver/i2c_master.h"
#include "qmi8658_wrapper.h"
#include "imu_sensor_thing.h"

#define TAG "TestModeInit"

// I2C配置
#define I2C_MASTER_SCL_IO 19        /*!< GPIO number used for I2C master clock */
#define I2C_MASTER_SDA_IO 18        /*!< GPIO number used for I2C master data  */
#define I2C_MASTER_NUM I2C_NUM_0    /*!< I2C master i2c port number, the number of i2c peripheral interfaces available will depend on the chip */
#define I2C_MASTER_FREQ_HZ 400000   /*!< I2C master clock frequency */
#define I2C_MASTER_TX_BUF_DISABLE 0 /*!< I2C master doesn't need buffer */
#define I2C_MASTER_RX_BUF_DISABLE 0 /*!< I2C master doesn't need buffer */

iot::ImuSensorThing* initialize_imu_in_test_mode() {
    ESP_LOGI(TAG, "Initializing IMU sensor in test mode");
    
    // 配置I2C控制器
    i2c_master_bus_config_t i2c_bus_config = {
        .clk_source = I2C_CLK_SRC_DEFAULT,
        .i2c_port = I2C_MASTER_NUM,
        .scl_io_num = I2C_MASTER_SCL_IO,
        .sda_io_num = I2C_MASTER_SDA_IO,
        .glitch_ignore_cnt = 7,
        .flags.enable_internal_pullup = true,
    };
    
    i2c_master_bus_handle_t i2c_bus = NULL;
    esp_err_t err = i2c_new_master_bus(&i2c_bus_config, &i2c_bus);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "Failed to create I2C bus: %s", esp_err_to_name(err));
        return nullptr;
    }
    
    // 创建QMI8658Wrapper实例
    QMI8658Wrapper* imu_sensor = new QMI8658Wrapper(i2c_bus);
    
    // 初始化传感器
    if (imu_sensor->Initialize() != QMI8658A_OK) {
        ESP_LOGE(TAG, "Failed to initialize IMU sensor");
        delete imu_sensor;
        return nullptr;
    }
    
    // 创建ImuSensorThing实例
    iot::ImuSensorThing* imu_thing = new iot::ImuSensorThing(imu_sensor);
    
    ESP_LOGI(TAG, "IMU sensor initialized successfully in test mode");
    return imu_thing;
}

void perform_imu_test() {
    ESP_LOGI(TAG, "Performing IMU sensor test");
    
    // 获取ImuSensorThing实例
    iot::ImuSensorThing* imu_thing = initialize_imu_in_test_mode();
    if (!imu_thing) {
        ESP_LOGE(TAG, "Failed to get IMU thing instance");
        return;
    }
    
    // 在这里可以添加测试逻辑,比如读取传感器数据并进行验证
    
    // 示例:读取几次传感器数据
    QMI8658Wrapper* imu_sensor = /* 获取传感器实例的方式 */;
    qmi8658a_data_t data;
    
    for (int i = 0; i < 10; i++) {
        if (imu_sensor->ReadSensorData(&data) == QMI8658A_OK) {
            ESP_LOGI(TAG, "IMU Data - Accel: (%.2f, %.2f, %.2f)g, Gyro: (%.2f, %.2f, %.2f)dps",
                     data.acc_x, data.acc_y, data.acc_z,
                     data.gyro_x, data.gyro_y, data.gyro_z);
            
            // 更新ImuSensorThing的数据
            imu_thing->UpdateData(data);
        }
        vTaskDelay(100 / portTICK_PERIOD_MS);
    }
    
    // 测试完成后清理资源
    // 注意:实际应用中可能需要更复杂的资源管理
}

4. 编译和链接配置

修改项目的CMakeLists.txt文件确保正确包含qmi8658-master驱动和新的包装类。

# CMakeLists.txt 片段

# 添加qmi8658-master驱动
set(QMI8658_DRIVER_DIR ${CMAKE_CURRENT_LIST_DIR}/boards/movecall-moji-esp32s3/qmi8658-master)
file(GLOB QMI8658_DRIVER_SOURCES ${QMI8658_DRIVER_DIR}/*.c)

# 添加新的包装类
set(WRAPPER_SOURCES 
    ${CMAKE_CURRENT_LIST_DIR}/qmi8658_wrapper.cc
    ${CMAKE_CURRENT_LIST_DIR}/test_mode_init.cc
)

# 添加到主应用程序
idf_component_register(
    SRCS ${MAIN_SOURCES} ${QMI8658_DRIVER_SOURCES} ${WRAPPER_SOURCES}
    INCLUDE_DIRS 
        ${CMAKE_CURRENT_LIST_DIR}
        ${QMI8658_DRIVER_DIR}
        # 其他包含目录...
)

四、解决数值不准确问题的策略

1. 硬件检查

  • 确保传感器VDD和VDDIO电压稳定在1.71-3.6V范围内
  • 检查I2C通信线的连接质量确保SCL和SDA信号良好
  • 验证I2C地址设置正确通常为0x6A或0x6B取决于SA0引脚连接

2. 校准优化

  • 使用qmi8658-master驱动的内置校准功能
  • 添加启动时自动加载保存的校准数据
  • 在设备静止状态下执行校准

3. 数据处理优化

  • 添加数据滤波以减少噪声
  • 实现异常值检测和处理
  • 对加速度计数据进行重力补偿

4. 时序和稳定性优化

  • 确保I2C通信的可靠性添加适当的重试机制
  • 使用FIFO模式批量读取数据减少通信开销
  • 实现电源管理策略,确保传感器稳定供电

五、实现步骤

  1. 创建QMI8658Wrapper类的头文件和实现文件
  2. 修改ImuSensorThing类以使用新的包装类
  3. 创建测试模式初始化代码
  4. 更新编译和链接配置
  5. 编译和测试应用程序

六、注意事项

  • 在使用新驱动之前,确保备份现有代码
  • 替换过程中保持与现有接口的兼容性
  • 实现适当的错误处理和资源管理
  • 在测试模式中初始化传感器时,确保其他系统组件已就绪
  • 考虑添加日志记录,以便于调试和问题分析