这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » DIY与开源设计 » 电子DIY » 【let'sdo|2026年第3期】03读懂LIS3DH加速度计——从寄存器到三

共1条 1/1 1 跳转至页

【let'sdo|2026年第3期】03读懂LIS3DH加速度计——从寄存器到三轴数据上屏

助工
2026-10-27 10:13:11     打赏

上一篇把开发链路打通,最后的 I2C 扫描里看到了那个熟悉的 0x19——LIS3DH 在总线上应答了,说明接线、上拉、时钟三样都对。但"扫到地址"和"读懂它、读出正确的数据"之间,还隔着一整本数据手册。这一篇就来填这段距离:先把 LIS3DH 的技术参数读明白,再搞清楚寄存器读写的语法要点,然后按一套固定流程把参数配好,最后把三轴加速度实时读出来、显示到串口上。和上一篇一样,我不只记"怎么配",更把每一步"为什么这么读、为什么这么配"写清楚,因为加速度计这种器件,配错一个位,数据看着正常,换算出来全是错的。

我的整体流程拆成六步:读技术参数 → 理解性能参数与读写语法 → 固化操作流程 → 配置模块参数 → 实时读取并显示状态 → 附代码与在线验证。

3d.gif

演示动图

一、先把 LIS3DH 的技术参数读明白

动手写代码前,我习惯先把数据手册的首页特性表和引脚表过一遍,回答三个问题:它是什么、怎么跟它说话、怎么确认连的就是它。

•  它是什么:LIS3DH 是 ST 的一颗 3 轴电容式 MEMS 加速度计,输出 X/Y/Z 三个方向的加速度,本项目里用它采电机/结构的振动数据。工作电压约 1.71~3.6V,正好吃 3.3V,Grove 模块板上已经做好稳压和上拉,直接插就行。

•  怎么跟它说话:数字接口支持 I2C(最高 400kHz) 和 SPI。本次走上一篇配好的 I2C。这里有个新手最容易翻车的点——从机地址不是固定的,它由 SA0(有的板子标 SDO) 引脚电平决定:SA0 拉低是 0x18,拉高是 0x19。上一篇我扫到的是 0x19,说明这块 Grove 模块把 SA0 接了高。代码里写地址一定要跟硬件一致,写错地址的结果就是 HAL_I2C_IsDeviceReady() 永远超时。

•  怎么确认连的就是它:寄存器 0x0F(WHO_AM_I) 固定返回 0x33。这是所有 ST 传感器都有的"身份证寄存器",作用是在读任何有用数据之前,先确认你连的到底是不是这颗芯片、通信是不是真的通。0x33 读错成 0x00 或 0xFF,别急着怀疑传感器坏,八成是线、地址或时序。

把这三个数字——电压 3.3V、地址 0x19、WHO_AM_I=0x33——记牢,后面所有操作都从这里出发。

image.png

图 1  LIS3DH 全部地址功能一览(寄存器地址地图:STATUS/OUT 等关键寄存器)


二、性能参数与寄存器读写语法要点

读参数不是背参数,而是挑出"会影响我怎么写代码"的那几项。LIS3DH 真正需要在乎的性能项有三个:

•  量程(Full Scale):可配 ±2g / ±4g / ±8g / ±16g 四档。量程越大,能测的加速度越大,但分辨率越粗。本项目测的是小电机的轻微振动,先按 ±2g 起步,灵敏度最高。

•  输出数据率(ODR):从 power-down 一直到上千 Hz 可选。ODR=0000 是掉电(power-down)模式——这是个巨大的坑,后面第四节会专门讲。先用 100Hz,对应"每秒 100 个采样点",对振动检测足够。

•  输出格式与分辨率:高分辨率(HR)模式下 16 位寄存器里放 12 位有效数据、左对齐,输出是 16 位二进制补码;±2g 下约 1mg/digit(更精确是 0.91mg/digit)。记住这个换算关系,否则读到一屏大数字完全不知道是几 g。

真正决定"代码怎么写"的是读写语法,这几点务必吃透:

•  读写位(RW):访问寄存器时,地址字节的 最高位(bit7)决定读还是写(写 0、读 1),中间 6 位是寄存器地址。用 HAL_I2C_Mem_Read/Write 时 HAL 会帮你处理,但你要知道底层在发什么。

•  地址自增位(INC / MULTI-BYTE,bit6):连续读多个寄存器(比如一次读 X/Y/Z 共 6 字节)时,必须把这个位置 1,让寄存器地址每读一个字节自动 +1。忘了置它,六个字节全读的是同一个寄存器——数据看起来"稳定",实则全错。

•  BDU(Block Data Update,块数据更新):这是最重要的一位。不开 BDU,传感器在更新输出时,你可能读到"高位是新值、低位是旧值"的撕裂数据。所以读多字节加速度前,一定在 CTRL_REG4 里把 BDU 打开。

•  补码:输出是 16 位补码,有符号。直接当无符号数处理,负方向的加速度会变成几万的大正数。读取后必须按 int16_t 解释再做换算。image.png

图 2  性能参数与读写语法要点(供电 1.71~3.6V、量程 ±2/±4/±8/±16g、12 位高分辨率、ODR 1Hz~5kHz、mg↔raw 补码换算)

三、推荐一套固定的操作流程

把上面零散的点串成一条不会出错的固定链路,我给自己定了这个顺序,每次新工程照着走:

1.  上电后等一小段稳定时间(十几毫秒即可),让器件内部参考稳定;

2.  读 WHO_AM_I 校验 0x33:读不到 0x33 就地停下排查线/地址,绝不往下走;

3.  先配 CTRL_REG4(量程 FS、开 BDU、开 HR):把"数据怎么表示"先定死;

4.  再配 CTRL_REG1(使能 X/Y/Z 三轴 + 设 ODR):把"让传感器开始输出"放在最后一步,避免半配置状态下读到脏数据;

5.  需要批量/连续采样时再配 FIFO(CTRL_REG5 开 FIFO、FIFO_CTRL_REG 选 Stream 模式 + 设阈值);本篇先用最简单的轮询把链路验通;

6.  运行期先轮询 STATUS_REG(0x27)的 ZYXDA 位,确认数据就绪,再连读 0x28 起的 6 个字节。

这套顺序的核心思想就一句话:先确认、再定义数据格式、最后才启动输出。看着死板,但它能把绝大多数"数据时对时错"的问题挡在配置阶段。

image.png

图 3  推荐操作流程:上电稳定 → 校验 WHO_AM_I → 定数据格式 → 启动输出 → FIFO → 轮询 STATUS

四、配置 LIS3DH 模块参数(逐位讲清楚)

落到具体寄存器值,本次只动两个控制寄存器就够了。

CTRL_REG1(0x20)= 0x57,二进制 0101 0111,逐位含义:

位域

值

含义

ODR[3:0]

0100

输出数据率 100Hz(0000 是 power-down,务必别填 0)

LPen

0

正常模式(非低功耗),保证高分辨率

ZEN/YEN/XEN

111

X/Y/Z 三轴全部使能

 

CTRL_REG4(0x23)= 0x88,二进制 1000 1000,逐位含义:

位域

值

含义

HR

1

开高分辨率模式,12 位有效数据左对齐

FS[1:0]

00

量程 ±2g,灵敏度最高,适合轻微振动

BLE/BDU/SIM

0 1 0

BDU=1(块数据更新,防撕裂,这条不能省),字节序与 SPI 模式保持默认

 

为什么是这两个值而不是别的:ODR 和量程是"性能"与"功耗/噪声"的折中,先用 100Hz + ±2g 是最稳的调试起点;等确认数据正常、要抓更高频振动时再往上加 ODR 或换量程。而 BDU 必须和"我要一次连读 6 字节"这个动作绑定——只要连读多字节,BDU 就该开。

写入用 HAL 很简单,注意写 CTRL_REG1 时把地址自增位置 1(0x20 | 0x40):

uint8_t reg;

/* 1)
校验身份证:读 WHO_AM_I(0x0F),期望 0x33 */
HAL_I2C_Mem_Read(&hi2c1, 0x19<<1, 0x0F, 1, &reg, 1, 100);
if (reg != 0x33) { /*
通信/接线有问题,停在这里排查 */ }

/* 2)
先定义数据格式:CTRL_REG4 = 0x88 (HR + ±2g + BDU) */
reg = 0x88;
HAL_I2C_Mem_Write(&hi2c1, 0x19<<1, 0x23, 1, &reg, 1, 100);

/* 3)
最后启动输出:CTRL_REG1 = 0x57 (100Hz, normal, XYZ) */
reg = 0x57;
HAL_I2C_Mem_Write(&hi2c1, 0x19<<1, 0x20, 1, &reg, 1, 100);

image.png

图 4  上位机工作模式配置界面(CTRL_REG1/3/4/5、量程/ODR、FIFO Stream、轴使能与下发回读)

五、实时读取三轴数据并显示模块状态

配置完,芯片已经开始按 100Hz 出数了。运行期我做两件事:先查状态、再连读数据,并把它们打印到串口当"模块状态面板"。

读取要点:轮询 STATUS_REG(0x27) 的 ZYXDA(bit3) 判断新一组数据是否就绪,用 OR(bit4) 判断是否发生了溢出(Overrun)(读得太慢、来不及取就会溢出,是判断"我的读取节奏够不够快"的信号)。数据就绪后,从 OUT_X_L(0x28) 连读 6 字节,地址字节 0x28|0x80|0x40(读 + 自增),一次把 X/Y/Z 各 16 位取回,再按补码换算成带符号的 mg。

uint8_t  buf[6];
int16_t  raw[3];
float    mg[3];
uint8_t  status;

/*
轮询 STATUS_REG,等数据就绪 */
HAL_I2C_Mem_Read(&hi2c1, 0x19<<1, 0x27, 1, &status, 1, 100);
if (status & 0x08) {                       /* ZYXDA=1
:三轴数据已就绪 */
    /*
连读 6 字节:寄存器地址 0x28,|0x80 读,|0x40 地址自增 */
    HAL_I2C_Mem_Read(&hi2c1, 0x19<<1, 0x28 | 0x80 | 0x40, 1, buf, 6, 100);
    for (int i = 0; i < 3; i++) {
        raw[i] = (int16_t)((buf[2*i+1] << 8) | buf[2*i]); /*
低字节在前,16位补码 */
        mg[i]  = raw[i] * 0.91f / 16.0f;   /* ±2g/HR
约 0.91mg/digit,先对齐再换算 */
    }
    printf("AX=%7.2fmg AY=%7.2fmg AZ=%7.2fmg | FS=±2g ODR=100Hz OVR=%d\r\n",
           mg[0], mg[1], mg[2], (status & 0x10) ?
1 : 0);
}

串口每 10ms 左右刷一行,既是数据、也是状态:三轴 mg 值是"测量结果",行尾的 FS=±2g、ODR=100Hz 是"当前量程与采样率",OVR 是溢出标志。这三样合起来就是我说的"模块状态"——不用另开调试器,光看串口就知道传感器跑得对不对、我取数够不够快。

image.png

图 5  采样与状态面板:读取节奏 100ms(约 10Hz),实时显示 X/Y/Z、量程、ODR 与 OVR 溢出标志

image.png

image.png


图 6  实时读取界面:配置区 + 三轴 3D 姿态可视化 + 实时数据面板(静止时 Z 轴 ≈ +1g)

六、附上代码与在线验证

完整的初始化 + 读取 + 打印代码已放到在线页面,可以直接查看寄存器读写时序和实际读取效果,方便对照自己板子调试:

•  在线演示 / 测试地址:https://web.unito.top/i2c/

开发板程序:letisdo_3d.zip

页面里把 I2C 的读写时序、WHO_AM_I 校验、CTRL_REG 配置和数据连读的过程都做成了可交互/可复现的形式,遇到"我这边读出来不对"的时候,可以先在这里比对时序,再回板子上找差异,比闷头改代码快很多。

七、测试视频(三种典型状态)

最后录了一段演示,把三种状态放一起看最直观,也正好呼应整个项目的最终目标——用加速度数据做异常检测:

八、踩到的几个坑

•  忘了开 BDU 读到"撕裂值":静止时 Z 轴偶尔蹦出几十上百 g 的毛刺,百思不得其解,后来才想起没在 CTRL_REG4 开 BDU,高低字节被更新过程拆开了。开 BDU 后毛刺立刻消失——只要连读多字节,BDU 就该常开。

•  没处理 16 位补码,负方向变成巨大正数:一开始把两字节拼完直接当无符号数用,翻转 180° 时 Z 轴从 +1g 变成了六万多。改成 int16_t 解释再做换算就对了——补码,有符号,别当无符号。

•  只配了量程、没给 ODR,数据恒为 0:CTRL_REG4 配得很顺,却忘了 CTRL_REG1 的 ODR 还停在 0000(power-down),传感器根本没启动,读出来全是 0。教训:ODR 是开关,配完格式最后一定要给它一个非零 ODR。

•  连读忘了置地址自增位:六个字节读回来一模一样,查了半天才发现地址字节没置自增位,六个字节全在读 OUT_X_L。连读 = 读位 + 自增位,一个都不能少。

•  SA0 悬空导致地址在 0x18/0x19 之间飘:换插了一次 Grove 线,扫描到的地址变来变去。确认 SA0 电平后固定用 0x19,地址才稳定下来。地址是硬件决定的,先量 SA0 再写代码。

九、小结与下一步

至此,第三篇的目标达成:读明白了 LIS3DH 的关键技术参数(3.3V / 地址 0x19 / WHO_AM_I=0x33)、吃透了读写语法(读位、地址自增、BDU、补码)、按"先校验、再定格式、最后启动"的固定流程把参数配成 100Hz / ±2g,并把三轴数据实时读出来、连同量程/ODR/溢出一起显示成了模块状态。从上一篇"扫到 0x19",到这一篇"读懂它、读对数据",传感器这条链路算是真正立住了。

下一步计划:

•  用 ULN2003 + 28BYJ-48 制造 停止 / 正常转 / 堵转 等可复现状态,同步、分文件地采集加速度数据;

•  按 NanoEdge AI Studio 要求的目录结构与数据格式整理这些振动样本,正式进入"训练一个异常检测模型"的环节——那才是这个项目真正有意思的部分。

感谢 DigiKey 与 EEPW 提供的支持,敬请期待下一篇。








关键词: LIS3DH     加速度计     三轴     3D显示    

共1条 1/1 1 跳转至页

回复

匿名不能发帖!请先 [ 登陆 注册 ]