一、ADXL345编程概述
概述
ADXL345是三轴加速度计,分辨率高(13位),测量范围达±16g。数字输出数据为16位二进制补码格式,可通过SPI(3线或4线)或I2C访问。
物理安装与方向
1. 通信接口
在本文中,主机通过SPI与ADXL345进行通信。通过在DATA_FORMAT寄存器(地址0x31)中清除D6位(SPI位),选择4线模式,并且配置时钟极性(CPOL)=1和时钟相位(CPHA)=1。
2. 初始化
ADXL345的初始化步骤如下:
1) 上电
2) 等待1.1ms
3) 初始化命令序列
4)初始化结束
这里的初始化命令序列,至少需要配置3个寄存器,如表1。详细的配置参考ADXL345数据手册。
步骤 | 寄存器地址 | 寄存器名字 | 寄存器值 | 功能描述 |
---|---|---|---|---|
1 | 0x31 | DATA_FORMAT | 0x0B | ±16g,13位模式 |
2 | 0x2D | POWER_CTL | 0x08 | 测量模式 |
3 | 0x2E | INT_ENABLE | 0x80 | 使能DATA_READY中断 |
3. 读取输出数据
DATA_READY中断信号表示数据寄存器中的三轴加速度数据已更新。当新数据就绪时它被置为高电平(通过DATA_FORMAT寄存器,低电平转高电平触发中断服务)。可从DATA0、DATAX1、DATAY0、DATAY1、DATAZ0、DATAZ1寄存器中读取数据。推荐多字节读取数据。下图为4线式SPI读写序列实例。
最高位 | 次高位 | A5 | A4 | A3 | A2 | A1 | A0 | |
功能描述 | 读/写 | 单字节/多字节 | 寄存器地址 | |||||
值 | 0-写 1-读 | 0-单字节 1-多字节 | / | / | / | / | / | / |
4. 数据格式
ADXL345是16位数据格式。从数据寄存器中获取加速度数据后要进行处理。DATAX0是X轴加速度低字节寄存器,DATAX1是高字节寄存器。13位模式下,高4位是符号位。参照ADXL345数据手册通过DATA_FORMAT寄存器设置更多数据格式。
ADXL345使用二进制补码数据格式。在13位模式下,1LSB代表3.9mg。
4. 使用偏移寄存器
ADXL345有偏移寄存器可进行偏移校准。偏移寄存器的数据格式是8位有符号数、二进制补码。偏移寄存器分辨率为15.6mg/LSB。将偏移值的相反数写入偏移寄存器来校准。如偏移为+156mg,就向偏移寄存器写入-156mg。

二、运行环境
1. 环境设置:①开发环境:安装stm32CubeMx和IDE(keil);
② 硬件连接:
PA5 ------> SPI1_SCK
PA6 ------> SPI1_MISO
PA7 ------> SPI1_MOSI
2. 创建项目,并启用必要的外设(如SPI1、USART1等),并生成初始化代码。
三、STM32CubeMX配置
1. 创建 STM32CubeMX 项目
-
打开 STM32CubeMX,创建一个新项目。
-
在芯片选择器中搜索并选择 STM32L051C8Tx(或其他具体型号)。
-
点击 "Start Project" 创建项目。
2. 配置时钟树
-
在 "Clock Configuration" 选项卡中,配置系统时钟源和频率。
-
如果使用外部晶振(HSE),确保启用 HSE 并配置 PLL 以生成所需的系统时钟频率(例如 32 MHz)。
-
如果使用内部晶振(HSI),直接使用 HSI 作为系统时钟源。
3. 配置 SPI 接口
-
ADXL345 通过 SPI 接口与 STM32 通信。
-
在 "Pinout & Configuration" 选项卡中,找到 SPI 外设(例如 SPI1)。
-
配置 SPI Mode为 Full-Duplex Master 模式。
-
配置 SPI 的引脚:①SCK:时钟信号;②MISO:主设备输入,从设备输出;③MOSI:主设备输出,从设备输入;④NSS:片选信号(可选,如果使用软件控制片选,可以忽略)。
-
配置 SPI 的参数(Parameter Settings):
-
Baud Rate:设置 SPI 通信速率(例如62.5KBit/s)。
-
Data Size:8 Bits。
-
Clock Polarity (CPOL):High。
-
Clock Phase (CPHA):2 Edge。
-
NSS Signal Type:如果使用软件控制片选,选择 "Software NSS Management"
4. 配置 GPIO 引脚
-
ADXL345 的片选信号(CS)通常通过 GPIO 控制。
-
在 "Pinout & Configuration" 选项卡中,选择一个 GPIO 引脚(例如 PB0)作为片选信号。
配置该引脚为 Output Push-Pull 模式,初始状态为高电平(CS 高电平表示 SPI 从设备未选中)。
5. 配置RCC
6. 配置 USART(可选)
-
如果需要通过串口调试输出数据,可以配置 USART。
-
在 "Pinout & Configuration" 选项卡中,找到 USART 外设(例如 USART1)。
-
配置 USART 为 Asynchronous 模式。
-
配置 USART 的引脚:
-
TX:发送数据。
-
RX:接收数据。
-
-
配置 USART 的参数:
-
Baud Rate:例如 115200。
-
Word Length:8 bits。
-
Parity:None。
-
Stop Bits:1。
-
7. 生成代码
-
在 "Project Manager" 选项卡中,配置项目名称、工具链(例如 MDK-ARM、STM32CubeIDE 等)和代码生成选项。
-
点击 "Generate Code" 生成代码。
四、编写代码(主要部分)
1. ADXL345.C
头文件和宏定义
#include "stm32l0xx_hal.h"
#include "adxl345.h"
#include "spi.h"
#include "usart.h"
// 其他必要的头文件
#define SET_SPI_CS_L() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET)
#define SET_SPI_CS_H() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET)
#define ADXL345_SCALE_FACTOR 0.0039f // ±16g 范围的灵敏度为 3.9 mg/LSB
#define M_PI 3.14159265358979323846
ADXL345寄存器读写操作
寄存器地址格式
- 最高位(MSB, 第7位):为1,读操作。为0,写操作
- 次高位(第6位):为1,启用多字节模式,连续读取多个寄存器的数据。
//PB0作为CS引脚,低电平有效
#define SET_SPI_CS_L() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET)
#define SET_SPI_CS_H() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET)
//读取ADXL345寄存器地址,从指定寄存器读取nReadbyte个字节
static void ADXL345_ReadRegister(uint8_t regAddress, uint8_t *buf, uint8_t nReadbyte)
{
if ( 1 == nReadbyte)
regAddress = (regAddress & 0x3F) | 0x80; //读单个字节
else
regAddress = (regAddress & 0x3F) | 0xC0; //读多个字节, 多字节读取时寄存器的自动递增
uint8_t txBuf[1+ nReadbyte]; memset(txBuf+1, 0, nReadbyte); //填充后续字节为0以生成时钟
uint8_t rxBuf[1+ nReadbyte]; memset(rxBuf, 0, sizeof(rxBuf)); //接收初始化为0
txBuf[0] = regAddress;
SET_SPI_CS_L();
HAL_SPI_TransmitReceive(&hspi1, txBuf, rxBuf, 1+nReadbyte, 100)
SET_SPI_CS_H();
memcpy(buf, rxBuf+1, nReadbyte); //有效数据从rxBuf[1]开始
}
//写寄存器
static void ADXL345_WriteRegister(uint8_t regAddress, uint8_t command)
{
regAddress &= 0x3F;
uint8_t data[2] = {regAddress, command};
SET_SPI_CS_L();
HAL_SPI_Transmit(&hspi1, data, 2, 10)
SET_SPI_CS_H();
}
ADXL345初始化
static uint8_t ADXL345_GetID(void)
{
uint8_t id = 0x00;
ADXL345_ReadRegister(DEVICE_ID, &id, 1);
return id;
}
void ADXL345_Init(void)
{
uint8_t deviceID = ADXL345_GetID(); //读取设备ID,确认与正确的ADXL345传感器通信
while (deviceID != 0xE5)
{
HAL_Delay(1000);
deviceID = ADXL345_GetID();
}
//进入待机模式,便于进行配置
ADXL345_WriteRegister(POWER_CTL, 0x00);
//设置数据格式
ADXL345_WriteRegister(DATA_FORMAT, 0x0B); //全分辨率,±16g范围和小端字节序并带符号扩展
//设置输出数据速率(带宽率)
ADXL345_WriteRegister(BW_RATE,0x0A); //100HZ
//进入测量模式
ADXL345_WriteRegister(POWER_CTL,0x08);
//初始化偏移寄存器
ADXL345_Calibrate();
//打印初始化成功信息
}
1. SPI通信速率(波特率或时钟频率)和输出数据速率(ODR)要匹配,避免数据丢失或产生噪声。如SPI通信速率2MHz或更高,ODR设置为3200Hz或1600Hz。如果SPI通信速率400kHz,ODR设置为800Hz。
数据读取与处理
根据adxl345手册page.26,建议所有寄存器执行多字节读取,以防止相继寄存器读取之间的数据变化。
//读原始x,y,z轴数据(LSB)
void ADXL345_ReadXYZ(short *x, short *y, short *z)
{
uint8_t data[6]= {0};
//读取X、Y、Z轴原始数据
ADXL345_ReadRegister(DATAX0 , data, 6);
//数据处理,将两个8位字节合并成一个16位的有符号整数,高位在前低位在后
*x = (int16_t)((int8_t)data[1]<<8)|data[0];
*y = (int16_t)((int8_t)data[3]<<8)|data[2];
*z = (int16_t)((int8_t)data[5]<<8)|data[4];
}
//ADXL345取平均值,x,y,z读取times次后取平均值,并转成实际的加速度值
void ADXL345_ReadAverage(float *x, float *y, float *z, uint8_t times)
{
int32_t x_num = 0, y_num = 0, z_num = 0;
for (uint8_t i = 0; i < times; i++)
{
short tx=0, ty=0, tz=0;
ADXL345_ReadXYZ(&tx, &ty, &tz);
x_num += (int32_t)tx; // 原始LSB累加
y_num += (int32_t)ty;
z_num += (int32_t)tz;
HAL_Delay(5);
}
float x_avg = (float)x_num / times; //平均值计算时先转成float,避免整数除法的精度丢失
float y_avg = (float)y_num / times;
float z_avg = (float)z_num / times;
//乘灵敏度系数以转换成实际的加速度值(单位:g)
*x = x_avg * ADXL345_SCALE_FACTOR;
*y = y_avg * ADXL345_SCALE_FACTOR;
*z = z_avg * ADXL345_SCALE_FACTOR;
}
//角度计算:计算相对于自然坐标系各轴的角度
void ADXL345_GetAngle(float *x_angle, float *y_angle)
{
float x = 0, y = 0, z = 0;
ADXL345_ReadAverage(&x, &y, &z, 10);
float x_denom = sqrt(y*y + z*z);
float y_denom = sqrt(x*x + z*z);
//添加除以零保护
if (fabs(x_denom) < 1e-6f) x_denom = 1e-6f;
if (fabs(y_denom) < 1e-6f) y_denom = 1e-6f;
*x_angle = atan(x/x_denom)*180/M_PI; //#define M_PI 3.14159265358979323846
*y_angle = atan(y/y_denom)*180/M_PI;
}
1. 加速度计算公式
将原始数据(Raw Data)转换为实际加速度(g)的公式为:
Acceleration (g)=Raw Data×LSB Sensitivity其中:
Raw Data 是从传感器读取的 16 位有符号整数(范围:-32768 到 +32767)。
LSB Sensitivity 是根据量程确定的灵敏度,如下表。
量程和对应的LSB灵敏度 量程(Range) LSB 灵敏度(Sensitivity) 所有g范围,全分辨率(13位) 3.9 mg/LSB (0.0039 g/LSB) ±2g,10位分辨率 3.9 mg/LSB (0.0039 g/LSB) ±4g,10位分辨率 7.8 mg/LSB (0.0078 g/LSB) ±8g,10位分辨率 15.6 mg/LSB (0.0156 g/LSB) ±16g,10位分辨率 31.2 mg/LSB (0.0312 g/LSB) 示例:
假设:
量程设置为 ±2g,LSB 灵敏度为 0.0039 g/LSB。
读取的原始数据为 1000。
则加速度为:
Acceleration (g)=1000×0.0039=3.9 g
2. 加速度值转换为角度
对于每个轴,计算其相对于地面的倾斜角度,可用公式:
- X轴的倾斜角(Roll):
- Y轴的倾斜角(Pitch):
- Z轴通常用于表示偏航角Yaw,但在纯静态情况下无法直接由加速度计获得,因为没有外力作用于Z轴方向(除了重力,但它不影响绕Z轴的旋转)。偏航角一般需要结合陀螺仪或者磁力计的数据进行计算。
- 其中,Ax,Ay,Az 分别是X、Y、Z轴上的加速度读数(已转换为g单位)
数据显示
//使用串口打印加速度值或角度信息
void ADXL345_PrintAccelerometerValues(void)
{
char buffer1[50] = {0};
char buffer2[50] = {0};
float x_value = 0, y_value = 0, z_value = 0;
float x_angle=0, y_angle=0;
ADXL345_ReadAverage(&x_value, &y_value, &z_value, 10);
ADXL345_GetAngle(&x_angle, &y_angle);
snprintf(buffer1, sizeof(buffer1), "x_value:%.2f, y_value:%.2f, z_value:%.2f\r\n", x_value, y_value, z_value);
snprintf(buffer2, sizeof(buffer2), "x_angle:%.2f, y_angle:%.2f\r\n", x_angle, y_angle);
HAL_UART_Transmit(&huart1, (uint8_t*)buffer1, strlen(buffer1), 10);
HAL_UART_Transmit(&huart1, (uint8_t*)buffer2, strlen(buffer2), 10);
HAL_Delay(2000);
}
误差校正(静态校准)
静止状态下,多次采样求平均值
//x,y,z静态校准
void ADXL345_Calibrate(void)
{
long x_temp = 0, y_temp = 0, z_temp = 0;
const uint8_t num_samples = 100; //采样次数
//读取多次数据算平均值
for(uint8_t i = 0; i < num_samples; i++)
{
short x, y, z;
ADXL345_ReadXYZ(&x, &y, &z);
x_temp += x;
y_temp += y;
z_temp += z;
HAL_Delay(10); //确保每次读取之间有足够时间间隔,避免数据读取过快导致不准确
}
//计算平均值
int16_t x_avg = x_temp/num_samples;
int16_t y_avg = y_temp/num_samples;
int16_t z_avg = z_temp/num_samples;
//计算偏移值(单位:LSB)
int8_t offsetx = (int8_t)roundf((-x_avg) / 4.0); //roundf返回四舍五入后的整数值
int8_t offsety = (int8_t)roundf((-y_avg) / 4.0);
// //Z轴期望值通常为256LSB(1g)
int8_t offsetz = (int8_t)roundf((256-z_avg) / 4.0);
//写入偏移寄存器,ADXL345 会自动补偿零偏误差
ADXL345_WriteRegister(OFSX, (uint8_t)offsetx);
ADXL345_WriteRegister(OFSY, (uint8_t)offsety);
ADXL345_WriteRegister(OFSZ, (uint8_t)offsetz);
}
1. 偏移量寄存器
用于存储每个轴的偏移校准值
X 轴偏移寄存器OFSX:地址0x1E
Y 轴偏移寄存器OFSY:地址0x1F
Z 轴偏移寄存器OFSZ:地址0x20
每个寄存器的值是一个 8 位有符号整数,范围为 -128 到 +127。
2. 偏移量的计算
步骤 1:静止状态下读取原始数据
将 ADXL345 放置在静止状态(理论上加速度为 0g),读取 X、Y、Z 轴的原始数据。
步骤 2:计算偏移量
偏移量的计算公式为:
![]()
其中:
Expected
为期望值(通常X、Y为0g(0 LSB),Z为1g(256 LSB))
Measured
为实测值(LSB)。步骤 3:写入偏移量寄存器
将计算得到的偏移量(单位LSB)写入对应的偏移量寄存器(0x1E、0x1F、0x20)。
2. ADXL345.h
#ifndef _ADXL345_H
#define _ADXL345_H
#include "spi.h"
#include "stdint.h"
#include "stm32l0xx_hal.h"
/********ADXL345寄存器命令定义********/
/******** #define 名称 寄存器地址 ********/
#define DEVICE_ID 0x00
#define OFSX 0x1E //x轴偏移
#define OFSY 0x1F //y轴偏移
#define OFSZ 0x20 //Z轴偏移
#define BW_RATE 0x2C //数据速率及功率模式控制
#define POWER_CTL 0x2D
#define DATA_FORMAT 0x31 //数据格式控制
#define DATAX0 0x32 //X轴数据0
#define DATAX1 0x33 //X轴数据1
#define DATAY0 0x34 //Y轴数据0
#define DATAY1 0x35 //Y轴数据1
#define DATAZ0 0x36 //Z轴数据0
#define DATAZ1 0x37 //Z轴数据1
void ADXL345_Init(void);
extern void ADXL345_Calibrate(void);
void ADXL345_ReadXYZ(short *x, short *y, short *z);
void ADXL345_ReadAverage(float *x, float *y, float *z, uint8_t times);
void ADXL345_GetAngle(float *x_angle, float *y_angle);
extern void ADXL345_PrintAccelerometerValues(void);
#endif
3. main.c
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_SPI1_Init();
MX_USART1_UART_Init();
ADXL345_Init();
while (1)
{
ADXL345_PrintAccelerometerValues();
}
}