4、加速度计驱动开发(ADXL345):芯片手册导读、寄存器配置、初始化流程、读取原始数据、FIFO模式与中断配置

好,咱们今天来啃一块硬骨头——ADXL345加速度计。这颗芯片在智能手表里太常见了,计步、抬腕亮屏、跌倒检测,都离不开它。我个人习惯是,拿到一颗新传感器,先不看代码,先看手册。你想想看,手册就是芯片的「使用说明书」,不看说明书就上电,大概率要翻车。

4.1 芯片手册导读:别被几百页吓到

ADXL345的手册大概40多页,不算厚。但我不建议你从头读到尾。我一般先看这几部分:

  • Features(特性):快速了解这颗芯片能干吗。比如它支持±2g/±4g/±8g/±16g量程,13位分辨率,还有FIFO和中断。
  • Pin Configuration(引脚图):确认怎么接线。I2C还是SPI?CS脚怎么拉?
  • Register Map(寄存器映射表):这是核心。所有配置都靠写寄存器,所有数据都靠读寄存器。
  • Application Information(应用信息):这里经常藏着「避坑指南」。比如上电后要等多久才能开始配置,我就在这吃过亏。
我的小技巧: 把寄存器映射表打印出来贴在工位上。写代码时扫一眼,比来回翻PDF快多了。

4.2 寄存器配置:核心寄存器一览

ADXL345的寄存器地址都是8位的,操作起来很直接。下面这几个寄存器,你必须要记住:

寄存器名称 地址 作用
DEVID 0x00 设备ID,固定为0xE5,用来确认I2C通信正常
POWER_CTL 0x2D 电源控制,必须写0x08才能退出待机模式
DATA_FORMAT 0x31 设置量程、分辨率、数据格式
BW_RATE 0x2C 设置输出数据速率(ODR)
FIFO_CTL 0x38 FIFO模式控制
INT_ENABLE 0x2E 中断使能

嗯,这里要注意:POWER_CTL寄存器的bit3(Measure位),如果不置1,芯片就一直待在待机模式,读出来的数据全是0。我曾经调试了一下午,最后发现是忘了写这个位——说出来都是泪。

4.3 初始化流程:三步走

初始化ADXL345,说白了就三步。我习惯写成这样:

// 第一步:确认设备ID
uint8_t devid = i2c_read(ADXL345_ADDR, 0x00);
if (devid != 0xE5) {
    // 通信有问题,别往下走了
    return ERROR_I2C;
}

// 第二步:配置基本参数
i2c_write(ADXL345_ADDR, 0x31, 0x0B);  // DATA_FORMAT: ±16g, 13位, 右对齐
i2c_write(ADXL345_ADDR, 0x2C, 0x0A);  // BW_RATE: 100Hz输出速率

// 第三步:退出待机模式
i2c_write(ADXL345_ADDR, 0x2D, 0x08);  // POWER_CTL: 测量模式

为什么先读DEVID?因为这是验证I2C总线是否通的最快方法。如果读不到0xE5,要么地址错了,要么线没焊好。我在项目里遇到过好几次,芯片没焊牢,读DEVID直接返回0xFF,省去了后面一大堆无意义的调试。

4.4 读取原始数据:X、Y、Z轴

加速度数据存在三个寄存器对里:

  • DATAX0 (0x32) 和 DATAX1 (0x33) —— X轴
  • DATAY0 (0x34) 和 DATAY1 (0x35) —— Y轴
  • DATAZ0 (0x36) 和 DATAZ1 (0x37) —— Z轴

注意,ADXL345支持多字节读取。你只需要发送起始地址0x32,然后连续读6个字节,芯片会自动递增地址。这样效率最高。

int16_t read_adxl345_axis(uint8_t reg_low) {
    uint8_t buf[2];
    i2c_read_bytes(ADXL345_ADDR, reg_low, buf, 2);
    // 注意:ADXL345是小端格式,低字节在前
    return (int16_t)(buf[0] | (buf[1] << 8));
}

// 使用示例
int16_t x = read_adxl345_axis(0x32);
int16_t y = read_adxl345_axis(0x34);
int16_t z = read_adxl345_axis(0x36);

读出来的原始值怎么转成g?公式很简单:加速度(g) = 原始值 × 量程 / 2^分辨率。比如±16g、13位分辨率,就是 原始值 × 32 / 8192。你想想看,如果量程设错了,算出来的加速度就全偏了。我见过有人把±2g的数据当±16g算,结果计步器疯狂乱跳。

4.5 FIFO模式与中断配置

FIFO是ADXL345的一个亮点。它能缓存最多32组数据,这样主控不用频繁去读,省电又省CPU。FIFO有四种模式:

  • Bypass(旁路):FIFO不用,数据直接读
  • FIFO(先进先出):存满32组后,新数据覆盖旧数据
  • Stream(流模式):存满后继续存,最旧的数据被丢弃
  • Trigger(触发模式):由中断触发开始存数据

我一般在智能手表里用Stream模式。因为手表要持续监测运动,不能丢数据。配置代码长这样:

// 配置FIFO为Stream模式,水印设置为16组
i2c_write(ADXL345_ADDR, 0x38, 0x90 | 16);  // FIFO_CTL: Stream模式, 水印=16

// 使能FIFO水印中断
i2c_write(ADXL345_ADDR, 0x2E, 0x04);  // INT_ENABLE: 使能水印中断

// 配置中断引脚(默认INT1)
i2c_write(ADXL345_ADDR, 0x2F, 0x04);  // INT_MAP: 水印中断映射到INT1
注意: 配置FIFO模式前,必须先把芯片置于待机模式(POWER_CTL的Measure位清0),改完FIFO_CTL后再重新进入测量模式。否则FIFO配置可能不生效。我踩过这个坑,折腾了两天。

中断来了之后,怎么读FIFO?先读FIFO_STATUS寄存器(0x39),看看里面有多少组数据。然后一次性读出来:

void read_fifo_data(int16_t *x_buf, int16_t *y_buf, int16_t *z_buf, uint8_t count) {
    uint8_t raw[6 * count];
    i2c_read_bytes(ADXL345_ADDR, 0x32, raw, 6 * count);
    for (int i = 0; i < count; i++) {
        x_buf[i] = (int16_t)(raw[i*6] | (raw[i*6+1] << 8));
        y_buf[i] = (int16_t)(raw[i*6+2] | (raw[i*6+3] << 8));
        z_buf[i] = (int16_t)(raw[i*6+4] | (raw[i*6+5] << 8));
    }
}

为什么要一次性读?因为如果你读一组、处理一组、再读下一组,中间的时间差会导致数据不同步。FIFO的好处就是让你批量拿数据,拿完一次处理完,干净利落。

4.6 避坑总结

最后,我把自己在ADXL345上踩过的坑列一下,你遇到了可以直接翻到这里:

  1. 上电后别急着配置:至少等5ms,让芯片内部稳定。我一开始没等,配置经常写不进去。
  2. 多字节读取时地址要连续:ADXL345支持自动递增,但前提是你发的起始地址是对的。如果读出来全是0,检查一下地址是不是写错了。
  3. FIFO水印中断可能不准:有时候水印设了16组,但中断来的时候FIFO里只有15组。别慌,读FIFO_STATUS确认实际数量再读。
  4. 中断引脚要配置上拉:ADXL345的中断输出是开漏的,外部必须接上拉电阻。否则中断信号拉不起来,主控永远收不到。

好了,ADXL345的驱动开发就聊到这儿。下一章咱们讲磁力计,那个东西更「妖」,到时候再细说。