资讯动态

STM32驱动LIS2DH12加速度计:从IIC时序到FIFO中断的实战指南

发布时间:2026/10/19 8:41:54 来源:尧图企业网站定制
简介这份资源面向使用STM32驱动三轴加速度计的嵌入式开发者提供LIS2DH12在IIC通信下的完整驱动程序可解决传感器初始化、寄存器配置与数据读取等常见问题适用于移动设备、物联网终端与健康监测等运动感知场景。压缩包共8个文件以4个c源文件和4个h头文件为主分别承担IIC底层读写、LIS2DH12寄存器操作、外部中断处理与接口声明等职责整体约6KB结构精简便于直接移植。目前已有4560人学习下载说明其在同类驱动资料中具备一定参考价值。代码覆盖IIC接口初始化、工作模式与采样率配置、加速度数据解码转换、跌落与6D运动等事件检测以及中断服务处理并涉及通信异常恢复与DMA传输思路读者可据此快速搭建STM32与LIS2DH12的交互框架同时对照LIS3DH12、BMA250等相似器件的驱动差异掌握加速度传感器驱动开发的通用方法。1. 从一块“不吭声”的加速度计说起LIS2DH12 在 STM32 上到底怎么跑起来板子焊好上电串口打印全零示波器一戳 SCL 纹丝不动——这是很多人第一次把 LIS2DH12 挂到 STM32 上时遇到的场景。LIS2DH12 是 ST 家一颗三轴 MEMS 加速度计12 位分辨率、支持 ±2g 到 ±16g 量程、内置 FIFO 和自由落体/单击双击中断走 IIC 或 SPI 都能通。它便宜、好买、资料多是姿态检测、振动采集、计步器、跌落保护这类项目的常客。但“能通”和“稳定能通”之间隔着一堆寄存器配置的坑WHO_AM_I 读不到、量程设了不生效、FIFO 水线永远不触发、中断引脚死活不拉低。这篇笔记拆的就是一份 LIS2DH12 的 STM32 驱动源码包从 IIC 时序到寄存器映射从初始化流程到中断配置把能抄的代码和会翻车的地方一次讲清。适合手里有 STM32、想快速把加速度计跑起来、又不想在数据手册里泡三天的人。2. 驱动包结构拆解IIC 抽象层、寄存器映射与初始化时序拿到一份驱动源码先别急着往工程里塞。花十分钟把目录和头文件翻一遍能省掉后面一半的编译错误和玄学问题。这份 LIS2DH12 驱动包的组织方式比较典型核心就三块IIC 通信抽象、寄存器定义、设备初始化与数据读取。下面按我实际拆包的顺序讲。2.1 文件清单与职责划分驱动包一般长这样不同作者命名略有差异但职责划分大同小异文件职责你需要改的地方lis2dh12.h寄存器地址宏、数据结构体、函数声明基本不动lis2dh12.c初始化、读写寄存器、量程/速率配置、数据解析按需改量程和 ODRlis2dh12_iic.hIIC 读写函数声明平台相关对接你的 HAL 或软件 IIClis2dh12_iic.cIIC 底层实现起始、停止、应答、读写字节必须改这是移植关键main.c示例初始化调用、循环读数据参考用关键点在于lis2dh12.c里所有对传感器的读写都不直接碰硬件而是调用lis2dh12_iic.c里暴露的那几个函数。这层抽象做得好不好决定了你换 MCU 时是改一个文件还是改十个文件。我见过不少驱动把 IIC 时序和寄存器操作揉在一起移植时痛不欲生。2.2 IIC 抽象层软件模拟还是硬件外设LIS2DH12 支持标准模式 100kHz 和快速模式 400kHz 的 IIC。STM32 上你有两条路用硬件 IIC 外设或者 GPIO 软件模拟。这份驱动包默认给的是软件模拟版本原因很实际——STM32 的硬件 IIC 在某些系列上有已知的时序问题调试起来像开盲盒而软件模拟虽然占 CPU但时序完全可控出问题好查。软件 IIC 的核心是四个基本操作起始条件、停止条件、发送一个字节、接收一个字节。下面这段是典型的实现骨架// 软件 IIC 起始条件SCL 高时 SDA 由高变低 void LIS2DH12_IIC_Start(void) { LIS2DH12_SDA_OUT(); // SDA 配置为输出 LIS2DH12_SDA_H(); // SDA 拉高 LIS2DH12_SCL_H(); // SCL 拉高 delay_us(4); // 保持时间太快传感器反应不过来 LIS2DH12_SDA_L(); // SCL 高期间 SDA 拉低 起始 delay_us(4); LIS2DH12_SCL_L(); // 钳住总线准备发数据 delay_us(4); } // 发送一个字节返回从机应答位0应答1非应答 uint8_t LIS2DH12_IIC_SendByte(uint8_t txd) { uint8_t ack; LIS2DH12_SDA_OUT(); LIS2DH12_SCL_L(); for (uint8_t i 0; i 8; i) { // 高位先发 if (txd 0x80) LIS2DH12_SDA_H(); else LIS2DH12_SDA_L(); txd 1; delay_us(2); LIS2DH12_SCL_H(); // 拉高时钟从机采样 delay_us(2); LIS2DH12_SCL_L(); delay_us(2); } LIS2DH12_SDA_IN(); // SDA 切输入准备读应答 delay_us(2); LIS2DH12_SCL_H(); delay_us(2); ack LIS2DH12_SDA_READ(); // 读 SDA 电平 LIS2DH12_SCL_L(); delay_us(2); return ack; }逻辑说明起始条件必须在 SCL 为高时拉低 SDA这是 IIC 协议规定的“开始”信号发送字节时数据在 SCL 低电平期间变化、高电平期间稳定从机在高电平采样。delay_us(2)到delay_us(4)这个延时不是随便写的——太快了传感器跟不上太慢了影响读取速率400kHz 快速模式下每个位周期 2.5us所以延时控制在 1~2us 比较稳。参数说明LIS2DH12_SDA_OUT()和LIS2DH12_SDA_IN()是宏分别把 SDA 引脚切成推挽输出和浮空输入。如果你的 MCU 不支持开漏输出记得外接上拉电阻典型值 4.7kΩ。上拉电阻太大比如 10kΩ在 400kHz 下波形上升沿会变缓导致通信失败这是很多人忽略的硬件坑。2.3 寄存器映射WHO_AM_I 是第一道门LIS2DH12 的寄存器地址是 7 位读写操作时地址左移一位最低位表示读/写。驱动头文件里通常这样定义#define LIS2DH12_ADDR 0x19 1 // 7 位地址 0x19左移后用于 IIC 传输 #define LIS2DH12_WHO_AM_I 0x0F // 设备 ID 寄存器固定返回 0x33 #define LIS2DH12_CTRL_REG1 0x20 // 控制寄存器 1ODR 和轴使能 #define LIS2DH12_CTRL_REG2 0x21 // 控制寄存器 2高通滤波 #define LIS2DH12_CTRL_REG3 0x22 // 控制寄存器 3中断引脚配置 #define LIS2DH12_CTRL_REG4 0x23 // 控制寄存器 4量程和分辨率 #define LIS2DH12_CTRL_REG5 0x24 // 控制寄存器 5FIFO 使能 #define LIS2DH12_OUT_X_L 0x28 // X 轴低字节 #define LIS2DH12_OUT_X_H 0x29 // X 轴高字节WHO_AM_I寄存器是上电后第一个要读的。它固定返回0x33读不到或者读到别的值说明 IIC 通信根本没建立后面所有配置都是白搭。我一般会在初始化函数最前面加一个循环重试读不到就报错返回别让程序带着错误往下跑。2.4 初始化时序上电、配置、使能三步走LIS2DH12 上电后不会自动开始测量必须按顺序配置。典型初始化流程如下uint8_t LIS2DH12_Init(void) { uint8_t id; LIS2DH12_IIC_Init(); // 初始化 IIC 引脚 // 第一步验证通信 id LIS2DH12_ReadReg(LIS2DH12_WHO_AM_I); if (id ! 0x33) { return 1; // 通信失败返回错误 } // 第二步配置 CTRL_REG1 // ODR100Hz (0101)LPen0 正常模式Zen/Yen/Xen 全使能 LIS2DH12_WriteReg(LIS2DH12_CTRL_REG1, 0x57); // 第三步配置 CTRL_REG4 // BDU1 连续读取不更新FS01 即 ±4gHR1 高分辨率 LIS2DH12_WriteReg(LIS2DH12_CTRL_REG4, 0x38); // 第四步配置 CTRL_REG3如果用中断 // I1_IA11把中断 1 映射到 INT1 引脚 LIS2DH12_WriteReg(LIS2DH12_CTRL_REG3, 0x40); return 0; }逻辑说明CTRL_REG1的0x57拆开看——bit7~4 是 ODR 位0101对应 100Hzbit3 是 LPen0 表示正常模式12 位bit2~0 是 Z/Y/X 轴使能全 1。CTRL_REG4的0x38拆开——bit7 是 BDU置 1 后读低字节时高字节被锁住防止数据撕裂bit5~4 是 FS01对应 ±4gbit3 是 HR置 1 启用高分辨率模式。参数说明ODR 不是随便选的。100Hz 适合大多数姿态检测如果你做振动分析需要 400Hz 甚至 1.6kHz但功耗会上去。量程选择要看你的应用——计步器 ±2g 够了跌落检测至少 ±8g否则大冲击会削顶。BDU 位在读多字节数据时一定要开不然可能出现 X 轴是新的、Y 轴是旧的这种“时空错乱”数据。3. 数据读取与中断配置从原始值到物理量的完整链路初始化跑通只是第一步真正难的是把数据读对、读稳以及让中断按你预期触发。这一章讲数据读取的两种方式、中断配置的寄存器细节以及 FIFO 怎么用。3.1 单次读取与连续读取什么时候用哪种LIS2DH12 的输出寄存器从0x28到0x2D依次是 X/Y/Z 轴的低字节和高字节。读取方式有两种单次读取每次只读一个轴或一次读六个字节适合低速轮询场景。void LIS2DH12_ReadXYZ(int16_t *x, int16_t *y, int16_t *z) { uint8_t buf[6]; // 连续读 6 个字节从 OUT_X_L 开始地址自动递增 LIS2DH12_ReadRegs(LIS2DH12_OUT_X_L, buf, 6); // 组合高低字节注意 LIS2DH12 是左对齐还是右对齐 // 12 位模式下数据在 bit15~4需要右移 4 位 *x (int16_t)((buf[1] 8) | buf[0]) 4; *y (int16_t)((buf[3] 8) | buf[2]) 4; *z (int16_t)((buf[5] 8) | buf[4]) 4; }逻辑说明LIS2DH12 在 12 位高分辨率模式下数据是左对齐的即 16 位寄存器的高 12 位有效低 4 位是 0。所以组合成 16 位后要右移 4 位才是真实值。如果你用 8 位模式就不需要移位但精度差很多。参数说明LIS2DH12_ReadRegs是连续读函数它先发起始条件、发设备地址写、发寄存器地址然后重新发起始条件、发设备地址读再连续读 N 个字节。LIS2DH12 支持地址自动递增所以读0x28开始连续 6 个字节会依次返回 X_L、X_H、Y_L、Y_H、Z_L、Z_H。连续读取FIFO 模式适合需要批量采集、降低 MCU 轮询频率的场景。FIFO 可以存 32 组数据配置水线后达到阈值触发中断MCU 再一次性读走。3.2 中断配置把“事件”变成“引脚电平”LIS2DH12 的中断系统比较灵活但也容易配错。它有两个中断引脚 INT1 和 INT2可以映射多种事件自由落体、单击、双击、唤醒、FIFO 水线等。配置中断分三步使能事件、映射到引脚、配置引脚极性。以 FIFO 水线中断为例// 配置 FIFO 水线为 20 组数据 LIS2DH12_WriteReg(LIS2DH12_FIFO_CTRL_REG, 0x80 | 20); // Stream 模式水线 20 // 使能 FIFO 水线中断到 INT1 LIS2DH12_WriteReg(LIS2DH12_CTRL_REG3, 0x04); // I1_WTM1 // 配置 INT1 引脚为推挽、高有效 LIS2DH12_WriteReg(LIS2DH12_CTRL_REG6, 0x02); // H_LACTIVE0 高有效逻辑说明FIFO_CTRL_REG的高 3 位是 FIFO 模式100是 Stream 模式持续覆盖旧数据110是 Stream-to-FIFO 模式触发后停止覆盖。低 5 位是水线值20 表示存满 20 组数据后触发中断。CTRL_REG3的 bit2 是I1_WTM置 1 把水线中断映射到 INT1 引脚。参数说明FIFO 模式选择要看场景。Stream 模式适合实时监控旧数据被覆盖不心疼Stream-to-FIFO 适合事件前后数据都要保留的场景比如跌倒检测触发前的数据也有价值。水线值不要设太小否则中断太频繁MCU 一直在响应也不要设太大32 是上限设 30 以上留给 MCU 的读取时间窗口就很窄了。3.3 原始值转物理量量程、灵敏度与校准读出来的int16_t是原始值要转成g或m/s²需要知道灵敏度。灵敏度由量程决定量程灵敏度 (mg/digit)12 位模式下满量程值±2g1±2048±4g2±2048±8g4±2048±16g12±2048转换公式很简单// 假设量程 ±4g灵敏度 2mg/digit float sensitivity 0.002f; // 单位g/digit float ax_g raw_x * sensitivity;但这里有个容易翻车的点12 位模式下无论哪个量程原始值的范围都是 ±2048但对应的物理量不同。±2g 时 2048 对应 2g±16g 时 2048 对应 16g。所以换量程后灵敏度系数必须同步改否则算出来的加速度差好几倍。校准方面LIS2DH12 出厂有校准但焊接应力会导致零偏。简单校准方法把传感器水平静止放置读 100 次取平均得到的 X/Y 轴零偏和 Z 轴 1g 偏差在后续计算中减掉。更严谨的做法是六面校准但一般项目两点校准够了。4. 避坑与排查IIC 读不到、数据跳变、中断不触发的血泪经验这一章是我自己踩过的坑也是被问得最多的几个问题。每条按“现象 → 原因 → 解决”写你对着排查就行。4.1 坑一WHO_AM_I 读出来是 0x00 或 0xFF现象初始化时读WHO_AM_I返回0x00或0xFF偶尔能读到0x33但很快又失败。原因0x00通常是从机根本没应答SDA 被拉低0xFF是总线空闲时上拉电阻把线拉高主机读不到应答。根因一般是三类IIC 引脚初始化错误SDA/SCL 配成了普通 GPIO 而不是开漏、上拉电阻缺失或阻值太大、设备地址搞错LIS2DH12 的 7 位地址是0x19但有些模块标的是 8 位地址0x32或0x33。解决先用万用表量 SDA 和 SCL 对 VCC 的电压静止时应该是高电平上拉起作用。如果只有 0.3V 左右检查上拉电阻。然后确认代码里地址是0x19 1 0x32写和0x33读。最后用逻辑分析仪抓一次波形看起始条件后从机有没有拉低 SDA 应答。没有逻辑分析仪的话把 IIC 速率降到 10kHz 试试很多时候是时序太快。4.2 坑二数据跳变严重静止时读数乱飞现象传感器静止放在桌上X/Y 轴读数在 ±50 以内跳Z 轴在 1000 左右跳偶尔蹦到几千。原因三个可能。一是 ODR 设太高100Hz 以上时电源纹波和机械振动都会被采进来二是没开 BDU读多字节时高低字节来自不同采样周期三是量程设太小环境振动导致削顶。解决先开 BDUCTRL_REG4的 bit7 置 1这是最容易被忽略的。然后把 ODR 降到 50Hz 或 25Hz 试试。如果还跳在电源引脚并一个 0.1uF 陶瓷电容LIS2DH12 对电源噪声比较敏感。最后检查量程室内静止场景 ±2g 够了但如果传感器附近有电机或扬声器换 ±4g。4.3 坑三FIFO 水线中断永远不触发现象配置了 FIFO 水线中断INT1 引脚一直高电平示波器看不到下降沿。原因最常见的是CTRL_REG3里中断映射位没配对或者CTRL_REG6里引脚极性配反了。还有一个隐蔽原因FIFO 模式设成了 Bypass000数据根本不进 FIFO水线自然不触发。解决按顺序检查——FIFO_CTRL_REG高 3 位是不是100Stream或110Stream-to-FIFOCTRL_REG3的I1_WTM位是不是 1CTRL_REG6的H_LACTIVE位是不是符合你的中断触发方式0 高有效1 低有效。如果都对了还不触发读FIFO_SRC_REG看FSS位它反映当前 FIFO 里有多少组数据如果一直是 0说明数据没进 FIFO回去查CTRL_REG5的FIFO_EN位是不是 1。4.4 坑四换量程后数据不对差了好几倍现象从 ±2g 换到 ±4g同样的静止状态Z 轴读数从 1000 变成 500 左右。原因量程换了但灵敏度系数没改。12 位模式下原始值范围不变但物理意义变了。±2g 时 1000 对应约 0.98g±4g 时 1000 对应约 1.95g。解决把量程和灵敏度做成配对配置换量程时同步换灵敏度。我一般会定义一个结构体typedef struct { uint8_t fs; // 量程配置值 float sensitivity; // mg/digit } Lis2dh12_Range_t; const Lis2dh12_Range_t range_table[] { {0x00, 1.0f}, // ±2g {0x10, 2.0f}, // ±4g {0x20, 4.0f}, // ±8g {0x30, 12.0f}, // ±16g };这样换量程时查表就行不会漏改。4.5 坑五中断引脚一直低电平MCU 反复进中断现象配置了单击中断INT1 引脚触发一次后一直保持低电平MCU 反复进中断服务函数。原因LIS2DH12 的中断是锁存式的事件触发后中断标志置位引脚拉低直到你读INT1_SRC寄存器才会清除。如果中断服务函数里没读这个寄存器标志永远不清引脚永远低。解决在中断服务函数里加一句读INT1_SRCvoid EXTI1_IRQHandler(void) { if (EXTI_GetITStatus(EXTI_Line1) ! RESET) { uint8_t src LIS2DH12_ReadReg(LIS2DH12_INT1_SRC); // src 的 bit6 是 IA表示有中断事件 // 读这个寄存器会自动清除中断标志 (void)src; EXTI_ClearITPendingBit(EXTI_Line1); } }这个坑我调了一下午才找到数据手册里写了但很容易略过。5. 进阶技巧用 FIFO 做低功耗采集与数据验证前面把基础链路跑通了这一章讲一个实际项目里很管用的技巧用 FIFO 配合 MCU 休眠做低功耗采集。这个方案我在一个电池供电的振动记录仪上用过平均电流从 8mA 降到 200uA 左右。5.1 低功耗采集的配置思路核心逻辑是让 LIS2DH12 自己按 ODR 采样并存入 FIFOMCU 进入 STOP 模式FIFO 水线中断触发后唤醒 MCUMCU 一次性读走 20 组数据处理完继续休眠。这样 MCU 的占空比极低功耗自然下来。配置步骤// 1. 配置 ODR50Hz正常模式三轴使能 LIS2DH12_WriteReg(LIS2DH12_CTRL_REG1, 0x47); // 2. 配置量程 ±4gBDU1高分辨率 LIS2DH12_WriteReg(LIS2DH12_CTRL_REG4, 0x38); // 3. 使能 FIFOStream 模式水线 20 LIS2DH12_WriteReg(LIS2DH12_CTRL_REG5, 0x40); // FIFO_EN1 LIS2DH12_WriteReg(LIS2DH12_FIFO_CTRL_REG, 0x80 | 20); // 4. 水线中断映射到 INT1 LIS2DH12_WriteReg(LIS2DH12_CTRL_REG3, 0x04); // 5. 配置 INT1 引脚为推挽输出高有效 LIS2DH12_WriteReg(LIS2DH12_CTRL_REG6, 0x02); // 6. MCU 配置 EXTI1 下降沿/上升沿触发进入 STOP 模式逻辑说明CTRL_REG5的 bit6 是FIFO_EN必须置 1否则 FIFO 不工作。FIFO_CTRL_REG的0x80 | 20表示 Stream 模式加水线 20。CTRL_REG3的0x04把水线中断映射到 INT1。CTRL_REG6的0x02设置 INT1 高有效。参数说明ODR 和功耗直接相关。50Hz 时典型电流约 11uA100Hz 约 20uA400Hz 约 80uA。水线值影响 MCU 唤醒频率20 组在 50Hz 下约 0.4 秒唤醒一次这个频率对大多数场景够用。如果水线设 10唤醒频率翻倍功耗上去设 30留给 MCU 读取的时间窗口只有 2 组数据的间隔容易溢出。5.2 数据验证怎么确认读到的数据是对的低功耗方案跑起来后怎么验证数据没丢、没串我一般用两个方法。方法一静态验证。传感器静止水平放置Z 轴应该稳定在 1g 附近X/Y 轴接近 0。读 100 组数据算均值和标准差。均值偏差超过 50mg 说明零偏需要校准标准差超过 10mg 说明噪声偏大检查电源和 ODR。方法二动态验证。把传感器快速翻转 90 度Z 轴应该从 1g 变到 0gX 或 Y 轴从 0g 变到 1g。用 FIFO 连续采集时翻转动作应该在数据序列里表现为一个平滑的过渡而不是跳变。如果出现跳变检查 BDU 是不是开了以及读取速度是不是跟不上 ODR。下面这段是批量读取 FIFO 并验证的代码骨架#define FIFO_DEPTH 20 void LIS2DH12_ReadFifoBatch(int16_t (*data)[3], uint8_t *count) { uint8_t fifo_src LIS2DH12_ReadReg(LIS2DH12_FIFO_SRC_REG); uint8_t levels fifo_src 0x1F; // 低 5 位是当前 FIFO 数据组数 if (levels FIFO_DEPTH) levels FIFO_DEPTH; *count levels; for (uint8_t i 0; i levels; i) { uint8_t buf[6]; LIS2DH12_ReadRegs(LIS2DH12_OUT_X_L, buf, 6); data[i][0] (int16_t)((buf[1] 8) | buf[0]) 4; data[i][1] (int16_t)((buf[3] 8) | buf[2]) 4; data[i][2] (int16_t)((buf[5] 8) | buf[4]) 4; } }逻辑说明FIFO_SRC_REG的低 5 位是FSS表示当前 FIFO 里存了多少组数据。读的时候要循环读每次读 6 个字节。注意读之前先确认FSS值如果超过水线说明 MCU 处理慢了需要优化。参数说明FIFO_DEPTH是数组容量要大于等于水线值。如果FSS返回 31 或 32说明 FIFO 快满了MCU 读取间隔太长要么降水线要么提高 MCU 处理速度。5.3 一个容易忽略的细节FIFO 模式切换的时机Stream 模式和 Stream-to-FIFO 模式可以在运行时切换但切换时 FIFO 里的数据会被清空。如果你在采集过程中动态切模式记得先把已有数据读走。我一般会在切换前读一次FIFO_SRC_REG确认FSS为 0 再切或者接受数据丢失。从那以后我每次配 FIFO 都强制走一遍先读FIFO_SRC_REG确认状态再写FIFO_CTRL_REG最后读回来验证写入值。这个习惯帮我省了好几次“配置写了但没生效”的排查时间。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑