LIS2DH12加速度计驱动调试:寄存器配置与I2C/SPI通信实战 简介LIS2DH12三轴加速度传感器的官方驱动代码包面向嵌入式软件工程师与传感器应用开发者解决外设通信、寄存器配置及加速度数据读取等基础集成问题。压缩包采用RAR格式内含4个文件2个C源文件实现驱动主体含初始化与数据读取逻辑1个头文件定义寄存器映射及接口声明1个txt说明文档提供使用指引总大小仅13KB轻量且便于移植。目前已有303人学习下载。驱动代码支持I²C与SPI双接口覆盖±2g至±16g量程选择、温度补偿及电源管理并提供简洁的数据读取示例方便用户快速验证传感器输出。借助该驱动开发者可跳过繁琐的时序调试将更多精力投入到倾斜测量、振动监测、冲击检测等应用逻辑中。1. 拿到 LIS2DH12 代码包先别急着编译先问清楚三个问题标题里这个 lis2dh12_code.rar 属于最常见的驱动压缩包——要么是 ST 官方驱动套到某个编译环境要么是上一个人从别的工程里扒出来改的。LIS2DH12 是 ST 的 3 轴超低功耗加速度计I2C/SPI 双总线同一条初始化序列在不同固件里会因为 LPen 和 HR 位的组合差出 8 位、10 位、12 位三种分辨率。拿到代码包我先做三件事确认总线和器件地址对得上硬件把初始化里 ODR、量程、BDU 三个位域抄出来核对把原始数到 g 的换算公式和量程灵敏度对齐。三件事里任何一件含糊跑起来就是数据乱跳或永远不变的怪值。2. LIS2DH12 寄存器初始化CTRL_REG1 到 CTRL_REG4 的位域怎么填2.1 LIS2DH12 寄存器地图WHO_AM_I、控制组与输出组的位置LIS2DH12 的寄存器空间从 0x00 到 0x3F必须记的地址不超过十个。0x0F 是 WHO_AM_I固定读回 0x330x20 到 0x25 是 CTRL_REG1 到 CTRL_REG6 六个控制寄存器0x27 是 STATUS_REG最低三位的 XDA/YDA/ZDA 表示各轴有没有新数据0x28 到 0x2D 是 OUT_X_L、OUT_X_H 直到 OUT_Z_H 六个输出寄存器默认全部小端排列。中断和 FIFO 相关的寄存器从 0x2E 往后排地址寄存器关键字段作用0x0FWHO_AM_I固定 0x33验证总线时序和器件在位0x20CTRL_REG1ODR[7:4]、LPen[3]、X/Y/Z 使能[2:0]输出速率与三轴开关0x23CTRL_REG4BDU[7]、FS[5:4]、HR[3]、ST[2:1]量程、分辨率、自检0x27STATUS_REGZYXDA[3]新数据可用标志0x28-0x2DOUT_X/Y/Z_L/H16 位左对齐数据加速度原始值0x2EFIFO_CTRL_REGFM[7:5]、FTH[3:0]FIFO 模式与水位0x30-0x33INT1_CFG/SRC/THS/DUR中断配置组阈值与持续时间提示驱动里第一步永远是读 WHO_AM_I。读不到 0x33 时先查 I2C 地址是不是 0x19/0x18由 SA0 引脚决定或者 SPI 的读写位有没有拼对不要先怀疑硬件。2.2 最小初始化序列ODR、量程与 BDU 一次配置我习惯把 LIS2DH12 的初始化固定在两个寄存器里完成CTRL_REG4 先写、CTRL_REG1 后写。这样量程和高分辨率模式先于 ODR 生效避免上电瞬间输出寄存器在半配置状态下被刷新也方便后面排查“数据跳变是不是初始化顺序导致的”。static uint8_t lis2dh12_init(void) { uint8_t id 0; /* 第 1 步WHO_AM_I 必须读到 0x33 */ if (lis2dh12_read_reg(0x0F, id) ! 0 || id ! 0x33) return 1; /* 第 2 步CTRL_REG4 0x88 * bit7 BDU 1 读序列期间锁存输出防高低字节撕裂 * bits5:4 FS 00 量程 ±2g * bit3 HR 1 高分辨率12 位有效数据 * bit0 SIM 0 4 线 SPII2C 模式下忽略 */ lis2dh12_write_reg(0x23, 0x88); /* 第 3 步CTRL_REG1 0x57 * bits7:4 ODR 0101 输出速率 100 Hz * bit3 LPen 0 普通模式与 HR1 搭配 * bits2:0 111 Z/Y/X 三轴全部使能 */ lis2dh12_write_reg(0x20, 0x57); return 0; }这段初始化里 0x88 和 0x57 是两个最常被改的参数。0x88 的 FS 位从 00 改成 01/10/11 对应 ±4g、±8g、±16g改完第 3.4 节里的灵敏度表必须跟着换0x57 的高四位改成 0011 就是 25 Hz改成 0110 就是 200 Hz低功耗唤醒场景一般取 25 Hz 到 100 Hz 之间。注意 LPen 和 HR 同时置 1 是无效组合数据手册明确要求这两个位互斥代码包里如果出现 0xA7 这类值说明是从别的芯片驱动抄过来的。2.3 CTRL_REG1 与 CTRL_REG4 的位域速查表ODR 位域不是线性递增背下来不划算查表最快CTRL_REG1[7:4]ODR说明0000掉电模式写 0x00 关停电流降到 1 µA 以下0001 / 0010 / 00111 Hz / 10 Hz / 25 Hz低功耗唤醒常用区间0100 / 0101 / 0110 / 011150 / 100 / 200 / 400 Hz常规姿态检测区间10001.344 kHz仅低功耗模式可用10011.344 kHz普通/高分辨率模式10105.376 kHz仅低功耗模式CTRL_REG4 里 BDU 建议始终置 1BLE 是字节序默认小端不要动FS[5:4] 与 HR[3] 共同决定每个 LSB 对应的毫 g 数FS 位量程12 位高分辨率10 位普通8 位低功耗00±2g1 mg/LSB4 mg/LSB16 mg/LSB01±4g2 mg/LSB8 mg/LSB32 mg/LSB10±8g4 mg/LSB16 mg/LSB64 mg/LSB11±16g12 mg/LSB48 mg/LSB192 mg/LSB网上流传的代码包里经常出现把 0x88 写成 0x80 或 0x08 的情况本质就是丢掉了 HR 位或 BDU 位。掉 HR 位数据还能读只是分辨率降到 10 位掉 BDU 位则是偶发跳变最隐蔽静止时偶尔蹦出一个异常大值多半就是它。3. LIS2DH12 的 I2C/SPI 读写驱动把三轴原始数读出来3.1 寄存器读写的三个约定地址、自增位和连读LIS2DH12 的 I2C 7 位地址由 SA0 引脚决定默认 0x19接地变 0x18。I2C 模式下寄存器地址就是一个 8 位字节直接发送从 0x28 开始连续读六个字节时器件会自动递增地址不需要额外标志。SPI 模式不一样地址字节要拼上控制位最高位置 1 表示读操作0x40 位置 1 表示多字节自增寄存器地址本身只占低六位因为地址空间最大 0x3F。代码包里常见的reg | 0x80和reg | 0x80 | 0x40宏写法就是指这两个约定。还有一个约定是数据先低后高OUT_X_L 在前、OUT_X_H 在后和很多国产传感器的习惯相反移植时最容易把高低字节拼反。模式首字节多字节主要注意点I2C 读reg连读自动递增7 位地址 0x19/0x18SPI 读单字节reg | 0x80不加 0x40片选一帧内完成SPI 读多字节reg | 0x80加 0x40从 0x28 起连读 6 字节SPI 写reg按需加 0x40不能带 0x803.2 I2C 读取 LIS2DH12 六字节输出寄存器的完整函数读取三轴的函数通常长这样int lis2dh12_read_xyz(int16_t *x, int16_t *y, int16_t *z) { uint8_t buf[6]; /* 0x28 是 OUT_X_LI2C 连读自动递增到 0x2D */ if (i2c_read(LIS2DH12_ADDR, 0x28, buf, 6) ! 0) return -1; *x (int16_t)((uint16_t)buf[1] 8 | buf[0]); *y (int16_t)((uint16_t)buf[3] 8 | buf[2]); *z (int16_t)((uint16_t)buf[5] 8 | buf[4]); return 0; }这里 i2c_read 是平台抽象出来的接口参数分别是器件地址、起始寄存器、缓冲区指针和长度。组合两个字节时必须用uint16_t做左移否则 buf[1] 作为有符号 char 时符号位会被扩展负加速度值会凭空多出 0xFF00 的高半段。BDU 位在这一步也在起作用它保证从你读 OUT_X_L 到读完 OUT_Z_H 的整段期间器件不刷新输出寄存器否则采样恰好跨越两次更新就会拼出错误数据。3.3 SPI 模式的地址字节与最小时序SPI 版本的变化只在地址字节构造收发逻辑和 I2C 版本共用static int lis2dh12_spi_read(uint8_t reg, uint8_t *buf, uint16_t len) { uint8_t cmd; /* bit71 表示读bit61 表示多字节自增 */ cmd (reg 0x3F) | 0x80; if (len 1) cmd | 0x40; cs_low(); /* 片选拉低开始一帧 */ spi_transfer_byte(cmd); /* 地址字节 */ spi_transfer_bytes(NULL, buf, len); /* 收数据MOSI 保持 */ cs_high(); /* 片选拉高结束 */ return 0; }写操作更简单地址字节只发reg 0x3F不带 0x80。值得注意的是片选必须在整帧期间保持拉低不能按字节切换片选否则 LIS2DH12 会把每个字节当成独立帧来解释。SPI 时钟上限是 10 MHz常规 1 MHz 到 8 MHz 都稳定超过上限会出现偶发错读。如果板子走 3 线 SPI 半双工需要先把 CTRL_REG4 的 SIM 位置 1并且主机侧收发方向切换时留一个时钟周期的稳定时间。3.4 原始数转 g 值左对齐与量程换算LIS2DH12 的 16 位输出寄存器是左对齐的。高分辨率 12 位数据占高 12 位、低 4 位固定为 0要先右移 4 位再乘灵敏度普通 10 位数据右移 6 位低功耗 8 位右移 8 位。static const float lis2dh12_sens[4] {0.001f, 0.002f, 0.004f, 0.012f}; float lis2dh12_to_g(int16_t raw, uint8_t fs) { return (float)(raw 4) * lis2dh12_sens[fs]; }fs 是初始化时写进 CTRL_REG4 的 FS 值0 到 3 对应 ±2g 到 ±16g灵敏度数组四个数字直接取自 2.3 节表格的 12 位列。最容易犯的错是把左对齐的 raw16 直接除以 2048 或 32768前者默认右对齐 12 位满量程后者把 16 位全当有效位两种算出来的 g 值都会差 4 到 16 倍。验证方法很直接传感器水平静止放桌面Z 轴模长应接近 1g、X/Y 接近 0如果 Z 读出 16g就是左对齐移位漏了。4. LIS2DH12 的 FIFO 与唤醒中断参数需要精算的四个位置4.1 FIFO 模式与水位FIFO_CTRL_REG 的 FM/FTH 填法LIS2DH12 内置 32 级深度的 FIFO每级是一帧完整的三轴数据。要让 FIFO 生效先把 CTRL_REG5 的 bit6 FIFO_EN 置 1再往 FIFO_CTRL_REG 写模式和水位/* CTRL_REG5 0x40打开 FIFO 使能 */ lis2dh12_write_reg(0x24, 0x40); /* FIFO_CTRL_REG 0x50流模式水位 16 帧 * bit7:5 FM 010 流模式满了覆盖最旧 * bit3:0 FTH 16 水位阈值 */ lis2dh12_write_reg(0x2E, 0x40 | 16);FM 字段按场景选不要套同一个模板FM[2:0]模式行为适用场景000旁路FIFO 不工作普通轮询001FIFO攒满 32 帧停止更新攒一批统一处理010流队列满覆盖最旧持续采样按批读取011流转 FIFO事件前流、事件后保留中断唤醒抓取前因水位 FTH 是 5 位范围 0 到 31写满后 FIFO_SRC_REG 的 WTM 位置 1。读取时每次从 0x28 连读六字节消耗一帧和 3.2 节同一个函数省掉的是每帧单独发地址的开销和总线往返时间。4.2 INT1 阈值与持续时间从毫 g 倒推 INT1_THS 和 INT1_DURATIONINT1_THS 是 7 位寄存器单位按“量程的 1/128”算。以最常用的 ±2g 量程为例一个 LSB 约 15.6 mg。做自由落体检测时把阈值定在 250 mg 附近也就是 0x10这个值能避开静止噪声又比真实跌落时的近零加速度高很多/* INT1_CFG 0x10只使能 Z 轴低阈值检测 * bit4 ZLIE 1检测 Z 轴加速度低于阈值的事件 */ lis2dh12_write_reg(0x30, 0x10); /* INT1_THS 0x10阈值 16 × 15.6mg ≈ 250mg */ lis2dh12_write_reg(0x32, 0x10); /* INT1_DURATION 0x03连续 3 个 ODR 周期满足才触发 */ lis2dh12_write_reg(0x33, 0x03);INT1_DURATION 的单位不是毫秒而是采样周期数实际延时等于该值除以 ODR。ODR 100 Hz 时写 3 意味着连续 30 ms 满足条件才触发能滤掉瞬时抖动ODR 改成 25 Hz 后同样的 3 变成 120 ms响应慢了近 4 倍。所以改 ODR 之后必须重新审视持续时间而不是只调阈值。6D 朝向检测则反过来把 INT1_CFG 的 AOI 和 6D 位同时置 1阈值决定倾斜多少度算翻转一般从 0x10 附近往上试用 INT1 引脚配合示波器确认实际触发角度。4.3 中断路由与回读CTRL_REG3 的 I1_AOI1 和 INT1_SRC中断配置有个经常漏掉的环节INT1_CFG 只管检测条件路由要另外在 CTRL_REG3 里打开把 bit7 I1_AOI1 置 1事件才会出现在 INT1 引脚上/* CTRL_REG3 0x80INT1 事件路由到 INT1 引脚 */ lis2dh12_write_reg(0x22, 0x80);事件发生后要读 INT1_SRC 确认是哪个轴触发的这个读操作本身也是清中断动作uint8_t src; lis2dh12_read_reg(0x31, src); if (src 0x40) /* bit6 IA有中断产生 */ { if (src 0x10) /* bit4 ZLZ 轴低于阈值 */ handle_freefall(); }如果 CTRL_REG5 的 LIR_INT1 置 1中断会锁存直到读完 INT1_SRC 才释放适合中断服务程序晚处理不置 LIR 时中断是脉冲式读晚了就看不到。注意 STATUS_REG 的 ZYXDA 只表示有没有新数据不能替代 INT1_SRC 的清中断职责混用会导致中断标志一直挂住不释放。4.4 四个必踩的坑左对齐、LPen 互斥、FIFO 覆盖和 5.376 kHz第一FIFO 流模式下读取速度跟不上 ODR 时最旧数据被覆盖FIFO_SRC_REG 的 OVRN 位置 1但 FSS 字段看不出发生过覆盖必须在读取帧时顺手检查 OVRN。第二LPen 与 HR 互斥初始化里 HR1 时再写 LPen1 不会同时生效器件按低功耗 8 位输出查表要整套换成 8 位列。第三5.376 kHz 只存在于低功耗模式高分辨率模式最高只能到 1.344 kHz想同时要高 ODR 和高分辨率属于没看数据手册。第四中断使能顺序先写 CTRL_REG3 路由再写 INT1_CFG避免中间态把误触发漏到引脚调试时表现为上电瞬间无故进一次中断。5. 验证 LIS2DH12 驱动没配错的三个自检手法代码包移植完之后不急着接业务逻辑先用三个手法把驱动从头到尾验一遍。第一是静止模长校验。板子水平放稳连续读 128 帧算模长 sqrt(x² y² z²)结果应在 0.95g 到 1.05g 之间。这个校验能同时抓住三件事量程换算表选错、左对齐移位漏掉、X/Y 轴映射接反。把板子分别沿每个轴竖起来读数绝对值最大的轴应该跟着物理方向走对不上就说明轴映射要交换。第二是器件自检。LIS2DH12 自带自检激励不需要外部治具。CTRL_REG4 的 ST 位写 01 是正自检、10 是负自检使能前后各采 200 帧取平均差值要落进数据手册 Self-test 一节的上下限内。使能正自检的写法是先清 ST 位再置位防止上次残留uint8_t ctrl4; lis2dh12_read_reg(0x23, ctrl4); ctrl4 0xF9; /* 清掉 ST[1:0] */ lis2dh12_write_reg(0x23, ctrl4 | 0x02); /* 01正自检 */自检做完立刻把 ST 位写回 00否则下次上电初始化读到的全是带自检偏置的数据。第三是 FIFO 水位验证。把 FTH 写成 1按 ODR 翻转 1/ODR 秒后读 FIFO_SRC_REGWTM 应为 1连读空后再读回EMPTY 应为 1。这一步确认 FIFO 链路和中断清标志没互相干扰。三个手法全部跑完之后把这三段校验放进驱动自测试函数里上电自动执行一遍比每次手抄寄存器判断靠谱得多。本文还有配套的精品资源点击获取