一、前言

本文完整讲解基于 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 引脚功能说明
SDAPA0软件 I2C 数据线
SCK(SCL)PA1软件 I2C 时钟线(SPI 标 SCK,I2C 模式等效 SCL)
CS3.3V拉高切换至 I2C 通信模式
SDO/ALT ADDRESSGNDI2C 从机地址 0x53
VCC3.3V传感器供电(严禁直连 5V,无稳压模块仅支持 2~3.6V)
GNDGND单片机与传感器共地,通信稳定前提
OLED 屏幕引脚STM32F103 引脚功能说明
SCLPB8OLED 软件 I2C 时钟
SDAPB9OLED 软件 I2C 数据
VCC3.3V屏幕供电
GNDGND共地

2.2 关键引脚补充说明

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

三、ADXL345 核心工作原理

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

3.1 信号转换流程

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

可检测两种加速度:

  1. 静态加速度(重力):传感器静止时仅受重力,用于判断倾斜角度、设备姿态;
  2. 动态加速度:晃动、撞击、运动产生,可做振动检测、敲击识别。

重点知识点:传感器只采集加速度,无法直接获取位移、速度;仅单次读数不能推算移动距离。

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 高:

寄存器地址寄存器名存储内容
0x32DATAX0X 轴低字节
0x33DATAX1X 轴高字节
0x34DATAY0Y 轴低字节
0x35DATAY1Y 轴高字节
0x36DATAZ0Z 轴低字节
0x37DATAZ1Z 轴高字节

一次性连续读取 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 关键寄存器配置表

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

寄存器名地址写入值功能作用
DEVID0x00只读校验传感器 ID,正确值 0xE5,通信故障识别依据
POWER_CTL0x2D0x00配置参数前进入待机模式
DATA_FORMAT0x310x0B开启全分辨率,量程 ±16g
BW_RATE0x2C0x0A正常功耗,传感器输出速率 100Hz
OFSX/OFSY/OFSZ0x1E~0x200x00三轴硬件偏移寄存器,默认不校准
INT_ENABLE0x2E0x00关闭所有中断,采用轮询读取数据
FIFO_CTL0x380x00不使用 FIFO 缓存
POWER_CTL0x2D0x08MEASURE 位置 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 数值等效重力
00mg0g
128499mg0.5g
256998mg1g
-256-998mg-1g

5.3 主程序整体运行流程

  1. 系统延时初始化 + OLED 屏幕初始化;
  2. 初始化 PA0/PA1 软件 I2C;
  3. 读取 DEVID 寄存器校验 ID:
    • ID≠0xE5:OLED 打印 Sensor error,1s 后重新检测;
    • ID=0xE5:正常进入采样流程;
  4. 连续读取 6 字节三轴原始数据;
  5. 拼接 16 位有符号原始值,换算 mg;
  6. OLED 刷新三轴加速度数据;
  7. 延时 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 位高精度。

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

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

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

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

简易校准步骤

  1. 传感器水平静止放置,多次采样取平均值,降低随机抖动;
  2. 确定理论标准值(平放 Z 轴 + 1000mg,X/Y 为 0mg);
  3. 平均值减去理论值,得到三轴零偏补偿值;
  4. 两种补偿方式:软件读取后手动减法、写入 OFSX/OFSY/OFSZ 硬件偏移寄存器。

拓展优化:多次采样滑动平均滤波、一阶低通滤波,消除数值抖动。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐