4、加速度计驱动开发(ADXL345):芯片手册导读、寄存器配置、初始化流程、读取原始数据、FIFO模式与中断配置
好,咱们今天来啃一块硬骨头——ADXL345加速度计。这颗芯片在智能手表里太常见了,计步、抬腕亮屏、跌倒检测,都离不开它。我个人习惯是,拿到一颗新传感器,先不看代码,先看手册。你想想看,手册就是芯片的「使用说明书」,不看说明书就上电,大概率要翻车。
4.1 芯片手册导读:别被几百页吓到
ADXL345的手册大概40多页,不算厚。但我不建议你从头读到尾。我一般先看这几部分:
- Features(特性):快速了解这颗芯片能干吗。比如它支持±2g/±4g/±8g/±16g量程,13位分辨率,还有FIFO和中断。
- Pin Configuration(引脚图):确认怎么接线。I2C还是SPI?CS脚怎么拉?
- Register Map(寄存器映射表):这是核心。所有配置都靠写寄存器,所有数据都靠读寄存器。
- Application Information(应用信息):这里经常藏着「避坑指南」。比如上电后要等多久才能开始配置,我就在这吃过亏。
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?先读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上踩过的坑列一下,你遇到了可以直接翻到这里:
- 上电后别急着配置:至少等5ms,让芯片内部稳定。我一开始没等,配置经常写不进去。
- 多字节读取时地址要连续:ADXL345支持自动递增,但前提是你发的起始地址是对的。如果读出来全是0,检查一下地址是不是写错了。
- FIFO水印中断可能不准:有时候水印设了16组,但中断来的时候FIFO里只有15组。别慌,读FIFO_STATUS确认实际数量再读。
- 中断引脚要配置上拉:ADXL345的中断输出是开漏的,外部必须接上拉电阻。否则中断信号拉不起来,主控永远收不到。
好了,ADXL345的驱动开发就聊到这儿。下一章咱们讲磁力计,那个东西更「妖」,到时候再细说。