简介:这份资源面向使用STM32开发加速度传感器驱动的嵌入式工程师与学生,聚焦LIS2DH12三轴线性加速度计在IIC通信协议下的完整驱动实现,可解决传感器初始化、寄存器配置与数据读取等常见开发问题。压缩包共8个文件,以4个c源文件与4个h头文件为主,分别承载IIC通信逻辑、LIS2DH12寄存器操作、外部中断处理及头文件声明,整体约6KB,结构精简便于直接移植到工程中。目前已有4560人学习下载,说明其在同类驱动资料中具备一定参考价值。内容覆盖IIC接口初始化、工作模式与采样率配置、加速度原始数据解码转换,以及跌落检测、6D运动检测和中断服务例程等事件处理思路,并涉及错误检查与DMA传输优化方向。对需要快速打通STM32与LIS2DH12通信链路、理解运动感知数据流程的开发者而言,可作为一份可直接复用的底层驱动参考。
1. 从一次读不出 WHO_AM_I 说起:LIS2DH12 驱动环境怎么搭
很多人第一次把 LIS2DH12 焊到板子上,上电、写代码、读寄存器,结果WHO_AM_I返回 0x00 或者 0xFF,然后就开始怀疑芯片坏了。我见过太多这种情况,最后查下来八成不是芯片问题,而是 IIC 地址搞错了、上拉电阻没焊、或者 STM32 的 IIC 配置里时钟频率拉太高。LIS2DH12 这颗三轴加速传感器在低功耗场景里用得非常多,和 LIS3DH12、BMA250 属于同一类常被拿来做姿态检测和震动采集的器件,但它的 IIC 地址是 0x18 还是 0x19 取决于 SA0 引脚接法,这一点如果没提前确认,后面所有调试都是白费。这篇笔记面向的是正在 STM32 上从零搭 LIS2DH12 驱动环境的开发者,不管你是用 HAL 库还是标准库,是裸机还是跑 RTOS,下面这套流程我反复走过很多遍,照着搭基本能一次跑通。
2. 环境准备:STM32 工程与 LIS2DH12 硬件连接确认
2.1 确认芯片版本与 IIC 地址
LIS2DH12 的 IIC 从机地址由 SA0 引脚决定。SA0 接地时地址为0x18(7 位地址),SA0 接 VDD 时为0x19。注意这里说的是 7 位地址,HAL 库的HAL_I2C_Master_Transmit需要传入左移一位后的 8 位地址,也就是0x18 << 1 = 0x30或0x19 << 1 = 0x32。这个细节坑过很多人,因为有些例程直接写 0x18 也能跑,那是因为它用的是软件 IIC 或者别的库,地址处理方式不同。
我一般会在原理图上先确认 SA0 的接法,然后用万用表量一下实际电平,不要只看原理图。曾经有一块板子原理图标的是接地,实际焊接时 SA0 虚焊,读出来一直是 0xFF,查了半天才发现是焊接问题。
2.2 STM32 端 IIC 外设配置
以 STM32F103 和 HAL 库为例,IIC 配置的关键参数如下:
// IIC 初始化,以 100kHz 标准模式为例 hi2c1.Instance = I2C1; hi2c1.Init.ClockSpeed = 100000; // 100kHz,LIS2DH12 支持到 400kHz hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2; hi2c1.Init.OwnAddress1 = 0; hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; hi2c1.Init.OwnAddress2 = 0; hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; HAL_I2C_Init(&hi2c1);ClockSpeed建议从 100kHz 起步,确认通信正常后再尝试 400kHz。LIS2DH12 数据手册标称支持 400kHz,但实际能不能跑稳取决于你的走线长度和上拉电阻。我一般会在 100kHz 下先验证功能,量产版本再评估是否提速。
NoStretchMode要设为DISABLE,也就是允许时钟拉伸。LIS2DH12 在多次读取时会拉低 SCL 来请求等待,如果禁止拉伸,高速读取时容易丢数据。
2.3 硬件连接检查清单
接线之前对照下面这张表过一遍,能省掉很多返工:
| 检查项 | 正确做法 | 常见错误 |
|---|---|---|
| VDD | 接 2.16V~3.6V,推荐 3.3V | 接 5V 直接烧芯片 |
| VDD_IO | 与 MCU 电平一致 | 悬空或接错电平 |
| SDA/SCL 上拉 | 各接 4.7kΩ 到 VDD_IO | 不加上拉或阻值过大 |
| SA0 | 明确接 GND 或 VDD | 悬空导致地址不确定 |
| CS | 接 VDD_IO 选择 IIC 模式 | 悬空或接地进入 SPI 模式 |
CS 引脚特别容易忽略。LIS2DH12 的 CS 为高电平时才使能 IIC,如果悬空或者接地,芯片会进入 SPI 模式,IIC 完全没反应。这个坑我在早期项目里踩过一次,示波器看 SDA/SCL 都有波形,但芯片就是不应答,最后发现是 CS 没拉高。
3. 驱动移植:寄存器操作与初始化流程
3.1 关键寄存器速查
LIS2DH12 的寄存器不多,驱动里真正需要操作的就下面这些:
| 寄存器名 | 地址 | 作用 | 常用值 |
|---|---|---|---|
| WHO_AM_I | 0x0F | 器件 ID,固定返回 0x33 | 只读 |
| CTRL_REG1 | 0x20 | 使能轴、设置 ODR | 0x57 |
| CTRL_REG2 | 0x21 | 高通滤波配置 | 默认 0x00 |
| CTRL_REG3 | 0x22 | 中断引脚配置 | 按需 |
| CTRL_REG4 | 0x23 | 量程、自检、SPI 模式 | 0x08 |
| CTRL_REG5 | 0x24 | FIFO 使能 | 按需 |
| CTRL_REG6 | 0x25 | 中断触发方式 | 按需 |
| OUT_X_L | 0x28 | X 轴低字节 | 只读 |
| OUT_X_H | 0x29 | X 轴高字节 | 只读 |
| STATUS_REG | 0x27 | 数据就绪标志 | 只读 |
WHO_AM_I 固定返回 0x33,这是验证 IIC 通信是否正常的第一道关卡。如果读出来不是 0x33,后面所有配置都不用看了,先解决通信问题。
3.2 初始化代码与参数说明
下面这段初始化代码我用了很多次,参数按 100Hz 输出速率、±2g 量程、开启 X/Y/Z 三轴来配:
#define LIS2DH12_ADDR_WRITE 0x30 // SA0 接地时的写地址 #define LIS2DH12_ADDR_READ 0x31 // 读地址 uint8_t lis2dh12_init(void) { uint8_t id = 0; uint8_t data; // 第一步:读 WHO_AM_I 确认通信 if (HAL_I2C_Mem_Read(&hi2c1, LIS2DH12_ADDR_READ, 0x0F, I2C_MEMADD_SIZE_8BIT, &id, 1, 100) != HAL_OK) { return 1; // IIC 通信失败 } if (id != 0x33) { return 2; // 器件 ID 不匹配 } // 第二步:CTRL_REG1 = 0x57 // ODR=100Hz(0101), LPen=0, Zen=1, Yen=1, Xen=1 data = 0x57; HAL_I2C_Mem_Write(&hi2c1, LIS2DH12_ADDR_WRITE, 0x20, I2C_MEMADD_SIZE_8BIT, &data, 1, 100); // 第三步:CTRL_REG4 = 0x08 // BDU=1(连续读不更新), BLE=0(小端), FS=00(±2g), HR=0 data = 0x08; HAL_I2C_Mem_Write(&hi2c1, LIS2DH12_ADDR_WRITE, 0x23, I2C_MEMADD_SIZE_8BIT, &data, 1, 100); return 0; }CTRL_REG1的 0x57 拆开看:bit7~bit4 是 ODR 位,0101 对应 100Hz;bit3 是 LPen,低功耗模式使能,这里关掉;bit2~bit0 分别是 Z、Y、X 轴使能,全开就是 111。合起来就是0101 0111,即 0x57。
CTRL_REG4的 0x08 中,bit7 是 BDU,设为 1 可以防止在读高低字节之间数据被刷新,避免读到拼接错误的加速度值。bit4 是 FS 量程位,00 对应 ±2g,如果你需要测更大加速度,改成 01(±4g)、10(±8g)、11(±16g),但分辨率会相应下降。
3.3 读取加速度数据的正确姿势
读数据时要注意 LIS2DH12 的输出寄存器是左右对齐的 16 位值,实际有效位取决于量程。±2g 量程下是 10 位精度,左对齐存放在 16 位寄存器里,所以读出来要右移 6 位再乘以灵敏度。
void lis2dh12_read_xyz(int16_t *x, int16_t *y, int16_t *z) { uint8_t buf[6]; // 从 OUT_X_L(0x28) 开始连续读 6 个字节 // 地址最高位置 1 表示自动递增 HAL_I2C_Mem_Read(&hi2c1, LIS2DH12_ADDR_READ, 0x28 | 0x80, I2C_MEMADD_SIZE_8BIT, buf, 6, 100); *x = (int16_t)((buf[1] << 8) | buf[0]) >> 6; *y = (int16_t)((buf[3] << 8) | buf[2]) >> 6; *z = (int16_t)((buf[5] << 8) | buf[4]) >> 6; }地址0x28 | 0x80里的 0x80 是自动递增标志位,LIS2DH12 支持地址自动递增,设了这个位之后连续读 6 个字节就能依次拿到 X、Y、Z 的高低字节,不用每读一个字节重新发地址。这个技巧在 FIFO 读取时同样适用。
右移 6 位是因为 ±2g 量程下数据是 10 位精度,左对齐在 16 位里,低 6 位是无效的。如果你改成 ±4g 量程,精度变成 8 位,就要右移 8 位。这个移位位数和量程的对应关系在数据手册里有表格,建议直接查表确认。
4. 避坑与排查:LIS2DH12 驱动调试中的五个高频问题
4.1 读 WHO_AM_I 返回 0x00 或 0xFF
现象:IIC 初始化完成,调用读 ID 函数,返回值始终是 0x00 或 0xFF。
原因:0x00 通常是从机没有应答,可能是地址错误、CS 引脚没拉高、或者芯片根本没上电。0xFF 一般是 SDA 被拉高但 SCL 没有正常时序,常见于上拉电阻缺失或阻值不对。
解决:先用示波器看 SDA/SCL 上有没有波形。有波形但不应答,检查 SA0 电平和 CS 电平。没有波形,检查 IIC 外设初始化是否成功、GPIO 是否配置为复用开漏模式。上拉电阻建议用 4.7kΩ,走线长的话降到 2.2kΩ 试试。
4.2 数据偶尔跳变或卡死
现象:加速度数据大部分时间正常,偶尔出现极大值或者 IIC 总线卡死。
原因:时钟频率过高导致时序余量不足,或者没有开启 BDU 导致高低字节读取期间数据被刷新。
解决:把 IIC 时钟从 400kHz 降到 100kHz 测试。确认CTRL_REG4的 BDU 位设为 1。如果总线卡死,检查是否有从机拉低 SDA 不放的情况,可以在初始化时加一段总线恢复逻辑:手动发送 9 个时钟脉冲再发 STOP 条件。
4.3 量程和灵敏度对不上
现象:读出来的数值换算成加速度后,和实际倾斜角度对不上。
原因:移位位数和量程不匹配,或者灵敏度系数用错了。
解决:±2g 量程对应灵敏度 1mg/digit,±4g 对应 2mg/digit,±8g 对应 4mg/digit,±16g 对应 12mg/digit。移位位数分别是 6、7、8、8。注意 ±16g 的移位位数和 ±8g 一样是 8,但灵敏度不同,这个容易搞混。
4.4 FIFO 模式下读不到数据
现象:配置了 FIFO 使能,但读 FIFO_SRC 寄存器一直显示空。
原因:FIFO 使能位在CTRL_REG5里,但很多人只配了CTRL_REG1就以为 FIFO 开了。另外 FIFO 模式选择在FIFO_CTRL_REG里,默认是 Bypass 模式,不存数据。
解决:先写CTRL_REG5的 bit6 使能 FIFO,再写FIFO_CTRL_REG选择 Stream 或 FIFO 模式。Stream 模式下 FIFO 满了会覆盖旧数据,FIFO 模式下满了就停止存入,按需选择。
4.5 中断引脚不触发
现象:配置了中断功能,但 INT1 或 INT2 引脚始终没有电平变化。
原因:中断触发阈值寄存器INT1_THS没配,或者CTRL_REG3里没有把对应中断路由到引脚上。
解决:中断功能需要配四个地方——INT1_CFG设触发条件,INT1_THS设阈值,INT1_DURATION设持续时间,CTRL_REG3把中断使能并路由到引脚。少配一个都不行。阈值寄存器的单位是 16mg/digit,比如你要设 100mg 阈值,就写 100/16≈6。
5. 装完怎么验证:从 WHO_AM_I 到实际姿态数据的完整检查链
驱动写完烧进去,不要急着跑业务逻辑,按下面这条检查链走一遍,能确认整套环境真正跑通了。
第一步,读 WHO_AM_I。返回 0x33 说明 IIC 物理层和基本通信没问题。这一步不过,后面不用看。
第二步,写CTRL_REG1后再读回来,确认写入的值和读出的值一致。这一步验证寄存器读写功能正常。如果写进去读出来不一样,检查是不是把只读寄存器当可写寄存器操作了。
第三步,静止状态下读加速度数据。把板子平放,Z 轴应该接近 +1g 或者 -1g(取决于芯片贴装方向),X 和 Y 接近 0。数值换算公式是:加速度(mg) = 原始值 × 灵敏度(mg/digit)。±2g 量程下灵敏度是 1mg/digit,所以原始值直接就是 mg 数。
第四步,翻转板子,确认对应轴的数值跟着变化。比如把板子翻过来,Z 轴应该从 +1000mg 左右变成 -1000mg 左右。这一步验证传感器方向和数据符号正确。
第五步,如果用了 FIFO 或中断,单独写测试代码验证。FIFO 测试可以连续读 32 组数据看是否递增,中断测试可以用示波器看引脚电平变化。
我一般会在驱动里加一个自检函数,把上面五步串起来,每次移植到新板子上先跑一遍自检,通过了再跑业务代码。这个习惯帮我省了很多来回排查的时间。从那以后我每次拿到新板子,第一件事就是跑一遍 WHO_AM_I 和静止数据检查,确认传感器环境没问题再往下做。希望帮到你。
本文还有配套的精品资源,点击获取