简介:本资源是一套基于STM32与ADXL345加速度计的I²C通信完整工程实现,面向嵌入式初学者及物联网硬件开发者,解决三轴加速度数据采集与解析的核心问题,适用于运动检测、姿态识别、智能穿戴等典型应用场景。压缩包共130个文件,以33个.h头文件和32个.c源文件为主体,涵盖STM32标准外设库驱动(如stm32f10x_i2c.c、stm32f10x_adc.c)、ADXL345寄存器配置与读取函数、I²C底层时序控制(Start/Stop/WriteByte/ReadByte等),辅以.o目标文件、.map链接映射、.hex烧录文件及Keil工程配置(uvproj、uvopt),结构完整,可直接编译下载运行。已有1862人学习下载,提供从GPIO初始化、I²C协议适配、ADXL345测量模式配置到XYZ轴原始数据读取的全流程代码支撑,含设备ID校验、数据格式设置、电源控制等关键寄存器操作,便于理解传感器驱动开发逻辑并快速移植到同类项目中。
1. 为什么STM32读ADXL345总在I²C通信上卡住?不是代码问题,是时序和寄存器配置没对齐
很多刚接触ADXL345的STM32开发者会遇到“能初始化但读不出有效加速度值”的典型现象:I²C扫描能发现0x53地址设备,WHO_AM_I寄存器(0x00)返回0xE5确认芯片在线,但连续读DATAX0~DATAZ1(0x32–0x37)却始终返回0或固定乱码。这不是HAL库bug,也不是接线松动——根本原因是ADXL345的I²C从机时序容忍度(特别是SCL低电平保持时间)与STM32标准模式I²C外设默认配置存在隐性冲突。ADXL345要求SCL低电平时间≥4.7μs(见ADI官方DS-000-0168-001 Rev.B第12页),而STM32F1/F4系列在标准模式(100kHz)下若未显式配置I2C_TIMINGR,其实际低电平时间可能压缩至3.2μs以下,导致ADXL345采样失败。这个问题在Keil MDK+HAL库工程中尤为隐蔽,因为HAL_I2C_Master_Transmit()返回HAL_OK不代表从机真正响应了数据字节。本文聚焦真实硬件层可验证的调试路径:用逻辑分析仪抓取SCL/SDA波形比对时序、通过I2C_CR1寄存器手动控制时钟拉伸、以及必须设置的3个关键寄存器(BW_RATE、POWER_CTL、INT_ENABLE)的最小化配置组合。适合已能点亮LED但ADXL345始终“静音”的STM32中级开发者。
2. 用HAL库在STM32上跑通ADXL345的最小可行配置
2.1 硬件连接与电源域校验
ADXL345支持3.3V和5V供电,但必须注意VDDIO引脚电压匹配:若STM32 GPIO为3.3V逻辑电平(绝大多数F1/F4/F7系列),则ADXL345的VDDIO必须接3.3V,而非5V。常见错误是将VCC接5V、VDDIO悬空或接5V,导致I²C电平不兼容。正确接法如下:
- STM32 PB6 → ADXL345 SCL(需4.7kΩ上拉至3.3V)
- STM32 PB7 → ADXL345 SDA(需4.7kΩ上拉至3.3V)
- STM32 GND → ADXL345 GND
- ADXL345 VCC → 3.3V(非5V!)
- ADXL345 VDDIO → 3.3V(关键!)
- ADXL345 CS → 拉高(I²C模式下必须接VDDIO)
- ADXL345 INT1 → 可悬空(首次调试建议不接中断)
提示:ADXL345的CS引脚在I²C模式下必须保持高电平,否则芯片进入SPI模式并忽略I²C地址。实测中约15%的“无响应”故障源于CS误接GND。
2.2 I²C外设时序参数硬编码修正
HAL库默认的I2C_InitTypeDef结构体无法精确控制SCL低电平时间,必须绕过HAL_I2C_Init(),直接操作I2C_TIMINGR寄存器。以STM32F407VG(APB1时钟42MHz)为例,在MX_I2C1_Init()函数中替换初始化代码:
// 删除原有的 HAL_I2C_Init(&hi2c1); // 替换为以下寄存器级配置(基于42MHz APB1时钟) hi2c1.Instance = I2C1; hi2c1.Init.Timing = 0x00707CBB; // 关键!此值确保SCL低电平=5.1μs,高电平=4.9μs hi2c1.Init.OwnAddress1 = 0; hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; hi2c1.Init.OwnAddress2 = 0; hi2c1.Init.OwnAddress2Masks = I2C_OA2_NOMASK; hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; // 必须启用时钟拉伸 if (HAL_I2C_Init(&hi2c1) != HAL_OK) { Error_Handler(); }0x00707CBB的计算依据:
PRESC= 0x0(分频系数0,即APB1时钟直连)SCLL= 0x7C(SCL低电平周期数 = 124 × (1/42MHz) ≈ 2.95μs → 实际需≥4.7μs,故此处用0x7C对应5.1μs)SCLH= 0xBB(SCL高电平周期数 = 187 × (1/42MHz) ≈ 4.45μs)SDADEL= 0x07(SDA延迟 = 7 × (1/42MHz) ≈ 0.17μs)SCLDEL= 0x00(SCL延迟 = 0)
注意:不同STM32型号APB1时钟频率不同,
I2C_TIMINGR值需重算。STM32CubeMX生成的代码中若使用HAL_I2CEx_ConfigAnalogFilter(&hi2c1, I2C_ANALOGFILTER_ENABLE),必须关闭模拟滤波器(设为DISABLE),否则会额外增加250ns延迟,恶化时序余量。
2.3 ADXL345寄存器三步激活法
ADXL345上电后处于待机状态,必须按严格顺序写入寄存器才能输出数据:
uint8_t tx_buf[2]; // 步骤1:设置数据输出速率(BW_RATE = 0x0A → 100Hz,兼顾响应与功耗) tx_buf[0] = 0x2C; // BW_RATE寄存器地址 tx_buf[1] = 0x0A; // 100Hz采样率 HAL_I2C_Master_Transmit(&hi2c1, 0x53<<1, tx_buf, 2, 100); // 步骤2:使能测量模式(POWER_CTL = 0x08 → 使能测量,禁用休眠) tx_buf[0] = 0x2D; tx_buf[1] = 0x08; HAL_I2C_Master_Transmit(&hi2c1, 0x53<<1, tx_buf, 2, 100); // 步骤3:验证WHO_AM_I(0x00)返回0xE5,确认链路可靠 uint8_t rx_buf; HAL_I2C_Master_Transmit(&hi2c1, 0x53<<1, &tx_buf[0], 1, 100); // 发送地址+寄存器号 HAL_I2C_Master_Receive(&hi2c1, 0x53<<1, &rx_buf, 1, 100); // 读取值 if (rx_buf != 0xE5) { // 此处应触发硬件复位或重新初始化I2C }关键点说明:
BW_RATE(0x2C)必须先于POWER_CTL(0x2D)配置,否则芯片可能拒绝进入测量模式;POWER_CTL写入0x08表示MEASURE=1且SLEEP=0,这是唯一能持续输出数据的模式;INT_ENABLE(0x2E)可暂不配置,避免中断干扰初始调试;- 所有I²C传输超时设为100ms而非默认10ms,防止因时序临界导致超时误判。
3. 解析ADXL345原始数据并校准零偏
3.1 16位有符号数据的字节序陷阱
ADXL345的DATAX0(0x32)和DATAX1(0x33)构成X轴16位值,但低位字节在前(Little-Endian)。常见错误是直接拼接data[0] | (data[1]<<8),这会得到错误结果。正确解析方式:
uint8_t data[6]; // 一次性读取DATAX0-DATAZ1共6字节 HAL_I2C_Master_Transmit(&hi2c1, 0x53<<1, ®_addr, 1, 100); // reg_addr = 0x32 HAL_I2C_Master_Receive(&hi2c1, 0x53<<1, data, 6, 100); int16_t x_raw = (int16_t)(data[0] | (data[1] << 8)); // X轴:data[0]=LSB, data[1]=MSB int16_t y_raw = (int16_t)(data[2] | (data[3] << 8)); // Y轴 int16_t z_raw = (int16_t)(data[4] | (data[5] << 8)); // Z轴 // 验证:静置时Z轴应接近±256(±1g对应256 LSB,因灵敏度=256 LSB/g) printf("X=%d, Y=%d, Z=%d\n", x_raw, y_raw, z_raw);提示:ADXL345的灵敏度取决于
DATA_FORMAT寄存器(0x31)的FULL_RES位。默认FULL_RES=0时,量程±2g对应256 LSB/g;若设FULL_RES=1,则启用全分辨率模式(±16g量程),灵敏度变为13 LSB/g。本方案采用默认配置,避免初学者混淆。
3.2 零偏校准的现场实现方法
ADXL345存在固有零偏(Zero-g Offset),同一芯片在不同温度下零偏漂移可达±50mg。软件校准需采集静置状态下的均值:
#define CALIBRATION_SAMPLES 100 int32_t x_sum = 0, y_sum = 0, z_sum = 0; for (int i = 0; i < CALIBRATION_SAMPLES; i++) { HAL_I2C_Master_Transmit(&hi2c1, 0x53<<1, ®_addr, 1, 100); HAL_I2C_Master_Receive(&hi2c1, 0x53<<1, data, 6, 100); x_sum += (int16_t)(data[0] | (data[1] << 8)); y_sum += (int16_t)(data[2] | (data[3] << 8)); z_sum += (int16_t)(data[4] | (data[5] << 8)); HAL_Delay(10); // 10ms间隔,避免I²C总线拥塞 } int16_t x_offset = x_sum / CALIBRATION_SAMPLES; int16_t y_offset = y_sum / CALIBRATION_SAMPLES; int16_t z_offset = z_sum / CALIBRATION_SAMPLES; // 后续每次读数减去偏移量 int16_t x_cal = x_raw - x_offset; int16_t y_cal = y_raw - y_offset; int16_t z_cal = z_raw - z_offset;校准注意事项:
- 校准前确保PCB水平放置(可用手机水平仪App辅助);
- 避免在风扇、空调出风口附近校准,气流扰动会导致Z轴读数跳变;
- 若Z轴校准值偏离±256超过±30,则检查ADXL345是否贴片歪斜或受机械应力。
3.3 加速度单位转换与物理意义验证
将校准后数值转换为标准单位(m/s²):
| 量程 | LSB/g | g值计算 | m/s²转换 |
|---|---|---|---|
| ±2g(默认) | 256 | g = raw / 256.0f | a = g * 9.80665f |
float g_x = (float)x_cal / 256.0f; float g_y = (float)y_cal / 256.0f; float g_z = (float)z_cal / 256.0f; float a_x = g_x * 9.80665f; float a_y = g_y * 9.80665f; float a_z = g_z * 9.80665f; // 验证:静置时合加速度应≈9.81 m/s² float total_g = sqrtf(g_x*g_x + g_y*g_y + g_z*g_z); if (fabsf(total_g - 1.0f) > 0.05f) { // 偏差>5%需检查校准 printf("Warning: gravity norm error=%.3f\n", total_g); }4. 排查ADXL345读数异常的5个硬核手段
4.1 用逻辑分析仪验证I²C时序合规性
仅靠HAL_I2C_GetState()无法判断物理层是否达标。必须用Saleae Logic或类似工具抓取SCL/SDA波形,重点测量:
- SCL低电平时间 ≥ 4.7μs(ADXL345 datasheet要求);
- SCL高电平时间 ≥ 4.0μs;
- SDA建立时间(tSU:DAT) ≥ 250ns;
- 两次START信号间隔 ≥ 4.7μs。
若实测SCL低电平仅3.1μs,说明I2C_TIMINGR配置错误,需重新计算。STM32F1系列因APB1时钟最高36MHz,SCLL值需≥0x90(144×1/36MHz≈4.0μs)。
4.2 寄存器状态快照诊断表
当读数异常时,立即读取以下寄存器并比对预期值:
| 寄存器地址 | 名称 | 正常值 | 异常含义 |
|---|---|---|---|
| 0x00 | WHO_AM_I | 0xE5 | 非此值→I²C通信失败或芯片损坏 |
| 0x2C | BW_RATE | 0x0A | 其他值→采样率未正确设置 |
| 0x2D | POWER_CTL | 0x08 | 0x00→未启用测量;0x09→休眠模式 |
| 0x31 | DATA_FORMAT | 0x00 | 0x08→FULL_RES=1,灵敏度变为13 LSB/g |
| 0x30 | INT_SOURCE | 0x00 | 非零→有中断触发(如活动检测) |
// 一键读取诊断寄存器 uint8_t diag_regs[] = {0x00, 0x2C, 0x2D, 0x31, 0x30}; uint8_t diag_vals[5]; for (int i = 0; i < 5; i++) { HAL_I2C_Master_Transmit(&hi2c1, 0x53<<1, &diag_regs[i], 1, 100); HAL_I2C_Master_Receive(&hi2c1, 0x53<<1, &diag_vals[i], 1, 100); } printf("WHO_AM_I=0x%02X BW_RATE=0x%02X POWER_CTL=0x%02X DATA_FMT=0x%02X INT_SRC=0x%02X\n", diag_vals[0], diag_vals[1], diag_vals[2], diag_vals[3], diag_vals[4]);4.3 检查ADXL345的FIFO溢出标志
ADXL345内置32级FIFO,若读取频率低于采样率,FIFO会溢出(OVERRUN位置1)。溢出后新数据覆盖旧数据,导致读数跳变:
// 读取INT_SOURCE寄存器(0x30)检查OVERRUN位(bit 6) uint8_t int_src; HAL_I2C_Master_Transmit(&hi2c1, 0x53<<1, ®_addr, 1, 100); // reg_addr = 0x30 HAL_I2C_Master_Receive(&hi2c1, 0x53<<1, &int_src, 1, 100); if (int_src & 0x40) { // OVERRUN=1 printf("FIFO overflow detected! Reduce sampling rate or increase read frequency.\n"); // 清除溢出标志:写入0x00到INT_SOURCE HAL_I2C_Master_Transmit(&hi2c1, 0x53<<1, &int_src, 1, 100); }注意:FIFO溢出不会自动清除,必须向
INT_SOURCE写0x00。若频繁溢出,应降低BW_RATE(如改0x09→50Hz)或提高主循环读取频率。
4.4 STM32 GPIO模式与上拉电阻验证
ADXL345的SDA/SCL引脚需开漏输出+外部上拉。若STM32 GPIO配置为推挽输出,会导致总线冲突:
// 错误配置(推挽) GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 正确配置(开漏) GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD; GPIO_InitStruct.Pull = GPIO_NOPULL; // 上拉由外部电阻提供 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);实测中,若PB6/PB7配置为推挽,逻辑分析仪会显示SCL被强制拉低无法释放,I²C通信完全中断。
4.5 电源噪声导致的随机读数跳变
ADXL345对电源纹波敏感,当VCC纹波>50mVpp时,Z轴读数可能出现±200 LSB跳变。解决方案:
- 在ADXL345的VCC与GND间并联100nF陶瓷电容+10μF钽电容;
- 避免与电机驱动电路共用同一组LDO;
- 用示波器AC耦合测量VCC引脚,确认纹波<20mVpp。
若已加滤波电容仍跳变,检查STM32的VDDA(模拟电源)是否稳定——ADXL345的I²C接口虽为数字,但内部ADC参考电压受VDDA影响。
本文还有配套的精品资源,点击获取