STM32F103+BMP180 气压温度传感器 + OLED 完整教程|软件 I2C 驱动、补偿算法、海拔计算全解析

📅 2026/7/18 21:34:57 👁️ 阅读次数 📝 编程学习
STM32F103+BMP180 气压温度传感器 + OLED 完整教程|软件 I2C 驱动、补偿算法、海拔计算全解析

前言

最近做嵌入式实训项目:STM32F103C8T6 搭配 BMP180 数字气压传感器,通过软件 I2C 读取温压原始数据,利用芯片出厂校准系数做补偿运算,再通过标准大气公式估算海拔,最终 12864 OLED 实时显示温度、气压、海拔。 本文完整梳理硬件接线、BMP180 工作原理、软件 I2C 通信、校准补偿逻辑、海拔换算、排错方案,适合单片机入门、课程设计、毕设直接复用。

视频讲解与演示:STM32F103+BMP180 气压温度传感器 + OLED 完整教程|软件 I2C 驱动、补偿算法、海拔计算全解析http:// https://www.bilibili.com/video/BV1HdKE6uEKm/?share_source=copy_web&vd_source=91b967b41a17a5686913521545a8c3fb

一、整体实现流程

外界温压变化 → 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 压阻式数字气压传感器,内部结构:微型压力膜片 + 压阻电桥 + 温度检测单元 + ADC+E2PROM 校准存储 + I2C 接口。

  1. 大气压力挤压膜片形变,压阻阻值改变;
  2. 温度同时会干扰压阻输出,所以必须同步采集温度做补偿;
  3. ADC 输出原始温度 UT、原始气压 UP,无工程意义,不能直接使用;
  4. 每颗芯片出厂写入 11 组唯一 16 位校准参数,存储在 E2PROM 寄存器 0xAA~0xBF,用于抵消个体偏移、温度漂移误差。

四、软件 I2C 通信细节(无硬件 I2C 外设,纯 GPIO 模拟)

1. I2C 设备地址

7 位基础地址:0x77

  • 写操作 8 位地址:0xEE(0x77<<1,最低位 0)
  • 读操作 8 位地址:0xEF(0x77<<1 | 1,最低位 1)

2. 标准读写时序

1)写寄存器:起始信号→发送写地址等待 ACK→寄存器地址→写入数据→停止信号 2)读寄存器:起始→写地址 + 寄存器地址→重复起始→读地址→读取字节→最后 NACK→停止信号 初始化阶段会发送 9 个恢复时钟,释放卡死的 I2C 总线,解决异常通信死机问题。

五、初始化流程与芯片校验

BMP180_Init()执行步骤:

  1. PB6、PB7 配置为开漏输出,释放总线电平;
  2. 读取 ID 寄存器 0xD0,标准 ID 固定 0x55;读不到则判定传感器离线,OLED 提示 Sensor error;
  3. 读取 0xAA~0xBF 全部校准系数,校验有效性;
  4. 校验通过,初始化完成。

重点:不同 BMP180 校准参数不能通用,必须上电实时读取自身 E2PROM。

六、温压测量完整流程(OSS=0 超低功耗模式)

规则:必须先测温度,再测气压

  1. 向 0xF4 写入 0x2E,启动温度转换,延时 5ms(手册最大转换时间 4.5ms,留余量);
  2. 读取 0xF6、0xF7 得到原始温度 UT;
  3. 用温度校准系数计算中间变量 B5(气压补偿必备参数);
  4. 向 0xF4 写入 0x34(OSS=0)启动气压转换,延时 5ms;
  5. 读取 0xF6/F7/F8 获取 3 字节原始气压 UP;
  6. 结合 B5 与全部校准参数,补偿计算真实气压;
  7. 标准大气公式计算估算海拔,刷新 OLED。

寄存器速查表: 0xD0:芯片 ID;0xF4:测量控制寄存器;0xF6/F7/F8:转换结果寄存器

七、补偿计算与单位转换

  1. 温度:输出单位 0.1℃,例 235=23.5℃;
  2. 气压:输出单位 Pa,1hPa=100Pa;屏幕显示拆分整数 / 小数位;
  3. 海拔计算公式(国际标准大气模型): Altitude = 44330 × [1 - (P / P0)^(1 / 5.255)] P = 当前实测气压,P0 固定 101325Pa(标准海平面气压)

海拔误差说明

海拔不是传感器直接测量值,纯气压推算结果:

  1. 当地实时海平面气压不等于 101325Pa,天气、季节、地域都会造成偏差;
  2. 想要精准海拔,需要根据当地气象数据修改 P0,或在已知高度点反向校准。

八、OLED 显示格式

BMP180 Temp: 23.5 C Press:1013.25 hPa Alt: 0042 m

异常提示:

  • Sensor error:芯片失联、ID 错误、校准参数失效;
  • Check PB6 / PB7:I2C 引脚接反、供电 / 接地故障; 主循环 500ms 刷新一次,通信失败自动重新初始化,恢复后自动正常采集。

九、工程文件函数分工

  1. 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 -> PB6,BMP180 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 /* 写入测量控制寄存器的转换命令。当前气压测量采用 OSS=0 模式。 */ #define BMP180_CMD_TEMPERATURE 0x2E #define BMP180_CMD_PRESSURE_OSS0 0x34 /* * BMP180 出厂校准参数。 * 每颗芯片的参数都不相同,必须从 0xAA~0xBF 寄存器读取, * 不能直接使用其他芯片的固定参数。 */ 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 表示收到低电平 ACK;0 表示无应答。 */ 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); #endif
  1. main.c:OLED 显示封装、屏幕数字打印函数;
/* * 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); }

十、常见故障排查

  1. 屏幕提示 Sensor error 排查顺序:3.3V 供电 + 共地→SCL/SDA 引脚是否接反 PB6/PB7→模块上拉电阻→杜邦线接触→读取 ID 寄存器 0x55;
  2. 气压正常,海拔偏差极大 原因:基准海平面气压固定 101325Pa,更换本地真实气压校准 P0 即可;
  3. 温压数据跳动明显 OSS=0 高速模式无滤波,可提升过采样等级 OSS 或多次采样均值滤波;
  4. 数值漂移、温度不准 传感器不要紧贴发热元件;进气孔不要封堵,避免气流直吹。