ADXL345 是亚德诺半导体(ADI)出品的一款 3 轴数字加速度计(三轴陀螺仪/重力传感器)
它测量的是加速度 (单位通常是 g,即重力加速度,1g ≈ 9.8m/s²)。它可以感知物体的倾斜角度 、运动状态(静止、走路、震动、跌落)。
测量原理
硅片内部有一个弹簧,另一端悬挂着一块可移动的质量块,在质量块的上方和下方(或两侧),分别固定着两块金属板。质量块和这些金属板构成了电容。
-
当芯片静止时,质量块处于中心位置,两边的电容值相等。
-
当芯片倾斜(受重力) 或加速/减速时,由于惯性,质量块会发生位移。
-
质量块靠近一边的金属板,这一边的电容增大;远离另一边的金属板,那一侧的电容减小。这就形成了差动电容(ΔC)。
倾斜板子,弹簧与质量块移动,产生微小的电容变化
质量块产生的电容变化微乎其微(皮法级别),必须经过内部的精密电路处理。
-
电容-电压转换 :内部有一个高精度的电荷放大器 ,它把差动电容的变化量(ΔC)精确地转换为一个模拟电压信号。这个电压信号的大小,直接对应了加速度的大小。
-
信号滤波:内部集成了低通滤波器,用于去除高频噪声(比如外部轻微的机械抖动),保证输出的模拟电压平滑且真实。
-
模数转换(ADC) :滤波后的模拟电压被送入内部的 13 位(或 16 位输出)逐次逼近型 ADC 。它把模拟电压转化为 有符号的二进制补码。此时,真实的"物理加速度"就变成了电脑能处理的"数字信号"。
微小的电压被内部的**"放大器和 ADC"**转成了 16 位的有符号整数。
ADC 转换出来的原始数据,并没有直接送到总线上,而是先经过了内部的"指挥中心"。
-
数据寄存器 :转换后的数据被存入 6 个 8 位寄存器(
0x32 ~ 0x37,分别对应 X/Y/Z 轴的高低字节)。 -
FIFO 缓冲区(数据缓存) :ADXL345 自带一个 32 级 FIFO(先进先出)。这是它最聪明的地方。
- 假设您的 MCU 很忙,没有及时来读取数据。ADXL345 会把最新测得的数据存入 FIFO 里,最多可以存 32 个。等您的 MCU 闲下来了,可以一次性把 32 个数据读走,保证数据不丢失。这在"高速运动"监控中尤为重要。
-
中断控制逻辑:内部可以配置阈值(比如"当加速度大于 ±4g 时触发")。一旦发生剧烈震动或自由落体,内部逻辑会立即让外部引脚(INT1 / INT2)产生电平翻转,主动"唤醒"主控去读数据。
数据在内部的**"寄存器"** 或**"FIFO"**里放好
通过 SPI 或 I2C 把 16 位整数拿回去,除以 256,得到真实的加速度 g 值。
具体参数
| 参数项 | 具体规格 |
|---|---|
| 通信接口 | I2C (最大 400kHz) 或 SPI (最大 5MHz) |
| 测量范围 | 可编程选择:±2g, ±4g, ±8g, ±16g |
| 分辨率 | 13 位(即原始数据范围是 -2048 ~ +2047) |
| 供电电压 | 2.0V ~ 3.6V (可直接接 STM32 的 3.3V,切记不能接 5V) |
| 超低功耗 | 仅需 23 µA(非常适合电池供电设备) |
| 内置功能 | 自带 32 级 FIFO 缓存(可以一次性存 32 个采样数据,减轻 MCU 的负担) |
引脚说明
|----------|--------------------|--------------------------|--------------------------------------|--------------------------------------------------------|
| VCC | 电源正极 | 供电 | 供电 | 接 3.3V (严禁接 5V) |
| GND | 接地 | 共地 | 共地 | 接 GND |
| CS | 片选信号 (Chip Select) | 由主控控制。必须拉低 (0V) 才能通信 | 必须接高电平 (3.3V)(或悬空) | SPI:接 GPIO (如 PA4) I2C:接 3.3V |
| SCL | 串行时钟 | SPI 时钟 (SCLK) | I2C 时钟 (SCL) | SPI:接 SPI1_SCK (如 PA5) I2C:接 I2C1_SCL (如 PB6) |
| SDA | 串行数据 | SPI 主机输出 (MOSI) | I2C 双向数据线 | SPI:接 SPI1_MOSI (如 PA7) I2C:接 I2C1_SDA (如 PB7) |
| SDO | 串行数据输出 / 地址选择 | SPI 从机输出 (MISO) | I2C 地址选择 : 接地(0)=0x53,接高(1)=0x1D | SPI:接 SPI1_MISO (如 PA6) I2C:接地 或 接 3.3V |
| INT1 | 中断输出 1 | 可选中断 | 可选中断 | 默认悬空(需要时接 GPIO) |
| INT2 | 中断输出 2 | 可选中断 | 可选中断 | 默认悬空(需要时接 GPIO) |
| 对比维度 | SPI 模式(您决定用的) | I2C 模式 |
|---|---|---|
| 通信线数量 | 4 根线 (CS, SCLK, MOSI, MISO) | 2 根线 (SCL, SDA) |
| 通信速度 | 快 (最高 5MHz) | 慢 (通常 100kHz~400kHz) |
| CS 引脚状态 | 必须接主控 GPIO,由代码手动拉低/拉高 | 接 3.3V(高电平) |
| SDO 引脚状态 | 必须接 SPI1_MISO | 接地(地址 0x53)或接高(地址 0x1D) |
| 代码复杂程度 | 需要配置 CS 引脚和 SPI 寄存器 | 只需要配置 I2C,发送地址和数据 |
| 适合场景 | 需要高频率、大量、连续读取数据(如姿态解算) | 接线简单,低速监测(如温和的倾角监测) |
关于姿态检测的场景可能需要高采样低延迟的条件,建议使用spi
时序图


寄存器相关



小端存储 ,且输出的是补码,因为加速度既有大小又有方向,物理世界存在负数。补码是计算机内部表示有符号数的最标准方式,它让 CPU 做加减法时无需额外逻辑。
连接方式



设备树

时钟相位写了就是1,不写默认为0,这里要选模式3,全写1
|------------|--------|--------|-------------------------|
| 1. 定精度 | 0x31 | 0x08 | 设为 ±2g 量程,全分辨率(灵敏度 256) |
| 2. 定速度 | 0x2C | 0x0A | 设为 100Hz 输出速率 |
| 3. 唤醒 | 0x2D | 0x08 | 开启测量,传感器开始工作 |
驱动
#include <linux/fs.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/spi/spi.h>
#include "asm/uaccess.h"
#include "linux/export.h"
#define DEV_NAME "ptadxl345"
ssize_t myread(struct file *, char __user *, size_t, loff_t *);
int myopen(struct inode *, struct file *);
int myrelease(struct inode *, struct file *);
int myprobe(struct spi_device *spi);
int myremove(struct spi_device *spi);
struct spi_device *pspi;
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = myopen,
.release = myrelease,
.read = myread,
};
struct of_device_id of_table[] = {{
.compatible = "pute,puteadxl345",
},
{}};
struct spi_device_id id_table[] = {{
.name = "puteadxl345",
},
{}};
static struct miscdevice misc_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEV_NAME,
.fops = &fops,
};
ssize_t myread(struct file *f, char __user *userdata, size_t size, loff_t *off)
{
int ret = 0;
// 1000 0000 read
// 0100 0000 multi byte
char reg_add = 0x32 | 0x80 | 0x40;
char rxbuf[6] = {0};
spi_write_then_read(pspi, ®_add, 1, rxbuf, 6);
ret = copy_to_user(userdata, rxbuf, sizeof(rxbuf));
if (ret)
{
pr_err("myread error,%d\n", ret);
return ret;
}
return sizeof(rxbuf);
}
int myopen(struct inode *node, struct file *f)
{
// adxl345 init
char txbuf[2] = {0x31, 0x08};
char rxbuf[2] = {0};
// 全分辨率 ±2g
spi_write_then_read(pspi, txbuf, 2, rxbuf, 0);
// 输出速率 100 Hz
txbuf[0] = 0x2c;
txbuf[1] = 0x0a;
spi_write_then_read(pspi, txbuf, 2, rxbuf, 0);
// 开始测量
txbuf[0] = 0x2d;
txbuf[1] = 0x08;
spi_write_then_read(pspi, txbuf, 2, rxbuf, 0);
pr_info("adxl 345 init ok\n");
return 0;
}
int myrelease(struct inode *node, struct file *f)
{
pr_info("close called\n");
return 0;
}
int myprobe(struct spi_device *spi)
{
int ret = 0;
pspi = spi;
ret = misc_register(&misc_dev);
if (ret < 0)
{
pr_err("myprobe misc_register\n");
return ret;
}
pr_info("adxl 345 misc_register probe ok\n");
return 0;
}
int myremove(struct spi_device *spi)
{
misc_deregister(&misc_dev);
return 0;
}
struct spi_driver spi_drv = {
.probe = myprobe,
.remove = myremove,
.driver =
{
.name = "puteadxl345",
.owner = THIS_MODULE,
.of_match_table = of_table,
},
.id_table = id_table,
};
module_spi_driver(spi_drv);
MODULE_LICENSE("GPL");
应用层处理
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char **argv)
{
int fd = open("/dev/ptadxl345", O_RDWR);
if (fd < 0)
{
perror("open");
return 1;
}
char buf[10] = {0};
short adxl_x = 0;
short adxl_y = 0;
short adxl_z = 0;
while (1)
{
read(fd, buf, 6);
adxl_x = buf[1] << 8 | buf[0];
adxl_y = buf[3] << 8 | buf[2];
adxl_z = buf[5] << 8 | buf[4];
// printf("x:%d y:%d z:%d\n", adxl_x, adxl_y, adxl_z);
float gx, gy, gz;
gx = adxl_x / 256.0f;
gy = adxl_y / 256.0f;
gz = adxl_z / 256.0f;
printf("x: %.2f g, y: %.2f g, z: %.2f g\n", gx, gy, gz);
sleep(1);
}
close(fd);
// system("pause");
return 0;
}
结果:

遇到的问题:
1.最后数据处理时,直接除以256,导致小数部分遗弃
因为C 语言规则 :两个整数相除,结果还是整数(向下取整,直接丢掉小数部分)。
解决办法很简单,写成256.0f或者
gx = (float)adxl_x / 256;
当整数与浮点数做除法时,整数会自动被提升(隐式转换)为浮点数,然后进行浮点数除法。
2.收集到的数据adxl_x必须是short类型
3.把初始化放到了read里,拖累了总线效率