19 KiB
19 KiB
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模式批量读取数据,减少通信开销
- 实现电源管理策略,确保传感器稳定供电
五、实现步骤
- 创建
QMI8658Wrapper类的头文件和实现文件 - 修改
ImuSensorThing类以使用新的包装类 - 创建测试模式初始化代码
- 更新编译和链接配置
- 编译和测试应用程序
六、注意事项
- 在使用新驱动之前,确保备份现有代码
- 替换过程中保持与现有接口的兼容性
- 实现适当的错误处理和资源管理
- 在测试模式中初始化传感器时,确保其他系统组件已就绪
- 考虑添加日志记录,以便于调试和问题分析