云计算百科
云计算领域专业知识百科平台

STM32F103 驱动ADXL345三轴加速度传感器(软件I2C+OLED实时显示)

一、前言

本文完整讲解基于 STM32F103C8T6 + ADXL345 MEMS 三轴加速度传感器 + 0.96 寸 I2C OLED 的整套工程原理、硬件接线、寄存器配置、数据换算与现象分析,采用软件模拟 I2C通信,无需硬件 I2C 外设,新手友好,适合单片机课程教学、姿态检测入门实验。

整套工程实现逻辑: 模块姿态 / 振动 / 运动改变 → ADXL345 内部 MEMS 采集加速度 → 软件 I2C 读取三轴原始数据 → 转换为 mg 单位 → OLED 实时打印 X/Y/Z 三轴加速度数值

视频讲解:STM32F103 驱动ADXL345三轴加速度传感器(软件I2C+OLED实时显示)https://www.bilibili.com/video/BV1wjMd6EEDW/?share_source=copy_web&vd_source=91b967b41a17a5686913521545a8c3fb

二、硬件接线说明

2.1 器件引脚对应表

ADXL345 模块引脚STM32F103 引脚功能说明
SDA PA0 软件 I2C 数据线
SCK(SCL) PA1 软件 I2C 时钟线(SPI 标 SCK,I2C 模式等效 SCL)
CS 3.3V 拉高切换至 I2C 通信模式
SDO/ALT ADDRESS GND I2C 从机地址 0x53
VCC 3.3V 传感器供电(严禁直连 5V,无稳压模块仅支持 2~3.6V)
GND GND 单片机与传感器共地,通信稳定前提
OLED 屏幕引脚STM32F103 引脚功能说明
SCL PB8 OLED 软件 I2C 时钟
SDA PB9 OLED 软件 I2C 数据
VCC 3.3V 屏幕供电
GND GND 共地

2.2 关键引脚补充说明

  • CS 引脚 拉高时 ADXL345 工作在 I2C 模式;若拉低则切换 SPI 通信,本工程全程使用 I2C,必须接 3.3V。
  • SDO 地址选择引脚
    • 接地:7 位 I2C 地址0x53,写地址0xA6,读地址0xA7(本工程使用)
    • 接 3.3V:7 位地址变为0x1D,代码需要同步修改,否则读不到数据
  • I2C 上拉电阻 I2C 为开漏总线,标准需要 4.7kΩ 上拉至 3.3V;市面上成品 ADXL345 模块大多自带板载上拉电阻,若出现数据乱跳、通信失败,自行增加上拉电阻排查。
  • 供电要求 芯片额定电压 2.0~3.6V,只有自带稳压、电平转换的 5V 兼容模块才能接 5V,教学实验统一推荐 3.3V 供电避免烧坏传感器。
  • 三、ADXL345 核心工作原理

    ADXL345 是数字输出 MEMS 三轴加速度传感器,核心是芯片内部微型机械质量块结构。

    3.1 信号转换流程

    姿态 / 晃动 / 振动 → 惯性使内部质量块产生微小位移 → 两侧差分电容容量发生变化 → 内部电路放大、滤波、ADC 模数转换 → 输出 X/Y/Z 三轴数字加速度值

    可检测两种加速度:

  • 静态加速度(重力):传感器静止时仅受重力,用于判断倾斜角度、设备姿态;
  • 动态加速度:晃动、撞击、运动产生,可做振动检测、敲击识别。
  • 重点知识点:传感器只采集加速度,无法直接获取位移、速度;仅单次读数不能推算移动距离。

    3.2 静止时数值规律

    地球恒定存在 1g 重力加速度(1000mg),设备平放静止时:

    • Z 轴垂直地面:Z≈±1000mg;
    • X、Y 平行地面:X、Y≈0mg; 翻转传感器后坐标轴正负号反转;任意倾斜状态下三轴矢量合加速度约等于 1000mg。

    自由落体特殊现象:完全失重状态下 X/Y/Z 三轴读数全部趋近 0mg。

    四、软件 I2C 通信与三轴数据读取逻辑

    本工程不使用 STM32 硬件 I2C 外设,将 PA0、PA1 配置为开漏输出,通过翻转 GPIO 电平模拟标准 I2C 时序。

    4.1 I2C 基础通信时序

    单次寄存器读写完整流程: 起始信号 → 从机地址 + 读写位 → 寄存器地址 → 读写数据 → 停止信号

    • 起始:SCL 高电平,SDA 由高拉低;
    • 应答 ACK:每传输 8bit 数据,从机拉低 SDA 产生第 9 位应答;
    • 停止:SCL 高电平,SDA 由低拉高;
    • 寄存器读操作:先发写指令指定寄存器,重复起始信号后切换读模式接收数据。

    4.2 连续读取 6 字节数据原因

    ADXL345 三轴数据寄存器地址连续排布,从0x32开始依次存储 X 低、X 高、Y 低、Y 高、Z 低、Z 高:

    寄存器地址寄存器名存储内容
    0x32 DATAX0 X 轴低字节
    0x33 DATAX1 X 轴高字节
    0x34 DATAY0 Y 轴低字节
    0x35 DATAY1 Y 轴高字节
    0x36 DATAZ0 Z 轴低字节
    0x37 DATAZ1 Z 轴高字节

    一次性连续读取 6 字节,保证 X/Y/Z 三轴数据来自同一采样周期,分开单轴读取会存在采样时间差,姿态数据失真。

    4.3 16 位有符号原始值合成规则

    数据存储格式:低字节在前,高字节在后,采用二进制补码存储正负加速度,C 语言处理代码:

    s16 x = (s16)((X高字节 << 8) | X低字节);
    s16 y = (s16)((Y高字节 << 8) | Y低字节);
    s16 z = (s16)((Z高字节 << 8) | Z低字节);

    五、寄存器初始化配置与单位换算

    5.1 关键寄存器配置表

    上电初始化严格按照下表配置,开启测量模式后才能读取有效加速度:

    寄存器名地址写入值功能作用
    DEVID 0x00 只读 校验传感器 ID,正确值 0xE5,通信故障识别依据
    POWER_CTL 0x2D 0x00 配置参数前进入待机模式
    DATA_FORMAT 0x31 0x0B 开启全分辨率,量程 ±16g
    BW_RATE 0x2C 0x0A 正常功耗,传感器输出速率 100Hz
    OFSX/OFSY/OFSZ 0x1E~0x20 0x00 三轴硬件偏移寄存器,默认不校准
    INT_ENABLE 0x2E 0x00 关闭所有中断,采用轮询读取数据
    FIFO_CTL 0x38 0x00 不使用 FIFO 缓存
    POWER_CTL 0x2D 0x08 MEASURE 位置 1,传感器启动测量

    5.2 原始值转 mg 单位计算

    工程配置 ±16g 全分辨率模式,分辨率参数:1 LSB = 3.9mg 换算公式:加速度(mg) = 原始采样值 × 3.9

    单片机避免浮点运算,使用整数等效计算:

    int mg = raw_value * 39 / 10;

    单位说明:文中 mg 代表毫重力加速度,和质量单位毫克无关,换算关系 1000mg = 1g ≈ 9.8m/s²

    换算示例:

    原始采样值计算后 mg 数值等效重力
    0 0mg 0g
    128 499mg 0.5g
    256 998mg 1g
    -256 -998mg -1g

    5.3 主程序整体运行流程

  • 系统延时初始化 + OLED 屏幕初始化;
  • 初始化 PA0/PA1 软件 I2C;
  • 读取 DEVID 寄存器校验 ID:
    • ID≠0xE5:OLED 打印 Sensor error,1s 后重新检测;
    • ID=0xE5:正常进入采样流程;
  • 连续读取 6 字节三轴原始数据;
  • 拼接 16 位有符号原始值,换算 mg;
  • OLED 刷新三轴加速度数据;
  • 延时 200ms,循环采样。
  • 补充:传感器内部数据刷新 100Hz(10ms 更新一次),程序 200ms 刷新屏幕,显示速度满足实验观察需求。

    main.c代码

    /*
    * ADXL345 three-axis acceleration teaching demo
    * ADXL345 SDA -> PA0, SCK/SCL -> PA1
    * OLED SCL -> PB8, SDA -> PB9
    */
    #include "delay.h"
    #include "sys.h"
    #include "oled.h"
    #include "ADXL345.h"

    #define ADXL345_REFRESH_MS 200U

    static void ADXL345_ShowData(const ADXL345_DataTypeDef *data);
    static void ADXL345_ShowError(u8 device_id);
    static void OLED_ShowSignedNum(u8 x, u8 y, s32 value, u8 digits, u8 size);
    static void OLED_ShowHexByte(u8 x, u8 y, u8 value, u8 size);

    int main(void)
    {
    ADXL345_DataTypeDef sensor_data;
    u8 sensor_ok;

    delay_init();
    NVIC_Configuration();
    OLED_Init();
    OLED_ColorTurn(0);
    OLED_DisplayTurn(0);

    OLED_Clear();
    OLED_ShowString(36, 0, (u8 *)"ADXL345", 16);
    OLED_ShowString(16, 22, (u8 *)"SDA PA0 SCK PA1", 12);
    OLED_ShowString(25, 42, (u8 *)"Initializing", 12);
    OLED_Refresh();

    sensor_ok = ADXL345_Init();

    while(1)
    {
    if((sensor_ok != 0U) && (ADXL345_ReadAcceleration(&sensor_data) != 0U))
    {
    ADXL345_ShowData(&sensor_data);
    delay_ms(ADXL345_REFRESH_MS);
    }
    else
    {
    ADXL345_ShowError(ADXL345_ReadDeviceID());
    delay_ms(1000);
    sensor_ok = ADXL345_Init();
    }
    }
    }

    static void ADXL345_ShowData(const ADXL345_DataTypeDef *data)
    {
    OLED_Clear();
    OLED_ShowString(16, 0, (u8 *)"ADXL345 +/-16g", 12);

    OLED_ShowString(0, 16, (u8 *)"X:", 12);
    OLED_ShowSignedNum(18, 16, ADXL345_RawToMilliG(data->x), 5U, 12);
    OLED_ShowString(54, 16, (u8 *)" mg", 12);

    OLED_ShowString(0, 32, (u8 *)"Y:", 12);
    OLED_ShowSignedNum(18, 32, ADXL345_RawToMilliG(data->y), 5U, 12);
    OLED_ShowString(54, 32, (u8 *)" mg", 12);

    OLED_ShowString(0, 48, (u8 *)"Z:", 12);
    OLED_ShowSignedNum(18, 48, ADXL345_RawToMilliG(data->z), 5U, 12);
    OLED_ShowString(54, 48, (u8 *)" mg", 12);

    OLED_Refresh();
    }

    static void ADXL345_ShowError(u8 device_id)
    {
    OLED_Clear();
    OLED_ShowString(36, 0, (u8 *)"ADXL345", 16);
    OLED_ShowString(22, 20, (u8 *)"Sensor error", 12);
    OLED_ShowString(10, 36, (u8 *)"SDA PA0 SCK PA1", 12);
    OLED_ShowString(34, 50, (u8 *)"ID:0x", 12);
    OLED_ShowHexByte(64, 50, device_id, 12);
    OLED_Refresh();
    }

    static void OLED_ShowSignedNum(u8 x, u8 y, s32 value, u8 digits, u8 size)
    {
    u32 magnitude;

    if(value < 0)
    {
    OLED_ShowChar(x, y, '-', size);
    magnitude = (u32)(-value);
    }
    else
    {
    OLED_ShowChar(x, y, '+', size);
    magnitude = (u32)value;
    }
    OLED_ShowNum((u8)(x + size / 2U), y, magnitude, digits, size);
    }

    static void OLED_ShowHexByte(u8 x, u8 y, u8 value, u8 size)
    {
    u8 high;
    u8 low;

    high = (u8)((value >> 4) & 0x0FU);
    low = (u8)(value & 0x0FU);
    OLED_ShowChar(x, y, (high < 10U) ? (u8)('0' + high) : (u8)('A' + high – 10U), size);
    OLED_ShowChar((u8)(x + size / 2U), y,
    (low < 10U) ? (u8)('0' + low) : (u8)('A' + low – 10U), size);
    }

    ADXL345.c代码

    #include "ADXL345.h"
    #include "delay.h"

    /*
    * ADXL345 软件 I2C 驱动
    *
    * 硬件连接:
    * ADXL345 SDA -> STM32F103C8T6 PA0
    * ADXL345 SCK/SCL -> STM32F103C8T6 PA1
    *
    * PA0 和 PA1 配置成开漏输出,通过软件模拟 I2C 时序。
    * CS 引脚必须接 3.3V 以选择 I2C 模式;ALT ADDRESS/SDO 接地时,
    * ADXL345 的 7 位从机地址为 0x53,对应写地址 0xA6、读地址 0xA7。
    */

    /* I2C 从机地址:最低位由读写方向自动补充。 */
    #define ADXL345_I2C_ADDRESS 0x53U
    #define ADXL345_WRITE_ADDRESS (ADXL345_I2C_ADDRESS << 1)
    #define ADXL345_READ_ADDRESS ((ADXL345_I2C_ADDRESS << 1) | 0x01U)

    /* 本驱动使用的 ADXL345 寄存器地址。 */
    #define ADXL345_REG_DEVID 0x00U /* 器件标识寄存器,固定值为 0xE5 */
    #define ADXL345_REG_OFSX 0x1EU /* X 轴零偏校准寄存器 */
    #define ADXL345_REG_OFSY 0x1FU /* Y 轴零偏校准寄存器 */
    #define ADXL345_REG_OFSZ 0x20U /* Z 轴零偏校准寄存器 */
    #define ADXL345_REG_BW_RATE 0x2CU /* 输出数据速率和低功耗控制寄存器 */
    #define ADXL345_REG_POWER_CTL 0x2DU /* 电源模式和测量模式控制寄存器 */
    #define ADXL345_REG_INT_ENABLE 0x2EU /* 中断使能寄存器 */
    #define ADXL345_REG_DATA_FORMAT 0x31U /* 量程、全分辨率和数据格式寄存器 */
    #define ADXL345_REG_DATAX0 0x32U /* 三轴数据起始地址:DATAX0~DATAZ1 */
    #define ADXL345_REG_FIFO_CTL 0x38U /* FIFO 工作模式控制寄存器 */

    /* 软件 I2C 引脚操作:开漏输出写 1 表示释放总线,写 0 表示拉低总线。 */
    #define ADXL345_SDA_HIGH() GPIO_SetBits(GPIOA, GPIO_Pin_0)
    #define ADXL345_SDA_LOW() GPIO_ResetBits(GPIOA, GPIO_Pin_0)
    #define ADXL345_SCL_HIGH() GPIO_SetBits(GPIOA, GPIO_Pin_1)
    #define ADXL345_SCL_LOW() GPIO_ResetBits(GPIOA, GPIO_Pin_1)
    #define ADXL345_READ_SDA() GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0)

    /* 软件 I2C 底层函数,只在本文件内部使用。 */
    static void ADXL345_I2C_Delay(void);
    static void ADXL345_I2C_Start(void);
    static void ADXL345_I2C_Stop(void);
    static void ADXL345_I2C_SendAck(u8 send_ack);
    static u8 ADXL345_I2C_SendByte(u8 data);
    static u8 ADXL345_I2C_ReadByte(void);
    static void ADXL345_I2C_BusRecovery(void);

    /*
    * I2C 时序延时。
    * 5us 半周期对应的总线速度低于 100kHz,适合教学和杜邦线连接环境。
    */
    static void ADXL345_I2C_Delay(void)
    {
    delay_us(5);
    }

    /*
    * 产生 I2C 起始信号:
    * SCL 为高电平时,SDA 由高电平变为低电平。
    */
    static void ADXL345_I2C_Start(void)
    {
    ADXL345_SDA_HIGH();
    ADXL345_SCL_HIGH();
    ADXL345_I2C_Delay();
    ADXL345_SDA_LOW();
    ADXL345_I2C_Delay();
    ADXL345_SCL_LOW();
    ADXL345_I2C_Delay();
    }

    /*
    * 产生 I2C 停止信号:
    * SCL 为高电平时,SDA 由低电平变为高电平。
    */
    static void ADXL345_I2C_Stop(void)
    {
    ADXL345_SCL_LOW();
    ADXL345_SDA_LOW();
    ADXL345_I2C_Delay();
    ADXL345_SCL_HIGH();
    ADXL345_I2C_Delay();
    ADXL345_SDA_HIGH();
    ADXL345_I2C_Delay();
    }

    /*
    * 从最高位开始向 I2C 总线发送一个字节,并读取从机应答。
    * 返回值:1 表示收到 ACK,0 表示收到 NACK。
    */
    static u8 ADXL345_I2C_SendByte(u8 data)
    {
    u8 i;
    u8 acknowledged;

    /* 依次发送 D7~D0,每个时钟周期发送一位。 */
    for(i = 0U; i < 8U; i++)
    {
    ADXL345_SCL_LOW();
    if((data & 0x80U) != 0U)
    ADXL345_SDA_HIGH();
    else
    ADXL345_SDA_LOW();
    ADXL345_I2C_Delay();
    ADXL345_SCL_HIGH();
    ADXL345_I2C_Delay();
    data <<= 1;
    }

    /*
    * 第 9 个时钟用于读取应答:先释放 SDA,再由 ADXL345 拉低 SDA。
    * SDA 为低表示 ACK,说明从机已经正确接收当前字节。
    */
    ADXL345_SCL_LOW();
    ADXL345_SDA_HIGH();
    ADXL345_I2C_Delay();
    ADXL345_SCL_HIGH();
    ADXL345_I2C_Delay();
    acknowledged = (ADXL345_READ_SDA() == Bit_RESET) ? 1U : 0U;
    ADXL345_SCL_LOW();
    ADXL345_I2C_Delay();

    return acknowledged;
    }

    /*
    * 从 I2C 总线读取一个字节,按 D7~D0 的顺序接收。
    * 本函数只负责读取 8 位数据,读完后的 ACK/NACK 由调用者发送。
    */
    static u8 ADXL345_I2C_ReadByte(void)
    {
    u8 i;
    u8 data;

    data = 0U;
    ADXL345_SDA_HIGH();
    for(i = 0U; i < 8U; i++)
    {
    ADXL345_SCL_LOW();
    ADXL345_I2C_Delay();
    ADXL345_SCL_HIGH();
    ADXL345_I2C_Delay();
    data <<= 1;
    if(ADXL345_READ_SDA() != Bit_RESET)
    data |= 0x01U;
    }
    ADXL345_SCL_LOW();
    ADXL345_I2C_Delay();

    return data;
    }

    /*
    * 主机向从机发送应答位。
    * send_ack = 1:发送 ACK,表示还要继续读取后续字节;
    * send_ack = 0:发送 NACK,表示当前字节是最后一个字节。
    */
    static void ADXL345_I2C_SendAck(u8 send_ack)
    {
    ADXL345_SCL_LOW();
    if(send_ack != 0U)
    ADXL345_SDA_LOW();
    else
    ADXL345_SDA_HIGH();
    ADXL345_I2C_Delay();
    ADXL345_SCL_HIGH();
    ADXL345_I2C_Delay();
    ADXL345_SCL_LOW();
    ADXL345_SDA_HIGH();
    ADXL345_I2C_Delay();
    }

    /*
    * I2C 总线恢复:释放 SDA 后输出 9 个 SCL 脉冲,再发送停止信号。
    * 如果从机因上次通信被意外中断而一直占用 SDA,该操作可使其退出未完成状态。
    */
    static void ADXL345_I2C_BusRecovery(void)
    {
    u8 i;

    ADXL345_SDA_HIGH();
    for(i = 0U; i < 9U; i++)
    {
    ADXL345_SCL_LOW();
    ADXL345_I2C_Delay();
    ADXL345_SCL_HIGH();
    ADXL345_I2C_Delay();
    }
    ADXL345_I2C_Stop();
    }

    /*
    * 向 ADXL345 的指定寄存器写入一个字节。
    * 通信顺序:起始信号 -> 写地址 -> 寄存器地址 -> 数据 -> 停止信号。
    * 任意阶段未收到 ACK 都会立即停止通信并返回 0。
    */
    u8 ADXL345_WriteRegister(u8 register_address, u8 data)
    {
    ADXL345_I2C_Start();
    if(ADXL345_I2C_SendByte(ADXL345_WRITE_ADDRESS) == 0U)
    {
    ADXL345_I2C_Stop();
    return 0U;
    }
    if(ADXL345_I2C_SendByte(register_address) == 0U)
    {
    ADXL345_I2C_Stop();
    return 0U;
    }
    if(ADXL345_I2C_SendByte(data) == 0U)
    {
    ADXL345_I2C_Stop();
    return 0U;
    }
    ADXL345_I2C_Stop();

    return 1U;
    }

    /*
    * 从指定起始寄存器连续读取多个字节。
    *
    * 先以写方向发送寄存器地址,再发送重复起始信号并切换到读方向;
    * 除最后一个字节外,每读完一个字节都发送 ACK,最后一个字节发送 NACK。
    */
    u8 ADXL345_ReadRegisters(u8 start_address, u8 *data, u8 length)
    {
    u8 i;

    if((data == 0) || (length == 0U))
    return 0U;

    ADXL345_I2C_Start();
    if(ADXL345_I2C_SendByte(ADXL345_WRITE_ADDRESS) == 0U)
    {
    ADXL345_I2C_Stop();
    return 0U;
    }
    if(ADXL345_I2C_SendByte(start_address) == 0U)
    {
    ADXL345_I2C_Stop();
    return 0U;
    }

    ADXL345_I2C_Start();
    if(ADXL345_I2C_SendByte(ADXL345_READ_ADDRESS) == 0U)
    {
    ADXL345_I2C_Stop();
    return 0U;
    }

    for(i = 0U; i < length; i++)
    {
    data[i] = ADXL345_I2C_ReadByte();
    ADXL345_I2C_SendAck((i < (u8)(length – 1U)) ? 1U : 0U);
    }
    ADXL345_I2C_Stop();

    return 1U;
    }

    /* 从指定寄存器读取一个字节,是连续读取接口的单字节封装。 */
    u8 ADXL345_ReadRegister(u8 register_address, u8 *data)
    {
    return ADXL345_ReadRegisters(register_address, data, 1U);
    }

    /*
    * 读取器件标识寄存器。
    * 正常连接的 ADXL345 应返回 0xE5;通信失败时返回初始值 0x00。
    */
    u8 ADXL345_ReadDeviceID(void)
    {
    u8 device_id;

    device_id = 0U;
    (void)ADXL345_ReadRegister(ADXL345_REG_DEVID, &device_id);
    return device_id;
    }

    /*
    * 初始化软件 I2C 引脚和 ADXL345 工作参数。
    *
    * 配置过程:
    * 1. 将 PA0、PA1 配置为 50MHz 开漏输出,并恢复 I2C 总线;
    * 2. 读取 DEVID,确认连接的器件确实是 ADXL345;
    * 3. 进入待机模式后配置全分辨率、±16g 和 100Hz 数据速率;
    * 4. 清零三轴偏移量,关闭本示例未使用的中断和 FIFO;
    * 5. 设置 MEASURE 位,开始连续测量。
    *
    * 返回值:1 表示初始化成功,0 表示器件标识错误或寄存器写入失败。
    */
    u8 ADXL345_Init(void)
    {
    GPIO_InitTypeDef gpio_init_structure;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    gpio_init_structure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
    gpio_init_structure.GPIO_Speed = GPIO_Speed_50MHz;
    gpio_init_structure.GPIO_Mode = GPIO_Mode_Out_OD;
    GPIO_Init(GPIOA, &gpio_init_structure);
    ADXL345_SDA_HIGH();
    ADXL345_SCL_HIGH();
    delay_ms(5);
    ADXL345_I2C_BusRecovery();

    if(ADXL345_ReadDeviceID() != ADXL345_DEVICE_ID)
    return 0U;

    /* 修改测量参数前先进入待机模式。 */
    if(ADXL345_WriteRegister(ADXL345_REG_POWER_CTL, 0x00U) == 0U)
    return 0U;
    /* FULL_RES = 1、Range = 11,选择全分辨率和 ±16g 量程。 */
    if(ADXL345_WriteRegister(ADXL345_REG_DATA_FORMAT, 0x0BU) == 0U)
    return 0U;
    /* LOW_POWER = 0、Rate = 0x0A,选择正常功耗和 100Hz 输出速率。 */
    if(ADXL345_WriteRegister(ADXL345_REG_BW_RATE, 0x0AU) == 0U)
    return 0U;
    /* 教学演示暂不进行零偏校准,三个偏移寄存器均写入 0。 */
    if(ADXL345_WriteRegister(ADXL345_REG_OFSX, 0x00U) == 0U)
    return 0U;
    if(ADXL345_WriteRegister(ADXL345_REG_OFSY, 0x00U) == 0U)
    return 0U;
    if(ADXL345_WriteRegister(ADXL345_REG_OFSZ, 0x00U) == 0U)
    return 0U;
    /* 本教学演示采用轮询读取,因此关闭中断和 FIFO 功能。 */
    if(ADXL345_WriteRegister(ADXL345_REG_INT_ENABLE, 0x00U) == 0U)
    return 0U;
    if(ADXL345_WriteRegister(ADXL345_REG_FIFO_CTL, 0x00U) == 0U)
    return 0U;
    /* POWER_CTL 的 MEASURE 位置 1,退出待机并开始连续转换。 */
    if(ADXL345_WriteRegister(ADXL345_REG_POWER_CTL, 0x08U) == 0U)
    return 0U;
    delay_ms(10);

    return 1U;
    }

    /*
    * 一次连续读取 DATAX0~DATAZ1 共 6 个寄存器。
    * ADXL345 按低字节在前、高字节在后的顺序输出数据,合并后转换为 s16,
    * 从而保留负加速度的二进制补码符号。
    */
    u8 ADXL345_ReadAcceleration(ADXL345_DataTypeDef *data)
    {
    u8 buffer[6];

    if(data == 0)
    return 0U;
    if(ADXL345_ReadRegisters(ADXL345_REG_DATAX0, buffer, 6U) == 0U)
    return 0U;

    /* 每个轴由相邻的低、高两个字节组成。 */
    data->x = (s16)(((u16)buffer[1] << 8) | buffer[0]);
    data->y = (s16)(((u16)buffer[3] << 8) | buffer[2]);
    data->z = (s16)(((u16)buffer[5] << 8) | buffer[4]);

    return 1U;
    }

    /*
    * 将某一轴的原始数据换算成 mg(毫重力加速度)。
    *
    * 当前为全分辨率模式,典型灵敏度为 3.9mg/LSB:
    * 加速度(mg) = 原始数据 × 3.9
    * 使用“先乘 39、再除 10”的整数运算,避免引入浮点运算代码。
    */
    s32 ADXL345_RawToMilliG(s16 raw_value)
    {
    return ((s32)raw_value * 39L) / 10L;
    }

    ADXL345.h代码

    #ifndef __ADXL345_H
    #define __ADXL345_H

    #include "sys.h"

    /*
    * ADXL345 三轴加速度传感器驱动头文件
    *
    * 硬件连接:
    * ADXL345 SDA -> STM32F103C8T6 PA0
    * ADXL345 SCK/SCL -> STM32F103C8T6 PA1
    *
    * 注意:ADXL345 的 CS 引脚需要接 3.3V,才能进入 I2C 通信模式;
    * ALT ADDRESS/SDO 接地时,器件的 7 位 I2C 地址为 0x53。
    */

    /* ADXL345 的固定器件标识,读取 DEVID 寄存器应得到 0xE5。 */
    #define ADXL345_DEVICE_ID 0xE5U

    /* 保存一次采样得到的 X、Y、Z 三轴原始有符号数据。 */
    typedef struct
    {
    s16 x; /* X 轴原始数据 */
    s16 y; /* Y 轴原始数据 */
    s16 z; /* Z 轴原始数据 */
    } ADXL345_DataTypeDef;

    /*
    * 初始化 GPIO 和 ADXL345。
    * 配置为全分辨率、±16g、100Hz 输出数据速率并进入测量模式。
    * 返回值:1 表示初始化成功,0 表示通信或器件检测失败。
    */
    u8 ADXL345_Init(void);

    /* 读取 DEVID 寄存器,正常模块应返回 ADXL345_DEVICE_ID(0xE5)。 */
    u8 ADXL345_ReadDeviceID(void);

    /* 向指定寄存器写入一个字节,成功返回 1,失败返回 0。 */
    u8 ADXL345_WriteRegister(u8 register_address, u8 data);

    /* 从指定寄存器读取一个字节,成功返回 1,失败返回 0。 */
    u8 ADXL345_ReadRegister(u8 register_address, u8 *data);

    /* 从 start_address 开始连续读取 length 个字节。 */
    u8 ADXL345_ReadRegisters(u8 start_address, u8 *data, u8 length);

    /* 连续读取 X、Y、Z 三轴加速度原始数据。 */
    u8 ADXL345_ReadAcceleration(ADXL345_DataTypeDef *data);

    /*
    * 将原始数据换算为 mg(毫重力加速度)。
    * 全分辨率模式下的典型比例系数为 3.9mg/LSB,1000mg 约等于 1g。
    */
    s32 ADXL345_RawToMilliG(s16 raw_value);

    #endif

    六、OLED 屏幕显示效果解析

    6.1 正常运行界面

    ADXL345 +/-16g
    X:+00000 mg
    Y:+00000 mg
    Z:+00998 mg

    字段释义:

    • +/-16g:当前传感器测量量程;
    • X/Y/Z:三轴加速度,正负号代表受力方向;
    • mg:毫重力加速度单位。

    6.2 通信故障报错界面

    ADXL345
    Sensor error
    SDA PA0 SCK PA1
    ID:0x00

    出现报错排查顺序:供电、共地、CS 引脚、SDO 地址引脚、SDA/SCL 接线、I2C 上拉电阻。

    七、量程、分辨率与实验现象分析

    ADXL345 支持 4 档量程:±2g、±4g、±8g、±16g;本工程选用 ±16g,可捕捉大幅度晃动、冲击,全分辨率模式下所有量程统一 3.9mg/LSB,±16g 可达 13 位高精度。

  • 倾斜实验 设备缓慢倾斜,重力矢量在三轴重新分配,一个轴数值减小,另一轴增大;静止状态下三轴平方和开根号≈1000mg。
  • 晃动冲击实验 快速摆动、敲击传感器,动态加速度叠加重力分量,单轴读数会超过 ±1000mg,属于正常现象,非硬件损坏。
  • 自由落体实验 脱手自由下落瞬间三轴读数全部接近 0mg,失重检测是 ADXL345 内置功能,本工程未启用中断。

  • 八、传感器误差来源与简易校准方案

    实际采样数值和理论值存在偏差,主要误差来源:

  • 零偏误差:无重力分量轴存在固定几十 mg 偏移;
  • 灵敏度误差:1g 位置读数偏离 1000mg;
  • 安装机械误差:PCB 安装不水平,重力分解至多轴;
  • 干扰误差:电源波动、长线接线、电机振动、温度漂移。
  • 简易校准步骤

  • 传感器水平静止放置,多次采样取平均值,降低随机抖动;
  • 确定理论标准值(平放 Z 轴 + 1000mg,X/Y 为 0mg);
  • 平均值减去理论值,得到三轴零偏补偿值;
  • 两种补偿方式:软件读取后手动减法、写入 OFSX/OFSY/OFSZ 硬件偏移寄存器。
  • 拓展优化:多次采样滑动平均滤波、一阶低通滤波,消除数值抖动。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » STM32F103 驱动ADXL345三轴加速度传感器(软件I2C+OLED实时显示)
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!