1. 项目概述
这个基于STM32的光照监测系统是我刚接触嵌入式开发时做的一个小项目,通过BH1750数字光照传感器采集环境光强度,并在0.96寸OLED屏幕上实时显示。整个系统采用I2C总线连接,硬件结构简单但功能完整,非常适合STM32初学者练手。
我选择这个项目作为入门练习有几个原因:首先,BH1750是数字输出的光照传感器,相比模拟传感器省去了ADC配置的麻烦;其次,I2C总线只需要两根线就能连接多个设备,布线简单;最后,OLED显示直观,可以快速验证传感器数据。这个项目涵盖了嵌入式开发的几个核心环节:外设初始化、传感器驱动编写、数据显示和人机交互。
2. 硬件设计与连接
2.1 核心器件选型
STM32F103C8T6:作为主控芯片,这款Cortex-M3内核的MCU性价比极高,72MHz主频完全能满足这个项目的需求,而且有丰富的开发资源。
BH1750FVI:数字环境光传感器,测量范围1-65535 lux,直接输出数字信号,精度±20%,支持I2C接口。相比传统的光敏电阻,它不需要额外的信号调理电路。
SSD1306 0.96寸OLED:I2C接口的128x64分辨率显示屏,对比度高,可视角度大,功耗低,非常适合嵌入式设备的显示需求。
2.2 硬件连接原理
两个I2C设备可以并联在同一组I2C总线上,具体接线如下:
- STM32的PB6(SCL) → BH1750的SCL + OLED的SCK
- STM32的PB7(SDA) → BH1750的SDA + OLED的SDA
- 3.3V电源 → 两个设备的VCC
- GND → 两个设备的GND
注意:I2C总线必须加上拉电阻,通常选择4.7KΩ,接在SCL和SDA线与VCC之间。没有上拉电阻会导致信号无法正常拉高,通信失败。
2.3 硬件设计注意事项
-
电源滤波:虽然项目简单,但建议在BH1750的VCC引脚附近加一个0.1μF的陶瓷电容滤波,提高测量稳定性。
-
地址冲突:BH1750的默认地址是0x23,而OLED通常是0x3C或0x3D,一般不会冲突。但如果使用多个I2C设备,需要注意地址分配。
-
布线长度:I2C总线在标准模式下(100kHz)布线长度不宜超过1米,快速模式下(400kHz)不宜超过30cm。本项目中设备距离很近,无需担心。
3. 软件设计与实现
3.1 开发环境配置
使用Keil MDK作为开发环境,需要安装以下支持包:
- STM32F1xx_DFP(设备支持包)
- ARM::CMSIS(内核支持包)
工程中需要包含以下关键库文件:
- stm32f10x.h(外设寄存器定义)
- stm32f10x_i2c.h(I2C驱动)
- u8g2库(OLED驱动)
3.2 I2C总线初始化
I2C初始化是项目中的第一个关键点,配置不当会导致整个系统无法工作:
c复制void I2C_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
I2C_InitTypeDef I2C_InitStructure;
// 使能GPIOB和I2C1时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
// PB6(SCL)和PB7(SDA)配置为复用开漏
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; // 关键配置!
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
// I2C参数配置
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStructure.I2C_OwnAddress1 = 0xAA; // 主设备地址,任意不冲突值
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStructure.I2C_ClockSpeed = 400000; // 400kHz
I2C_Init(I2C1, &I2C_InitStructure);
I2C_Cmd(I2C1, ENABLE);
}
关键点:GPIO必须配置为复用开漏模式(GPIO_Mode_AF_OD),不能使用推挽输出。I2C总线是开漏结构,需要上拉电阻才能正常工作。
3.3 BH1750驱动实现
BH1750的驱动主要包括三个部分:上电初始化、测量模式设置和数据读取。
3.3.1 传感器上电
c复制void BH1750_PowerOn(void)
{
I2C_Start();
I2C_Send_Byte(0x23 << 1); // 器件地址+写方向
I2C_Wait_Ack();
I2C_Send_Byte(0x01); // POWER ON指令
I2C_Wait_Ack();
I2C_Stop();
DelayMs(180); // 等待传感器稳定
}
上电后需要等待至少180ms让传感器内部稳定,否则第一次读数可能不准确。
3.3.2 设置测量模式
c复制void BH1750_SetMode(uint8_t mode)
{
I2C_Start();
I2C_Send_Byte(0x23 << 1);
I2C_Wait_Ack();
I2C_Send_Byte(mode); // 测量模式指令
I2C_Wait_Ack();
I2C_Stop();
if(mode == 0x10) DelayMs(180); // 高分辨率模式需要额外等待
}
常用的测量模式有:
- 0x10:连续H分辨率模式(1lx分辨率,测量时间120ms)
- 0x11:连续H分辨率模式2(0.5lx分辨率,测量时间120ms)
- 0x13:连续L分辨率模式(4lx分辨率,测量时间16ms)
3.3.3 读取光照数据
c复制float BH1750_Read(void)
{
uint8_t buf[2];
I2C_Start();
I2C_Send_Byte((0x23 << 1) | 0x01); // 读模式
I2C_Wait_Ack();
buf[0] = I2C_Read_Byte(1); // 高字节,发送ACK
buf[1] = I2C_Read_Byte(0); // 低字节,发送NACK
I2C_Stop();
uint16_t val = (buf[0]<<8) | buf[1];
return val / 1.2; // 转换为lux单位
}
数据转换公式来自BH1750数据手册,原始数据除以1.2得到实际lux值。测量范围取决于当前模式,H分辨率模式下最大可达65535/1.2≈54612 lux。
3.4 OLED显示实现
使用u8g2库驱动OLED显示,这是一个功能强大且兼容多种显示器的嵌入式图形库。
3.4.1 u8g2初始化
c复制u8g2_t u8g2;
void OLED_Init(void)
{
u8g2_Setup_ssd1306_i2c_128x64_noname_f(&u8g2, U8G2_R0, u8x8_byte_sw_i2c, u8x8_gpio_and_delay);
u8g2_InitDisplay(&u8g2);
u8g2_SetPowerSave(&u8g2, 0);
u8g2_ClearBuffer(&u8g2);
}
3.4.2 数据显示刷新
c复制void OLED_Refresh(float lux)
{
char str[16];
sprintf(str, "Lux: %.1f", lux);
u8g2_ClearBuffer(&u8g2);
u8g2_SetFont(&u8g2, u8g2_font_ncenB14_tr);
u8g2_DrawStr(&u8g2, 5, 30, str);
// 根据光照强度调整OLED亮度
if(lux > 1000)
u8g2_SetContrast(&u8g2, 255); // 强光下提高亮度
else
u8g2_SetContrast(&u8g2, 120); // 弱光下降低亮度
u8g2_SendBuffer(&u8g2);
}
优化提示:频繁使用sprintf处理浮点数会占用较多资源,在资源紧张的MCU上可以考虑使用整数运算和itoa函数替代。
3.5 主程序逻辑
主循环中控制采样频率和系统休眠:
c复制int main(void)
{
SystemInit();
I2C_Config();
OLED_Init();
BH1750_PowerOn();
BH1750_SetMode(0x10); // 设置为连续H分辨率模式
while(1)
{
static uint32_t last = 0;
if(HAL_GetTick() - last > 500) // 500ms采样一次
{
float lux = BH1750_Read();
OLED_Refresh(lux);
last = HAL_GetTick();
// 强光警告
if(lux > 20000) OLED_Blink(3);
}
__WFI(); // 进入休眠模式省电
}
}
使用WFI(Wait For Interrupt)指令可以让CPU在空闲时进入休眠状态,实测可将系统电流从8mA降至3mA左右,对电池供电的应用很有意义。
4. 调试与优化
4.1 常见问题排查
-
I2C通信失败
- 检查上拉电阻是否连接(4.7KΩ)
- 确认GPIO配置为复用开漏模式
- 用逻辑分析仪抓取I2C波形,检查时序
- 确认设备地址正确(BH1750默认0x23,OLED通常0x3C)
-
BH1750读数不稳定
- 确保上电后等待足够时间(>180ms)
- 检查电源是否稳定,VCC引脚加滤波电容
- 避免强光直射传感器导致饱和
-
OLED无显示
- 确认I2C地址(尝试0x3C和0x3D)
- 检查复位信号是否正常
- 确认u8g2初始化代码正确
4.2 性能优化技巧
-
降低采样频率:根据应用场景调整采样间隔,室内环境光照变化缓慢,可以设置为1-2秒采样一次。
-
数据滤波:添加简单的滑动平均滤波,减少显示数值的跳动:
c复制#define FILTER_SIZE 5 float filter_buf[FILTER_SIZE]; uint8_t filter_index = 0; float filter_add(float val) { filter_buf[filter_index] = val; filter_index = (filter_index + 1) % FILTER_SIZE; float sum = 0; for(int i=0; i<FILTER_SIZE; i++) sum += filter_buf[i]; return sum / FILTER_SIZE; } -
低功耗优化:
- 在两次采样之间让MCU进入STOP模式
- 关闭不用的外设时钟
- 降低系统时钟频率
5. 项目扩展思路
这个基础项目可以进一步扩展为更实用的应用:
-
光照数据记录:添加SD卡模块,定期记录光照数据,用于环境监测。
-
无线传输:集成蓝牙或WiFi模块,将数据发送到手机或云平台。
-
自动控制:根据光照强度控制LED灯或窗帘电机,实现智能照明。
-
多传感器融合:结合温湿度传感器,打造更全面的环境监测系统。
-
GUI界面:在OLED上实现菜单系统,可设置报警阈值等参数。
这个STM32与BH1750的项目虽然简单,但涵盖了嵌入式开发的多个重要概念,是学习I2C通信、传感器驱动和低功耗设计的优秀范例。通过这个项目,我深刻理解了硬件初始化的细节重要性,以及嵌入式系统中时序和电源管理的微妙之处。
