STM32驱动MS5837数字水压传感器:I2C通信与校准实战
2026/9/11 15:38:43 网站建设 项目流程

简介:基于STM32与MS5837水压传感器的完整测量例程,面向嵌入式开发者以及水下测量、潜水设备、水文监测等应用场景。工程通过I2C总线读取MS5837的压力与温度数据,完成压力到水深换算和上电校准,适合用来掌握I2C通信时序、传感器复位与转换流程、压力温度标定方法,以及STM32标准外设库的工程组织方式。压缩包共包含85个文件,以C源码和头文件为主,具体涵盖MS5837驱动、IIC读写、24CXX存储、定时器、LED、按键、USART等模块,另有Keil工程文件、启动文件、HEX固件和工程清理脚本,整体约330KB,目录清晰,便于直接打开编译和烧写验证。目前已有1300人学习下载。代码按HARDWARE、CORE、SYSTEM、USER及STM32F10x_FWLib分层组织,不仅可作为学习I2C总线时序、上电校准算法与嵌入式C开发的参考资料,还能帮助开发者在MS5837压力变送、水深测量等类似产品上快速移植复用。

1. 为什么 STM32 工程里选 MS5837 而不是普通模拟水压传感器

STM32 做水压测量时,很多人会先想到模拟压力变送器,4~20 mA 或 0.5~4.5 V 输出,再接运放和 ADC。这个方案不是不行,而是麻烦:供电纹波、基准电压偏差、零点漂移都会直接进结果,校准一次还要换环境重测。MS5837 这类数字水压传感器把压力敏感元件、24 位 ADC、温度传感器和出厂标定系数封装在 3.3×3.3 mm 的小体积里,读出来的是 D1/D2 原始值,再结合 PROM 里的 6 个系数换算成 mbar 压力,绕开了整个模拟信号链。这里按“水压传感器32例程_stm32_MS5837_水压传感器_”这个参考工程的名字拆开讲:从 I2C 命令时序、PROM 系数换算到换芯片后的移植和校准,适合正在做水位监测、鱼缸水压、潜水平台,或者拿 MS5837 做基于 STM32 毕业设计的读者。

2. MS5837 的 I2C 通信协议与 STM32 驱动准备

MS5837 是 TE Connectivity 的压阻式数字水压传感器,常见型号有 02BA(2 bar 量程)和 30BA(30 bar 量程),两份驱动代码几乎完全一致,只有换算公式里的常量和量程判断不同。写代码前要先理解它和普通 I2C 芯片的一大区别:MS5837 没有寄存器地址空间,通信完全由命令字驱动,一次完整交互是“主发命令 → 主收数据”,所以不能用往 AT24C32 那种HAL_I2C_Mem_Read的套路去读。下面把这套命令结构和驱动准备拆细。

2.1 MS5837 的 PROM 数据结构与命令字速查

MS5837 内部有一块 128 位的 PROM,出厂时写入 8 个 16 位无符号整数:C1 压力灵敏度、C2 压力零点偏移、C3 温度对灵敏度的修正、C4 温度对零点偏移的修正、C5 参考温度、C6 温度 ADC 对温度的修正,外加一个 CRC 校验字和一个保留字。读取时 I2C 地址后跟 1 字节命令,常用命令字集中在下面的表中。这里有个容易踩的坑:PROM 字地址不是从 0 开始连续排列的,而是 0xA0、0xA2、0xA4……每次递增 2,如果照搬普通驱动里的“按寄存器地址读”,读两次就会错位。

操作命令字说明
复位0x1E上电后必须执行,至少等 2.8 ms
读 PROM 字 00xA0包含 CRC 与工厂标记
读 PROM 字 1~60xA2~0xAC对应 C1~C6
读 PROM 字 70xAE保留字
启动压力转换 D10x40 / 0x42 / 0x44 / 0x46 / 0x48OSR 256 到 4096
启动温度转换 D20x50 / 0x52 / 0x54 / 0x56 / 0x58OSR 256 到 4096
读 ADC 结果0x00返回 3 字节大端,左对齐

先说复位。PROM 在芯片上电初期处于未知状态,提前读通常得到 0xFF 或者随机数,例程里必须把 0x1E 复位命令放在整段初始化函数的开头,并且等足 3 ms 才能继续。再说转换命令。D1 是压力转换,D2 是温度转换,OSR 越大转换时间越长、分辨率越高,例程里如果对实时性没有特别要求,直接上 4096 就好。

2.2 STM32 HAL 库的 I2C 配置与上拉电阻选择

在 CubeMX 里把 I2C 外设打开后,速度选 Standard Mode(100 kHz)或 Fast Mode(400 kHz)。MS5837 数据手册标注 I2C 时钟上限 400 kHz,例程默认按 400 kHz 跑没问题,但 PCB 走线超过 5 cm 或上拉电阻选了 10 kΩ 时,建议降回 100 kHz。上拉电阻一般取 2.2 kΩ 到 4.7 kΩ 比较稳,3.3 V 供电时 4.7 kΩ 配 400 kHz 问题不大,如果 SCL 波形上升沿老是不合格,把上拉换成 2.2 kΩ 立竿见影。

2.2.1 从机地址的 7 位与 8 位换算

MS5837 的 7 位 I2C 从机地址由 CSB 引脚决定:CSB 接 GND 时是 0x76,接 VCC 时是 0x77。HAL 库的HAL_I2C_Master_Transmit第一个地址参数要传“左移 1 位后的 8 位地址”,也就是0x76 << 1。常见错误是直接把 0x76 丢给库函数,导致总线上实际发出错误的读写位组合,传感器不 ACK,HAL_I2C_IsDeviceReady一直返回超时。调 I2C 时如果用逻辑分析仪,直接看从机地址字节就能判断问题出在这。

2.2.2 例程移植到 GD32F103 与 APM32 的差异

GD32F103 例程和 APM32 工程里直接拿 STM32 例程改,第一反应是换芯片型号。实际上要注意外设时钟开启函数不同,GD32 的 I2C0 对应 STM32 的 I2C1,__HAL_RCC_I2C1_CLK_ENABLE()要换成 GD32 库里的对应时钟使能宏。APM32F103 因为外设基地址和 STM32F103 高度接近,很多 HAL 工程只改芯片选择就能重编译,但 APM32 的 IWDG、RTC 寄存器布局有差异,例程如果不碰这些外设,通常没问题。移植后先跑一个最简单的循环,每秒读一次 PROM 并打印,能过这一关再往下走。

2.3 例程中 I2C 超时与 ACK 异常的处理策略

I2C 总线卡死后,现象通常是HAL_I2C_GetState停在HAL_I2C_STATE_BUSY,后面的 Transmit/Receive 全部超时。例程里的处理分两步:先HAL_I2C_DeInit再重新MX_I2C1_Init,把外设彻底复位;然后对 SCL 打 9 个时钟脉冲,让总线上可能处于半通信状态的从机回到空闲,这两步封装成ms5837_bus_reset()。如果连 PROM 都读不到,先量 CSB 引脚电平确认地址,确认 VDD 3.3 V 和 GND 没有虚焊,最后再怀疑传感器本身。

3. 跑通水压传感器例程的最小代码实现

这一节给出一个可以在 STM32F103、F407、G030 上直接编译的 MS5837 驱动骨架,依赖 HAL 库。代码按“初始化 → 读 PROM → 采样 → 换算”的顺序组织,和网上常见例程的思路一致,但把容易踩的字节序、延时和 64 位中间量问题全部按踩过的经验写进去。

3.1 MS5837 初始化序列:复位、读 PROM、CRC 校验

上电后先发 0x1E 复位,等 3 ms,再依次读 8 个 PROM 字。读 PROM 时注意小端机器上不能把接收缓冲区强转成uint16_t*,否则高低字节会反。正确写法下面这样。

uint16_t ms5837_prom[8]; void ms5837_init(void) { uint8_t cmd = 0x1E; uint8_t buf[2]; HAL_I2C_Master_Transmit(&hi2c1, 0x76 << 1, &cmd, 1, 100); HAL_Delay(3); for (uint8_t i = 0; i < 8; i++) { cmd = 0xA0 + i * 2; HAL_I2C_Master_Transmit(&hi2c1, 0x76 << 1, &cmd, 1, 100); HAL_I2C_Master_Receive(&hi2c1, 0x76 << 1, buf, 2, 100); ms5837_prom[i] = (uint16_t)((buf[0] << 8) | buf[1]); } }

每发一个命令后,HAL 库都会自动产生 Stop 条件,MS5837 的 PROM 读取时序不要求重复 Start,所以这样的分段读写没问题。0xA0 + i * 2对应命令表中的地址,i 从 0 到 7,正好覆盖 0xA0 到 0xAE。超时参数 100 是 100 ms,正常情况下每次传输几十微秒就完成;如果频繁超时,先把 I2C 速度降到 100 kHz,再查硬件连接。

PROM 读完后可以做一次 CRC4 校验,防止 I2C 时序异常导致系数错乱。CRC4 算法按数据手册的 16 位移位版实现,多项式为 0x3000,计算结果的低 4 位要和 PROM[0] 低 4 位一致。这个校验函数会修改传入数组的第一个元素,调用前先备份原值。

uint8_t ms5837_crc4(uint16_t *prom) { uint16_t n_rem = 0; uint8_t n_bit; uint16_t n_byte; prom[0] &= 0x3FFF; /* 去掉 CRC 位,注意会修改原数组 */ for (uint8_t cnt = 0; cnt < 16; cnt++) { if (cnt & 1) n_byte = prom[cnt >> 1] & 0x00FF; else n_byte = prom[cnt >> 1] >> 8; n_rem ^= n_byte; for (n_bit = 0; n_bit < 8; n_bit++) { if (n_rem & 0x8000) n_rem = (n_rem << 1) ^ 0x3000; else n_rem <<= 1; } } return (uint8_t)((n_rem >> 12) & 0x0F); }

这段算法把 128 位 PROM 数据按字节输入,逐位做模二除法,最后 4 位就是 CRC 校验值。具体取值和芯片内部硬件电路对应,不需要去理解每一行的数学意义,但移植时系数不能乱改。调试阶段建议把 CRC 错误打印出来,量产时可以直接删掉,省去每次初始化的计算时间。

3.2 温度与压力转换命令的正确顺序与等待时间

MS5837 内部的 ADC 只有一个,D1 和 D2 必须分时转换。例程里的推荐顺序是:先发 D2 温度命令,等转换完成读回,再发 D1 压力命令,再读回。这样做的原因是温度系数会用于修正压力,温度采样时刻离压力采样时刻越近,修正越准。如果你的应用里水温变化很快,这个顺序就很重要;如果是静态水压,先读哪个差别不大。等待时间按 OSR 查表,下面这张表是例程里的常用参数。

OSR典型转换时间例程等待时间
256约 1.1 ms2 ms
1024约 2.2 ms5 ms
4096约 9.0 ms15 ms

等待时间宁多勿少。OSR=4096 时如果只等 5 ms,ADC 输出寄存器还在更新,读回的高字节是旧值,拼接出来就是错乱数据。例程里用HAL_Delay等,处理好 SysTick 中断就不容易卡住。如果工程里同时用了 RTOS,不要把HAL_Delay放在临界区里,转换等待最好换成信号量或osDelay

uint32_t ms5837_read_adc(uint8_t cmd) { uint8_t buf[3]; uint32_t adc_raw; HAL_I2C_Master_Transmit(&hi2c1, 0x76 << 1, &cmd, 1, 100); HAL_Delay(15); cmd = 0x00; HAL_I2C_Master_Transmit(&hi2c1, 0x76 << 1, &cmd, 1, 100); HAL_I2C_Master_Receive(&hi2c1, 0x76 << 1, buf, 3, 100); adc_raw = ((uint32_t)buf[0] << 16) | ((uint32_t)buf[1] << 8) | buf[2]; return adc_raw; }

调用方式:d2 = ms5837_read_adc(0x58);d1 = ms5837_read_adc(0x48);。0x58 是温度转换 OSR=4096,0x48 是压力转换 OSR=4096。读 ADC 结果的命令 0x00 是一个单字节操作码,不是寄存器地址,所以先发一次 0x00,再接收 3 字节数据。有的例程写成一条HAL_I2C_Mem_Read(addr, 0x00, ...)也能读,但分两步写排错时更清楚。

提示:实际转换时间受供电电压和温度影响,数据手册给出的是典型值,例程里按最大值的两倍等待最稳妥,代价只是采样率略微下降。

3.3 把 D1/D2 原始值换算成压力与温度

拿到 D1、D2 之后,按数据手册一阶公式计算:

int32_t dT = d2 - (int32_t)ms5837_prom[5] * 256; int32_t temp = 2000 + dT * (int32_t)ms5837_prom[6] / 8388608; int64_t off = (int64_t)ms5837_prom[2] * 65536 + (int64_t)ms5837_prom[4] * dT / 128; int64_t sens = (int64_t)ms5837_prom[1] * 32768 + (int64_t)ms5837_prom[3] * dT / 256;

dT是实际温度 ADC 与参考温度的差值;temp的单位是 0.01°C,2000 就是 20.00°C;offsens分别代表零点偏移和灵敏度,它们都依赖dT,把温度的影响拉进了压力计算。这里有个最常见的 bug:d1 * sens很容易超过 32 位有符号整数的上限,所以sens一定要声明成int64_t,如果例程里写成int,压力值会在量程中段莫名其妙跳变,表现为一串 0xF 开头的负数。

最终压力单位为 mbar,温度单位为摄氏度的计算如下:

int32_t pressure = ((int64_t)d1 * sens / 2097152 - off) / 32768; float temp_c = temp / 100.0f;

2097152是 2 的 21 次方,32768是 2 的 15 次方,这两个数来自芯片内部信号的增益配置,不能随便改。算出来的pressure是绝对压力,包含当前大气压;后面做水深时要单独减去水面气压,那个步骤放到第 5 章校准部分说。

4. 例程移植到具体 STM32 型号时的常见问题

网上能找到的 MS5837 例程大多是 F103 + 标准外设库,而你手上的板子可能是 G030、H743,或者是 GD32F103、APM32 这类兼容芯片。移植时最容易出问题的三个地方是芯片型号切换、延时函数卡死、Keil 工程配置不对。

4.1 从 F103 换到 G030:时钟源与 I2C 外设差异

STM32G030 的 I2C 外设和 F103 不同:F103 是传统 I2C,G030 是带独立时序寄存器的新版 I2C,HAL 函数名一样,但初始化参数差异很大。从 F103 例程移植到 G030 时,不要手工复制寄存器代码,直接用 CubeMX 重新生成MX_I2C1_Init即可。G030 的 I2C 时钟源可以选 PCLK 或系统时钟,CubeMX 默认配置在部分版本里会选到低速源,导致 400 kHz 实际只有 200 kHz,采样率看似正常但波形时序很怪。下面把 F103 和 G030 的主要差异列出来方便排查。

项目F103G030
I2C 外设传统 I2C新版 I2C,带时序寄存器
时钟源PCLK1可选 PCLK 或 SYSCLK
从机地址长度7 位/10 位7 位,配置更细
HAL 初始化通用参数由 Timing 寄存器自动计算

换型号之后建议先跑一次 I2C 扫描,把所有连接到总线上的设备地址打印出来,确认 MS5837 出现在 0x76 或 0x77,再继续跑例程。

4.2 例程卡死在 HAL_Delay 的排查方法

如果你跑过江科大 STM32 教程里的软件定时器那一课,对 HAL_Delay 在中断关闭时的表现应该有印象。MS5837 例程卡死在这个函数里,原因通常是 SysTick 中断被关闭或优先级被调高到阻塞了,比如在同一个工程里接入 DHT11、DS18B20 这类软件时序传感器,它们会短暂关闭中断,期间若有代码调用HAL_DelayuwTick不再累加,看起来就像进了死循环。解决办法有两种:一是把HAL_Delay换成基于 DWT 的延时函数,不依赖中断;二是在进入软件时序临界区之前调用HAL_SuspendTick(),退出后再恢复,但要注意 RTOS 环境下额外处理调度器。

4.3 Keil5 芯片包安装与批量切换单片机型号

新板子如果用的是 STM32G0 或 H7,Keil5 会提示找不到器件,这时要先装对应的 Device Family Pack。G0 系列的包名是 STM32G0xx_DFP,在 Pack Installer 里勾选后等待安装,下载慢就手动下载离线包安装。“keil5 兼容 c51 和 stm32 安装”这类问题指的是同一台机器上同时装 C51 和 ARM 两套编译环境,两者互不干扰,ARM 的例程只要装好 DFP 就能编译。若想在 CubeMX 里把例程从 F103 改成 G030,只需要在工程设置里重新选择芯片型号,重新生成代码后 HAL 库会自动切换,但你自己手写的寄存器操作仍要参照新芯片的参考手册逐一核对。APM32 能不能直接用 STM32 程序,取决于外设基地址是否一致,多数 APM32F103 的工程在替换芯片型号后可以重编译,但如果用到 IWDG、RTC 这类寄存器布局不同的外设,还是要改代码。

5. 提高水压测量精度的校准技巧与验证方法

5.1 零点校准与水深换算

MS5837 的出厂标定已经很准,但用水位计场景时,仍然建议做一次零点校准:上电稳定 5 分钟,让传感器在空气中连续读数 100 次取平均,记为P0_mbar。之后的水深换算不再用 1013.25 mbar 作为基准,而是用这个实测零点:

float depth_m = (pressure_mbar - P0_mbar) / 98.0665f;

98.0665 的由来是 1 m 水柱约等于 98.0665 mbar(淡水、标准重力加速度)。换成海水密度 1025 kg/m³ 时,系数改为 100.515。如果pressure_mbarP0_mbar小,深度就是负值,说明传感器露在水面以上,属于正常情况。

5.2 多点标定与最小二乘拟合

如果手头有标准压力泵或者一个已知深度的水柱,可以做更细的多点标定:在量程的 0%、50%、100% 三个点记录“绝对压力读数”和“参考压力”,然后做一次线性回归,得到 slope 和 offset 烧进 STM32 的 Flash。量产时每片传感器按编号存一组校准参数,统一公式pressure_cal = raw * slope + offset输出。标定时温度尽量稳定在 25°C 附近,否则会把温漂混进零点偏差里。

5.3 用 1 米水柱做重复性验证

验证方法用一根 1 米透明水管就够:传感器吊在管底,注水到 1 m 标线,静置 20 秒后串口以 1 Hz 打印压力换算值,观察最后 10 次读数是否稳定在 ±2 mbar 以内。如果 1 m 处的深度计算值偏差超过 5 cm,先检查温度是不是用 0.01°C 单位直接当成摄氏度用了,这个错误会让温度补偿项差 100 倍,压力值随之出现明显偏移;确认无误后,再考虑传感器量程选型和安装位置是否有气泡残留。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询