ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

STM32F103驱动ICM20948:DMP姿态解算实战与优化指南

STM32F103驱动ICM20948:DMP姿态解算实战与优化指南 简介本资源是一套面向嵌入式初学者与中级开发者的STM32F103平台ICM20948九轴传感器完整驱动解决方案聚焦运动感知类项目快速落地解决DMP功能调用复杂、寄存器配置繁琐、HAL库适配困难等典型痛点。压缩包含397个文件8.27MB以116个.h头文件和83个.c源文件构成核心驱动框架涵盖ICM20948底层I²C/SPI通信、DMP固件加载、姿态解算初始化及中断回调处理.o、.d、.axf等编译中间文件与Keil工程.uvprojx/.uvoptx已预配置完成开箱即用。内容预览显示包含inv_mems_base_control系列DMP控制模块、ml_math_func数学函数支持及stm32f1xx_hal外设驱动体现完整软硬件协同设计。已有371人学习下载提供可直接烧录验证的HEX文件、清晰分层的目录结构及实测通过的调试配置如keilkilll.bat一键清理大幅降低运动传感功能集成门槛。1. 项目缘起为什么要在STM32F103上折腾ICM20948最近在做一个需要高精度姿态感知的小项目核心需求是实时获取设备的俯仰、横滚和航向角。市面上常见的方案是MPU6050这确实是个经典选择资料多成本低。但我这次的需求有点不一样除了基本的姿态我还希望传感器能提供更干净的原始数据并且在动态响应和温漂控制上表现更好。于是我把目光投向了它的“升级版”——ICM20948。ICM20948是InvenSense现在属于TDK推出的一款9轴运动传感器集成了3轴陀螺仪、3轴加速度计、3轴磁力计还有一个内置的数字运动处理器DMP。相比MPU6050它的性能指标更优比如陀螺仪的量程和噪声控制更好更重要的是它的DMP功能更强大可以直接在传感器内部完成复杂的姿态解算把处理好的四元数或欧拉角直接输出给主控这能极大地减轻像STM32F103这类主频只有72MHz的MCU的运算负担。那么为什么偏偏是STM32F103呢原因很现实成本、生态和够用。F103是STM32家族中经久不衰的“国民MCU”价格亲民开发资料浩如烟海几乎每个嵌入式工程师的抽屉里都能翻出几块。它的性能对于很多中等复杂度的应用是足够的但直接运行复杂的姿态解算算法如Mahony或Madgwick滤波还是会有些吃力特别是当系统还有其他任务时。因此将最耗时的姿态解算工作“外包”给ICM20948的DMP让F103专注于业务逻辑和通信是一个非常理想的组合。然而理想很丰满现实却有点骨感。ICM20948的官方驱动和DMP库通常是为性能更强的平台如STM32F4/F7甚至直接跑Linux的处理器准备的直接移植到F103上会遇到不少坑比如内存不足、DMP固件加载失败、I2C通信不稳定等。网上关于MPU6050的教程一抓一大把但ICM20948在F103上的完整驱动尤其是DMP功能的实战分享却相对零散。这就是我动手做这个项目的初衷梳理出一条清晰的路径把ICM20948特别是它的DMP功能在STM32F103这个经典平台上稳稳地跑起来。2. 硬件连接与底层通信驱动搭建在写任何一行代码之前正确的硬件连接是基石。ICM20948支持标准的I2C和SPI通信为了节省IO口且方便布线我选择了I2C接口。STM32F103的硬件I2C口碑有些两极分化有人觉得不好用但经过合理配置它完全可以稳定工作。2.1 引脚连接与上拉电阻我的硬件连接如下ICM20948的VDD接3.3V。绝对不要接5V会烧毁芯片。GND接共地。SDA接STM32F103的PB7(I2C1_SDA) 或PB11(I2C2_SDA)我用的I2C1。SCL接STM32F103的PB6(I2C1_SCL) 或PB10(I2C2_SCL)。AD0引脚接地这将ICM20948的I2C从机地址设置为0x68。如果接3.3V地址则是0x69。这个地址在后续驱动中会用到。这里有一个关键细节I2C总线必须加上拉电阻。STM32的IO虽然是开漏输出但芯片内部的上拉电阻通常较弱约40kΩ对于高速或长距离传输可能不够。我通常在SDA和SCL线上各接一个4.7kΩ的电阻到3.3V这是确保通信稳定的重要一步。如果通信不稳定时好时坏或完全无法识别设备首先检查上拉电阻。2.2 使用HAL库配置I2C我使用STM32CubeMX进行初始化配置生成HAL库代码。配置要点如下在Pinout Configuration中激活I2C1模式选择I2C。在Configuration-I2C1的参数设置中I2C Speed Mode: 选择Standard Mode。ICM20948在标准模式下最高支持400kHz快速模式支持1MHz但为了稳定性我初次调试先用100kHz或400kHz。I2C Clock Speed: 设为100000Hz (100kHz) 进行初步测试稳定后可尝试400000Hz。注意检查Clock Configuration标签页确保给I2C1的时钟源通常是APB1已经正确使能且频率合理。生成代码后CubeMX会帮我们完成GPIO和I2C外设的初始化。我们需要自己编写针对ICM20948的读写函数。这里我封装了两个基础函数icm20948_i2c_read和icm20948_i2c_write。// icm20948_i2c.c #include “icm20948_i2c.h” #include “main.h” // 包含hi2c1的定义 extern I2C_HandleTypeDef hi2c1; // 假设使用I2C1 #define ICM20948_I2C_ADDR (0x68 1) // HAL库要求左移一位 HAL_StatusTypeDef icm20948_i2c_read(uint8_t reg, uint8_t *data, uint16_t len) { return HAL_I2C_Mem_Read(hi2c1, ICM20948_I2C_ADDR, reg, I2C_MEMADD_SIZE_8BIT, data, len, 100); } HAL_StatusTypeDef icm20948_i2c_write(uint8_t reg, uint8_t data) { return HAL_I2C_Mem_Write(hi2c1, ICM20948_I2C_ADDR, reg, I2C_MEMADD_SIZE_8BIT, data, 1, 100); }注意HAL库的HAL_I2C_Mem_Read/Write函数非常方便它自动处理了发送寄存器地址和读/写数据的整个过程。最后一个参数是超时时间毫秒根据你的系统时钟调整避免因等待太久而卡死。2.3 通信测试与设备ID读取编写好底层读写函数后第一件事就是测试通信是否正常。ICM20948有一个固定的WHO_AM_I寄存器地址为0x00上电后读取它的值应该是0xEA。uint8_t whoami 0; if(icm20948_i2c_read(0x00, whoami, 1) HAL_OK) { if(whoami 0xEA) { printf(“ICM20948通信正常设备ID: 0x%02X\r\n”, whoami); } else { printf(“设备ID错误读到: 0x%02X\r\n”, whoami); } } else { printf(“I2C通信失败\r\n”); }如果这一步失败请按以下顺序排查硬件检查电源是否为3.3VGND是否共地SDA/SCL线是否接反上拉电阻是否焊上逻辑分析仪/示波器抓包这是最直接的诊断工具。观察SDA和SCL波形看起始信号、地址帧、ACK信号是否正常。如果SCL线一直为低可能是总线被锁死尝试对STM32的I2C外设进行重新初始化DeInit/Init。软件延时在初始化I2C后和首次通信前加一个HAL_Delay(100)给传感器足够的上电启动时间。降低速率将I2C时钟速度从400kHz降到100kHz甚至50kHz再试。3. 深入ICM20948寄存器结构与初始化流程通信打通后就要开始配置传感器了。ICM20948的寄存器结构比MPU6050复杂它采用了“银行Bank”选择机制。简单来说它的寄存器地址空间是分页的你需要先告诉芯片你要操作哪个“银行”Bank 0~3的寄存器然后才能对该银行下的寄存器进行读写。3.1 银行选择机制详解ICM20948内部有多个功能模块如陀螺仪、加速度计、磁力计它们的配置寄存器分布在不同的银行。寄存器REG_BANK_SEL地址0x7F就是用来切换银行的。向0x7F写入0x00选择User Bank 0。这里主要是全局控制和数据寄存器。写入0x10选择User Bank 2。这里是加速度计和陀螺仪的配置寄存器。写入0x20选择User Bank 3。这里是磁力计AK09916的配置和状态寄存器。因此任何对非Bank 0寄存器的操作都必须遵循“先选银行再操作”的两步法。我封装了一个带银行选择的读写函数。HAL_StatusTypeDef icm20948_write_reg(uint8_t bank, uint8_t reg, uint8_t value) { HAL_StatusTypeDef status; // 第一步切换银行 if(bank ! current_bank) { // current_bank是一个全局变量记录当前银行 status icm20948_i2c_write(0x7F, bank); if(status ! HAL_OK) return status; current_bank bank; HAL_Delay(1); // 切换后稍作延时 } // 第二步写入目标寄存器 return icm20948_i2c_write(reg, value); }3.2 传感器初始化步骤一个典型的初始化流程如下目的是让传感器进入一个已知的、可用的状态复位设备向PWR_MGMT_1寄存器Bank 0, 0x06写入0x80等待至少100ms让复位完成。再写入0x01使用内部晶振作为时钟源。唤醒传感器确保PWR_MGMT_1寄存器的SLEEP位为0不休眠。配置加速度计和陀螺仪切换到Bank 2。配置GYRO_CONFIG_10x01设置陀螺仪的量程例如±2000dps和数字低通滤波器DLPF。配置ACCEL_CONFIG_10x14设置加速度计的量程例如±4g和DLPF。这里DLPF的配置很重要它决定了输出数据的带宽和噪声。如果你的应用对高频振动不敏感可以适当降低带宽来减少噪声。例如设置陀螺仪DLPF为0x01带宽约197Hz。配置采样率传感器有一个内部的采样率分频器SMPLRT_DIVBank 0, 0x19。陀螺仪和加速度计的输出数据率ODR由内部时钟通常1.125kHz除以(1 SMPLRT_DIV)得到。如果你设置SMPLRT_DIV 4那么ODR 1125 / (14) 225 Hz。切换回Bank 0准备读取数据。这里有一个实操心得初始化完成后不要立刻读取数据。最好让传感器稳定运行几十毫秒到一秒再进行第一次数据读取这样得到的数据会更准确。3.3 原始数据读取与转换ICM20948的加速度计和陀螺仪数据寄存器在Bank 0地址从0x2D开始每个轴的数据占2个字节高位在前MSB。我们需要一次性读取6个轴共12个字节的数据。typedef struct { int16_t accel_x; int16_t accel_y; int16_t accel_z; int16_t gyro_x; int16_t gyro_y; int16_t gyro_z; } RawData_t; RawData_t raw_data; uint8_t buffer[12]; if(icm20948_i2c_read(0x2D, buffer, 12) HAL_OK) { raw_data.accel_x (buffer[0] 8) | buffer[1]; raw_data.accel_y (buffer[2] 8) | buffer[3]; raw_data.accel_z (buffer[4] 8) | buffer[5]; raw_data.gyro_x (buffer[6] 8) | buffer[7]; raw_data.gyro_y (buffer[8] 8) | buffer[9]; raw_data.gyro_z (buffer[10] 8) | buffer[11]; }读到的raw_data是原始ADC值需要根据初始化时设置的量程转换为物理值。例如加速度计量程设为±4g对应的灵敏度为8192 LSB/g。那么accel_x_g raw_data.accel_x / 8192.0f;// 单位g 陀螺仪量程设为±2000dps灵敏度为16.4 LSB/(°/s)。那么gyro_x_dps raw_data.gyro_x / 16.4f;// 单位°/s至此我们已经完成了ICM20948最基础的驱动可以稳定地读取到原始的加速度和角速度数据。但这只是第一步原始数据噪声大且无法直接得到我们想要的“姿态角”。接下来才是重头戏DMP。4. DMP功能移植从官方库到STM32F103的挑战DMPDigital Motion Processor是ICM20948内部的一个可编程微处理器我们可以把一段编译好的固件firmware加载进去。这段固件包含了复杂的传感器融合算法通常基于卡尔曼滤波或互补滤波的变种能够直接输出处理后的姿态数据四元数或欧拉角精度高且完全解放了主控MCU。4.1 获取DMP固件与驱动库InvenSense官方为ICM20948提供了DMP库和固件但通常不直接公开。你需要从ICM20948的官方评估板如TDK的“ICM20948 9-Axis Evaluation Board”配套的软件包中获取或者从一些芯片分销商的技术支持那里申请。关键文件通常包括icm20948_dmp_firmware.h一个巨大的数组里面就是编译好的DMP固件镜像。icm20948_dmp3_driver.c/.h用于与DMP交互的驱动函数包括加载固件、配置输出、读取数据等。inv_mpu.h/.c或类似名称的文件一个抽象层封装了底层的I2C读写和DMP操作。这些文件原本是为性能更强的平台如运行Linux的ARM Cortex-A编写的直接丢给STM32F103会遇到几个大问题。4.2 内存优化STM32F103的紧箍咒F103系列的内存不大以常见的C8T6为例只有20KB的SRAM。官方的DMP驱动库尤其是那个固件数组非常消耗内存。固件数组icm20948_dmp_firmware.h里的数组可能有十几KB。你需要确保它被存放在Flash中而不是默认的RAM里。在Keil或STM32CubeIDE中可以通过const关键字并将其定义在特定的代码段来实现。例如const uint8_t dmp_firmware[] __attribute__((section(“.rodata”))) { … };驱动库的全局变量官方驱动库内部会定义一些全局的缓冲区用于存储传感器数据、FIFO数据等这些缓冲区可能也很大。你需要仔细查看icm20948_dmp3_driver.c文件找到这些缓冲区比如gyro_data[],accel_data[],fifo_buffer[]并根据你的实际需求减小它们的尺寸。例如FIFO缓冲区可能默认是1024字节你可以尝试减小到512甚至256字节前提是能满足你的数据吞吐率。栈空间DMP初始化函数调用链可能比较深需要确保你的线程或主循环有足够的栈空间否则可能导致硬件错误HardFault。在启动文件如startup_stm32f103xb.s或IDE的配置中适当增大栈Stack的大小比如从默认的0x400增加到0x800。4.3 I2C通信稳定性适配官方库的I2C读写接口通常是抽象的你需要实现它。这就是我们之前写的icm20948_i2c_read/write函数派上用场的时候。你需要找到一个叫i2c_write和i2c_read的函数指针或需要实现的弱函数将它们指向我们自己的实现。但这里有个坑官方库的I2C读写可能假设每次操作都是成功的且没有超时。而在嵌入式实时系统中我们必须处理I2C总线可能被占用或出错的情况。因此在我们的底层函数里必须要有超时和错误重试机制。我通常会在函数内部封装一个简单的重试逻辑HAL_StatusTypeDef icm20948_i2c_read_retry(uint8_t reg, uint8_t *data, uint16_t len, uint8_t retries) { HAL_StatusTypeDef status; for(uint8_t i 0; i retries; i) { status icm20948_i2c_read(reg, data, len); if(status HAL_OK) { return HAL_OK; } HAL_Delay(1); // 重试前短暂延时 // 可选在这里尝试复位I2C总线 // __HAL_I2C_CLEAR_FLAG(hi2c1, I2C_FLAG_AF); // 清除ACK失败标志 } return status; // 返回最后一次错误状态 }在DMP初始化这种关键阶段调用带重试的读写函数能大大提高成功率。4.4 DMP初始化与固件加载流程移植好底层接口并优化内存后就可以调用官方库的初始化函数了。流程大致如下调用mpu_init()这个函数会初始化底层传感器并检查WHO_AM_I。调用dmp_load_motion_driver_firmware()这是最关键的一步它会把我们存放在Flash里的那个巨大的固件数组通过I2C一点一点地写入ICM20948内部的DMP程序存储器。这个过程比较耗时可能需要几十到几百毫秒期间一定要保证I2C通信绝对稳定。如果这一步失败后续所有DMP功能都无法使用。配置DMP输出调用诸如dmp_enable_feature()之类的函数来使能你需要的功能。例如你可以使能四元数输出、欧拉角输出、计步器、点击检测等。这里要仔细阅读官方库的文档或注释了解每个特性的标志位。设置DMP输出速率通过dmp_set_fifo_rate()函数设置DMP解算后数据的输出频率例如100Hz。这个频率可以高于或低于传感器的原始ODRDMP内部会进行插值或滤波。使能DMP并启动FIFO最后调用dmp_enable_fifo(true)或类似函数启动DMP运算并将结果推入传感器的FIFO缓冲区。踩坑记录固件加载失败是最常见的问题。除了I2C通信问题还要注意供电稳定性。在加载固件期间确保电源没有大的波动。有时在MCU刚上电、电源尚未完全稳定时就急于加载DMP固件会导致失败。一个稳妥的做法是在系统初始化完成、延时几百毫秒后再进行DMP加载。5. 读取与解析DMP姿态数据DMP初始化成功后姿态数据就不再从原始数据寄存器读取了而是从FIFO先入先出缓冲区里读取。DMP会按照我们设置的速率持续将解算好的数据包写入FIFO。5.1 FIFO数据读取与解析我们需要定期例如在定时器中断里去检查FIFO中的数据长度然后一次性读取一批数据。uint16_t fifo_count 0; uint8_t fifo_buffer[512]; // 缓冲区大小需足够容纳一次读取的数据包 float q0, q1, q2, q3; // 四元数 // 1. 获取FIFO中当前存储的数据字节数 mpu_get_fifo_count(fifo_count); if(fifo_count dmp_get_fifo_packet_size()) { // 确保有一个完整的数据包 // 2. 从FIFO读取一个数据包 if(mpu_read_fifo(fifo_buffer) 0) { // 3. 解析数据包提取四元数 if(dmp_decode_fifo(fifo_buffer) 0) { // 4. 从DMP驱动库的全局变量中获取解析后的四元数 dmp_get_quaternion(q0, q1, q2, q3); // 现在你得到了姿态四元数 } } }dmp_get_fifo_packet_size()返回一个DMP数据包的大小这取决于你使能了哪些输出特性例如只使能四元数包就小如果还使能了原始数据、计步等包就大。5.2 从四元数到欧拉角DMP输出的是四元数这是表示三维旋转最紧凑、无奇点的方式。但对于大多数应用如无人机、云台我们更习惯使用欧拉角俯仰Pitch、横滚Roll、航向Yaw。需要进行转换。#include math.h void quaternion_to_euler(float q0, float q1, float q2, float q3, float *roll, float *pitch, float *yaw) { // 四元数到欧拉角Z-Y-X顺序即航向-俯仰-横滚的转换公式 *roll atan2f(2.0f * (q0 * q1 q2 * q3), 1.0f - 2.0f * (q1 * q1 q2 * q2)); *pitch asinf(2.0f * (q0 * q2 - q3 * q1)); *yaw atan2f(2.0f * (q0 * q3 q1 * q2), 1.0f - 2.0f * (q2 * q2 q3 * q3)); // 将弧度转换为角度 *roll * 180.0f / M_PI; *pitch * 180.0f / M_PI; *yaw * 180.0f / M_PI; }重要提示欧拉角存在“万向节死锁”问题当俯仰角接近±90度时航向角和横滚角会失去意义。如果你的应用会经历大角度运动最好直接使用四元数进行后续计算如控制滤波只在需要显示或记录时才转换为欧拉角。5.3 磁力计校准与航向角融合ICM20948内部还集成了一个AK09916磁力计但默认的DMP固件可能没有启用磁力计融合。这意味着仅靠陀螺仪和加速度计解算出的航向角Yaw是会漂移的因为陀螺仪积分会产生累积误差。要获得稳定、无漂移的航向角必须融合磁力计数据。这通常需要使能磁力计DMP特性在dmp_enable_feature()中使能相关的标志位如DMP_FEATURE_6X_LP_QUAT或包含磁力计的融合模式。这需要对应的DMP固件支持。进行磁力计校准这是必须的步骤。磁力计非常容易受到周围硬铁和软铁干扰。你需要让设备在水平面上缓慢旋转几圈采集各个方向的磁力计原始数据通过椭圆拟合等算法计算出一个校准矩阵偏移和缩放因子。官方DMP库可能提供了自动或手动的校准例程需要仔细调用。加载校准参数将计算出的校准参数写入DMP或传感器寄存器。如果使用的DMP固件不支持磁力计融合或者你觉得校准太麻烦还有一个折中方案只在系统静止或低速时用磁力计和加速度计计算出的航向角去校正陀螺仪积分的漂移。这需要你在MCU端实现一个互补滤波器或简单的卡尔曼滤波器。6. 实战调试技巧与性能优化让代码跑起来只是第一步让它跑得稳、跑得准才是真正的挑战。6.1 数据稳定性与抖动处理即使使用了DMP输出的姿态角可能仍然会有小幅高频抖动。这可能是由传感器噪声、机械振动或解算残余误差引起的。软件低通滤波对最终解算出的欧拉角进行一阶低通滤波。这是一个简单有效的方法。float filtered_angle 0; float alpha 0.1f; // 滤波系数越小越平滑但延迟越大 // 在每次得到新角度new_angle时 filtered_angle alpha * new_angle (1 - alpha) * filtered_angle;调整DMP参数回顾初始化时设置的陀螺仪和加速度计的DLPF带宽。如果应用场景没有剧烈的高频运动可以进一步降低带宽增大DLPF配置值以牺牲一点响应速度换取更平滑的数据。检查安装确保传感器模块被牢固地安装在你的设备上避免因线缆或模块本身松动带来的额外振动噪声。6.2 系统延迟与实时性评估DMP处理和数据传输需要时间。你需要评估从物理运动发生到MCU得到处理后的姿态数据之间的总延迟。传感器内部延迟由DLPF和传感器ODR决定。例如DLPF带宽设为92Hz会引入约几个毫秒的延迟。DMP处理延迟DMP运行需要时间但因为它与传感器数据采集是并行的这部分延迟通常被包含在ODR周期内。I2C读取延迟你的MCU从FIFO读取数据需要时间。为了减少读取频率可以设置FIFO在积累多个数据包后再触发MCU读取通过中断但这会增加批量处理的延迟。测试方法突然将设备旋转一个固定角度如90度通过串口打印时间戳和角度计算角度变化达到稳定值的63%时所经历的时间这大致就是系统的阶跃响应时间。对于需要快速响应的应用如竞速无人机这个时间需要控制在10-20毫秒以内。6.3 功耗考量ICM20948在全力运行陀螺仪加速度计磁力计DMP全开时功耗可能在几个mA级别。对于电池供电设备需要优化循环休眠如果不需时刻感知可以让MCU控制传感器进入低功耗模式通过PWR_MGMT_1寄存器定期唤醒采样。降低ODR在满足性能要求的前提下尽可能降低采样率和输出数据率。关闭不用的传感器如果不需要磁力计可以通过寄存器彻底关闭它PWR_MGMT_2。6.4 常见问题排查表问题现象可能原因排查步骤读取不到设备ID (WHO_AM_I)1. I2C接线错误或接触不良2. 电源问题非3.3V3. 上拉电阻未接或阻值过大4. I2C时钟速率过快1. 用万用表检查线路通断和电压2. 用逻辑分析仪抓取I2C波形3. 将I2C速率降至100kHz以下测试DMP固件加载失败1. I2C通信在长数据传输中不稳定2. 内存不足固件数组存放位置错误3. 供电不稳加载过程中断电4. 使用的DMP固件文件与驱动库版本不匹配1. 在加载函数中加入打印看在哪一步失败2. 检查map文件确认固件数组在Flash中3. 在加载前延时确保电源稳定4. 尝试官方提供的其他版本固件姿态角输出为0或NaN1. DMP未成功初始化或使能2. FIFO读取解析错误3. 四元数到欧拉角转换公式错误4. 传感器未水平静止放置进行自检某些DMP需要1. 检查每一步DMP初始化函数的返回值2. 打印原始FIFO数据看格式是否正确3. 核对转换公式注意弧度与角度4. 上电后让设备静止几秒航向角 (Yaw) 持续漂移1. 未使用磁力计融合2. 磁力计未校准受硬铁干扰3. 周围存在强变化磁场如电机、变压器1. 确认DMP特性已使能磁力计融合2. 执行完整的磁力计校准流程3. 将传感器远离干扰源或使用磁屏蔽材料数据输出频率不对1.SMPLRT_DIV寄存器配置错误2.dmp_set_fifo_rate()参数错误3. MCU读取FIFO的速度跟不上DMP输出速度导致FIFO溢出1. 计算理论ODR并与实际测量对比2. 检查dmp_set_fifo_rate输入参数单位是否为Hz3. 在中断里及时读取FIFO或增大FIFO缓冲区整个项目从硬件连接到DMP姿态稳定输出是一个典型的嵌入式传感器集成过程充满了细节和坑点。成功的关键在于对传感器寄存器手册的理解、对DMP库代码的耐心梳理以及扎实的调试能力。当看到串口助手上稳定输出的俯仰、横滚角以及用校准后的磁力计得到的、不再乱转的航向角时那种成就感是对之前所有折腾的最好回报。这个驱动框架已经成功应用于我的几个小项目中包括一个自平衡小车和一个手持姿态跟踪器在STM32F103这个老将上ICM20948的DMP功能完全能够稳定可靠地工作。本文还有配套的精品资源点击获取
返回列表