简介:这份资源面向学习STM32嵌入式开发与传感器应用的初学者及进阶者,围绕BMP180气压计的气压、海拔高度与温度测量展开,提供从源码到仿真的完整实践方案。压缩包共406个文件,约30.52MB,以c与h源文件为主体,配合Proteus工程、Keil工程配置、hex固件及设计文档,覆盖驱动开发、通信协议与仿真验证各环节。已有357人学习下载,说明其在同类项目中具备一定参考价值。读者可从中获得STM32与BMP180的I2C通信实现、传感器校准与数据读取流程、气压转海拔的算法处理,以及Proteus硬件仿真验证思路,配合全套资料理解系统设计逻辑,适合课程设计、毕业设计或自学嵌入式数据采集的读者对照实践。
1. 从一块气压计芯片说起:这套方案到底解决什么问题
BMP180 这颗料在电子类课程设计里出镜率极高,但真正把它做“完整”的人不多。多数同学卡在两个地方:一是 I2C 时序调不通,读出来全是 0xFF;二是海拔换算公式抄了一版,结果室温下漂得离谱。这套基于 STM32 的 BMP180 气压、海拔、温度测量方案,配 Proteus 仿真、源码和全套资料,解决的正是“从芯片手册到能跑能看”的完整链路问题。它适合正在做传感器采集类毕设、课设的学生,也适合需要快速搭一个气压测量原型的从业者。整套东西的核心不是炫技,而是把 I2C 通信、补偿算法、串口/液晶显示三件事串成一条可复现的线,让你在仿真里先跑通,再上实物。
2. 需求与选型:为什么是 STM32 + BMP180 这套组合
2.1 测量需求拆解:三个量、两个精度档
先把需求说清楚,后面所有设计才有依据。这个项目要输出三个物理量:大气压强、海拔高度、环境温度。压强范围覆盖 300~1100 hPa,温度覆盖 -40~85℃,这是 BMP180 数据手册给出的量程。精度上分两档:温度分辨率 0.1℃,压强分辨率 1 hPa(超低功耗模式下约 0.06 hPa 的 RMS 噪声,标准模式下更稳)。海拔不是直接测的,而是由压强反算,所以海拔精度完全取决于压强精度和当地海平面基准气压的设定。
这里有个容易被忽略的点:海拔换算必须有一个参考气压 P0。很多同学直接写死 1013.25 hPa,结果在实验室里显示的海拔和实际楼层对不上。通常的做法是把 P0 做成可配置参数,或者上电时用当前压强标定一次。我在方案里把 P0 放在一个宏定义里,方便改。
2.2 主控与传感器选型理由
选 STM32 而不是 51,理由很实在:BMP180 是 I2C 器件,STM32 的硬件 I2C 外设成熟,配合 DMA 或中断能稳定读多字节;而 51 模拟 I2C 在高速下容易丢时序。另外 STM32 的浮点运算能力(哪怕是软件浮点)跑补偿算法比 51 舒服得多,BMP180 的补偿公式里有多次乘除和移位,51 上跑起来代码臃肿。选型上我一般用 STM32F103C8T6 这类资源够用、资料多的型号,仿真里对应 Proteus 的 STM32 模型。
BMP180 相比 BMP280 的优势是它自带温度补偿所需的出厂校准系数(11 个系数存在 EEPROM 里),补偿算法虽然比 BMP280 复杂,但资料全、仿真模型在 Proteus 里也有现成的。这套方案选它,本质是选了一个“学习曲线可控、仿真可跑”的组合。
2.3 仿真先行的工作流
Proteus 仿真的价值在于:你可以在没有实物的情况下验证 I2C 时序、补偿算法和显示逻辑。方案里的仿真工程把 STM32、BMP180 模型、串口虚拟终端和 LCD1602 都接好了。工作流是:先在仿真里确认读到的原始数据合理(不是 0 或 0xFFFF),再验证补偿后的温压值在合理区间,最后看海拔显示。这个顺序很重要,跳过任何一步都会在后面调试时抓瞎。
提示:Proteus 的 BMP180 模型不一定完全复现真实器件的噪声特性,仿真通过不等于实物通过,上实物前务必用逻辑分析仪抓一次 I2C 波形。
3. 总体设计:三层结构与四个模块的职责划分
3.1 分层架构:驱动层、算法层、应用层
整套代码按三层组织,这是让方案可维护的关键。驱动层负责 I2C 底层读写和 BMP180 寄存器访问;算法层封装校准系数读取和温压补偿计算;应用层做海拔换算、数据显示和串口输出。三层之间只通过函数接口交互,驱动层不关心数据怎么用,算法层不碰硬件寄存器。
这种分层的实际好处是:换主控时只改驱动层,换显示方式时只改应用层。我见过太多把 I2C 读写和补偿公式揉在一个 main 函数里的代码,改一个地方全崩。方案里的源码目录结构大致是bsp_i2c.c、bmp180.c、app_measure.c、main.c,职责清晰。
3.2 模块职责与数据流
四个核心模块的职责这样划分:I2C 驱动模块提供I2C_ReadByte/I2C_WriteByte和带寄存器地址的读写封装;BMP180 驱动模块负责读校准系数、启动温度/压强转换、读原始值;补偿算法模块实现数据手册里的bmp180_compensate系列函数;应用模块做海拔计算和格式化输出。
数据流是单向的:应用层调用BMP180_GetTemperature()和BMP180_GetPressure(),这两个函数内部先触发转换、等转换完成、读原始值、调补偿函数,返回浮点结果。海拔由应用层用压强算。这样设计后,每个函数都能单独测试。
3.3 关键参数与接口约定
几个必须定死的参数:I2C 从机地址 0xEE(写)/0xEF(读),这是 7 位地址 0x77 左移一位的结果;转换等待时间,温度转换约 4.5ms,压强转换按 oversampling 设置不同,标准模式约 7.5ms;校准系数共 11 个,分 AC1~AC6、B1、B2、MB、MC、MD。接口上,所有补偿函数输入原始整型、输出浮点,避免中间精度丢失。
| 参数 | 取值 | 说明 |
|---|---|---|
| I2C 从机地址 | 0xEE / 0xEF | 7 位地址 0x77 移位 |
| 温度转换时间 | 4.5 ms | 固定值 |
| 压强转换时间 | 4.5~25.5 ms | 随 oversampling 变化 |
| 校准系数数量 | 11 | 存于 EEPROM 0xAA 起 |
| 默认海平面气压 | 1013.25 hPa | 可宏配置 |
4. 核心实现:I2C 时序、补偿算法与海拔换算
4.1 I2C 读写与 BMP180 寄存器访问
驱动层最基础的是带寄存器地址的读写。BMP180 的读流程是:写从机地址(写方向)→ 写寄存器地址 → 重复起始 → 写从机地址(读方向)→ 读数据。下面这段是读多字节的核心实现,用硬件 I2C 的阻塞方式,仿真和实物都能用。
/* 从 BMP180 指定寄存器连续读 len 字节 */ uint8_t BMP180_ReadReg(uint8_t reg, uint8_t *buf, uint8_t len) { /* 第一步:发送寄存器地址 */ while (I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)); /* 等总线空闲 */ I2C_GenerateSTART(I2C1, ENABLE); /* 起始信号 */ while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); I2C_Send7bitAddress(I2C1, BMP180_ADDR, I2C_Direction_Transmitter); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); I2C_SendData(I2C1, reg); /* 写寄存器地址 */ while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); /* 第二步:重复起始,切换为读方向 */ I2C_GenerateSTART(I2C1, ENABLE); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); I2C_Send7bitAddress(I2C1, BMP180_ADDR, I2C_Direction_Receiver); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)); /* 第三步:循环读,最后一个字节前发 NACK */ while (len) { if (len == 1) { I2C_AcknowledgeConfig(I2C1, DISABLE); /* 最后一字节 NACK */ I2C_GenerateSTOP(I2C1, ENABLE); } while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED)); *buf++ = I2C_ReceiveData(I2C1); len--; } I2C_AcknowledgeConfig(I2C1, ENABLE); /* 恢复 ACK */ return 0; }这段代码的关键在最后一个字节的处理:读最后一个字节前必须先关 ACK 再发 STOP,否则从机继续输出,总线挂死。这是 I2C 调试里最常见的翻车点之一。参数上,BMP180_ADDR定义为 0xEE,reg是寄存器地址,len是读取长度。校准系数从 0xAA 开始连续读 22 字节,正好覆盖 11 个 16 位系数。
4.2 校准系数读取与温度补偿
BMP180 的补偿必须先读校准系数,这些系数是出厂时写入 EEPROM 的,每个芯片不同。读出来后按数据手册的公式算。温度补偿相对简单,先读原始温度 UT,再套公式。
/* 读取 11 个校准系数,大端拼接 */ void BMP180_ReadCalibration(void) { uint8_t buf[22]; BMP180_ReadReg(0xAA, buf, 22); cal.AC1 = (buf[0] << 8) | buf[1]; cal.AC2 = (buf[2] << 8) | buf[3]; cal.AC3 = (buf[4] << 8) | buf[5]; cal.AC4 = (buf[6] << 8) | buf[7]; cal.AC5 = (buf[8] << 8) | buf[9]; cal.AC6 = (buf[10] << 8) | buf[11]; cal.B1 = (buf[12] << 8) | buf[13]; cal.B2 = (buf[14] << 8) | buf[15]; cal.MB = (buf[16] << 8) | buf[17]; cal.MC = (buf[18] << 8) | buf[19]; cal.MD = (buf[20] << 8) | buf[21]; } /* 温度补偿,返回 0.1℃ 单位 */ int32_t BMP180_CompensateTemp(int32_t ut) { int32_t x1, x2, b5; x1 = ((ut - (int32_t)cal.AC6) * (int32_t)cal.AC5) >> 15; x2 = ((int32_t)cal.MC << 11) / (x1 + cal.MD); b5 = x1 + x2; /* b5 供压强补偿复用 */ return (b5 + 8) >> 4; /* 单位 0.1℃ */ }这里b5是个中间量,温度补偿算出来后要保存下来给压强补偿用,很多实现把它做成全局变量或通过指针传出。参数上,ut是原始温度值,返回值的单位是 0.1℃,所以 235 表示 23.5℃。注意移位方向,>>15和<<11写反了结果会差几个数量级,这是抄公式时最容易错的地方。
4.3 压强补偿与海拔换算
压强补偿比温度复杂,涉及 B6、X1、X2、X3 和 B3 的迭代计算。核心是把原始压强 UP 和温度中间量 b5 一起代入。
/* 压强补偿,返回 Pa */ int32_t BMP180_CompensatePressure(int32_t up, int32_t b5) { int32_t x1, x2, x3, b3, b6, p; uint32_t b4, b7; b6 = b5 - 4000; x1 = ((int32_t)cal.B2 * ((b6 * b6) >> 12)) >> 11; x2 = ((int32_t)cal.AC2 * b6) >> 11; x3 = x1 + x2; b3 = ((((int32_t)cal.AC1 * 4 + x3) << OSS) + 2) >> 2; /* OSS 为过采样设置 */ x1 = ((int32_t)cal.AC3 * b6) >> 13; x2 = ((int32_t)cal.B1 * ((b6 * b6) >> 12)) >> 16; x3 = ((x1 + x2) + 2) >> 2; b4 = ((uint32_t)cal.AC4 * (uint32_t)(x3 + 32768)) >> 15; b7 = ((uint32_t)up - b3) * (50000 >> OSS); if (b7 < 0x80000000) p = (b7 * 2) / b4; else p = (b7 / b4) * 2; x1 = (p >> 8) * (p >> 8); x1 = (x1 * 3038) >> 16; x2 = (-7357 * p) >> 16; return p + ((x1 + x2 + 3791) >> 4); /* 单位 Pa */ }OSS是过采样设置,0 到 3 对应不同精度和转换时间,仿真里用 0 就够。海拔换算用国际气压高度公式,简化版是alt = 44330 * (1 - (P/P0)^(1/5.255)),P 是实测压强,P0 是海平面基准。这个公式在低海拔段误差小,高海拔段偏差大,课程设计场景够用。我一般把 P0 做成宏,方便按当地实际气压修正。
4.4 显示与串口输出
应用层把三个量格式化后送 LCD1602 或串口。LCD 显示两行:第一行温度和压强,第二行海拔。串口按固定格式每秒输出一次,方便用虚拟终端看趋势。这里没什么玄学,注意浮点转字符串时保留一位小数即可,sprintf在 STM32 上要开浮点支持,或者自己写定点转换避免库体积膨胀。
5. 避坑与排查:五个最容易翻车的点
5.1 读回全是 0xFF 或 0x00
现象:校准系数读出来全是 0xFF,或者原始温度恒为 0。原因通常是 I2C 地址错、时序不对或从机没响应。先确认地址是 0xEE 不是 0x77(7 位地址要左移),再用逻辑分析仪看起始信号后有没有 ACK。如果 Proteus 里模型没接上拉电阻,也会读失败,补两个 4.7k 上拉到 3.3V。
5.2 温度正常但压强离谱
现象:温度显示合理,压强却是几万 Pa 或负数。原因多半是b5没正确传给压强补偿函数,或者OSS宏和实际过采样设置不一致。检查BMP180_CompensateTemp的返回值有没有保存,压强函数里<< OSS的 OSS 是否和启动转换时写控制寄存器的值匹配。
5.3 海拔显示和实际楼层对不上
现象:压强看着正常,海拔却差几百米。原因是 P0 用了默认 1013.25 hPa,而当天实际海平面气压不是这个值。解决办法是把 P0 改成当地实时气压,或者用已知海拔点反标定一次。课程设计里可以在报告里说明这个基准的选取依据,答辩时是个加分点。
5.4 仿真通过实物失败
现象:Proteus 里一切正常,焊到板子上读不到数据。原因通常是实物 BMP180 模块自带稳压和电平转换,I2C 上拉位置不同,或者杜邦线太长导致时序畸变。先降 I2C 速率到 100kHz 以下试,再检查模块 VCC 是不是 3.3V。血泪经验:别用太长杜邦线,20cm 以内最稳。
5.5 转换等待时间不够
现象:读到的原始值偶尔跳变或明显偏小。原因是启动转换后没等够时间就去读。温度固定等 4.5ms,压强按 OSS 等 4.5~25.5ms,用delay_ms保守多等 2ms 不会有问题。别用while轮询状态寄存器却不加超时,总线异常时会死循环。
6. 验收与答辩:怎么证明这套方案真的跑通了
验收这件事,我一般分三步走,每一步都有可展示的证据,答辩时直接按这个顺序讲。
第一步,静态检查。打开仿真工程,确认 STM32 最小系统、BMP180 模型、LCD 和串口终端都在,电源和地接对。运行前先在代码里把校准系数打印到串口,11 个值应该都是非 0 且互不相同的整数。如果全是 0 或 0xFF,说明 I2C 没通,别往下走。
第二步,动态验证。运行仿真,观察串口每秒输出的温压海拔。温度应在 20~30℃ 之间(仿真模型默认室温),压强在 1000 hPa 附近,海拔在几十米内。用手动改 P0 的方式验证海拔公式:P0 调大,海拔显示应变小,方向对了说明公式没写反。这一步的验收标准可以列成表:
| 验收项 | 合格标准 | 检查方法 |
|---|---|---|
| 校准系数 | 11 个非零且互异 | 串口打印 |
| 温度 | 20~30℃ | 串口/LCD 读数 |
| 压强 | 950~1050 hPa | 串口/LCD 读数 |
| 海拔 | 随 P0 反向变化 | 改宏后重跑 |
| 刷新周期 | 约 1 秒 | 串口时间戳 |
第三步,答辩陈述。别一上来讲代码,先讲需求:为什么要测这三个量、精度要求哪来的。然后讲架构图,三层四模块各干什么。重点讲补偿算法里b5的复用和OSS的作用,这是能体现你读懂数据手册的地方。最后主动说一个你踩过的坑和怎么解决的,比如 I2C 最后一字节 NACK 的处理,比背稿子有说服力。
有个细节容易被问:为什么海拔不用 GPS 或气压模块直接输出?答:BMP180 只出压强,海拔是算出来的,基准气压的选取决定了绝对精度,这是方案的设计取舍,不是缺陷。把这句话说清楚,答辩基本稳。
从那以后我每次做传感器类方案,都强制先跑一遍“读 ID/读系数”的最小验证,再往上叠功能,这个习惯帮我省了太多返工。希望帮到你。
本文还有配套的精品资源,点击获取