2、原始数据采集:I2C/SPI接口读取、原始数据格式、单位换算(LSB/g)、数据速率设置

好,咱们正式开始动手了。上一章聊完了加速度计的基本原理,这一章咱们要真刀真枪地把数据从芯片里读出来。你想想看,传感器再牛,数据读不出来也是白搭。

我个人习惯把原始数据采集分成四步走:选接口 → 读寄存器 → 做换算 → 设速率。咱们一个一个来。

2.1 接口选择:I2C 还是 SPI?

现在的加速度计芯片,基本都同时支持 I2C 和 SPI 两种接口。我刚开始做手环项目时,也纠结过到底用哪个。后来踩过坑才明白——选接口,主要看你的数据量和引脚资源

对比项 I2C SPI
引脚数 2 根线(SDA、SCL) 4 根线(CS、SCLK、MOSI、MISO)
速度 标准 100kHz / 快速 400kHz 最高可达 10MHz 以上
多设备支持 总线式,可挂多个设备 片选式,每个设备需独立 CS
适合场景 引脚紧张、速率要求不高 高速采集、实时性要求高
我的经验:手环这种穿戴设备,MCU 引脚通常很金贵。我建议用 I2C,两根线搞定。但如果你要做 1000Hz 以上的高频采集,老老实实上 SPI,别犹豫。

2.2 原始数据格式:16位有符号整数

不管用 I2C 还是 SPI,读回来的数据格式是一样的。绝大多数加速度计的输出寄存器都是 16 位,以二进制补码形式存储。

举个例子,ADXL345 的 X 轴数据存在两个寄存器里:

  • 0x32(DATAX0):低 8 位
  • 0x33(DATAX1):高 8 位

读的时候要注意顺序。我记得第一次写驱动时,直接把高低字节拼起来,结果数据全是乱的。后来才发现——先读低字节,再读高字节,这是芯片的硬件设计决定的。

// 伪代码示例:读取 X 轴原始值
uint8_t low  = read_register(0x32);
uint8_t high = read_register(0x33);
int16_t raw  = (int16_t)((high << 8) | low);
注意:有些芯片(比如 MPU6050)的数据寄存器是 16 位对齐的,读的时候要连续读两个字节,不能分开读。否则中间如果发生更新,高低字节可能不匹配。

2.3 单位换算:LSB/g 到底是个啥?

读回来的 raw 值,单位是 LSB(Least Significant Bit),不是 g。你得把它换算成物理单位。

每个加速度计都有个关键参数叫 灵敏度,单位就是 LSB/g。比如你设了 ±2g 量程,灵敏度可能是 16384 LSB/g。意思是:每 1g 的加速度,对应 16384 个 LSB 的数值变化

换算公式很简单:

加速度(g) = 原始值(LSB) / 灵敏度(LSB/g)

举个例子:

  • 你读到的 raw 值是 16384
  • 灵敏度是 16384 LSB/g
  • 那么加速度 = 16384 / 16384 = 1.0 g

嗯,这里要注意——不同量程下灵敏度不一样。我整理了一张常用表:

量程 灵敏度(LSB/g) 典型芯片
±2g 16384 ADXL345、MPU6050
±4g 8192 ADXL345、LSM6DS3
±8g 4096 ADXL345、BMI160
±16g 2048 ADXL345、ICM-20948
避坑指南:我曾经在项目里直接用了 ±2g 的灵敏度去算 ±16g 量程的数据,结果算出来的加速度全偏了。后来排查了半天才发现是灵敏度没切换。切记——量程变了,灵敏度必须跟着变

2.4 数据速率设置:别让 CPU 累死

加速度计的输出数据速率(ODR,Output Data Rate)是可以配置的。说白了就是芯片每秒给你送多少次数据。

常见的 ODR 范围从 1Hz 到 3200Hz 不等。你想想看,如果你设了 3200Hz,MCU 每秒要处理 3200 次中断,CPU 直接跑满。但如果你设 10Hz,又可能漏掉关键动作。

我个人建议:

  • 计步场景:50Hz ~ 100Hz 足够了
  • 手势识别:100Hz ~ 200Hz
  • 振动检测:200Hz ~ 400Hz
  • 高精度运动分析:400Hz 以上

配置 ODR 通常是通过写芯片的控制寄存器。以 ADXL345 为例:

// 设置 ODR 为 100Hz
// BW_RATE 寄存器地址 0x2C
// 100Hz 对应的值为 0x0A
write_register(0x2C, 0x0A);
小技巧:我习惯在初始化时先读一下芯片的 WHO_AM_I 寄存器(0x00),确认通信正常了,再配置 ODR。这样能快速定位是硬件问题还是软件问题。

2.5 完整初始化流程

最后,我把整个原始数据采集的流程串起来,给你一个完整的初始化示例:

// 加速度计初始化伪代码
void accel_init() {
    // 1. 检查芯片 ID
    uint8_t id = read_register(0x00);
    if (id != 0xE5) {  // ADXL345 的 ID
        error_handler("芯片通信失败");
        return;
    }
    
    // 2. 设置量程为 ±2g
    // DATA_FORMAT 寄存器 0x31,全量程 00 表示 ±2g
    write_register(0x31, 0x00);
    
    // 3. 设置 ODR 为 100Hz
    write_register(0x2C, 0x0A);
    
    // 4. 进入测量模式
    // POWER_CTL 寄存器 0x2D,bit3 置 1
    write_register(0x2D, 0x08);
    
    // 5. 读取原始数据
    uint8_t x_low  = read_register(0x32);
    uint8_t x_high = read_register(0x33);
    int16_t x_raw  = (int16_t)((x_high << 8) | x_low);
    
    // 6. 换算为 g 值
    float x_g = (float)x_raw / 16384.0f;
}

嗯,到这里,原始数据采集这块就讲完了。说白了就是三步:选接口 → 读寄存器 → 做换算。下一章咱们聊聊怎么对这些原始数据做滤波处理,把噪声干掉。