Skip to content

第 11 章:QMC6309 磁力计与罗盘航向

📂 示例代码11_mag_compass

11.1 知识要点

  • QMC6309 三轴磁力计工作原理(霍尔效应,I2C 接口,地址 0x7C)
  • 硬铁(hard-iron)干扰:电路板上的永磁体造成固定偏移,用 min/max 法校准
  • 软铁(soft-iron)干扰:铁磁材料使磁场椭圆化,用缩放矩阵校正为圆
  • 倾斜补偿航向(tilt-compensated heading):利用 roll/pitch 将水平磁场分量投影到水平面
  • 互补滤波融合陀螺仪积分与磁力计绝对航向,抑制各自的短板

11.2 课程内容

本章演示 QMC6309 磁力计的完整使用流程:初始化、原始数据读取、硬铁/软铁校准、倾斜补偿航向计算,以及通过 USB CDC 控制台交互。掌握本章后,可将磁力计融合进 AHRS,实现绝对航向估计。

11.3 基础学习

QMC6309 寄存器

寄存器地址说明
CHIP_ID0x00固定值 0x90,用于验证通信
X_LSB0x01X 轴数据低字节(共 6 字节 XYZ)
STATUS0x09bit0=DRDY(数据就绪)
CTRL10x0AOSR、工作模式
CTRL20x0B软复位、ODR、量程

初始化流程:软复位 → 验证 CHIP_ID → 配置 CTRL1(OSR)→ 配置 CTRL2(ODR、量程)→ 设置连续测量模式。

硬铁校准

让设备在水平面旋转 360°,记录 X/Y 轴的最大值和最小值:

c
hard_iron[0] = (max_x + min_x) / 2.0f;
hard_iron[1] = (max_y + min_y) / 2.0f;

校准后:x_cal = x_raw - hard_iron[0]

软铁校准

XY 轴量程不等时,磁场轨迹为椭圆。用对角缩放矩阵将其拉伸为圆:

c
float avg_range = (range_x + range_y) / 2.0f;
soft_iron[0] = avg_range / range_x;   // X 缩放
soft_iron[4] = avg_range / range_y;   // Y 缩放
soft_iron[8] = 1.0f;                  // Z 不变(地面车辆)

倾斜补偿

直接用 atan2(my, mx) 只在水平放置时准确。倾斜时需先将磁场投影到水平面:

c
float mx_h = mx * cosf(pitch) + mz * sinf(pitch);
float my_h = mx * sinf(roll) * sinf(pitch)
           + my * cosf(roll)
           - mz * sinf(roll) * cosf(pitch);
float heading = atan2f(-my_h, mx_h) * 180.0f / M_PI;
if (heading < 0) heading += 360.0f;

其中 roll/pitch 来自 IMU(第 10 章 Madgwick AHRS)。

互补滤波

磁力计噪声大但无累积误差;陀螺仪短期精准但有漂移。互补滤波取长补短:

c
// 静止时 alpha=0.98,运动时 alpha=0.995
yaw_cf += gyro_z * dt;
float err = mag_heading - yaw_cf;
// 角度差归一化到 [-π, π]
yaw_cf += (1.0f - alpha) * err;

11.4 程序学习

初始化

c
void qmc6309_init(i2c_master_dev_handle_t dev) {
    s_dev = dev;
    memset(s_hard_iron, 0, sizeof(s_hard_iron));
    memcpy(s_soft_iron, k_identity, sizeof(s_soft_iron));

    // 软复位
    write_reg(QMC_REG_CTRL2, CTRL2_SOFT_RST);
    vTaskDelay(pdMS_TO_TICKS(20));
    write_reg(QMC_REG_CTRL2, 0x00);
    vTaskDelay(pdMS_TO_TICKS(20));

    // 验证 CHIP_ID
    uint8_t id = 0;
    read_reg(QMC_REG_CHIP_ID, &id, 100);
    if (id != 0x90) printf("WARN: QMC6309 CHIP_ID=0x%02X\n", id);

    // OSR2=4, OSR1=3, 单次模式(后续切换为连续)
    uint8_t ctrl1 = CTRL1_OSR2(4) | CTRL1_OSR1(3) | CTRL1_MODE(0);
    write_reg(QMC_REG_CTRL1, ctrl1);
    // ODR=4 (200Hz), RNG=2 (8G)
    write_reg(QMC_REG_CTRL2, CTRL2_ODR(4) | CTRL2_RNG(2));
    // 切换为连续测量模式
    ctrl1 = (ctrl1 & ~0x03) | CTRL1_MODE(3);
    write_reg(QMC_REG_CTRL1, ctrl1);
    vTaskDelay(pdMS_TO_TICKS(100));
}

读取与校准应用

c
bool qmc6309_read(qmc6309_data_t *out) {
    uint8_t status = 0;
    read_reg(QMC_REG_STATUS, &status, QMC_READ_I2C_TIMEOUT_MS);
    if (!(status & STATUS_DRDY)) return false;

    uint8_t data[6];
    read_burst(QMC_REG_X_LSB, data, 6);
    float rx = (int16_t)((data[1] << 8) | data[0]) / s_sensitivity;
    float ry = (int16_t)((data[3] << 8) | data[2]) / s_sensitivity;
    float rz = (int16_t)((data[5] << 8) | data[4]) / s_sensitivity;

    // 应用硬铁 + 软铁校准
    float dx = rx - s_hard_iron[0];
    float dy = ry - s_hard_iron[1];
    float dz = rz - s_hard_iron[2];
    out->x = s_soft_iron[0]*dx + s_soft_iron[1]*dy + s_soft_iron[2]*dz;
    out->y = s_soft_iron[3]*dx + s_soft_iron[4]*dy + s_soft_iron[5]*dz;
    out->z = s_soft_iron[6]*dx + s_soft_iron[7]*dy + s_soft_iron[8]*dz;

    out->heading = atan2f(out->y, out->x) * 180.0f / M_PI;
    if (out->heading < 0) out->heading += 360.0f;
    return true;
}

校准流程

c
void qmc6309_calibrate(uint32_t duration_ms) {
    // 初始化 min/max
    for (int i = 0; i < 3; i++) { s_min_val[i] = 1e6f; s_max_val[i] = -1e6f; }

    int64_t start = esp_timer_get_time();
    while ((esp_timer_get_time() - start) < (int64_t)duration_ms * 1000) {
        // 读取原始高斯值并更新 min/max
        // ...
        vTaskDelay(pdMS_TO_TICKS(10));
    }
    // 计算硬铁偏移和软铁缩放
    for (int i = 0; i < 3; i++)
        s_hard_iron[i] = (s_max_val[i] + s_min_val[i]) / 2.0f;
    float avg_xy = ((s_max_val[0]-s_min_val[0]) + (s_max_val[1]-s_min_val[1])) / 2.0f;
    s_soft_iron[0] = avg_xy / (s_max_val[0] - s_min_val[0]);
    s_soft_iron[4] = avg_xy / (s_max_val[1] - s_min_val[1]);
}

主循环

c
while (1) {
    qmc6309_data_t d;
    if (qmc6309_read(&d)) {
        printf("hdg=%.1f x=%.4f y=%.4f z=%.4f\n",
               d.heading, d.x, d.y, d.z);
    }

    // 非阻塞 USB CDC 命令解析
    uint8_t byte;
    if (usb_serial_jtag_read_bytes(&byte, 1, 0) == 1) {
        // 积累到换行符后处理命令
        // cal [sec] / heading / help
    }
    vTaskDelay(pdMS_TO_TICKS(100));
}

11.5 课程总结

本章掌握了 QMC6309 磁力计的 I2C 驱动编写、硬铁/软铁两步校准流程,以及倾斜补偿航向计算。磁力计提供绝对航向参考,但对电磁干扰敏感;第 12 章完整示例中,通过互补滤波将磁力计与陀螺仪融合,在动态场景下获得稳定的罗盘航向。

Built for OSRCORE robot development board.