前言最近做嵌入式实训项目STM32F103C8T6 搭配 BMP180 数字气压传感器通过软件 I2C 读取温压原始数据利用芯片出厂校准系数做补偿运算再通过标准大气公式估算海拔最终 12864 OLED 实时显示温度、气压、海拔。 本文完整梳理硬件接线、BMP180 工作原理、软件 I2C 通信、校准补偿逻辑、海拔换算、排错方案适合单片机入门、课程设计、毕设直接复用。视频讲解与演示STM32F103BMP180 气压温度传感器 OLED 完整教程软件 I2C 驱动、补偿算法、海拔计算全解析http:// https://www.bilibili.com/video/BV1HdKE6uEKm/?share_sourcecopy_webvd_source91b967b41a17a5686913521545a8c3fb一、整体实现流程外界温压变化 → BMP180 MEMS 压阻元件产生信号 → 内部 ADC 输出原始 UT/UP → STM32 软件 I2C 读取原始数据 片内校准参数 → 补偿计算真实温压 → 公式换算海拔 → OLED 屏幕刷新展示核心逻辑一句话BMP180 只输出未补偿原始 ADC 值有效温压、海拔全部由 STM32 软件运算得出。二、硬件接线方案双组独立软件 I2C 互不干扰1. BMP180 接线BMP180 引脚STM32 引脚功能SCLPB6I2C 时钟SDAPB7I2C 数据VCC3.3V供电芯片额定 1.8~3.6V禁止直连 5V 裸芯片GNDGND共地通信稳定必备2. OLED 接线独立 I2C不占用 BMP 总线OLED 引脚STM32 引脚功能SCLPB8I2C 时钟SDAPB9I2C 数据VCC3.3V供电GNDGND共地3. I2C 上拉电阻关键说明I2C 为开漏输出总线必须上拉手册推荐 2.2k~10kΩ典型 4.7kΩ。 市面上成品 BMP180 模块自带上拉裸芯片 / 无板载电阻模块需外接3.3V 分别串 4.7k 电阻接 SCL、SDA否则无应答、通信报错。三、BMP180 内部工作原理BMP180 是博世 MEMS 压阻式数字气压传感器内部结构微型压力膜片 压阻电桥 温度检测单元 ADCE2PROM 校准存储 I2C 接口。大气压力挤压膜片形变压阻阻值改变温度同时会干扰压阻输出所以必须同步采集温度做补偿ADC 输出原始温度 UT、原始气压 UP无工程意义不能直接使用每颗芯片出厂写入 11 组唯一 16 位校准参数存储在 E2PROM 寄存器 0xAA~0xBF用于抵消个体偏移、温度漂移误差。四、软件 I2C 通信细节无硬件 I2C 外设纯 GPIO 模拟1. I2C 设备地址7 位基础地址0x77写操作 8 位地址0xEE0x771最低位 0读操作 8 位地址0xEF0x771 | 1最低位 12. 标准读写时序1写寄存器起始信号→发送写地址等待 ACK→寄存器地址→写入数据→停止信号 2读寄存器起始→写地址 寄存器地址→重复起始→读地址→读取字节→最后 NACK→停止信号 初始化阶段会发送 9 个恢复时钟释放卡死的 I2C 总线解决异常通信死机问题。五、初始化流程与芯片校验BMP180_Init()执行步骤PB6、PB7 配置为开漏输出释放总线电平读取 ID 寄存器 0xD0标准 ID 固定 0x55读不到则判定传感器离线OLED 提示 Sensor error读取 0xAA~0xBF 全部校准系数校验有效性校验通过初始化完成。重点不同 BMP180 校准参数不能通用必须上电实时读取自身 E2PROM。六、温压测量完整流程OSS0 超低功耗模式规则必须先测温度再测气压向 0xF4 写入 0x2E启动温度转换延时 5ms手册最大转换时间 4.5ms留余量读取 0xF6、0xF7 得到原始温度 UT用温度校准系数计算中间变量 B5气压补偿必备参数向 0xF4 写入 0x34OSS0启动气压转换延时 5ms读取 0xF6/F7/F8 获取 3 字节原始气压 UP结合 B5 与全部校准参数补偿计算真实气压标准大气公式计算估算海拔刷新 OLED。寄存器速查表 0xD0芯片 ID0xF4测量控制寄存器0xF6/F7/F8转换结果寄存器七、补偿计算与单位转换温度输出单位 0.1℃例 23523.5℃气压输出单位 Pa1hPa100Pa屏幕显示拆分整数 / 小数位海拔计算公式国际标准大气模型 Altitude 44330 × [1 - (P / P0)^(1 / 5.255)] P 当前实测气压P0 固定 101325Pa标准海平面气压海拔误差说明海拔不是传感器直接测量值纯气压推算结果当地实时海平面气压不等于 101325Pa天气、季节、地域都会造成偏差想要精准海拔需要根据当地气象数据修改 P0或在已知高度点反向校准。八、OLED 显示格式BMP180 Temp: 23.5 C Press:1013.25 hPa Alt: 0042 m异常提示Sensor error芯片失联、ID 错误、校准参数失效Check PB6 / PB7I2C 引脚接反、供电 / 接地故障 主循环 500ms 刷新一次通信失败自动重新初始化恢复后自动正常采集。九、工程文件函数分工BMP180.c底层 I2C 时序、初始化、读取校准、温压采集计算#include BMP180.h #include delay.h #include math.h /* * 文件名称BMP180.c * 功能说明 * 1. 使用 PB6、PB7 模拟 I2C 时序与 BMP180 进行通信 * 2. 读取芯片内部 E2PROM 中的出厂校准系数 * 3. 读取原始温度和原始气压并按照数据手册完成补偿计算 * 4. 使用标准大气公式估算海拔。 * * 硬件连接BMP180 SCL - PB6BMP180 SDA - PB7。 */ /* BMP180 软件 I2C 使用的 GPIO 引脚。 */ #define BMP180_SCL_PIN GPIO_Pin_6 #define BMP180_SDA_PIN GPIO_Pin_7 /* BMP180 的 7 位地址为 0x77加入读写位后分别为 0xEE 和 0xEF。 */ #define BMP180_ADDRESS_WRITE 0xEE #define BMP180_ADDRESS_READ 0xEF /* BMP180 主要寄存器地址。 */ #define BMP180_REG_CHIP_ID 0xD0 #define BMP180_REG_CONTROL 0xF4 #define BMP180_REG_RESULT 0xF6 /* 写入测量控制寄存器的转换命令。当前气压测量采用 OSS0 模式。 */ #define BMP180_CMD_TEMPERATURE 0x2E #define BMP180_CMD_PRESSURE_OSS0 0x34 /* * BMP180 出厂校准参数。 * 每颗芯片的参数都不相同必须从 0xAA0xBF 寄存器读取 * 不能直接使用其他芯片的固定参数。 */ typedef struct { s16 ac1; s16 ac2; s16 ac3; u16 ac4; u16 ac5; u16 ac6; s16 b1; s16 b2; s16 mb; s16 mc; s16 md; } BMP180_CalibrationTypeDef; /* 保存当前芯片的校准参数以及驱动是否已成功初始化。 */ static BMP180_CalibrationTypeDef bmp180_cal; static u8 bmp180_ready 0; /* 软件 I2C 时序延时用于控制 SCL 和 SDA 的变化速度。 */ static void BMP180_I2C_Delay(void) { delay_us(3); } /* 释放 SCL。PB6 为开漏输出写 1 后由上拉电阻产生高电平。 */ static void BMP180_SCL_High(void) { GPIO_SetBits(GPIOB, BMP180_SCL_PIN); } /* 将 SCL 主动拉低。 */ static void BMP180_SCL_Low(void) { GPIO_ResetBits(GPIOB, BMP180_SCL_PIN); } /* 释放 SDA使 BMP180 可以在数据线或应答周期驱动该引脚。 */ static void BMP180_SDA_High(void) { GPIO_SetBits(GPIOB, BMP180_SDA_PIN); } /* 将 SDA 主动拉低。 */ static void BMP180_SDA_Low(void) { GPIO_ResetBits(GPIOB, BMP180_SDA_PIN); } /* 读取 SDA 的实际电平用于接收数据和检测从机 ACK。 */ static u8 BMP180_SDA_Read(void) { return (u8)GPIO_ReadInputDataBit(GPIOB, BMP180_SDA_PIN); } /* * 产生 I2C 停止信号 * SCL 为高电平期间SDA 从低电平跳变为高电平。 */ static void BMP180_I2C_Stop(void) { BMP180_SCL_Low(); BMP180_SDA_Low(); BMP180_I2C_Delay(); BMP180_SCL_High(); BMP180_I2C_Delay(); BMP180_SDA_High(); BMP180_I2C_Delay(); } /* * 产生 I2C 起始或重复起始信号 * SCL 为高电平期间SDA 从高电平跳变为低电平。 */ static void BMP180_I2C_Start(void) { BMP180_SDA_High(); BMP180_SCL_High(); BMP180_I2C_Delay(); BMP180_SDA_Low(); BMP180_I2C_Delay(); BMP180_SCL_Low(); } /* * 通过软件 I2C 发送一个字节数据从最高位开始发送。 * 发送完成后释放 SDA并在第 9 个时钟周期读取 BMP180 的应答。 * 返回值1 表示收到低电平 ACK0 表示无应答。 */ static u8 BMP180_I2C_WriteByte(u8 value) { u8 i; u8 ack; for(i 0; i 8; i) { BMP180_SCL_Low(); if((value 0x80U) ! 0U) { BMP180_SDA_High(); } else { BMP180_SDA_Low(); } BMP180_I2C_Delay(); BMP180_SCL_High(); BMP180_I2C_Delay(); value 1; } /* 第 9 个时钟周期用于读取从机应答。 */ BMP180_SCL_Low(); BMP180_SDA_High(); BMP180_I2C_Delay(); BMP180_SCL_High(); BMP180_I2C_Delay(); ack (BMP180_SDA_Read() 0U) ? 1U : 0U; BMP180_SCL_Low(); return ack; } /* * 通过软件 I2C 接收一个字节数据从最高位开始接收。 * send_ack 非 0 时发送 ACK表示主机还要继续读取 * send_ack 为 0 时发送 NACK表示当前字节是最后一个字节。 */ static u8 BMP180_I2C_ReadByte(u8 send_ack) { u8 i; u8 value 0; BMP180_SDA_High(); for(i 0; i 8; i) { BMP180_SCL_Low(); BMP180_I2C_Delay(); BMP180_SCL_High(); value 1; if(BMP180_SDA_Read() ! 0U) { value | 1U; } BMP180_I2C_Delay(); } /* 第 9 个时钟周期由主机向 BMP180 返回 ACK 或 NACK。 */ BMP180_SCL_Low(); if(send_ack ! 0U) { BMP180_SDA_Low(); } else { BMP180_SDA_High(); } BMP180_I2C_Delay(); BMP180_SCL_High(); BMP180_I2C_Delay(); BMP180_SCL_Low(); BMP180_SDA_High(); return value; } /* * I2C 总线恢复释放 SDA 后输出 9 个 SCL 时钟再发送停止信号。 * 当上一次通信异常中断、从机仍占用 SDA 时该操作有助于释放总线。 */ static void BMP180_I2C_BusRecovery(void) { u8 i; BMP180_SDA_High(); for(i 0; i 9; i) { BMP180_SCL_Low(); BMP180_I2C_Delay(); BMP180_SCL_High(); BMP180_I2C_Delay(); } BMP180_I2C_Stop(); } /* 向 BMP180 的指定寄存器写入一个字节任一阶段无 ACK 都返回失败。 */ static u8 BMP180_WriteRegister(u8 reg, u8 value) { BMP180_I2C_Start(); if(BMP180_I2C_WriteByte(BMP180_ADDRESS_WRITE) 0U || BMP180_I2C_WriteByte(reg) 0U || BMP180_I2C_WriteByte(value) 0U) { BMP180_I2C_Stop(); return 0; } BMP180_I2C_Stop(); return 1; } /* * 从指定寄存器开始连续读取 length 个字节。 * 先以写方向发送寄存器地址再发送重复起始信号并切换到读方向。 */ static u8 BMP180_ReadRegisters(u8 reg, u8 *buffer, u8 length) { u8 i; if(buffer 0 || length 0U) { return 0; } /* 写入待读取的起始寄存器地址。 */ BMP180_I2C_Start(); if(BMP180_I2C_WriteByte(BMP180_ADDRESS_WRITE) 0U || BMP180_I2C_WriteByte(reg) 0U) { BMP180_I2C_Stop(); return 0; } /* 重复起始后切换到读方向最后一个字节返回 NACK。 */ BMP180_I2C_Start(); if(BMP180_I2C_WriteByte(BMP180_ADDRESS_READ) 0U) { BMP180_I2C_Stop(); return 0; } for(i 0; i length; i) { buffer[i] BMP180_I2C_ReadByte((i 1U length) ? 1U : 0U); } BMP180_I2C_Stop(); return 1; } /* 连续读取大端格式的两个字节并组合成一个 16 位无符号数。 */ static u8 BMP180_ReadU16(u8 reg, u16 *value) { u8 bytes[2]; if(value 0 || BMP180_ReadRegisters(reg, bytes, 2) 0U) { return 0; } *value (u16)(((u16)bytes[0] 8) | bytes[1]); return 1; } /* * 从 E2PROM 读取 11 个出厂校准系数。 * AC1、AC2、AC3、B1、B2、MB、MC、MD 为有符号数 * AC4、AC5、AC6 为无符号数。 */ static u8 BMP180_ReadCalibration(void) { u16 value; if(BMP180_ReadU16(0xAA, value) 0U) return 0; bmp180_cal.ac1 (s16)value; if(BMP180_ReadU16(0xAC, value) 0U) return 0; bmp180_cal.ac2 (s16)value; if(BMP180_ReadU16(0xAE, value) 0U) return 0; bmp180_cal.ac3 (s16)value; if(BMP180_ReadU16(0xB0, value) 0U) return 0; bmp180_cal.ac4 value; if(BMP180_ReadU16(0xB2, value) 0U) return 0; bmp180_cal.ac5 value; if(BMP180_ReadU16(0xB4, value) 0U) return 0; bmp180_cal.ac6 value; if(BMP180_ReadU16(0xB6, value) 0U) return 0; bmp180_cal.b1 (s16)value; if(BMP180_ReadU16(0xB8, value) 0U) return 0; bmp180_cal.b2 (s16)value; if(BMP180_ReadU16(0xBA, value) 0U) return 0; bmp180_cal.mb (s16)value; if(BMP180_ReadU16(0xBC, value) 0U) return 0; bmp180_cal.mc (s16)value; if(BMP180_ReadU16(0xBE, value) 0U) return 0; bmp180_cal.md (s16)value; /* 关键无符号校准参数为 0x0000 或 0xFFFF 时认为校准数据无效。 */ if(bmp180_cal.ac4 0U || bmp180_cal.ac4 0xFFFFU || bmp180_cal.ac5 0U || bmp180_cal.ac5 0xFFFFU || bmp180_cal.ac6 0U || bmp180_cal.ac6 0xFFFFU) { return 0; } return 1; } /* * 初始化 BMP180 * 1. 将 PB6、PB7 配置为 50MHz 开漏输出 * 2. 恢复并释放软件 I2C 总线 * 3. 读取 0xD0确认芯片 ID 为 0x55 * 4. 读取并检查出厂校准参数。 */ u8 BMP180_Init(void) { GPIO_InitTypeDef gpio; u8 chip_id; bmp180_ready 0; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); gpio.GPIO_Pin BMP180_SCL_PIN | BMP180_SDA_PIN; gpio.GPIO_Speed GPIO_Speed_50MHz; gpio.GPIO_Mode GPIO_Mode_Out_OD; GPIO_Init(GPIOB, gpio); GPIO_SetBits(GPIOB, BMP180_SCL_PIN | BMP180_SDA_PIN); BMP180_I2C_BusRecovery(); /* 芯片 ID 不为 0x55 时通常表示接线、供电或 I2C 通信异常。 */ if(BMP180_ReadRegisters(BMP180_REG_CHIP_ID, chip_id, 1) 0U || chip_id ! 0x55U) { return 0; } if(BMP180_ReadCalibration() 0U) { return 0; } bmp180_ready 1; return 1; } /* * 读取并计算 BMP180 数据。 * BMP180 的气压结果依赖温度补偿中间量 B5 * 因此必须先完成温度测量和补偿再进行气压测量和补偿。 */ u8 BMP180_ReadData(BMP180_DataTypeDef *data) { u8 raw_bytes[3]; u16 raw_temperature; u32 raw_pressure; s32 x1; s32 x2; s32 x3; s32 b3; s32 b5; s32 b6; u32 b4; u32 b7; s32 pressure; s32 denominator; double altitude; /* 防止使用空指针或在传感器尚未成功初始化时读取数据。 */ if(data 0 || bmp180_ready 0U) { return 0; } /* 第一步启动温度转换。0x2E 对应一次温度测量。 */ if(BMP180_WriteRegister(BMP180_REG_CONTROL, BMP180_CMD_TEMPERATURE) 0U) { return 0; } /* 数据手册规定温度转换最长约 4.5ms这里等待 5ms。 */ delay_ms(5); if(BMP180_ReadRegisters(BMP180_REG_RESULT, raw_bytes, 2) 0U) { return 0; } raw_temperature (u16)(((u16)raw_bytes[0] 8) | raw_bytes[1]); /* * 使用 UT、AC5、AC6、MC、MD 计算真实温度及中间量 B5。 * temperature 的单位为 0.1℃。 */ x1 (((s32)raw_temperature - (s32)bmp180_cal.ac6) * (s32)bmp180_cal.ac5) 15; denominator x1 (s32)bmp180_cal.md; if(denominator 0) { return 0; } x2 ((s32)bmp180_cal.mc 11) / denominator; b5 x1 x2; >#ifndef __BMP180_H #define __BMP180_H #include sys.h /* * 文件名称BMP180.h * 功能说明声明 BMP180 驱动对外提供的数据类型和操作接口。 * * BMP180 使用独立的软件 I2C 总线 * SCL - PB6 * SDA - PB7 * OLED 仍使用 PB8/PB9两个模块的通信引脚互不影响。 */ /* BMP180 完成补偿计算后的测量结果。 */ typedef struct { s32 temperature; /* 温度单位为 0.1℃例如 235 表示 23.5℃。 */ u32 pressure; /* 绝对气压单位为 Pa例如 101325 表示 1013.25hPa。 */ s32 altitude; /* 估算海拔单位为 m以 101325Pa 为海平面参考气压。 */ } BMP180_DataTypeDef; /* * 初始化 BMP180 软件 I2C、检查芯片 ID 并读取芯片校准参数。 * 返回值1 表示初始化成功0 表示芯片无应答、ID 错误或校准数据无效。 */ u8 BMP180_Init(void); /* * 启动温度和气压转换读取原始数据并完成补偿及海拔计算。 * 参数 data用于保存温度、气压和估算海拔的结构体指针。 * 返回值1 表示读取成功0 表示参数无效、未初始化或通信失败。 */ u8 BMP180_ReadData(BMP180_DataTypeDef *data); #endifmain.cOLED 显示封装、屏幕数字打印函数/* * BMP180 environmental monitor * BMP180 SCL - PB6, SDA - PB7 * OLED SCL - PB8, SDA - PB9 (unchanged) */ #include delay.h #include sys.h #include oled.h #include BMP180.h #define BMP180_REFRESH_MS 500 static void BMP180_ShowData(const BMP180_DataTypeDef *data); static void OLED_ShowSignedNum(u8 x, u8 y, s32 value, u8 len, u8 size); int main(void) { BMP180_DataTypeDef sensor_data; u8 sensor_ok; delay_init(); delay_ms(1000); NVIC_Configuration(); OLED_Init(); OLED_ColorTurn(0); OLED_DisplayTurn(0); OLED_Clear(); OLED_ShowString(32, 0, (u8 *)BMP180, 16); OLED_ShowString(8, 24, (u8 *)SCL PB6 SDA PB7, 12); OLED_ShowString(20, 44, (u8 *)Initializing, 12); OLED_Refresh(); sensor_ok BMP180_Init(); delay_ms(200); while(1) { if(sensor_ok ! 0U BMP180_ReadData(sensor_data) ! 0U) { BMP180_ShowData(sensor_data); } else { OLED_Clear(); OLED_ShowString(32, 0, (u8 *)BMP180, 16); OLED_ShowString(18, 24, (u8 *)Sensor error, 12); OLED_ShowString(3, 42, (u8 *)Check PB6 / PB7, 12); OLED_Refresh(); delay_ms(500); sensor_ok BMP180_Init(); } delay_ms(BMP180_REFRESH_MS); } } static void BMP180_ShowData(const BMP180_DataTypeDef *data) { u32 temperature_abs; u32 pressure_hpa; temperature_abs (u32)((data-temperature 0) ? -data-temperature : data-temperature); pressure_hpa data-pressure / 100U; OLED_Clear(); OLED_ShowString(32, 0, (u8 *)BMP180, 16); OLED_ShowString(0, 18, (u8 *)Temp:, 12); if(data-temperature 0) OLED_ShowString(30, 18, (u8 *)-, 12); OLED_ShowNum(36, 18, temperature_abs / 10U, 2, 12); OLED_ShowString(48, 18, (u8 *)., 12); OLED_ShowNum(54, 18, temperature_abs % 10U, 1, 12); OLED_ShowString(60, 18, (u8 *) C, 12); OLED_ShowString(0, 32, (u8 *)Press:, 12); OLED_ShowNum(36, 32, pressure_hpa, 4, 12); OLED_ShowString(60, 32, (u8 *)., 12); OLED_ShowNum(66, 32, data-pressure % 100U, 2, 12); OLED_ShowString(78, 32, (u8 *) hPa, 12); OLED_ShowString(0, 46, (u8 *)Alt:, 12); OLED_ShowSignedNum(30, 46, data-altitude, 5, 12); OLED_ShowString(60, 46, (u8 *) m, 12); OLED_Refresh(); } static void OLED_ShowSignedNum(u8 x, u8 y, s32 value, u8 len, u8 size) { u32 magnitude; if(value 0) { OLED_ShowString(x, y, (u8 *)-, size); magnitude (u32)(-value); } else { OLED_ShowString(x, y, (u8 *) , size); magnitude (u32)value; } OLED_ShowNum((u8)(x size / 2U), y, magnitude, (u8)(len - 1U), size); }十、常见故障排查屏幕提示 Sensor error 排查顺序3.3V 供电 共地→SCL/SDA 引脚是否接反 PB6/PB7→模块上拉电阻→杜邦线接触→读取 ID 寄存器 0x55气压正常海拔偏差极大 原因基准海平面气压固定 101325Pa更换本地真实气压校准 P0 即可温压数据跳动明显 OSS0 高速模式无滤波可提升过采样等级 OSS 或多次采样均值滤波数值漂移、温度不准 传感器不要紧贴发热元件进气孔不要封堵避免气流直吹。