这个基于STM32的光照监测系统是我在嵌入式开发学习过程中完成的一个实用小项目。它通过BH1750数字光照传感器采集环境光强度,并将实时数据通过I2C总线传输到0.96寸OLED屏幕上显示。整个系统硬件结构简洁,软件实现完整,非常适合作为STM32外设开发和传感器应用的入门实践案例。
我在开发过程中遇到了不少典型问题,比如I2C总线通信失败、传感器数据异常等,这些经验教训都会在文中详细分享。项目虽然简单,但涵盖了嵌入式开发的几个关键环节:外设初始化、传感器驱动开发、数据显示和人机交互设计。通过这个项目,新手可以快速掌握STM32的硬件I2C使用、传感器数据采集和OLED显示等实用技能。
主控芯片选用的是STM32F103C8T6,也就是我们常说的"蓝莓派"最小系统板。这款芯片性价比极高,具有丰富的外设资源,特别适合这类传感器应用场景。它的主要优势包括:
BH1750是一款数字式环境光强度传感器,相比传统的光敏电阻有以下优势:
OLED显示屏选用的是0.96寸SSD1306驱动的I2C版本,这种屏幕具有:
硬件连接非常简单,所有设备都挂载在同一个I2C总线上。具体接线如下:
| STM32引脚 | 连接目标 | 备注 |
|---|---|---|
| PB6 | BH1750 SCL / OLED SCL | I2C时钟线 |
| PB7 | BH1750 SDA / OLED SDA | I2C数据线 |
| 3.3V | BH1750 VCC / OLED VCC | 电源正极 |
| GND | BH1750 GND / OLED GND | 电源地 |
重要提示:I2C总线必须接上拉电阻!推荐使用4.7kΩ电阻连接SCL和SDA到3.3V。如果没有上拉电阻,总线电平无法被正确拉高,会导致通信失败。
BH1750的地址引脚ADDR在本项目中接地,因此器件地址为0x23。如果ADDR接VCC,地址则变为0x5C。OLED的I2C地址通常是0x3C或0x3D,具体取决于厂商设置。
硬件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);
// 使能I2C1
I2C_Cmd(I2C1, ENABLE);
}
这段代码有几个关键点需要注意:
BH1750的驱动主要包括初始化、启动测量和读取数据三个部分。
初始化函数:
c复制void BH1750_Init(void)
{
// 发送电源开启命令
I2C_Start();
I2C_Send_Byte(0x23 << 1); // 器件地址+写操作
I2C_Wait_Ack();
I2C_Send_Byte(0x01); // POWER ON命令
I2C_Wait_Ack();
I2C_Stop();
// 设置测量模式为连续高分辨率模式
I2C_Start();
I2C_Send_Byte(0x23 << 1);
I2C_Wait_Ack();
I2C_Send_Byte(0x10); // 连续H分辨率模式
I2C_Wait_Ack();
I2C_Stop();
DelayMs(180); // 等待传感器稳定
}
数据读取函数:
c复制float BH1750_ReadLux(void)
{
uint8_t buf[2];
uint16_t raw_value;
float lux;
// 启动I2C读操作
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();
// 组合数据并转换为lux值
raw_value = (buf[0] << 8) | buf[1];
lux = raw_value / 1.2f; // 根据手册转换公式
return lux;
}
BH1750支持多种测量模式,本项目使用的是连续高分辨率模式(0x10),这种模式下传感器会自动连续测量,主控只需要定期读取数据即可。其他常用模式包括:
OLED显示部分使用了u8g2图形库,这是一个功能强大且轻量级的嵌入式图形库。显示光照值的核心代码如下:
c复制void OLED_DisplayLux(float lux)
{
char str[20];
// 将浮点数转换为字符串
sprintf(str, "Lux: %.1f", lux);
// 清空显示缓冲区
u8g2_ClearBuffer(&u8g2);
// 设置字体和显示位置
u8g2_SetFont(&u8g2, u8g2_font_ncenB14_tr);
u8g2_DrawStr(&u8g2, 5, 30, str);
// 根据光照强度调整屏幕亮度
if(lux > 1000) {
u8g2_SetContrast(&u8g2, 255); // 强光下提高亮度
} else {
u8g2_SetContrast(&u8g2, 120); // 弱光下降低亮度
}
// 更新显示
u8g2_SendBuffer(&u8g2);
}
为了提高显示效果,我添加了自动亮度调节功能。在强光环境下提高OLED对比度,确保显示内容清晰可见;在弱光环境下降低对比度,既保证可读性又减少功耗。
为了降低系统功耗,我采取了以下几种措施:
c复制while(1) {
static uint32_t last_time = 0;
// 每500ms采样一次
if(HAL_GetTick() - last_time > 500) {
float lux = BH1750_ReadLux();
OLED_DisplayLux(lux);
last_time = HAL_GetTick();
}
__WFI(); // 进入休眠模式
}
动态刷新率:根据光照变化速度调整采样频率。当光照稳定时降低采样率,变化剧烈时提高采样率。
OLED局部刷新:只更新变化的部分显示区域,而不是整个屏幕。
原始传感器数据可能会有噪声,为了提高显示稳定性,我实现了简单的滑动平均滤波:
c复制#define FILTER_SIZE 5
float lux_filter[FILTER_SIZE] = {0};
uint8_t filter_index = 0;
float FilterLux(float new_lux)
{
float sum = 0;
// 更新滤波数组
lux_filter[filter_index] = new_lux;
filter_index = (filter_index + 1) % FILTER_SIZE;
// 计算平均值
for(int i = 0; i < FILTER_SIZE; i++) {
sum += lux_filter[i];
}
return sum / FILTER_SIZE;
}
这种滤波算法简单有效,能够平滑光照值的突变,使显示更加稳定。FILTER_SIZE可以根据实际需求调整,值越大滤波效果越明显,但响应速度会变慢。
在实际应用中,I2C通信可能会因为各种原因失败。为了提高系统鲁棒性,我添加了以下异常处理机制:
c复制uint8_t I2C_Wait_Ack(void)
{
uint32_t timeout = 1000; // 超时时间
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)) {
if(--timeout == 0) {
I2C_Recovery(); // 恢复I2C总线
return 1; // 返回错误
}
}
return 0; // 成功
}
c复制void I2C_Recovery(void)
{
// 禁用I2C
I2C_Cmd(I2C1, DISABLE);
// 重新初始化GPIO
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
// 手动生成停止条件
GPIO_SetBits(GPIOB, GPIO_Pin_6 | GPIO_Pin_7);
DelayUs(5);
GPIO_ResetBits(GPIOB, GPIO_Pin_7); // SDA低
DelayUs(5);
GPIO_SetBits(GPIOB, GPIO_Pin_6); // SCL高
DelayUs(5);
GPIO_SetBits(GPIOB, GPIO_Pin_7); // SDA高
// 重新初始化I2C
I2C_Config();
}
在实际开发和调试过程中,我遇到了不少问题,以下是几个典型问题及其解决方案:
现象:无法读取传感器数据,I2C总线无响应。
可能原因及解决方案:
现象:读取的光照值与实际环境不符。
可能原因及解决方案:
现象:屏幕显示乱码或完全不显示。
可能原因及解决方案:
这个基础项目可以进一步扩展,增加更多实用功能:
c复制#define LUX_THRESHOLD_HIGH 20000
#define LUX_THRESHOLD_LOW 50
void CheckLuxThreshold(float lux)
{
static uint8_t alarm_state = 0;
if(lux > LUX_THRESHOLD_HIGH || lux < LUX_THRESHOLD_LOW) {
if(!alarm_state) {
Buzzer_Beep(3); // 蜂鸣器响3声
alarm_state = 1;
}
} else {
alarm_state = 0;
}
}
数据记录功能:添加SD卡模块,定期记录光照数据,便于后期分析。
无线传输:集成蓝牙或WiFi模块,将光照数据发送到手机或云平台。
自动调节系统:根据光照强度自动控制窗帘、灯光等设备,实现智能家居应用。
多传感器融合:结合温湿度传感器,打造更全面的环境监测系统。