1. 项目概述与硬件选型
这个项目实现了一个典型的嵌入式传感器数据采集与显示系统,使用STM32F103作为主控芯片,通过I2C接口连接BMP280气压温湿度传感器,并将采集到的数据实时显示在OLED屏幕上。这种组合在气象站、室内环境监测、无人机高度计等场景中非常实用。
选择STM32F103C8T6(俗称"蓝莓板")作为主控是因为:
- 72MHz主频的Cortex-M3内核足够处理传感器数据
- 内置硬件I2C控制器,通信稳定
- 丰富的GPIO和定时器资源
- 成熟的生态系统和低廉的价格
BMP280是Bosch推出的数字气压传感器,相比前代BMP180有这些优势:
- 更低的功耗(2.7μA @1Hz采样率)
- 更高的精度(±0.12hPa气压精度)
- 集成温度传感器
- 支持I2C和SPI双接口
OLED选择常见的0.96寸128x64 I2C模块,因其:
- 高对比度,无需背光
- 极低的功耗
- 简单的4线接口(VCC,GND,SCL,SDA)
2. 硬件连接与电路设计
2.1 引脚分配方案
推荐使用以下连接方式(基于最常见的STM32F103C8T6最小系统板):
| 设备 | 引脚功能 | STM32引脚 | 备注 |
|---|---|---|---|
| BMP280 | VCC | 3.3V | 必须使用3.3V供电 |
| GND | GND | ||
| SCL | PB6 | I2C1时钟线 | |
| SDA | PB7 | I2C1数据线 | |
| CSB | 悬空 | 接地则选择SPI模式 | |
| SDO | 接GND | I2C地址选择(0x76) | |
| OLED | VCC | 3.3V | |
| GND | GND | ||
| SCL | PB6 | 与BMP280共用I2C | |
| SDA | PB7 | 需注意I2C地址冲突 |
注意:BMP280的SDO引脚接GND时I2C地址为0x76,接VCC时为0x77。OLED通常默认为0x3C,因此不会冲突。
2.2 电源设计要点
虽然开发板已有稳压电路,但需注意:
- BMP280必须使用3.3V供电,5V会损坏传感器
- 长距离连接时,建议在传感器端增加0.1μF去耦电容
- I2C总线上拉电阻:
- 开发板通常已集成4.7kΩ上拉
- 若通信不稳定,可尝试减小到2.2kΩ
- 多设备共用时勿重复上拉
3. 软件开发环境搭建
3.1 工具链配置
推荐使用STM32CubeIDE + HAL库开发:
- 安装STM32CubeIDE(包含STM32CubeMX)
- 新建工程时选择STM32F103C8系列
- 配置时钟树:
- HSE 8MHz
- SYSCLK 72MHz
- APB1 36MHz (I2C时钟源)
- 开启I2C1:
- 标准模式(100kHz)
- 无需中断和DMA
3.2 关键驱动代码解析
3.2.1 BMP280驱动实现
c复制// bmp280.h 关键定义
#define BMP280_I2C_ADDR 0x76
#define BMP280_REG_ID 0xD0
#define BMP280_REG_CTRL_MEAS 0xF4
#define BMP280_REG_CONFIG 0xF5
#define BMP280_REG_PRESS_MSB 0xF7
typedef struct {
int32_t temp; // 温度 x100 (℃)
int32_t press; // 压力 (Pa)
uint16_t dig_T1;
int16_t dig_T2, dig_T3;
uint16_t dig_P1;
int16_t dig_P2, dig_P3, dig_P4, dig_P5, dig_P6, dig_P7, dig_P8, dig_P9;
} BMP280_Handle;
3.2.2 OLED驱动适配
c复制// oled.h 配置适配
#define OLED_I2C_ADDR 0x3C
#define OLED_WIDTH 128
#define OLED_HEIGHT 64
void OLED_Init(I2C_HandleTypeDef *hi2c) {
uint8_t init_cmds[] = {
0xAE, 0xD5, 0x80, 0xA8, 0x3F,
0xD3, 0x00, 0x40, 0x8D, 0x14,
0x20, 0x00, 0xA1, 0xC8, 0xDA,
0x12, 0x81, 0xCF, 0xD9, 0xF1,
0xDB, 0x40, 0xA4, 0xA6, 0xAF
};
HAL_I2C_Mem_Write(hi2c, OLED_I2C_ADDR, 0x00, 1, init_cmds, sizeof(init_cmds), 100);
}
4. 传感器数据采集与处理
4.1 BMP280初始化流程
- 读取芯片ID(0xD0)确认通信正常
- 读取校准参数(0x88-0xA1, 0xE1-0xF0)
- 配置控制寄存器:
- 0xF4: 温度采样x2 + 压力采样x16 + 正常模式
- 0xF5: 标准IIR滤波 + 1Hz输出数据率
c复制HAL_StatusTypeDef BMP280_Init(BMP280_Handle *dev, I2C_HandleTypeDef *hi2c) {
uint8_t id;
HAL_I2C_Mem_Read(hi2c, BMP280_I2C_ADDR, BMP280_REG_ID, 1, &id, 1, 100);
if(id != 0x58) return HAL_ERROR;
uint8_t calib[24];
HAL_I2C_Mem_Read(hi2c, BMP280_I2C_ADDR, 0x88, 1, calib, 24, 100);
dev->dig_T1 = (calib[1]<<8)|calib[0];
dev->dig_T2 = (calib[3]<<8)|calib[2];
// 其他校准参数赋值...
uint8_t ctrl_meas = 0b01010111; // Tempx2, Pressx16, Normal
HAL_I2C_Mem_Write(hi2c, BMP280_I2C_ADDR, BMP280_REG_CTRL_MEAS, 1, &ctrl_meas, 1, 100);
uint8_t config = 0b00010000; // 标准滤波, 1Hz
HAL_I2C_Mem_Write(hi2c, BMP280_I2C_ADDR, BMP280_REG_CONFIG, 1, &config, 1, 100);
return HAL_OK;
}
4.2 温度压力补偿算法
BMP280原始数据需要经过复杂的补偿计算:
c复制void BMP280_Compensate(BMP280_Handle *dev) {
int32_t adc_T = (dev->raw_data[3]<<12)|(dev->raw_data[4]<<4)|(dev->raw_data[5]>>4);
int32_t adc_P = (dev->raw_data[0]<<12)|(dev->raw_data[1]<<4)|(dev->raw_data[2]>>4);
// 温度补偿
int32_t var1 = ((((adc_T>>3) - ((int32_t)dev->dig_T1<<1))) * ((int32_t)dev->dig_T2)) >> 11;
int32_t var2 = (((((adc_T>>4) - ((int32_t)dev->dig_T1)) *
((adc_T>>4) - ((int32_t)dev->dig_T1))) >> 12) *
((int32_t)dev->dig_T3)) >> 14;
int32_t t_fine = var1 + var2;
dev->temp = (t_fine * 5 + 128) >> 8; // 单位0.01℃
// 压力补偿
int64_t var3 = ((int64_t)t_fine) - 128000;
int64_t var4 = var3 * var3 * (int64_t)dev->dig_P6;
var4 = var4 + ((var3 * (int64_t)dev->dig_P5)<<17);
var4 = var4 + (((int64_t)dev->dig_P4)<<35);
var3 = ((var3 * var3 * (int64_t)dev->dig_P3)>>8) + ((var3 * (int64_t)dev->dig_P2)<<12);
var3 = (((((int64_t)1)<<47)+var3)) * ((int64_t)dev->dig_P1)>>33;
if(var3 == 0) return;
int64_t p = 1048576 - adc_P;
p = (((p<<31) - var4)*3125)/var3;
var3 = (((int64_t)dev->dig_P9) * (p>>13) * (p>>13))>>25;
var4 = (((int64_t)dev->dig_P8) * p)>>19;
p = ((p + var3 + var4)>>8) + (((int64_t)dev->dig_P7)<<4);
dev->press = (uint32_t)p;
}
5. OLED显示界面设计
5.1 显示内容布局
推荐采用以下信息布局:
code复制+-----------------------+
| 温度: 25.6℃ |
| 气压: 1012.3 hPa |
| 海拔: 156.2 m |
| 湿度: 45% RH |
| 更新: 3s前 |
+-----------------------+
5.2 显示刷新优化
避免全屏刷新导致的闪烁:
c复制void OLED_Refresh(BMP280_Handle *dev) {
char buf[20];
static uint32_t last_update = 0;
if(HAL_GetTick() - last_update < 1000) return;
last_update = HAL_GetTick();
OLED_ClearArea(0, 0, 128, 16); // 只清空文本区域
sprintf(buf, "Temp: %.1fC", dev->temp/100.0);
OLED_ShowString(0, 0, buf);
sprintf(buf, "Press: %.1fhPa", dev->press/100.0);
OLED_ShowString(0, 16, buf);
float altitude = 44330*(1-pow(dev->press/101325.0, 1/5.255));
sprintf(buf, "Alt: %.1fm", altitude);
OLED_ShowString(0, 32, buf);
}
6. 系统整合与优化
6.1 主程序架构
c复制int main(void) {
HAL_Init();
SystemClock_Config();
BMP280_Handle bmp280 = {0};
I2C_HandleTypeDef hi2c1;
// I2C初始化...
BMP280_Init(&bmp280, &hi2c1);
OLED_Init(&hi2c1);
while(1) {
BMP280_ReadData(&bmp280, &hi2c1);
BMP280_Compensate(&bmp280);
OLED_Refresh(&bmp280);
HAL_Delay(300); // 控制刷新率
}
}
6.2 低功耗优化技巧
-
调整BMP280工作模式:
- 休眠模式时电流仅0.1μA
- 采样后立即进入休眠
c复制void BMP280_Sleep(I2C_HandleTypeDef *hi2c) { uint8_t ctrl = 0b00000000; // Sleep模式 HAL_I2C_Mem_Write(hi2c, BMP280_I2C_ADDR, BMP280_REG_CTRL_MEAS, 1, &ctrl, 1, 100); } -
OLED局部刷新:
- 只更新变化的部分
- 关闭滚动功能
-
STM32低功耗模式:
c复制
HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);
7. 常见问题排查
7.1 I2C通信失败
现象:HAL_I2C_Mem_Read/Write返回HAL_ERROR
排查步骤:
- 用逻辑分析仪检查SCL/SDA波形
- 确认上拉电阻值(通常4.7kΩ)
- 检查地址是否正确(BMP280:0x76/0x77, OLED:0x3C)
- 降低I2C时钟速度(尝试10kHz)
7.2 数据异常
温度/压力值明显错误:
- 检查补偿算法是否正确应用
- 确认校准参数读取完整
- 检查电源电压是否稳定(3.3V±5%)
- 避免传感器暴露在极端温度下
7.3 OLED显示异常
显示乱码或闪烁:
- 检查初始化序列是否完整
- 确认I2C速率不超过400kHz
- 增加写入后的延时(特别是初始化阶段)
- 检查电源纹波(建议加10μF电容)
8. 项目扩展方向
- 增加BME280传感器同时测量湿度
- 添加SD卡存储历史数据
- 通过ESP8266实现WiFi数据传输
- 设计3D打印外壳做成便携设备
- 开发上位机软件显示趋势图
实际部署中发现,在长时间运行后,BMP280的温度读数会因自发热产生约0.5℃的偏差。解决方法是:
- 在初始化后等待5分钟再读取数据
- 或通过实验测定偏移值进行软件补偿
- 或降低采样率减少发热