1. ADXL345传感器与STM32的完美结合
ADXL345是ADI公司推出的一款低功耗三轴加速度计,测量范围可达±16g,分辨率高达13位。这款传感器在消费电子、工业设备、医疗仪器等领域有着广泛应用。而STM32作为意法半导体推出的经典ARM Cortex-M系列微控制器,凭借其丰富的外设接口和出色的性能,成为嵌入式开发者的首选平台之一。
将ADXL345通过I2C接口与STM32连接,可以构建一个高精度的运动检测系统。I2C(Inter-Integrated Circuit)是一种简单、高效的双线制串行通信协议,特别适合传感器与微控制器之间的短距离通信。相比SPI接口,I2C节省了引脚资源,在STM32资源受限的应用场景中尤为实用。
在实际项目中,这种组合常被用于:
- 智能手环、运动手表的姿态检测
- 工业设备的振动监测
- 无人机飞控系统的姿态感知
- 游戏控制器的动作捕捉
- 汽车电子中的碰撞检测
2. 硬件设计与电路连接
2.1 元器件选型与准备
除了核心的STM32最小系统和ADXL345传感器模块外,我们还需要准备:
- 4.7kΩ上拉电阻(用于I2C总线)
- 杜邦线若干
- 稳压电源或USB供电
- 示波器(可选,用于调试)
注意:ADXL345支持1.8V-3.6V宽电压工作,但STM32的I/O电平通常为3.3V,建议整个系统采用3.3V供电以确保信号兼容性。
2.2 电路连接详解
ADXL345的I2C接口引脚定义如下:
| 传感器引脚 | STM32引脚 | 说明 |
|---|---|---|
| VCC | 3.3V | 电源正极 |
| GND | GND | 电源地 |
| SDA | PB7 | I2C数据线 |
| SCL | PB6 | I2C时钟线 |
| CS | 3.3V | 置高选择I2C模式 |
| INT1/INT2 | 悬空或接中断引脚 | 中断输出(可选) |
连接时的关键细节:
- 必须为SDA和SCL线各接一个4.7kΩ上拉电阻到3.3V
- 如果使用中断功能,INT引脚可接到STM32的外部中断引脚
- 避免将传感器靠近电机等强干扰源
- 对于长距离传输(>20cm),建议使用屏蔽线
3. STM32软件环境配置
3.1 开发工具链搭建
推荐使用STM32CubeIDE作为开发环境,它集成了:
- STM32CubeMX图形化配置工具
- 基于Eclipse的IDE
- ARM GCC编译工具链
- 调试支持
配置步骤:
- 安装STM32CubeIDE(最新版本)
- 新建工程,选择对应的STM32型号
- 启用I2C外设(通常为I2C1)
- 配置时钟树,确保I2C时钟不超过400kHz(标准模式)
3.2 I2C外设初始化代码
使用HAL库初始化I2C的典型配置:
c复制I2C_HandleTypeDef hi2c1;
void MX_I2C1_Init(void)
{
hi2c1.Instance = I2C1;
hi2c1.Init.ClockSpeed = 100000; // 100kHz
hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
hi2c1.Init.OwnAddress1 = 0;
hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
hi2c1.Init.OwnAddress2 = 0;
hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
if (HAL_I2C_Init(&hi2c1) != HAL_OK)
{
Error_Handler();
}
}
调试技巧:如果I2C通信失败,先用逻辑分析仪检查SDA/SCL波形,确认起始条件、地址位和ACK信号是否正常。
4. ADXL345驱动开发
4.1 传感器寄存器配置
ADXL345的关键寄存器:
| 寄存器地址 | 名称 | 功能 |
|---|---|---|
| 0x31 | DATA_FORMAT | 设置数据格式、范围和分辨率 |
| 0x2C | BW_RATE | 设置输出数据速率 |
| 0x2D | POWER_CTL | 电源控制模式 |
| 0x1E-0x23 | OFSX/OFSY/OFSZ | 偏移校准 |
| 0x32-0x37 | DATAX0-DATAZ1 | 加速度数据 |
初始化序列示例:
c复制#define ADXL345_ADDR 0x53 << 1 // 7位地址左移1位
void ADXL345_Init(void)
{
uint8_t data[2];
// 退出睡眠模式
data[0] = 0x2D; // POWER_CTL寄存器
data[1] = 0x08; // 测量模式
HAL_I2C_Master_Transmit(&hi2c1, ADXL345_ADDR, data, 2, 100);
// 设置数据格式:±4g,全分辨率
data[0] = 0x31;
data[1] = 0x09;
HAL_I2C_Master_Transmit(&hi2c1, ADXL345_ADDR, data, 2, 100);
// 设置输出速率为100Hz
data[0] = 0x2C;
data[1] = 0x0A;
HAL_I2C_Master_Transmit(&hi2c1, ADXL345_ADDR, data, 2, 100);
}
4.2 加速度数据读取实现
六轴数据读取函数:
c复制typedef struct {
int16_t x;
int16_t y;
int16_t z;
} AccelData;
void ADXL345_ReadAccel(AccelData *data)
{
uint8_t buffer[6];
uint8_t reg = 0x32; // DATAX0寄存器地址
// 先写入要读取的起始寄存器地址
HAL_I2C_Master_Transmit(&hi2c1, ADXL345_ADDR, ®, 1, 100);
// 连续读取6个字节的数据
HAL_I2C_Master_Receive(&hi2c1, ADXL345_ADDR, buffer, 6, 100);
// 组合高低字节
data->x = (int16_t)(buffer[1] << 8 | buffer[0]);
data->y = (int16_t)(buffer[3] << 8 | buffer[2]);
data->z = (int16_t)(buffer[5] << 8 | buffer[4]);
}
数据转换公式:
实际加速度(g) = 原始值 × 量程 / 分辨率
例如在±4g量程下:
加速度X(g) = x_raw × 8 / 32768
5. 高级功能实现与优化
5.1 中断配置与运动检测
ADXL345支持多种中断模式,配置步骤:
- 设置INT_ENABLE寄存器启用特定中断
- 配置INT_MAP寄存器选择INT1或INT2引脚
- 设置THRESH_ACT等阈值寄存器
- 连接INT引脚到STM32的外部中断输入
示例:敲击检测配置
c复制void ADXL345_EnableTapDetection(void)
{
uint8_t data[2];
// 设置敲击阈值 (0x1D)
data[0] = 0x1D;
data[1] = 0x30; // 约3g
HAL_I2C_Master_Transmit(&hi2c1, ADXL345_ADDR, data, 2, 100);
// 设置敲击持续时间 (0x21)
data[0] = 0x21;
data[1] = 0x20; // 20ms
HAL_I2C_Master_Transmit(&hi2c1, ADXL345_ADDR, data, 2, 100);
// 启用敲击检测中断
data[0] = 0x2E; // INT_ENABLE
data[1] = 0x60; // 启用单敲和双敲检测
HAL_I2C_Master_Transmit(&hi2c1, ADXL345_ADDR, data, 2, 100);
}
5.2 传感器校准技术
校准流程:
- 将传感器静止放置在水平面上
- 连续采样100次取平均值
- 计算各轴偏移量并写入OFSX/Y/Z寄存器
- 验证校准结果
校准代码示例:
c复制void ADXL345_Calibrate(void)
{
AccelData avg = {0};
const uint8_t samples = 100;
// 采集样本
for(int i=0; i<samples; i++) {
AccelData temp;
ADXL345_ReadAccel(&temp);
avg.x += temp.x;
avg.y += temp.y;
avg.z += temp.z;
HAL_Delay(10);
}
// 计算平均值
avg.x /= samples;
avg.y /= samples;
avg.z /= samples;
// 计算偏移量(LSB/g取决于当前量程)
// 假设±4g量程(8mg/LSB),期望Z轴为1g
uint8_t ofs[3];
ofs[0] = (uint8_t)(-avg.x / 8);
ofs[1] = (uint8_t)(-avg.y / 8);
ofs[2] = (uint8_t)((1*1000/8 - avg.z) / 8);
// 写入偏移寄存器
uint8_t data[2];
data[0] = 0x1E; // OFSX
data[1] = ofs[0];
HAL_I2C_Master_Transmit(&hi2c1, ADXL345_ADDR, data, 2, 100);
data[0] = 0x1F; // OFSY
data[1] = ofs[1];
HAL_I2C_Master_Transmit(&hi2c1, ADXL345_ADDR, data, 2, 100);
data[0] = 0x20; // OFSZ
data[1] = ofs[2];
HAL_I2C_Master_Transmit(&hi2c1, ADXL345_ADDR, data, 2, 100);
}
6. 常见问题与解决方案
6.1 I2C通信失败排查
现象:HAL_I2C_Master_Transmit返回HAL_ERROR或HAL_TIMEOUT
可能原因及解决方案:
-
硬件连接问题
- 检查SDA/SCL线是否接反
- 确认上拉电阻已正确连接
- 测量电源电压是否稳定
-
地址配置错误
- ADXL345的I2C地址通常是0x53(7位)
- 注意HAL库要求左移1位,即0xA6
-
时序问题
- 降低I2C时钟频率(如从400kHz降到100kHz)
- 增加HAL_Delay()在连续操作之间
-
总线冲突
- 确保没有其他设备占用I2C总线
- 检查总线是否被意外拉低
6.2 数据异常处理
异常现象1:Z轴数据明显偏离1g(静止时)
- 可能原因:未校准或校准不准确
- 解决方案:重新执行校准流程
异常现象2:数据跳动幅度大
- 可能原因:电源噪声或机械振动
- 解决方案:
- 增加软件滤波(如移动平均)
- 检查电源去耦电容
- 使用硬件低通滤波
异常现象3:某个轴数据始终为0
- 可能原因:传感器损坏或接触不良
- 解决方案:
- 重新焊接传感器引脚
- 更换传感器模块测试
6.3 低功耗优化技巧
-
使用自动睡眠模式
- 配置POWER_CTL寄存器的LINK和AUTO_SLEEP位
- 设置合适的采样率(BW_RATE)
-
中断唤醒机制
- 配置活动检测中断
- STM32进入STOP模式,由ADXL345中断唤醒
-
动态调整采样率
- 静止时使用低采样率(如6.25Hz)
- 检测到活动后切换到高采样率(如100Hz)
示例代码:
c复制void Enter_LowPower_Mode(void)
{
uint8_t data[2];
// 设置低采样率
data[0] = 0x2C; // BW_RATE
data[1] = 0x06; // 6.25Hz
HAL_I2C_Master_Transmit(&hi2c1, ADXL345_ADDR, data, 2, 100);
// 启用活动检测中断
data[0] = 0x2E; // INT_ENABLE
data[1] = 0x10; // 活动检测
HAL_I2C_Master_Transmit(&hi2c1, ADXL345_ADDR, data, 2, 100);
// STM32进入STOP模式
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
// 唤醒后恢复设置
SystemClock_Config();
MX_I2C1_Init();
data[0] = 0x2C;
data[1] = 0x0A; // 100Hz
HAL_I2C_Master_Transmit(&hi2c1, ADXL345_ADDR, data, 2, 100);
}
7. 实际应用案例扩展
7.1 姿态角计算
通过三轴加速度数据可以估算设备的俯仰角(pitch)和横滚角(roll):
c复制void CalculateAngles(AccelData *accel, float *pitch, float *roll)
{
// 转换为重力加速度单位
float ax = accel->x * 0.0039; // ±4g量程时的转换系数
float ay = accel->y * 0.0039;
float az = accel->z * 0.0039;
// 计算角度(弧度)
*pitch = atan2(ay, sqrt(ax*ax + az*az));
*roll = atan2(-ax, sqrt(ay*ay + az*az));
// 转为角度制
*pitch *= 57.2958;
*roll *= 57.2958;
}
注意:这种方法在静态或准静态条件下效果较好,动态情况下需要结合陀螺仪数据进行传感器融合。
7.2 计步器算法实现
简易计步算法流程:
- 采集三轴加速度数据
- 计算合加速度:acc_total = sqrt(x²+y²+z²)
- 应用低通滤波去除高频噪声
- 检测波峰波谷,符合条件时计步
c复制#define FILTER_ALPHA 0.2f
#define STEP_THRESHOLD 1.2f
#define STEP_DELAY_MS 300
typedef struct {
float filtered_acc;
uint32_t last_step_time;
uint16_t step_count;
} StepDetector;
void DetectStep(AccelData *accel, StepDetector *detector)
{
static uint8_t was_above = 0;
float acc_total = sqrt(accel->x*accel->x +
accel->y*accel->y +
accel->z*accel->z) * 0.0039;
// 低通滤波
detector->filtered_acc = FILTER_ALPHA * acc_total +
(1-FILTER_ALPHA) * detector->filtered_acc;
// 检测波峰
if(detector->filtered_acc > STEP_THRESHOLD && !was_above) {
was_above = 1;
uint32_t now = HAL_GetTick();
if(now - detector->last_step_time > STEP_DELAY_MS) {
detector->step_count++;
detector->last_step_time = now;
}
}
else if(detector->filtered_acc < STEP_THRESHOLD && was_above) {
was_above = 0;
}
}
7.3 数据可视化实现
通过串口将加速度数据发送到上位机,使用Python实现可视化:
STM32端代码:
c复制void SendAccelData(AccelData *data)
{
char buffer[64];
sprintf(buffer, "%.2f,%.2f,%.2f\n",
data->x*0.0039, data->y*0.0039, data->z*0.0039);
HAL_UART_Transmit(&huart1, (uint8_t*)buffer, strlen(buffer), 100);
}
Python端代码:
python复制import serial
import matplotlib.pyplot as plt
from collections import deque
ser = serial.Serial('COM3', 115200, timeout=1)
plt.ion()
fig = plt.figure()
ax = fig.add_subplot(111)
max_points = 100
x = deque(maxlen=max_points)
y = deque(maxlen=max_points)
z = deque(maxlen=max_points)
while True:
line = ser.readline().decode().strip()
if line:
ax, ay, az = map(float, line.split(','))
x.append(ax)
y.append(ay)
z.append(az)
plt.cla()
plt.plot(x, label='X')
plt.plot(y, label='Y')
plt.plot(z, label='Z')
plt.legend()
plt.pause(0.01)
8. 性能优化与进阶技巧
8.1 DMA加速数据传输
使用DMA可以显著提高I2C通信效率,特别是在高速采样时:
- 在CubeMX中启用I2C的DMA功能
- 为发送和接收各分配一个DMA通道
- 修改读取函数使用DMA
示例代码:
c复制void ADXL345_ReadAccel_DMA(AccelData *data)
{
uint8_t reg = 0x32;
// 启动DMA传输
HAL_I2C_Master_Transmit_DMA(&hi2c1, ADXL345_ADDR, ®, 1);
while(HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY);
HAL_I2C_Master_Receive_DMA(&hi2c1, ADXL345_ADDR, (uint8_t*)data, 6);
while(HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY);
// 调整字节顺序
data->x = (int16_t)(((uint16_t)data->x << 8) | ((uint16_t)data->x >> 8));
data->y = (int16_t)(((uint16_t)data->y << 8) | ((uint16_t)data->y >> 8));
data->z = (int16_t)(((uint16_t)data->z << 8) | ((uint16_t)data->z >> 8));
}
8.2 多传感器融合
结合其他传感器(如陀螺仪、磁力计)可以提高运动检测精度:
- 硬件连接:增加MPU6050等IMU传感器
- 软件实现:采用互补滤波或卡尔曼滤波算法
- 数据同步:使用硬件中断触发多传感器同步采样
简单互补滤波实现:
c复制void ComplementaryFilter(AccelData *accel, GyroData *gyro,
float *pitch, float *roll, float dt)
{
static float angle_p = 0, angle_r = 0;
// 加速度计角度
float acc_p = atan2(accel->y, accel->z) * 57.2958;
float acc_r = atan2(-accel->x, sqrt(accel->y*accel->y + accel->z*accel->z)) * 57.2958;
// 互补滤波
angle_p = 0.98 * (angle_p + gyro->x * dt) + 0.02 * acc_p;
angle_r = 0.98 * (angle_r + gyro->y * dt) + 0.02 * acc_r;
*pitch = angle_p;
*roll = angle_r;
}
8.3 固件升级与配置存储
实现传感器参数的可配置性:
- 使用STM32的Flash存储配置参数
- 通过串口命令动态修改配置
- 实现配置保存与加载功能
示例配置结构:
c复制typedef struct {
uint8_t range; // 量程
uint8_t rate; // 采样率
uint8_t int_enable; // 中断使能
int16_t offsets[3]; // 校准偏移
} SensorConfig;
void SaveConfigToFlash(SensorConfig *cfg)
{
HAL_FLASH_Unlock();
__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_WRPERR | FLASH_FLAG_PGERR);
FLASH_Erase_Sector(FLASH_SECTOR_1, VOLTAGE_RANGE_3);
uint32_t address = FLASH_USER_START_ADDR;
uint64_t data;
uint8_t *ptr = (uint8_t*)cfg;
for(int i=0; i<sizeof(SensorConfig); i+=8) {
memcpy(&data, ptr+i, 8);
HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, address+i, data);
}
HAL_FLASH_Lock();
}
void LoadConfigFromFlash(SensorConfig *cfg)
{
uint32_t address = FLASH_USER_START_ADDR;
memcpy(cfg, (void*)address, sizeof(SensorConfig));
// 验证数据有效性
if(cfg->range > 3 || cfg->rate > 15) {
// 加载默认配置
cfg->range = 1; // ±4g
cfg->rate = 10; // 100Hz
cfg->int_enable = 0;
memset(cfg->offsets, 0, sizeof(cfg->offsets));
}
}
9. 项目实战:构建完整的运动监测系统
9.1 系统架构设计
完整系统包含以下模块:
- 传感器层:ADXL345数据采集
- 处理层:STM32实时数据处理
- 通信层:UART/WiFi/蓝牙数据传输
- 应用层:上位机显示与存储
数据流示意图:
code复制ADXL345 → I2C → STM32 → 滤波算法 → 特征提取 → 串口 → PC
9.2 状态机设计
实现多模式状态机:
c复制typedef enum {
MODE_SLEEP,
MODE_LOW_POWER,
MODE_STANDARD,
MODE_HIGH_RATE
} SystemMode;
void SystemStateMachine(SystemMode *mode, AccelData *accel)
{
static uint32_t last_activity = 0;
static float activity_level = 0;
// 更新活动水平(移动平均)
float acc_mag = sqrt(accel->x*accel->x + accel->y*accel->y + accel->z*accel->z);
activity_level = 0.9 * activity_level + 0.1 * acc_mag;
switch(*mode) {
case MODE_SLEEP:
if(activity_level > 1.5) {
*mode = MODE_LOW_POWER;
SetSampleRate(25); // 25Hz
}
break;
case MODE_LOW_POWER:
if(activity_level > 2.0) {
*mode = MODE_STANDARD;
SetSampleRate(100); // 100Hz
last_activity = HAL_GetTick();
}
else if(activity_level < 0.8) {
*mode = MODE_SLEEP;
}
break;
case MODE_STANDARD:
if(activity_level > 3.0) {
*mode = MODE_HIGH_RATE;
SetSampleRate(400); // 400Hz
}
else if(HAL_GetTick() - last_activity > 10000) {
*mode = MODE_LOW_POWER;
SetSampleRate(25);
}
break;
case MODE_HIGH_RATE:
if(activity_level < 2.5) {
*mode = MODE_STANDARD;
SetSampleRate(100);
last_activity = HAL_GetTick();
}
break;
}
}
9.3 功耗优化实测数据
不同模式下的电流消耗对比:
| 工作模式 | 采样率 | STM32电流 | ADXL345电流 | 总电流 |
|---|---|---|---|---|
| 深度睡眠 | - | 2μA | 0.1μA | 2.1μA |
| 低功耗监测 | 6.25Hz | 120μA | 40μA | 160μA |
| 标准工作 | 100Hz | 1.8mA | 140μA | 1.94mA |
| 高性能模式 | 400Hz | 3.5mA | 350μA | 3.85mA |
实测表明,通过智能状态切换,典型运动监测应用的平均电流可控制在300μA以下,使用200mAh的纽扣电池可连续工作近一个月。
10. 开发经验与心得分享
在实际项目开发中,有几点深刻体会:
-
传感器安装位置对数据质量影响极大。尽量避免将PCB安装在有机械共振的位置,必要时增加减震措施。我曾在一个无人机项目中因为传感器安装过于靠近电机,导致振动噪声完全淹没了有用信号。
-
I2C总线稳定性是关键。长线传输时:
- 使用双绞线
- 适当降低时钟频率
- 在总线两端增加100pF电容
- 考虑使用I2C缓冲器(如PCA9615)
-
校准流程不能省略。即使使用同一批次的传感器,各轴的灵敏度也会有±10%的差异。建议:
- 生产时每个设备都进行校准
- 保存校准参数到Flash
- 提供校准指令接口
-
数据时间戳很重要。在实现计步等时间相关算法时,务必记录每个样本的精确采集时间,避免因处理延迟导致算法失效。可以使用STM32的DWT周期计数器获取微秒级时间戳。
-
电磁兼容设计不容忽视。遇到一个工业现场案例,变频器导致传感器数据异常。最终解决方案:
- 增加电源滤波(π型滤波)
- 使用屏蔽电缆
- 软件上增加异常值检测
-
关于中断使用的建议:
- 配置NVIC优先级,避免中断丢失
- 中断服务函数尽量简短
- 使用标志位在主循环中处理复杂逻辑
- 注意中断引脚的消抖处理
