简介:这是一套围绕ADXL375三轴加速度计的硬件SPI接口采集代码例程,主要面向嵌入式开发者,解决高频实时数据采集与后续调试分析问题。ADXL375支持±2g至±16g量程与低功耗模式,适合运动检测、冲击监测等场景;例程展示了SPI初始化、寄存器配置、数据读取及上下位机联动方案。压缩包共241个文件,约8.96MB,以C/C++源文件、头文件、Keil工程文件为主,并含上位机程序与串口调试工具,可完成参数配置、数据解析与波形观察。包内还提供编译生成的hex/axf固件及调试缓存文件,便于快速烧录验证。目前已有599人学习下载,适合正在开发高采样率加速度应用的工程师参考,能直接迁移驱动代码并理解硬件SPI在高速传感采集中的关键用法。
1. 先认识这颗芯片:ADXL375到底解决什么问题
拿到ADXL375这颗芯片写代码例程,很多人第一反应是翻出ADXL345的旧工程,改个设备地址就直接上电。我一开始也是这么干的,结果读回来的数据不仅数值离谱,某些冲击测试里甚至直接饱和。这颗看似“兼容”的高量程加速度计,实际使用中有几个必须提前搞清楚的坑,否则会浪费大量调试时间。
这篇文章把我实际调通ADXL375代码例程的完整过程写下来,包括硬件接线、寄存器配置、数据读取与换算、冲击检测场景的设计思路,以及我在项目里踩过的一些坑。适合刚接触这颗芯片、准备做跌落/碰撞/冲击监测的朋友参考。
1.1 为什么需要量程±200g的加速度计
普通消费级加速度计量程通常只有±2g到±16g,日常姿态检测、计步、倾角测量完全够用。但一旦进入机械冲击、碰撞测试、跌落记录这类场景,瞬时加速度很容易突破100g。手机从1米高度掉到水泥地,峰值加速度可以达到几百g;自动化产线上的撞机、物流运输中的暴力分拣,冲击峰值也远超普通传感器的量程。
ADXL375的±200g满量程设计,就是为这类“瞬间大冲击”场景准备的。它能在不饱和的前提下,把碰撞瞬间的真实加速度波峰记录下来。这一点非常关键,因为一旦传感器饱和,你只能知道“冲击很大”,但具体大到什么程度、持续了多久、方向如何,全部丢失。
1.2 ADXL375与ADXL345的关系,以及一个容易误判的细节
ADXL375和ADXL345在寄存器层面高度兼容,I2C地址、寄存器地址、数据格式基本一致,这也是很多人直接套用ADXL345例程的原因。但两者有一个关键差异:ADXL345在full_res模式下灵敏度会随量程变化,而ADXL375无论range位写什么,量程都固定为±200g,灵敏度也固定为20.5 LSB/g。
另一个容易误判的细节是设备ID。ADXL375的DEVID寄存器(0x00)返回值是0xE5,和ADXL345完全一样。也就是说,你不能靠读设备ID来区分这两颗芯片,必须结合量程设置和实际数据来确认当前用的是哪一颗。我在调试时就遇到过这样的情况:代码是从ADXL345例程改过来的,板子上实际贴的是ADXL375,设备ID读出来正常,但数据换算一直不对,折腾了半天才发现是灵敏度系数问题。
2. 硬件与I2C通信基础
2.1 引脚连接与最小系统
ADXL375的封装是16引脚LGA,体积很小,手工焊接有一定难度。实际项目中我比较推荐用官方评估板或者自己画转接板,避免在焊接环节引入问题。引脚功能上,最核心的就是VDD、GND、SDA、SCL、ALT、CS、INT1/INT2这几个。
我用I2C模式,最小系统接线如下:
| 引脚 | 连接 | 说明 |
|---|---|---|
| VDD | 3.3V | 供电范围2.0V~3.6V,不要接5V |
| GND | GND | 地 |
| SDA | MCU I2C数据线 | 需要上拉电阻 |
| SCL | MCU I2C时钟线 | 需要上拉电阻 |
| CS | VDD | I2C模式下必须拉高 |
| ALT | GND | 拉低时I2C地址为0x53 |
这里特别强调两个点。第一,CS引脚在I2C模式下必须接VDD,如果悬空或者接地,芯片会认为你要用SPI通信,I2C总线完全无响应。第二,SDA和SCL必须有上拉电阻,一般用4.7kΩ。如果MCU内部已经有上拉,可以先用内部上拉测试,但批量产品建议还是外部放一个,总线信号质量更稳。
电源去耦方面,VDD引脚旁边放一个0.1uF和一个1uF电容,尽量靠近芯片。这个倒不是ADXL375的特殊要求,所有数字传感器都一样,但实际测试中我发现,去耦电容缺失时,冲击瞬间的数据毛刺会明显变多。
2.2 I2C地址与寄存器速查
ADXL375的7位I2C地址由ALT引脚决定,注意这里和ADXL345的规则一致:ALT接GND时地址为0x53,接VDD时为0x1D。对应到HAL库的8位地址,分别是0xA6(写)/0xA7(读),或者0x3A/0x3B。
常用寄存器我整理了一个速查表,调试时对照着看效率高很多:
| 寄存器 | 地址 | 作用 | 典型值 |
|---|---|---|---|
| DEVID | 0x00 | 设备ID | 0xE5 |
| BW_RATE | 0x2C | 输出数据率 | 0x0B(400Hz) |
| POWER_CTL | 0x2D | 电源模式控制 | 0x08(测量模式) |
| DATA_FORMAT | 0x31 | 数据格式与量程 | 0x0B |
| DATAX0 | 0x32 | X轴低字节 | 只读 |
| DATAX1 | 0x33 | X轴高字节 | 只读 |
| DATAY0 | 0x34 | Y轴低字节 | 只读 |
| DATAY1 | 0x35 | Y轴高字节 | 只读 |
| DATAZ0 | 0x36 | Z轴低字节 | 只读 |
| DATAZ1 | 0x37 | Z轴高字节 | 只读 |
这里有个值得注意的点:为什么BW_RATE要设置成400Hz而不是更高?ADXL375的标称带宽可以到2kHz以上,但实际系统瓶颈往往在MCU的I2C读取频率和数据处理能力上。对于大多数冲击检测场景,400Hz已经能捕捉到完整的冲击波峰,同时给MCU留出足够的时间处理其他任务。如果是快撞击、高频率振动测试,再往上调数据率。
3. 代码例程逐段拆解
3.1 初始化:三个寄存器的配置顺序
初始化ADXL375的代码并不复杂,核心就是写三个寄存器。但顺序和值是有讲究的,我拆开来讲。
首先是DATA_FORMAT(0x31),我写成0x0B。这个字节的含义是bit3(full_res)置1,即使用全分辨率模式,灵敏度固定为20.5 LSB/g。bit2(justify)保持0,输出数据左对齐。bit1:bit0(range)写11,但由于ADXL375量程固定,这两个位实际不影响灵敏度。写0x0B主要是为了和ADXL345的±16g配置保持一致,方便两套代码共用一个初始化函数。
其次是BW_RATE(0x2C),写成0x0B,对应400Hz输出数据率。需要注意,高数据率会带来更高的功耗和I2C总线负载。如果是电池供电的低功耗设备,可以降到0x09(100Hz)甚至0x08(50Hz),但冲击检测场景不建议低于100Hz,否则冲击峰值可能落在采样间隔之间,测出来的峰值会偏低。
最后是POWER_CTL(0x2D),写成0x08,即bit3置1,进入测量模式。这个必须最后写,因为芯片上电默认是待机模式,前两个寄存器配置完成后再开启测量,能避免配置过程中产生意外的数据输出。
初始化函数用STM32 HAL库写如下:
#define ADXL375_ADDR 0x53 uint8_t adxl375_init(I2C_HandleTypeDef *hi2c) { uint8_t id = 0; // 1. 读设备ID,确认I2C通信正常 if (HAL_I2C_Mem_Read(hi2c, ADXL375_ADDR << 1, 0x00, 1, &id, 1, 100) != HAL_OK) { return 1; } if (id != 0xE5) { return 2; // 设备ID不对,检查接线或地址 } // 2. 配置数据格式 uint8_t cfg = 0x0B; HAL_I2C_Mem_Write(hi2c, ADXL375_ADDR << 1, 0x31, 1, &cfg, 1, 100); // 3. 配置输出数据率 cfg = 0x0B; // 400Hz HAL_I2C_Mem_Write(hi2c, ADXL375_ADDR << 1, 0x2C, 1, &cfg, 1, 100); // 4. 进入测量模式 cfg = 0x08; HAL_I2C_Mem_Write(hi2c, ADXL375_ADDR << 1, 0x2D, 1, &cfg, 1, 100); return 0; }这段代码里的HAL_I2C_Mem_Read/Write函数,会自动处理I2C总线上的设备地址、寄存器地址和读写标志,比直接操作I2C原始时序要省心很多。如果你用的是其他MCU平台,逻辑是一样的,替换成对应的I2C底层接口即可。
3.2 数据读取与g值换算,重点在右移两位
读数据用的是连续读方式,从寄存器0x32开始一次性读6个字节,分别对应X、Y、Z轴的低字节和高字节。
typedef struct { float x; float y; float z; float magnitude; } adxl375_data_t; uint8_t adxl375_read(I2C_HandleTypeDef *hi2c, adxl375_data_t *out) { uint8_t buf[6]; if (HAL_I2C_Mem_Read(hi2c, ADXL375_ADDR << 1, 0x32, 1, buf, 6, 100) != HAL_OK) { return 1; } // 组合成16位数据 int16_t raw_x = (int16_t)((buf[1] << 8) | buf[0]); int16_t raw_y = (int16_t)((buf[3] << 8) | buf[2]); int16_t raw_z = (int16_t)((buf[5] << 8) | buf[4]); // 右移2位,再除以灵敏度得到g值 out->x = (float)(raw_x >> 2) / 20.5f; out->y = (float)(raw_y >> 2) / 20.5f; out->z = (float)(raw_z >> 2) / 20.5f; out->magnitude = sqrtf(out->x * out->x + out->y * out->y + out->z * out->z); return 0; }这里最容易出错的就是右移两位。ADXL375是14位分辨率,但在16位寄存器中是左对齐存储的。如果直接拿组合后的int16_t去除以20.5,算出来的加速度会偏大4倍。我一开始就踩了这个坑:静止时三轴读数应该接近0g到1g,但直接算出来却有3g多,明显不对。
有人可能会问,为什么不用右对齐方式?DATA_FORMAT寄存器的bit2(justify)置1可以改为右对齐,那样就不用右移了。但ADXL345和ADXL375的惯例写法是左对齐,而且很多现成例程都是左对齐后右移,所以我建议保持一致,减少认知负担。
3.3 一个简单的冲击检测主循环
有了初始化和读取函数,就可以写一个简单的冲击检测逻辑。基本思路是:周期性读取三轴加速度,计算合向量magnitude,当合向量超过设定的冲击阈值时,记录当前数据并触发告警。
adxl375_data_t sensor_data; uint32_t shock_count = 0; while (1) { if (adxl375_read(&hi2c1, &sensor_data) == 0) { if (sensor_data.magnitude > SHOCK_THRESHOLD_G) { // 例如50g shock_count++; // 记录时间戳、数据,触发告警 log_shock_event(&sensor_data); } } HAL_Delay(2); // 控制轮询频率约500Hz }阈值怎么定?需要结合具体应用场景。如果是物流跌落监测,一般把阈值设在30g到50g之间,低于这个值属于正常搬运振动。如果是碰撞测试,阈值可能要放到100g以上。这里注意,阈值不宜设得太低,否则正常振动会频繁触发误报。
4. 冲击检测场景的关键设计
4.1 数据率与带宽的取舍
冲击信号的持续时间通常只有几毫秒到几十毫秒。以半正弦冲击为例,脉宽10ms的冲击波峰,如果用100Hz采样率,可能只采到1~2个点,峰值精度非常差。我做过对比测试,同一个冲击源,100Hz采样得到峰值60g,400Hz采样能得到85g,而参考高带宽传感器测出来是88g。这说明采样率对峰值检测精度的影响非常大。
但数据率也不是越高越好。I2C总线上跑3200Hz数据率时,每次读取6字节需要约1.2ms(400kHz I2C下),MCU几乎被完全占满,而且中断处理、存储都可能成为瓶颈。对于多数嵌入式应用,400Hz到800Hz是比较平衡的选择。
4.2 中断与FIFO的使用思路
ADXL375继承了ADXL345的中断和FIFO功能,这两个在低功耗冲击检测中非常有用。思路是这样的:正常巡检时MCU进入休眠,ADXL375持续采集,通过FIFO积累数据。当FIFO达到设定水印或者检测到活动事件时,通过INT1引脚唤醒MCU,MCU一次性读出多组数据。
但这里有个坑:ADXL375的活动检测阈值寄存器(THRESH_ACT,0x24)的单位是62.5mg/LSB,8位寄存器最大只能表示约15.9g。对于几十上百g的冲击,活动检测功能根本不能用。所以真正的冲击检测还得靠主循环轮询合向量,或者用FIFO水印触发定时批量读取,然后在软件里做阈值判断。
我在一个物流记录仪项目里的做法是:数据率设为400Hz,FIFO配置为流模式,水印设为16次采样,每16个数据点触发一次中断。中断服务程序里只置标志位,主循环读标志位后一次性取回16组数据,逐点计算合向量,超过阈值的才记录。这样既不会漏掉冲击峰值,MCU的负载也控制在可接受范围内。
4.3 安装方式对测量结果的影响
高g加速度计的安装方式直接影响测量准确性。ADXL375这种±200g的传感器,如果贴在软性材料上,冲击能量会被材料吸收一部分,测出来的峰值偏低。正确做法是用刚性连接,比如螺钉固定或硬胶粘接,确保传感器和被测物体之间没有相对位移。
我在振动台上做过一次测试,同一个冲击条件,用双面胶固定的传感器测到的峰值,比螺钉固定的低了大约15%。对于精度要求不高的场景可能无所谓,但做碰撞测试或者产品认证时,这个误差足以影响结论。
5. 调试到的坑与排查技巧
5.1 读不到设备ID,I2C无响应
这是最常见的故障。排查顺序我建议这样:
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 读ID超时 | CS引脚悬空或接地 | 确认CS接VDD |
| 读ID超时 | I2C地址错误 | ALT接地用0x53,接VDD用0x1D |
| 读ID超时 | SDA/SCL没有上拉 | 外接4.7kΩ上拉 |
| 读ID返回0xFF | 焊接虚焊 | 检查引脚,补焊 |
| 读ID返回0x00 | 供电异常 | 测量VDD电压 |
这里面CS引脚的问题最隐蔽,因为很多ADXL345的评估板已经把CS内部处理好了,但自己画的板子容易忽略。我就是因为CS悬空,整整浪费了半天时间。
5.2 数据换算后数值偏大或者偏小
如果静止时读数明显不对,先检查右移两位有没有做。如果没右移,读数偏大约4倍。如果右移多了或者符号处理不对,可能出现负数异常。还有一个容易忽略的点:int16_t类型转换时,如果直接写(buf[1] << 8) | buf[0],在某些编译器下(buf[1] << 8)会提升为int类型,bit15的符号位可能出问题。建议像我代码里那样,先强制转换成int16_t,再做右移。
5.3 冲击数据毛刺多,波形不干净
冲击场景下数据毛刺的来源通常是电源噪声和地弹。解决方法:VDD旁路电容加到位,传感器地线和主控地线要短、宽,最好用独立的模拟地。另外,输出数据率提高后,如果I2C时钟质量不好,也可能导致偶发读错数据。可以在软件上做一次简单滤波,比如连续读三次取最大值,因为冲击检测关心的是峰值,取最大值比平均值更合理。
5.4 关于自检功能的建议
ADXL375的DATA_FORMAT寄存器bit7是自检位,写1后会在对应轴上产生一个已知的偏置,用来验证传感器是否正常工作。我建议每次主板自检时都做一下这个测试:给X轴施加自检,读取数据,确认读数变化在预期范围内,再进入正常检测流程。这个功能在批量生产中特别有用,能快速筛选出焊接不良或者芯片损坏的板子。
我个人在实际操作中的体会是,ADXL375这颗芯片本身不复杂,难点几乎都集中在“你以为你用的是ADXL345”这个惯性思维上。量程、灵敏度、寄存器地址,看起来都一样,但一旦换算系数没改过来,数据就全错了。所以调这类芯片,第一步永远是确认DEVID、第二步是静止状态下验证零漂和重力方向,第三步才敢跑冲击测试。最后再分享一个小技巧:写代码前,先用逻辑分析仪抓一次I2C波形,确认时序和地址是对的,比在软件里反复排查高效得多。
本文还有配套的精品资源,点击获取