lingzhiLab 发表于 2025-3-7 09:02

零知开源——ESP8266结合ICM20948实现高精度姿态解算

本帖最后由 lingzhiLab 于 2025-3-7 09:15 编辑

#技术资源# #申请原创# 零知实验室发布新版ICM20948模块,可以非常方便的应用在零知各个系列开发板或其他类似MCU,它可以作为已经停产的MPU9250的替代品,下面演示它在零知ESP8266上的使用。

一、ICM20948深度解析:九轴传感器的核心技术
1.1 什么是IMU?
IMU(Inertial Measurement Unit)即惯性测量单元,是融合加速度计、陀螺仪和磁力计的核心传感器。ICM20948作为新一代九轴IMU,具备以下技术特性:


IMU20948技术特性

参数
规格
技术优势

加速度测量范围
±2g/±4g/±8g/±16g
16位ADC,0.98mg/LSB@±16g

陀螺仪量程
±250/±500/±1000/±2000 dps
0.0038°/s/LSB@±250dps

磁力计量程
±4900μT
16位分辨率,0.15μT/LSB

数据输出速率
最高1125Hz
支持SPI/I2C双接口

工作电压
1.71V-3.6V
超低功耗模式<5μA


1.2 硬件架构解析
芯片内部采用三层堆叠结构:

MEMS传感层:包含三轴加速度计和陀螺仪
ASIC处理层:集成数字运动处理器(DMP)
磁力计层:AK09916磁力计通过I2C从接口连接

1.3 九轴数据融合原理
姿态解算通过传感器融合算法实现:
姿态矩阵=加速度计校准✖陀螺仪积分✖磁力计补偿
典型算法对比:


算法
计算复杂度
精度
适用场景

互补滤波

一般
低速运动

卡尔曼滤波


动态环境

Mahony
中等
较高
嵌入式系统


二、硬件系统搭建
2.1 物料清单


组件
型号

主控板
零知ESP8266

九轴传感器
ICM20948

连接线
杜邦线

电源
USB适配器



2.2 电路连接详解
零知ESP8266和ICM20948九轴加速度传感器的接线图:


零知ESP8266
ICM20948

3.3V
VCC

GND
GND

SCL
SCL

SDA
SDA



三、软件系统开发
3.1 校准验证代码// 在setup()中添加的校准验证if(SerialDebug) {
Serial.println("Post-Calibration Accel Bias (mg):");
Serial.print(1000*myIMU.accelBias);
Serial.print(" ");
Serial.print(1000*myIMU.accelBias);
Serial.print(" ");
Serial.println(1000*myIMU.accelBias);
}
将加速度偏置转换为mg单位(1g=1000mg)
X/Y轴偏置应<50mg,Z轴接近0(理想值)
确保校准过程有效,避免硬件安装误差

3.2 动态零位补偿
static int calibration_cnt = 0;if(calibration_cnt < 1000 && abs(myIMU.gx)<0.5 && abs(myIMU.gy)<0.5) {
myIMU.accelBias += myIMU.ax * 0.001;
myIMU.accelBias += myIMU.ay * 0.001;
calibration_cnt++;
}前1000次采样持续修正加速度偏置
0.001为学习率系数,控制校准速度
实现动态自适应,消除温度漂移影响

3.3传感器数据预处理
1.加速度计处理
myIMU.ax = (float)myIMU.accelCount * myIMU.aRes - myIMU.accelBias;
myIMU.ay = (float)myIMU.accelCount * myIMU.aRes - myIMU.accelBias;
myIMU.az = (float)myIMU.accelCount * myIMU.aRes - myIMU.accelBias;
数据处理流程:
accelCount:原始ADC值
aRes:分辨率计算(例如±16g量程时为2048 LSB/g)
减去校准偏置消除零位误差
关键参数:
量程设置:建议初始化时配置为±8g
分辨率公式:aRes = 16.0 / 32768.0 (16位ADC)

2.陀螺仪处理
myIMU.gx = (float)myIMU.gyroCount * myIMU.gRes - myIMU.gyroBias;
myIMU.gy = (float)myIMU.gyroCount * myIMU.gRes - myIMU.gyroBias;
myIMU.gz = (float)myIMU.gyroCount * myIMU.gRes - myIMU.gyroBias;
漂移控制:典型偏置值应<1°/s温度每升高1℃,零偏变化约0.01°/s改进建议:添加温度补偿函数

3.磁力计数据融合
float mx_raw = (float)myIMU.magCount * myIMU.mRes; // X/Y交换float my_raw = (float)myIMU.magCount * myIMU.mRes;float mz_raw = -(float)myIMU.magCount * myIMU.mRes; // Z反转

myIMU.mx = (mx_raw - myIMU.magBias) * myIMU.magScale;
myIMU.my = (my_raw - myIMU.magBias) * myIMU.magScale;
myIMU.mz = (mz_raw - myIMU.magBias) * myIMU.magScale;
magBias:硬铁干扰补偿
magScale:软铁畸变校正
注意:校准数据需对应新坐标系

3.4姿态解算核心算法
1.Mahony滤波器调用
MahonyQuaternionUpdate(
myIMU.ay,// 加速度Y→X
myIMU.ax,   // 加速度X→Y
-myIMU.az,// 加速度Z反转
myIMU.gy * DEG_TO_RAD, // 陀螺Y→X
myIMU.gx * DEG_TO_RAD, // 陀螺X→Y
-myIMU.gz * DEG_TO_RAD,// 陀螺Z反转
myIMU.mx,
myIMU.my,
myIMU.mz,
myIMU.deltat
);

2.欧拉角转换
myIMU.yaw   = atan2(2.0f * (*(getQ()+1) * *(getQ()+2) + *getQ()
                  * *(getQ()+3)), *getQ() * *getQ() + *(getQ()+1)
                  * *(getQ()+1) - *(getQ()+2) * *(getQ()+2) - *(getQ()+3)
                  * *(getQ()+3));
      myIMU.pitch = -asin(2.0f * (*(getQ()+1) * *(getQ()+3) - *getQ()
                  * *(getQ()+2)));
      myIMU.roll= atan2(2.0f * (*getQ() * *(getQ()+1) + *(getQ()+2)
                  * *(getQ()+3)), *getQ() * *getQ() - *(getQ()+1)
                  * *(getQ()+1) - *(getQ()+2) * *(getQ()+2) + *(getQ()+3)
                  * *(getQ()+3));
      myIMU.pitch *= RAD_TO_DEG;
      myIMU.yaw   *= RAD_TO_DEG;

      // Declination of SparkFun Electronics (40°05'26.6"N 105°11'05.9"W) is
      //         8° 30' E± 0° 21' (or 8.5°) on 2016-07-19
      // - http://www.ngdc.noaa.gov/geomag-web/#declination
      myIMU.yaw-= 8.5;
      myIMU.roll *= RAD_TO_DEG;


3.5数据输出
串口协议设计
//打印格式与processing端格式一致
Serial.print("Or: ");
Serial.print(myIMU.yaw, 2);
Serial.print(" ");

Serial.print(myIMU.pitch, 2);
Serial.print(" ");

Serial.print(myIMU.roll, 2);
Serial.println(" ");

3.6Processing 3D可视化验证
将代码库文件安装包解压到C:\Users\Administrator\Documents\Processing\libraries,然后在Processing中选择开发板对应的串口号,就可以看到我们的3D模型根据九轴的姿态进行变化啦:
import processing.serial.*;
// 传感器数据float roll, pitch, yaw;
PVector accelerometer = new PVector();
PVector gyroscope = new PVector();
PVector magneticField = new PVector();
// 3D模型
PShape model;
PImage bgImage;
// 串口配置
Serial port;
String[] serialPorts;int selectedPort = 0;
boolean printSerial = false;
void setup() {
size(1024, 800, P3D);
frameRate(60);

// 加载资源
bgImage = loadImage("background.png");
model = loadShape("biplane.obj"); // 确保使用标准OBJ格式
model.scale(30);

// 初始化串口
serialPorts = Serial.list();
if(serialPorts.length > 0) connectSerial(serialPorts);
}
void draw() {
background(bgImage);

// 3D场景设置
pushMatrix();
translate(width/2, height/2, 0);
lights();

// 应用旋转
rotateX(radians(pitch));
rotateY(radians(yaw));
rotateZ(radians(roll));

// 绘制模型
shape(model);
popMatrix();

// 显示数据
displaySensorData();
}
void serialEvent(Serial p) {
try {
    String rawData = p.readStringUntil('\n').trim();
    if(printSerial) println(rawData);
   
    String[] parts = split(rawData, ' ');
    if(parts.length >= 4) {
      switch(parts) {
      case "Or:": // 欧拉角格式:Or: yaw pitch roll
          yaw = float(parts);
          pitch = float(parts);
          roll = float(parts);
          break;
      case "Accel:":
          accelerometer.set(float(parts), float(parts), float(parts));
          break;
      case "Gyro:":
          gyroscope.set(float(parts), float(parts), float(parts));
          break;
      case "Mag:":
          magneticField.set(float(parts), float(parts), float(parts));
          break;
      }
    }
} catch(Exception e) {
    println("Serial Error: " + e.getMessage());
}
}
void displaySensorData() {
fill(0, 255, 0);
textSize(16);
textAlign(LEFT, TOP);
String data = "Accelerometer(g): "
    + nfp(accelerometer.x,1,2) + ", "
    + nfp(accelerometer.y,1,2) + ", "
    + nfp(accelerometer.z,1,2) + "\n"
    + "Gyroscope(deg/s): "
    + nfp(gyroscope.x,1,2) + ", "
    + nfp(gyroscope.y,1,2) + ", "
    + nfp(gyroscope.z,1,2) + "\n"
    + "Orientation: \n"
    + "Yaw: " + nfp(yaw,1,1) + "°\n"
    + "Pitch: " + nfp(pitch,1,1) + "°\n"
    + "Roll: " + nfp(roll,1,1) + "°";
text(data, 20, 20);
}
void connectSerial(String portName) {
if(port != null) port.stop();
try {
    port = new Serial(this, portName, 115200);
    port.bufferUntil('\n');
    println("Connected to: " + portName);
} catch(Exception e) {
    println("Connection failed: " + e.getMessage());
}
}
void keyPressed() {
// 切换串口
if(key == ' ') {
    selectedPort = (selectedPort + 1) % serialPorts.length;
    connectSerial(serialPorts);
}
// 切换调试输出
if(key == 'P' || key == 'p') printSerial = !printSerial;
// 重置视角
if(key == 'R' || key == 'r') {
    yaw = pitch = roll = 0;
}
}
四、实现结果分析
观察串口打印输出的DMP姿态解算数据如下:


Processing 3D可视化验证:

Processing 3D可视化验证


五、项目资源汇总
5.1 参考资料
ICM20948数据手册
ESP8266技术参考

5.2 源码获取
https://github.com/Leeri1y/ICM20948-ESP8266参考Github仓库

64位Windows系统的Processing安装包:
通过网盘分享的文件:processing-4.3.3.7z链接: https://pan.baidu.com/s/12B4F33M1caRncVjSJiFPTg?pwd=9h5i 提取码: 9h5i

5.3 扩展学习
Mahony滤波器数学推导
欢迎各位道友相互讨论,一直在学习的路上!



页: [1]
查看完整版本: 零知开源——ESP8266结合ICM20948实现高精度姿态解算