NRF52832通过TWI读取MPU9250六轴数据完整实现指南
发布时间:2026/9/16 4:09:56来源:尧图网络
简介面向嵌入式蓝牙开发与传感器数据采集学习者提供蓝牙芯片NRF52832通过IIC接口读取MPU9250原始数据的完整例程源码。例程基于52832硬件IICTWI接口可获取三轴加速度、各轴角速度以及地磁传感器的原始读数函数分层清晰便于直接移植或二次开发。压缩包内含250个文件以144个头文件与102个C源文件为主体覆盖TWI驱动、定时器、存储、DFU传输等SDK功能模块另带工程配置与启动文件整体大小约560KB。已有400人学习下载尤其适合正在调试NRF52832与MPU9250联动或需要参考硬件IIC驱动写法的开发者快速上手。1. NRF52832接MPU9250这件事比想象中多三步把NRF52832和MPU9250放在一起大多数人的第一反应是无非就是两根线的事。真正上手会发现IIC总线上除了SCL和SDA还有器件地址、寄存器地址、时钟极性、上拉电阻和字节序在等着你。NRF52832的TWI模块完全兼容标准IIC协议但它的寄存器命名、传输状态机和阻塞方式跟STM32的HAL库差别很大MPU9250也不只是一个陀螺仪加加速度计它内部还藏着一个AK8963磁力计芯片地址和数据格式都不同。这篇文章把从TWI外设初始化到读取六轴原始数据的完整软件路径走一遍给出可以直接抄的初始化参数、寄存器序列和排错方法。适合正在做BLE运动传感、姿态解算或低成本可穿戴方案的人。2. NRF52832的TWI外设与MPU9250寄存器映射2.1 为什么NRF52832把IIC叫TWINRF52832的数据手册里找不到 IIC 或 I2C 这两个词它使用的是TWITwo Wire Interface这个名字。原因是Nordic的TWI外设不敢说自己是IIC标准的正统实现因为它的时序完全兼容但不经过IIC仲裁器所以在协议栈里用了TWI这个词。对应用层而言TWI就是IICSCL用于时钟SDA用于数据7位地址寻址速率支持100kbps和400kbps。兼容不等于相同。NRF52832的TWI有状态机设计数据传输由EVENTS_LASTRX、EVENTS_LASTTX等事件驱动每个字节的收发都要由CPU逐个喂入。这种设计与STM32上那种DMA挂上就撒手不管的外设体验完全不同。好处是代码行为极其可预测坏处是首次上手的人容易在两个地方卡住一是发送完地址后不知道芯片在等ACK二是连续读取时没控制好REPEATED_START位。高字节在前的寄存器读取方式也容易踩坑。MPU9250的陀螺仪输出寄存器GYRO_XOUT_H和GYRO_XOUT_L高位在前低位在后。这个顺序恰好和ARM Cortex-M4的小端存储模式相反如果直接把两个字节拼成int16_t而不做手动移位数值会完全错乱。2.2 MPU9250关键寄存器地址表MPU9250的寄存器表很长但在只读原始数据这个目标下真正需要写的最少只有4个寄存器然后读6个寄存器。这里有两个量程位配置不对后面所有推导都用不上。寄存器名地址十六进制配置值功能PWR_MGMT_10x6B0x00退出睡眠选择内部时钟源SMPLRT_DIV0x190x04采样率分频输出频率1kHz/(14)200HzCONFIG0x1A0x03数字低通滤波陀螺仪带宽约44HzGYRO_CONFIG0x1B0x18量程±2000dps灵敏度16.4LSB/dpsACCEL_CONFIG0x1C0x18量程±16g灵敏度2048LSB/gACCEL_XOUT_H0x3B读加速度计X轴高位字节GYRO_XOUT_H0x43读陀螺仪X轴高位字节这里要特别说GYRO_CONFIG写0x18而不是0x00的原因。0x1B寄存器bit4和bit3是量程选择位00代表±250dps01代表±500dps10代表±1000dps11代表±2000dps。如果写0x00读到的原始值变化范围只在±250dps对应的±250×16.44100这个区间手势稍微大一点就溢出而且换算成物理量时用的灵敏度系数还是250dps那一档。直接上±2000dps灵敏度固定为16.4LSB/dps数据变化范围更大后面接姿态解算模块时也不用来回改系数。2.3 读MPU9250原始数据的最小寄存器序列从MPU9250读加速度计和陀螺仪数据需要一个固定的总线序列。先发器件地址加写位再发要读的寄存器起始地址随后重新发起起始位发器件地址加读位再连续读取6个字节得到三轴加速度计再读取6个字节得到三轴陀螺仪。这套序列对MPU9250来讲分为两步写寄存器地址0x3B读6字节写寄存器地址0x43读6字节这里有一个工程上的关键点MPU9250的地址是7位0x68但总线上实际发送的是8位最低位补0或1来表示读写方向。0x68左移一位后写为0xD0读为0xD1。稍不注意就会把0x68当成8位地址直接发出去结果总线上设备根本不ACK卡死在NACK检测上。uint8_t addr_accel 0x3B; uint8_t accel_raw[6]; uint8_t gyro_raw[6];设备地址AD0引脚接地时地址是0x68接高电平时地址变成0x69。MPU9250模块上通常默认AD0悬空或接地但如果你的模块上AD0被拉高整个地址体系就全错了。排查NACK时这是第一个要看的硬件点。3. 搭建IIC读取MPU9250的工程骨架与初始化配置3.1 NRF52832的TWI初始化参数用nrfx_twi驱动库来操作TWI外设这是Nordic软件生态里的标准做法。nrfx_twi相比直接操作寄存器层把DRV_CFG、时钟频率、中断回调都封装进了结构体代码可读性和维护性都更好。初始化代码里有多组参数下面的写法是经过验证的。#include nrfx_twi.h #include nrf_drv_clock.h #define TWI_INSTANCE_ID 0 #define MPU9250_ADDR 0x68 static const nrfx_twi_t m_twi NRFX_TWI_INSTANCE(TWI_INSTANCE_ID); static volatile bool m_xfer_done false; static void twi_handler(nrfx_twi_evt_t const * p_event, void * p_context) { switch (p_event-type) { case NRFX_TWI_EVT_DONE: m_xfer_done true; break; case NRFX_TWI_EVT_ADDR_NACK: // 设备没有应答检查地址和硬件连接 break; case NRFX_TWI_EVT_DATA_NACK: // 器件收到了地址但拒绝数据检查寄存器地址 break; default: break; } } void twi_init(void) { const nrfx_twi_config_t config { .scl 27, // P0.27 .sda 26, // P0.26 .frequency NRF_TWI_FREQ_400K, .interrupt_priority APP_IRQ_PRIORITY_HIGH, .hold_bus_uninit false }; nrfx_twi_init(m_twi, config, twi_handler, NULL); nrfx_twi_enable(m_twi); }这里把一个重要的参数藏在config里frequency设为400K。MPU9250支持最高400kHz的IIC速率但不代表NRF52832的IO口能直接撑住这个速率。如果PCB上SCL/SDA走线较长或上拉电阻阻值偏大400kHz下波形会出现明显的圆角和上升沿过缓轻则数据偶尔跳变重则一直NACK。稳健的做法是先用100K把业务跑通再用400K压测而不是一上来就追求最高速。3.2 MPU9250上电配置函数TWI外设初始化完成后MPU9250还处于睡眠状态。PWR_MGMT_1寄存器默认值是0x40bit6置1表示睡眠这是芯片上电的复位状态。需要先写0x00唤醒再配置采样率和量程。建议在唤醒后加一个10ms的延时让内部时钟稳定。#define REG_PWR_MGMT_1 0x6B #define REG_SMPLRT_DIV 0x19 #define REG_CONFIG 0x1A #define REG_GYRO_CONFIG 0x1B #define REG_ACCEL_CONFIG 0x1C static void mpu9250_reg_write(uint8_t reg, uint8_t data) { uint8_t buf[2] {reg, data}; m_xfer_done false; nrfx_twi_tx(m_twi, MPU9250_ADDR, buf, 2, false); while (!m_xfer_done); } void mpu9250_init(void) { mpu9250_reg_write(REG_PWR_MGMT_1, 0x00); nrf_delay_ms(10); mpu9250_reg_write(REG_SMPLRT_DIV, 0x04); mpu9250_reg_write(REG_CONFIG, 0x03); mpu9250_reg_write(REG_GYRO_CONFIG, 0x18); mpu9250_reg_write(REG_ACCEL_CONFIG, 0x18); }nrfx_twi_tx的最后一个参数no_stop很关键。这里传的是false表示传输结束时释放总线发STOP位。在一些连续操作的场景下比如先写寄存器地址再读数据这个参数需要传true来产生REPEATED START否则设备状态机会出错。很多移植IIC代码失败的人问题都出在这一个布尔值上。3.3 编译验证与常见报错先把工程编译过一遍排除掉代码层面的问题后再接硬件。如果使用的是SDK17或更新版本nrfx_twi已经是默认集成。链接错误里最常见的是nrf_drv_clock相关的符号找不到这是没有在sdk_config.h里开启NRF_CLOCK_ENABLED导致的。TWI外设在启用时内部会请求低频时钟某些SDK版本里时钟模块默认是关闭的。报错信息如果包含NRFX_TWI_EVT_ADDR_NACK回调说明总线上的设备没有响应。此时用示波器或逻辑分析仪抓SCL和SDA看地址起始后第9个时钟周期SDA是否被拉低。没有拉低就说明MPU9250地址不匹配或SDA线断了。4. 读取并拆解MPU9250六轴原始数据的函数实现4.1 使用nrfx_twi库的寄存器读序列读取MPU9250寄存器数据的难点在于必须先发送寄存器地址然后在不停止总线的条件下切换为读模式。nrfx_twi提供了nrfx_twi_tx发送后不产生STOP位的能力完整读序列的实现如下static void mpu9250_reg_read(uint8_t reg, uint8_t * buf, uint8_t len) { m_xfer_done false; nrfx_twi_tx(m_twi, MPU9250_ADDR, reg, 1, true); while (!m_xfer_done); m_xfer_done false; nrfx_twi_rx(m_twi, MPU9250_ADDR, buf, len); while (!m_xfer_done); } void mpu9250_read_raw(uint8_t * accel, uint8_t * gyro) { mpu9250_reg_read(0x3B, accel, 6); mpu9250_reg_read(0x43, gyro, 6); }第一次调用nrfx_twi_tx时传true给no_stop作用是发送地址字节后不产生STOP位而是直接进入下一个传输阶段。紧接着调用nrfx_twi_rx此时NRF52832自动产生REPEATED START器件地址读位。MPU9250在收到这个序列后会从之前写入的寄存器地址0x3B或0x43开始连续输出数据每读一个字节地址自动加1。最终读满6个字节后nrfx_twi_rx内部自动发送STOP位释放总线。4.2 加速度计和陀螺仪原始数据的字节拼接与解析MPU9250输出的每个轴都是16位有符号数范围从-32768到32767高字节在前。C语言中直接使用位运算来拼接不要使用指针强转因为ARM Cortex-M4是小端存储而MPU9250的数据在总线上是大端序。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; } mpu9250_raw_t; #define ACCEL_SENSITIVITY 2048.0f #define GYRO_SENSITIVITY 16.4f mpu9250_raw_t sensor_data; uint8_t accel_buf[6] {0}; uint8_t gyro_buf[6] {0}; mpu9250_read_raw(accel_buf, gyro_buf); sensor_data.accel_x (int16_t)((accel_buf[0] 8) | accel_buf[1]); sensor_data.accel_y (int16_t)((accel_buf[2] 8) | accel_buf[3]); sensor_data.accel_z (int16_t)((accel_buf[4] 8) | accel_buf[5]); sensor_data.gyro_x (int16_t)((gyro_buf[0] 8) | gyro_buf[1]); sensor_data.gyro_y (int16_t)((gyro_buf[2] 8) | gyro_buf[3]); sensor_data.gyro_z (int16_t)((gyro_buf[4] 8) | gyro_buf[5]); float ax_g sensor_data.accel_x / ACCEL_SENSITIVITY; float gx_dps sensor_data.gyro_x / GYRO_SENSITIVITY;位移运算(accel_buf[0] 8)在这里有讲究。accel_buf[0]是uint8_t左侧转成16位无符号数后再与低位字节做或运算最后在强制类型转换时被解释为int16_t。如果直接把accel_buf[0] 8的结果赋给int16_t而不做uint8_t到int16_t的中转当高位字节的bit7为1时符号位会被错误地当作数据位解码出的数值会出现8192的偏移。4.3 把原始数据封装成BLE广播数据在NRF52832上读完原始数据下一步自然是把数据通过BLE传出去。传输方式有两种用Notification走GATT通道或者把数据压缩进广播包。GATT通道需要先建立连接广播包则不需要连接就能持续发送。运动传感器场景里广播包的实时性更合适。一个BLE广播包有效载荷最多31字节6轴数据共12字节加上设备名称和标志位放得下2份完整数据外加1个字节的序列号。uint8_t adv_data[14]; uint16_t idx 0; adv_data[idx] 0x02; // length adv_data[idx] 0x01; // flags adv_data[idx] 0x06; // LE general discoverable adv_data[idx] 0x0E; // length 1 13 adv_data[idx] 0xFF; // manufacturer specific adv_data[idx] 0x59; // company low byte adv_data[idx] 0x00; // company high byte adv_data[idx] (sensor_data.accel_x 8) 0xFF; adv_data[idx] sensor_data.accel_x 0xFF; // 后续依次填入 accel_y、accel_z、gyro_x、gyro_y、gyro_z广播包里的数据要手动拆成高低字节不能在结构体上直接取地址做memcpy。原因有两个。一是结构体成员之间存在对齐填充字节直接拷贝会把填充数据也发出去。二是MPU9250原始数据是big-endian字节序而Cortex-M4是小端序前面拼接收到的数据时已经转换过一次。发送时要把每个int16_t拆成两个字节高位在前收到的一方按同样的规则还原。5. 排错技巧与验证方法5.1 用逻辑分析仪抓IIC时序软件开发完成后验证工作最有效的工具是逻辑分析仪。把通道0接SCL通道1接SDA触发条件设为SDA下降沿采样率设为2MHz以上。抓到的波形中第一条低脉冲是SDA拉低表示起始位随后8个时钟沿是地址位。把SCL高电平时的SDA电平按顺序记录下来得到的7位地址加上最后1位读写方向能直接确认总线上访问的设备地址是否正确。如果抓到的地址是0xD0数据线上却没有任何ACK响应看SDA线上拉电阻。测量SCL和SDA对地的阻值正常范围在2.2k到10k之间阻值过大时信号上升沿过慢容易被设备误判为低电平。阻值过小则会导致灌电流过大部分MPU9250模块会直接过热保护。5.2 常见NACK和数据跳变的三个根因NACK的根因排除顺序有讲究。优先确认地址左移后的低bit方向位如果发送0x68而不是0xD0MPU9250收到后不会响应总线上表现为起始位之后第9个时钟沿SDA保持高电平。其次看SCL和SDA是否接反这是一个极其常见的硬件接错。最后检查MPU9250的VDDIO引脚是否接上电源有些模块上VDD和VDDIO是分开的只接VDD不接VDDIO芯片不会启动IIC接口。数据跳变和NACK的表现不同。能读到数据但数值不稳定或者偶尔跳变优先怀疑采样率配置。SMPLRT_DIV设为0时MPU9250内部以1kHz频率采样IIC总线上400kbps读12字节需要约240微秒采样间隔和读取时间重叠会导致数据在半更新状态时被读走。把采样率降到200Hz后这个重叠的概率会大幅降低。另一种常见做法是读两次丢弃第一次只把第二次的数据作为有效值本质上是等待数据更新完成。本文还有配套的精品资源点击获取
网站建设高端定制企业官网