
简介这份资源面向在STM32平台上开发电子罗盘、运动追踪与导航应用的嵌入式工程师及学习者提供LSM303DLH三轴磁传感器与三轴加速度计的完整驱动程序。压缩包共4个文件包含2个.c源文件与2个.h头文件整体约4KB其中自定义I2C驱动负责底层通信LSM303驱动负责传感器配置与数据读取头文件则定义结构体、枚举与函数原型便于工程直接引用。资源覆盖I2C协议实现、HAL库与自定义驱动编程、传感器量程与数据速率配置、磁感应与加速度数据读取及校准等知识点读者可据此快速完成传感器初始化、周期采样与方向解算为电子罗盘和运动检测功能打下基础。目前已有1072人学习下载适合希望掌握传感器驱动移植与调试思路的开发者参考。1. LSM303DLH 三轴磁传感加速度驱动从寄存器到 HAL 库的落地路径如果你手头有一块 LSM303DLH 模块想用 STM32 的 HAL 库把三轴磁力计和三轴加速度计同时跑起来大概率会遇到一个尴尬局面网上能找到的例程要么是标准库时代的古董代码要么只调通了加速度计、磁力计那部分语焉不详。LSM303DLH 这颗芯片本身不算新但它把磁传感和加速度集成在一个封装里I2C 地址、寄存器映射、量程配置各有各的脾气直接照搬别的传感器代码基本会翻车。这份驱动资源解决的就是这个问题——它把 LSM303DLH 的磁力计和加速度计两条数据链路都打通基于 STM32 HAL 库封装适合正在做姿态解算、电子罗盘、倾角检测的嵌入式开发者。不管你是刚上手 HAL 库的新手还是想从标准库迁移过来的老手下面这套流程都能直接抄作业。2. LSM303DLH 的 I2C 通信与寄存器映射为什么不能直接套用 MPU6050 的代码2.1 双从机地址的坑磁力计和加速度计不在同一个地址上LSM303DLH 最容易被忽略的一点是它的加速度计和磁力计在 I2C 总线上是两个独立的从机地址。加速度计部分固定为 0x327 位地址左移一位后的写地址磁力计部分固定为 0x3C。很多开发者习惯性地用同一个设备地址去读所有寄存器结果加速度数据正常、磁力数据全是 0 或者 0xFFFF然后开始怀疑硬件坏了。常见做法是定义两个宏分别管理两条 I2C 事务/* LSM303DLH 设备地址定义注意 7 位地址左移一位 */ #define LSM303DLH_ACC_ADDR (0x32) /* 加速度计写地址 */ #define LSM303DLH_MAG_ADDR (0x3C) /* 磁力计写地址 */ /* HAL 库 I2C 读写封装 */ HAL_StatusTypeDef LSM303DLH_WriteReg(uint8_t devAddr, uint8_t reg, uint8_t data) { return HAL_I2C_Mem_Write(hi2c1, devAddr, reg, I2C_MEMADD_SIZE_8BIT, data, 1, 100); } HAL_StatusTypeDef LSM303DLH_ReadReg(uint8_t devAddr, uint8_t reg, uint8_t *buf, uint16_t len) { return HAL_I2C_Mem_Read(hi2c1, devAddr, reg, I2C_MEMADD_SIZE_8BIT, buf, len, 100); }这里HAL_I2C_Mem_Write的第二个参数是 HAL 库要求的「左移后的地址」所以宏定义里直接写了 0x32 和 0x3C。如果你用的是软件 I2C地址要写成 0x19 和 0x1E7 位原始地址这一点在移植时务必确认。超时参数 100ms 是保守值实际总线正常时几毫秒就返回但调试阶段给宽一点能避免偶发 NACK 导致的误判。2.2 关键寄存器清单CTRL_REG 系列怎么配LSM303DLH 的配置寄存器分布在两个地址空间里加速度计侧有 CTRL_REG1_A 到 CTRL_REG4_A磁力计侧有 CRA_REG_M、CRB_REG_M、MR_REG_M。下面这张表是我实际调通后整理的必配寄存器照着填就能出数据寄存器地址推荐值作用CTRL_REG1_A0x200x2750Hz 输出率开启 X/Y/Z 轴CTRL_REG2_A0x210x00高通滤波默认关闭CTRL_REG3_A0x220x00中断不映射到引脚CTRL_REG4_A0x230x00±2g 量程连续更新模式CRA_REG_M0x000x1430Hz 磁数据输出率CRB_REG_M0x010x20±1.3 高斯量程MR_REG_M0x020x00连续转换模式配置顺序有讲究先配加速度计再配磁力计中间加一点延时。磁力计从断电模式切到连续转换模式后第一组数据需要等一个转换周期才有效立刻读会拿到旧值或零值。void LSM303DLH_Init(void) { /* 加速度计配置50HzXYZ 使能 */ LSM303DLH_WriteReg(LSM303DLH_ACC_ADDR, 0x20, 0x27); /* ±2g连续更新 */ LSM303DLH_WriteReg(LSM303DLH_ACC_ADDR, 0x23, 0x00); HAL_Delay(10); /* 磁力计配置30Hz 输出率 */ LSM303DLH_WriteReg(LSM303DLH_MAG_ADDR, 0x00, 0x14); /* ±1.3 高斯量程 */ LSM303DLH_WriteReg(LSM303DLH_MAG_ADDR, 0x01, 0x20); /* 连续转换模式 */ LSM303DLH_WriteReg(LSM303DLH_MAG_ADDR, 0x02, 0x00); HAL_Delay(20); /* 等第一组磁数据就绪 */ }HAL_Delay(20)这个延时不是随便写的。磁力计在 30Hz 输出率下一个转换周期约 33ms但首组数据通常在 10ms 内就能就绪给 20ms 是留余量。如果你把输出率改成 75Hz这个延时可以缩短到 10ms 以内。注意CTRL_REG4_A的 bit7 是 BDU 位置 1 可以防止读数据时高低字节被刷新导致数据撕裂但在 50Hz 下不置位也基本没问题追求稳定的话建议置 1。3. 加速度与磁场数据的读取和单位换算从原始 LSB 到物理量3.1 加速度计数据读取六字节连续读与符号处理加速度计的输出寄存器从 0x28 开始依次是 OUT_X_L_A、OUT_X_H_A、OUT_Y_L_A、OUT_Y_H_A、OUT_Z_L_A、OUT_Z_H_A。LSM303DLH 支持地址自动递增只要在读的时候把寄存器地址的最高位置 1即 0x28 | 0x80 0xA8就能一次性读出六个字节。HAL 库的HAL_I2C_Mem_Read在连续读时是否自动处理递增取决于芯片是否支持LSM303DLH 是支持的所以直接给起始地址 0x28 | 0x80 即可。typedef struct { int16_t x; int16_t y; int16_t z; } AxisData_t; void LSM303DLH_ReadAcc(AxisData_t *acc) { uint8_t buf[6]; /* 0x28 | 0x80 开启地址自动递增 */ LSM303DLH_ReadReg(LSM303DLH_ACC_ADDR, 0x28 | 0x80, buf, 6); acc-x (int16_t)((buf[1] 8) | buf[0]); acc-y (int16_t)((buf[3] 8) | buf[2]); acc-z (int16_t)((buf[5] 8) | buf[4]); }这里拼接时先高后低因为 LSM303DLH 是小端格式低字节在低地址。(int16_t)强制转换不能省否则负数会变成大正数。量程为 ±2g 时灵敏度是 1mg/LSB也就是说原始值 1000 对应 1g。如果你改成 ±4g 或 ±8g灵敏度分别变成 2mg/LSB 和 4mg/LSB换算时除数要相应调整。3.2 磁力计数据读取高低字节顺序与量程换算磁力计的输出寄存器从 0x03 开始顺序是 OUT_X_H_M、OUT_X_L_M、OUT_Z_H_M、OUT_Z_L_M、OUT_Y_H_M、OUT_Y_L_M。注意这里和加速度计不一样磁力计是高字节在前而且 Z 轴排在 Y 轴前面。这个寄存器顺序是 LSM303DLH 的经典坑很多人读出来的 Y 和 Z 是反的或者数值完全不对就是因为按加速度计的顺序去套了。void LSM303DLH_ReadMag(AxisData_t *mag) { uint8_t buf[6]; /* 磁力计输出从 0x03 开始同样开启自动递增 */ LSM303DLH_ReadReg(LSM303DLH_MAG_ADDR, 0x03 | 0x80, buf, 6); /* 注意顺序X_H, X_L, Z_H, Z_L, Y_H, Y_L */ mag-x (int16_t)((buf[0] 8) | buf[1]); mag-z (int16_t)((buf[2] 8) | buf[3]); mag-y (int16_t)((buf[4] 8) | buf[5]); }量程为 ±1.3 高斯时XY 轴灵敏度是 1100 LSB/GaussZ 轴是 980 LSB/Gauss。换算成微特斯拉1 Gauss 100 uTXY 轴约 0.09 uT/LSBZ 轴约 0.102 uT/LSB。实际使用中如果只做方向判断不换算也能用但做姿态融合时单位必须统一。3.3 数据就绪判断别用延时代替状态寄存器很多例程用固定延时来等数据比如HAL_Delay(20)后再读。这在低速下能用但一旦输出率提高或者系统负载变重就会读到重复数据或旧数据。正确做法是读状态寄存器加速度计的状态寄存器是 0x27磁力计的状态寄存器是 0x09。bit3 置位表示 ZYX 数据全部就绪。uint8_t LSM303DLH_AccDataReady(void) { uint8_t status; LSM303DLH_ReadReg(LSM303DLH_ACC_ADDR, 0x27, status, 1); return (status 0x08) ? 1 : 0; } uint8_t LSM303DLH_MagDataReady(void) { uint8_t status; LSM303DLH_ReadReg(LSM303DLH_MAG_ADDR, 0x09, status, 1); return (status 0x01) ? 1 : 0; }磁力计的状态寄存器 bit0 是 DRDY 位和加速度计的 bit3 位置不同这个细节在数据手册里写得很清楚但容易被跳过。用状态判断后主循环里就不需要硬延时了数据更新更及时也避免了 CPU 空转。4. 避坑与排查LSM303DLH 调试中最容易翻车的五个点4.1 现象磁力计读数始终为 0 或 0xFFFF原因磁力计没有从断电模式切到连续转换模式或者 MR_REG_M 写入了错误的值。LSM303DLH 上电默认是断电模式必须显式写 0x00 到 MR_REG_M 才能启动连续转换。另一个可能是 I2C 地址用错把磁力计地址写成了加速度计地址。解决先用逻辑分析仪或示波器抓 I2C 波形确认 0x3C 地址有 ACK 响应。然后单独读 MR_REG_M 寄存器确认写入的值确实是 0x00。如果读回来是 0x03说明芯片处于断电模式配置没生效。4.2 现象加速度数据正常但倾角计算出来抖动严重原因没有开启 BDUBlock Data Update位导致读取过程中高低字节被新数据刷新出现瞬时跳变。另外如果传感器安装在有振动的结构上±2g 量程下噪声会很明显。解决把 CTRL_REG4_A 的 bit7 置 1开启 BDU。同时在软件里加一个滑动平均滤波窗口大小 4 到 8 即可太大反而引入滞后。如果振动确实大考虑换 ±4g 量程牺牲一点分辨率换稳定性。4.3 现象磁力计 X 轴正常Y 和 Z 轴数据互换或符号相反原因寄存器读取顺序搞错了。磁力计的输出顺序是 X_H、X_L、Z_H、Z_L、Y_H、Y_L不是 XYZ 顺序。另外如果模块焊接时芯片方向转了 90 度物理轴和寄存器轴会对不上。解决对照数据手册的寄存器映射表逐字节确认。如果确认顺序没错但方向不对在代码里加一个轴映射宏把物理轴和逻辑轴对应起来。符号相反的话取反即可但要注意这会影响到后续的姿态解算。4.4 现象I2C 通信偶尔失败HAL 库返回 HAL_ERROR 或 HAL_TIMEOUT原因上拉电阻阻值不合适。LSM303DLH 在快速模式下对总线电容敏感如果上拉电阻太大比如 10k上升沿变缓高速通信时容易出错。另外如果总线上挂了多个设备地址冲突也会导致偶发失败。解决把上拉电阻换成 4.7k 或 2.2k用示波器看 SCL 和 SDA 的上升沿是否干净。如果总线上有其他设备逐个断开排查。HAL 库的超时时间可以适当放宽到 200ms但根本解决还是要靠硬件。4.5 现象移植到不同 STM32 型号后I2C 完全没反应原因不同 STM32 系列的 I2C 外设时钟配置不同HAL 库的HAL_I2C_Init里时钟频率参数没改。另外有些型号的 I2C 引脚需要重映射或者配置为开漏输出。解决检查hi2c1.Init.ClockSpeed是否和实际总线速率匹配标准模式 100kHz快速模式 400kHz。用 CubeMX 重新生成初始化代码是最稳妥的办法手动改容易漏掉 GPIO 配置。如果用的是硬件 I2C 且引脚有重映射确认__HAL_AFIO_REMAP_I2C1_ENABLE()之类的宏有没有调用。5. 进阶技巧用状态机替代阻塞读取把驱动塞进 RTOS 任务5.1 状态机读取框架阻塞式读取在裸机主循环里能用但一旦上了 RTOS 或者需要同时处理多个传感器就会拖慢整个系统。我一般会把 LSM303DLH 的读取拆成三个状态等待数据就绪、读取原始数据、换算并发布。这样每个状态执行时间都很短不会阻塞其他任务。typedef enum { LSM303_STATE_IDLE, LSM303_STATE_WAIT_ACC, LSM303_STATE_WAIT_MAG, LSM303_STATE_READY } LSM303_State_t; void LSM303_Task(void *argument) { LSM303_State_t state LSM303_STATE_IDLE; AxisData_t acc, mag; for (;;) { switch (state) { case LSM303_STATE_IDLE: if (LSM303DLH_AccDataReady()) { state LSM303_STATE_WAIT_ACC; } break; case LSM303_STATE_WAIT_ACC: LSM303DLH_ReadAcc(acc); state LSM303_STATE_WAIT_MAG; break; case LSM303_STATE_WAIT_MAG: if (LSM303DLH_MagDataReady()) { LSM303DLH_ReadMag(mag); state LSM303_STATE_READY; } break; case LSM303_STATE_READY: /* 发布数据到队列或全局结构体 */ state LSM303_STATE_IDLE; break; } osDelay(1); /* 1ms 调度间隔不占用 CPU */ } }这个状态机的好处是加速度和磁力计的读取解耦了磁力计 30Hz、加速度计 50Hz各自按自己的节奏更新不会互相等待。osDelay(1)让出 CPU其他任务照常跑。如果你不用 RTOS把osDelay换成HAL_Delay(1)也能用但实时性会差一些。5.2 验证数据是否可信的土办法调通之后怎么确认数据是对的我的习惯是把模块平放在桌面上加速度计 Z 轴应该接近 1000±2g 量程下对应 1gX 和 Y 接近 0。然后绕 Z 轴慢慢旋转磁力计的 X 和 Y 应该呈现正弦和余弦变化幅值基本一致。如果 X 和 Y 幅值差很多说明存在硬磁干扰需要做椭圆拟合校准。这个验证方法不需要额外工具上手就能做。5.3 从 LSM303DLH 迁移到 LSM303AGR 的注意事项如果你后面要换用 LSM303AGR寄存器映射和 LSM303DLH 不完全兼容。AGR 的磁力计部分换成了不同的工艺量程和灵敏度都变了CTRL_REG 的位定义也有调整。迁移时不要只改设备地址配置寄存器的值要重新对照数据手册算一遍。我一般会在驱动层加一个芯片型号宏把差异部分隔离在初始化函数里上层的数据读取接口保持不变。从那以后我每次调新传感器都强制先读一遍 WHO_AM_I 寄存器确认通信正常再动配置寄存器。这个习惯帮我省了很多来回排查的时间。希望帮到你。本文还有配套的精品资源点击获取