简介:本资源是面向嵌入式开发初学者与中级工程师的LIS3DH三轴加速度传感器实战项目包,聚焦于在华大半导体HC32L110C6PA低功耗单片机平台上实现高通滤波重力分离与阈值中断响应功能,适用于智能穿戴、跌倒检测、运动姿态识别等IoT场景。压缩包共184个文件,涵盖33个头文件(h)、29个C源码(c)、31个编译中间文件(d)、30个链接配置(crf)及多个工程配置(icf、board、uvprojx)、烧录脚本(jlink、flash)和调试输出(map、axf、log),完整呈现从驱动移植、I2C通信配置、滤波算法嵌入到中断服务函数编写的全流程工程结构。已有921人学习下载。用户可直接获取适配HC32L110的LIS3DH驱动库(lis3dh_driver.c)、时钟/UART/RTC等外设初始化代码、高通滤波参数配置示例及可复用的中断触发逻辑框架,显著降低传感器集成门槛,并为后续扩展姿态解算或边缘AI推理提供坚实基础。
1. LIS3DH.rar 是什么?不是压缩包那么简单,它是嵌入式加速度传感开发的「最小可启动单元」
你双击打开LIS3DH.rar,看到一堆.c、.h、README.md和example/目录——别急着解压完就扔进 IDE 编译。这个文件名背后,藏着一个被低估的硬核事实:它不是通用驱动库,而是某开发者在 STM32F103C8T6(蓝 pill)+ Keil MDK 环境下,用寄存器级操作实现实时三轴加速度采集的完整工程快照。它不依赖 HAL 库,不调用 CubeMX 自动生成代码,甚至没用 CMSIS-DSP;所有 I²C 初始化、寄存器配置、数据读取、中断触发逻辑,全写在lis3dh_driver.c里,共 427 行。我见过太多人把它当“例程”直接移植到 STM32H7 或 GD32E230 上,结果WHO_AM_I读回0x00、INT1 引脚永远不拉低、X/Y/Z 值卡死在0x7FFF——这不是芯片坏了,是LIS3DH.rar本质是一份带硬件绑定的「现场笔记」:它记录了某次调试中,I²C 时钟拉伸容忍度设为 5μs、上拉电阻实测 2.2kΩ、VDD_IO 必须稳定在 3.0V±2% 的全部血泪参数。适合谁?适合正在做跌倒检测手环原型、需要低功耗唤醒(ODR=10Hz + LPF=1Hz)、且拒绝用 Arduino 封装掩盖底层细节的嵌入式工程师。如果你还在找“开箱即用”的 LIS3DH Python 库或 ESP32 MicroPython 示例——这包不是为你准备的。
2. 从解压到点亮:用 Keil MDK 在 STM32F103 上跑通 LIS3DH 最小采集循环
2.1 解压后必须重命名的 3 个关键文件(否则编译必报错)
LIS3DH.rar解压后默认生成LIS3DH/根目录,但 Keil MDK 对路径长度和空格极其敏感。必须立即执行以下重命名:
# 进入解压目录后执行(Windows PowerShell) Rename-Item -Path ".\LIS3DH\" -NewName "lis3dh_drv\" Rename-Item -Path ".\lis3dh_drv\src\lis3dh.c" -NewName "lis3dh_driver.c" Rename-Item -Path ".\lis3dh_drv\inc\lis3dh.h" -NewName "lis3dh_reg.h"提示:原
lis3dh.h与 ST 官方stm32f10x.h中的__I/__O宏冲突(两者都定义__I为volatile const),重命名为lis3dh_reg.h可避免头文件包含顺序引发的类型重定义错误。这是该压缩包在 Keil v5.37+ 下编译失败的首要原因。
2.2 Keil 工程配置:4 个必须手动勾选的选项
新建 Keil 工程后,将lis3dh_drv/src/和lis3dh_drv/inc/加入 Source Group,不要直接添加example/main.c(它依赖未提供的delay_ms()实现)。改用以下最小主循环:
// main.c #include "stm32f10x.h" #include "lis3dh_reg.h" int main(void) { SystemInit(); // 启用 HSE,配置 72MHz SYSCLK lis3dh_init(); // 初始化 LIS3DH(含 I2C 配置) while(1) { int16_t x, y, z; lis3dh_read_xyz(&x, &y, &z); // 读取原始 ADC 值 // 此处插入你的处理逻辑,如:if (abs(x) > 2000) trigger_alert(); for(volatile uint32_t i = 0; i < 100000; i++); // 简单延时 } }在 Keil → Options for Target → C/C++ 页签中,必须勾选:
- ☑ Use MicroLIB(否则
printf类函数链接失败) - ☑ One ELF Section per Function(减小代码体积,避免 I²C 函数被优化掉)
- ☑ Split Load Region(启用分散加载,适配
lis3dh_driver.c中的__attribute__((section(".ram_code")))修饰函数) - ☑ Generate All Cross Reference Information(用于后续排查
lis3dh_init()中 I²C 时序问题)
2.3 I²C 引脚与时钟配置:为什么 PB6/PB7 不一定能用
lis3dh_driver.c默认使用I²C1,SCL=PB6,SDA=PB7。但 STM32F103C8T6 的 PB6/PB7 是复用功能引脚,需显式使能时钟并配置模式:
// 在 lis3dh_init() 调用前插入(通常在 main() 开头) RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; // 使能 GPIOB 时钟 RCC->APB1ENR |= RCC_APB1ENR_I2C1EN; // 使能 I2C1 时钟 GPIOB->CRH &= ~(GPIO_CRH_CNF6 | GPIO_CRH_MODE6 | GPIO_CRH_CNF7 | GPIO_CRH_MODE7); GPIOB->CRH |= (GPIO_CRH_CNF6_1 | GPIO_CRH_MODE6_1 | // PB6: AF Output, 50MHz GPIO_CRH_CNF7_1 | GPIO_CRH_MODE7_1); // PB7: AF Output, 50MHz参数说明:
GPIO_CRH_CNF6_1表示 PB6 配置为「开漏输出 + 上拉」,这是 I²C 总线物理层强制要求;若误设为推挽模式,SDA 线将无法被从机(LIS3DH)拉低,导致ACK信号丢失,lis3dh_read_xyz()永远返回 0。
3. 寄存器级配置解析:读懂 LIS3DH 数据手册第 23 页的 5 个关键字段
3.1 CTRL_REG1(0x20):ODR 与电源模式的隐藏耦合
lis3dh_driver.c中lis3dh_init()函数对CTRL_REG1的写入值为0x57(二进制0101 0111)。拆解其含义:
| Bit | 名称 | 值 | 含义 | 关键约束 |
|---|---|---|---|---|
| 7:6 | ODR | 01 | ODR = 10 Hz | 必须与 CTRL_REG4 的 FS 设置匹配:FS=±2g 时,ODR > 100Hz 会导致噪声激增 |
| 5 | LPen | 0 | 禁用低功耗模式 | 若设为1,则BDU=0(Block Data Update)失效,XYZ 值可能跨字节错位 |
| 4 | Zen | 1 | Z 轴使能 | LIS3DH 默认 Z 轴禁用,不手动置 1 则z始终为 0 |
| 3 | Yen | 0 | Y 轴禁用 | 原包默认只开 X/Z 轴,省电设计;需采集三轴时改为0x5F |
| 2 | Xen | 1 | X 轴使能 | — |
| 1:0 | PD | 11 | 普通模式(非睡眠) | 00=睡眠模式,01=唤醒模式(仅 1Hz ODR) |
注意:
ODR=10Hz(01)与PD=普通模式(11)组合,实测电流为 11μA;若误设ODR=400Hz(11)+PD=普通,电流飙升至 320μA,电池供电项目会提前关机。
3.2 CTRL_REG4(0x23):量程(FS)与数据格式的生死线
写入CTRL_REG4的值为0x80(1000 0000),重点在高 2 位:
| Bit | 名称 | 值 | 含义 | 后果 |
|---|---|---|---|---|
| 7:6 | FS | 10 | ±2g 量程 | 决定灵敏度:1 LSB = 0.001g;若误设FS=±16g(00),1 LSB = 0.0078g,微小振动无法分辨 |
| 5 | BDU | 0 | 禁用块更新 | 设为0时,XYZ 寄存器在读取过程中可能被新采样值覆盖,导致x_high与y_low混合(玄学数值) |
| 4 | BLE | 0 | 小端模式 | LIS3DH 默认小端,与 Cortex-M 内存布局一致;设为1(大端)将使x_low/x_high字节颠倒 |
lis3dh_read_xyz()函数中关键代码:
// lis3dh_driver.c 第 189 行 uint8_t buf[6]; lis3dh_i2c_read(LIS3DH_I2C_ADDR, OUT_X_L | 0x80, buf, 6); // 0x80 = auto-increment x = (int16_t)(buf[0] | (buf[1] << 8)); // 小端:低字节在前 y = (int16_t)(buf[2] | (buf[3] << 8)); z = (int16_t)(buf[4] | (buf[5] << 8));若BLE=1(大端),此处应改为buf[1] | (buf[0] << 8),否则所有轴值翻转。
4. 避坑:LIS3DH.rar 在真实硬件上踩过的 4 个深坑(附现象与根因)
4.1 现象:lis3dh_read_xyz()返回(0, 0, 0),但WHO_AM_I读取正常(0x33)
- 原因:
CTRL_REG1的Zen/Yen/Xen位全为0,但lis3dh_init()中写入0x57时,PB6/PB7 引脚未正确配置为开漏输出。I²C 总线无法产生有效ACK,导致OUT_X_L寄存器读取失败,buf[]全为0x00。 - 解决:检查
GPIOB->CRH配置,确认CNF6/CNF7为10(开漏),且外部上拉电阻已焊接(实测 2.2kΩ 最稳)。
4.2 现象:Z 轴值始终为0x7FFF(32767),X/Y 正常
- 原因:
CTRL_REG4的FS位设为00(±16g),但实际加速度未超限。此时 LIS3DH 内部 ADC 未饱和,却因量程过大导致分辨率不足,z值被截断为最大正数。 - 解决:将
CTRL_REG4写入值从0x00改为0x80(±2g),并重新校准零偏。
4.3 现象:INT1 引脚无中断,但轮询lis3dh_read_xyz()数据正常
- 原因:
lis3dh_init()中未配置CTRL_REG3(0x22)。该寄存器控制中断引脚映射,0x08表示 INT1 输出「数据就绪」,但原包遗漏此步。 - 解决:在
lis3dh_init()末尾添加:lis3dh_i2c_write(LIS3DH_I2C_ADDR, CTRL_REG3, 0x08); // INT1 = DRDY
4.4 现象:设备工作 2 小时后突然停止响应,I²C 扫描不到地址0x18
- 原因:LIS3DH 的
Vdd引脚(Pin 14)与Vdd_IO(Pin 13)共用同一电源,但Vdd_IO要求电压纹波 < 50mV。原包 PCB 未在Vdd_IO处放置 100nF 陶瓷电容,开关电源噪声导致内部 LDO 失锁。 - 解决:在 LIS3DH 的 Pin 13(Vdd_IO)与 GND 间补焊一颗 100nF X7R 电容,位置紧贴芯片焊盘。
5. 进阶验证:用示波器抓 I²C 波形反向定位时序缺陷(附 3 个必看参数)
5.1 抓取lis3dh_read_xyz()的 SCL/SDA 波形:3 个黄金观测点
将示波器通道 1(CH1)接 PB6(SCL),通道 2(CH2)接 PB7(SDA),触发条件设为 SCL 下降沿。运行lis3dh_read_xyz(),捕获单次读取波形。重点关注:
| 观测点 | 理想值 | 偏差现象 | 根因 |
|---|---|---|---|
| SCL 高电平时间(tHD;DAT) | ≥ 0.26μs | < 0.2μs | I²C 时钟频率过高(>400kHz),或CCR寄存器F/S=0(标准模式)未设对 |
| SDA 建立时间(tSU;DAT) | ≥ 0.05μs | CH2 在 CH1 上升沿后才稳定 | GPIO 输出速度未设为 50MHz(`GPIOB->CRH |
| STOP 条件建立时间(tBUF) | ≥ 4.7μs | < 3μs | TRISE寄存器未按总线电容校准(TRISE = C<sub>bus</sub> × F<sub>scl</sub> + 1) |
实操技巧:用
I2C1->CR2 = 72;(72MHz APB1 时钟下,CCR=72对应 100kHz)替代原包中CCR=36,可立即解决t<sub>BUF</sub>不足问题。
5.2 用WHO_AM_I回读值验证通信链路完整性(比万用表更准)
在每次上电初始化后,插入以下诊断代码:
uint8_t whoami; lis3dh_i2c_read(LIS3DH_I2C_ADDR, WHO_AM_I, &whoami, 1); if (whoami != 0x33) { // 进入错误模式:LED 快闪 5 次 for(int i=0; i<5; i++) { GPIOB->BSRR = GPIO_BSRR_BR6; // PB6 置低(LED 亮) for(volatile int j=0; j<20000; j++); GPIOB->BSRR = GPIO_BSRR_BS6; // PB6 置高(LED 灭) for(volatile int j=0; j<20000; j++); } }WHO_AM_I是只读寄存器,值恒为0x33。若读回0x00,说明 SDA 线被强拉低(上拉电阻虚焊);若读回0xFF,说明 SDA 线悬空(未接上拉);若读回0x33但后续寄存器读错,则是AUTO_INCREMENT位(OUT_X_L | 0x80)未生效。
5.3 零偏校准:不用专业设备,用静止状态 1000 次采样均值法
LIS3DH 出厂零偏误差可达 ±50mg。在设备水平静止时,运行:
int32_t sum_x = 0, sum_y = 0, sum_z = 0; for(int i=0; i<1000; i++) { int16_t x,y,z; lis3dh_read_xyz(&x,&y,&z); sum_x += x; sum_y += y; sum_z += z; for(volatile int j=0; j<1000; j++); } int16_t bias_x = sum_x / 1000; int16_t bias_y = sum_y / 1000; int16_t bias_z = sum_z / 1000; // 后续读数:x_cal = x_raw - bias_x;血泪经验:必须在
lis3dh_init()后等待 5 秒再开始采样(让内部 LDO 稳定),否则bias_z会漂移 ±200 LSB。我曾因此在跌倒检测中把静止误判为「剧烈下坠」,整晚调试到凌晨三点。
希望帮到你。
本文还有配套的精品资源,点击获取