简介这份资源面向使用STM32平台开发嵌入式传感应用的工程师与学习者提供一套基于HAL库的LIS3DH三轴加速度计驱动源码解决从零编写寄存器配置与数据读取代码耗时易错的问题。包内共3个文件以源码与说明类文件为主压缩包约3KB体量轻便便于直接嵌入现有工程。核心驱动封装了初始化、寄存器配置、XYZ轴加速度读取、中断设置及低功耗模式控制等常用功能配套主函数示例演示如何在STM32工程中快速接入传感器并获取实时加速度数据说明文档则交代了I2C/SPI接口选择、关键寄存器配置逻辑与典型应用注意事项。代码基于标准CMSIS与HAL库编写适配F1/F4/H7等主流系列无需第三方中间件可直接用于Keil、STM32CubeIDE等环境。目前已有36人学习适合需要快速验证姿态检测、振动监测等场景的开发者参考复用。1. 从一次调不通的倾角项目说起LIS3DH 在 STM32 上到底该怎么落地去年帮一个做便携设备的团队看板子他们用 STM32F103 挂 LIS3DH 做倾角检测串口打出来的三轴数据要么全零要么跳得没法看。翻代码发现初始化里 WHO_AM_I 都没读直接往 CTRL_REG1 写了个值就开始读 OUT_X_L。这类问题在 LIS3DH 驱动移植里太常见了——芯片本身不复杂但寄存器配置、量程换算、FIFO 和水印中断这几块没踩过一遍很难一次跑通。这份 STM32 平台 LIS3DH 三轴加速度计驱动源码包包含底层驱动、头文件和可编译例程目标就是让你跳过“从零翻数据手册”的阶段直接拿到一套能读原始值、能算加速度、能配中断的工程骨架。适合正在做姿态检测、振动采集、低功耗唤醒的嵌入式开发者尤其是用 I2C 或 SPI 挂 LIS3DH 但卡在数据不对、中断不触发的人。2. 拆开源码包LIS3DH 驱动分层与 STM32 硬件接口选型2.1 驱动文件结构与职责划分拿到一个驱动包先别急着编译。我一般会先看目录里有哪些文件、各自管什么。这套源码包的结构比较典型核心文件大致是这几类文件职责你需要关注的点lis3dh.h寄存器地址宏、数据结构体、函数声明量程和 ODR 的枚举定义是否完整lis3dh.c初始化、读写寄存器、数据解析底层读写函数是否留了接口钩子lis3dh_reg.h纯寄存器地址和位定义和官方数据手册是否一致main.c/ 例程平台初始化和主循环调用I2C/SPI 句柄怎么传进去bsp_i2c.c或bsp_spi.cSTM32 硬件抽象层时序延时、片选控制这种分层的意义在于lis3dh.c里不应该出现HAL_I2C_Mem_Read这种具体平台的调用而是通过函数指针或者弱函数把读写操作暴露出来。我见过太多驱动把 HAL 调用写死在传感器逻辑里换到另一颗 STM32 或者换成 SPI 就得大改。这套包如果做对了分层你移植时只需要改 BSP 那一层。提示打开lis3dh.c先搜HAL_关键字如果大量出现说明耦合较深移植时要多留一层封装。2.2 I2C 还是 SPILIS3DH 接口选型的实际考量LIS3DH 同时支持 I2C 和 SPI选哪个不是拍脑袋。I2C 接线少两根线能挂多个器件适合板子空间紧张、速率要求不高的场景。LIS3DH 的 I2C 最高 400kHz读一次三轴数据大概需要几十微秒到上百微秒做 100Hz 采样绰绰有余。SPI 速率可以到 10MHz如果你要做高频振动采集比如 1kHz 以上 ODRSPI 的吞吐优势就出来了。但 SPI 有个坑LIS3DH 的 SPI 是四线模式读操作时最高位要置 1写操作置 0。很多新手直接拿标准 SPI 读写函数去操作读出来全是 0xFF。源码包里如果 SPI 读写函数做了0x80或操作说明作者处理过这个细节。// SPI 读寄存器地址最高位置 1 表示读 uint8_t lis3dh_spi_read(uint8_t reg) { uint8_t tx[2] {reg | 0x80, 0x00}; // 读标志位 uint8_t rx[2] {0}; HAL_GPIO_WritePin(CS_PORT, CS_PIN, GPIO_PIN_RESET); HAL_SPI_TransmitReceive(hspi1, tx, rx, 2, 100); HAL_GPIO_WritePin(CS_PORT, CS_PIN, GPIO_PIN_SET); return rx[1]; }这段代码的关键在reg | 0x80。LIS3DH 数据手册里明确写了 SPI 读操作时地址字节的 MSB 必须为 1写操作时为 0。如果你用 I2C地址就是 7 位从机地址加读写位不需要这个操作。参数上hspi1需要你根据实际用的 SPI 外设改CS_PORT和CS_PIN对应片选引脚。超时时间 100ms 是保守值实际 SPI 时钟下几微秒就完成了。2.3 初始化流程从 WHO_AM_I 到 CTRL_REG 配置不管 I2C 还是 SPI初始化第一步永远是读 WHO_AM_I 寄存器地址0x0F期望值是0x33。这一步不做后面数据不对你都不知道是芯片没焊好还是配置错了。uint8_t who lis3dh_read_reg(LIS3DH_WHO_AM_I); if (who ! 0x33) { printf(LIS3DH not found, who0x%02X\r\n, who); return -1; } // 配置 CTRL_REG1: ODR100Hz, 使能三轴 lis3dh_write_reg(LIS3DH_CTRL_REG1, 0x57); // CTRL_REG4: 量程 ±2g, 高分辨率模式 lis3dh_write_reg(LIS3DH_CTRL_REG4, 0x08);CTRL_REG1的0x57拆开看低三位111表示 X/Y/Z 三轴使能第 4 到 7 位0101对应 100Hz 输出数据率。CTRL_REG4的0x08是 BDU 位置 1保证高低字节读取一致同时量程设为 ±2g。如果你要 ±4g 或 ±16g改CTRL_REG4的 bit4-5同时换算系数也要跟着变。常见做法是把量程和灵敏度做成宏定义换量程时只改一处。3. 数据读取与物理量换算从原始 LSB 到 mg 的完整链路3.1 三轴数据寄存器读取与符号处理LIS3DH 的输出寄存器是OUT_X_L0x28到OUT_Z_H0x2D每个轴 16 位低字节在前。读的时候如果开了 BDU可以连续读 6 个字节没开 BDU 就得先读高字节再读低字节防止数据在读取过程中更新导致错位。int16_t lis3dh_read_axis(uint8_t reg_l) { uint8_t buf[2]; lis3dh_read_buf(reg_l, buf, 2); // 连续读低字节和高字节 return (int16_t)((buf[1] 8) | buf[0]); }这里返回的是int16_t因为加速度有正负。LIS3DH 输出是二进制补码直接拼起来转有符号数就行。注意buf[1] 8要先转成 16 位再或否则在某些编译器上会出问题。读完之后原始值范围取决于量程±2g 时是 -32768 到 32767 对应 -2g 到 2g。3.2 灵敏度系数与 mg 换算换算公式很简单加速度(mg) 原始值 / 灵敏度。LIS3DH 在不同量程和模式下的灵敏度不一样这是最容易翻车的地方。常见配置对照量程模式灵敏度 (LSB/mg)换算除数±2g正常11±2g高分辨率44±4g正常22±8g正常44±16g正常1212如果你开了高分辨率模式CTRL_REG4的 bit3 置 1±2g 下灵敏度是 4 LSB/mg也就是说原始值 4000 对应 1000mg 即 1g。我见过有人用正常模式的系数去算高分辨率的数据结果重力加速度算出来 4g还以为芯片坏了。float lis3dh_to_mg(int16_t raw, uint8_t range, uint8_t hires) { float sensitivity; if (hires) { sensitivity 4.0f; // 高分辨率 ±2g 固定 4 LSB/mg } else { switch (range) { case LIS3DH_RANGE_2G: sensitivity 1.0f; break; case LIS3DH_RANGE_4G: sensitivity 2.0f; break; case LIS3DH_RANGE_8G: sensitivity 4.0f; break; case LIS3DH_RANGE_16G: sensitivity 12.0f; break; default: sensitivity 1.0f; } } return raw / sensitivity; }参数range和hires必须和初始化时写入CTRL_REG4的值保持一致。建议在驱动里用一个全局结构体保存当前配置换算时直接读避免手动传参传错。3.3 中断与 FIFO 的配置要点LIS3DH 有两个中断引脚 INT1 和 INT2可以配置成数据就绪、自由落体、运动检测等。做低功耗唤醒时通常把 INT1 配成数据就绪或运动阈值中断MCU 平时休眠中断来了再读数据。// 使能 INT1 数据就绪中断 lis3dh_write_reg(LIS3DH_CTRL_REG3, 0x10); // I1_DRDY1 // 配置 INT1 为推挽输出高电平有效 lis3dh_write_reg(LIS3DH_CTRL_REG6, 0x02);CTRL_REG3的0x10是 bit4 置 1对应 I1_DRDY1 数据就绪信号。CTRL_REG6的0x02设置 INT1 为高电平有效。FIFO 模式在FIFO_CTRL_REG里配如果要做批量采集设成 Stream 模式水位中断触发后一次性读 32 组数据能有效降低 MCU 唤醒次数。注意 FIFO 模式下 ODR 不能设太低否则数据还没读就被覆盖了。4. 避坑排查LIS3DH 驱动调试中最容易翻车的五个点4.1 读出来全是 0 或 0xFF现象初始化后读OUT_X_L到OUT_Z_H六个字节全是 0 或者全是 0xFF。原因I2C 地址不对是最常见的。LIS3DH 的 7 位地址是0x18或0x19取决于 SA0 引脚电平。HAL 库的HAL_I2C_Mem_Read需要传 8 位地址也就是0x18 1 0x30或0x19 1 0x32。很多人直接把 7 位地址传进去结果读不到。SPI 的话检查片选是否拉低、时钟极性相位是否匹配。解决先用 WHO_AM_I 验证通信。如果 WHO_AM_I 读不到用逻辑分析仪抓 I2C 波形看从机有没有 ACK。SPI 的话示波器看 CLK 和 MOSI 有没有波形。4.2 数据跳变严重、噪声大现象静止放置时三轴数据波动几十甚至上百 LSB。原因电源纹波大、地线处理不好或者 ODR 设太高但没开滤波。LIS3DH 内部有高通滤波但默认是关的。另外如果量程设成 ±16g灵敏度低小信号分辨率不够也会显得跳。解决先确认电源干净VDD 和 VDD_IO 都要加去耦电容。软件上开CTRL_REG2的高通滤波或者降低 ODR 到 50Hz 以下。做倾角检测时量程选 ±2g 高分辨率模式别用 ±16g。4.3 中断引脚一直不触发现象配置了 INT1 数据就绪中断但 MCU 从来没进过中断服务函数。原因CTRL_REG3的 I1_DRDY1 位没使能或者CTRL_REG6的 INT1 极性配反了。还有一种情况是 INT1 引脚在 STM32 侧没配成外部中断模式或者中断优先级被别的外设挡住了。解决先读CTRL_REG3和CTRL_REG6确认写入成功。用万用表量 INT1 引脚数据更新时应该有电平变化。如果电平有变化但 MCU 不进中断检查 STM32 的 EXTI 配置和 NVIC 使能。4.4 高低字节读取错位现象加速度值偶尔出现巨大跳变比如从 0.1g 跳到 3g 又跳回来。原因没开 BDUBlock Data Update读低字节和高字节之间数据更新了拼出来的值就是错的。解决CTRL_REG4的 bit7 置 1 开启 BDU。开启后读低字节时会锁住高字节直到高字节被读走才更新。代价是数据更新率会受读取速度影响但换来了数据一致性。4.5 移植到另一颗 STM32 后编译报错现象换了个 STM32 型号驱动编译一堆错误HAL 头文件找不到或者函数名不对。原因驱动里直接包含了具体型号的 HAL 头文件比如stm32f1xx_hal.h换到 F4 就找不到。解决把平台相关的包含和调用抽到 BSP 层驱动层只保留标准头文件。用宏定义区分不同平台或者用函数指针注册读写函数。这套源码包如果分层做得好移植时只需要改 BSP 里的几个函数实现。5. 进阶技巧用 FIFO 水印中断做低功耗批量采集前面讲的都是单次读取实际产品里更常见的是低功耗场景MCU 大部分时间在 Stop 模式LIS3DH 以 100Hz 持续采样FIFO 攒够 25 组数据后触发水印中断MCU 醒来一次性读走 150 字节处理完继续睡。这样 MCU 的占空比可以降到 1% 以下。配置 FIFO 水印中断的步骤// 1. 设置 FIFO 水印阈值25 组数据 lis3dh_write_reg(LIS3DH_FIFO_CTRL_REG, 0x80 | 25); // Stream 模式 水印 25 // 2. 使能 INT1 的 FIFO 水印中断 lis3dh_write_reg(LIS3DH_CTRL_REG3, 0x04); // I1_WTM // 3. 主循环中检测中断标志后读取 void lis3dh_read_fifo(void) { uint8_t buf[6]; for (int i 0; i 25; i) { lis3dh_read_buf(LIS3DH_OUT_X_L, buf, 6); int16_t x (int16_t)((buf[1] 8) | buf[0]); int16_t y (int16_t)((buf[3] 8) | buf[2]); int16_t z (int16_t)((buf[5] 8) | buf[4]); // 处理 x, y, z } }FIFO_CTRL_REG的0x80是 Stream 模式低 5 位是水印阈值。Stream 模式下 FIFO 满了会覆盖旧数据适合连续采集。如果要保留数据不覆盖用 FIFO 模式0x40但攒满 32 组后就不再写入需要手动复位。CTRL_REG3的0x04是 I1_WTM 位使能水印中断到 INT1。这里有个容易忽略的点FIFO 读取速度必须跟得上 ODR。100Hz 下每组数据间隔 10ms25 组是 250ms你必须在 250ms 内读完否则 Stream 模式下旧数据被覆盖。如果 MCU 处理慢要么降低 ODR要么减小水印阈值。验证方法静止放置时读 FIFOZ 轴应该稳定在 1000mg 左右重力分量X 和 Y 接近 0。如果 Z 轴算出来是 4000mg检查灵敏度系数是不是用了高分辨率模式的 4 而实际配的是正常模式。我一般会在驱动里加一个自检函数上电时读 10 组数据算方差方差过大就报错避免装到设备里才发现问题。从那以后我每次拿到新的传感器驱动包都强制先跑 WHO_AM_I 和静止自检确认通信和换算链路没问题再往下做业务逻辑。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?