STM32和BH1750光照传感器和IIC总线通讯OLED显示程序源码,通过BH1750,光照传感器采集光照信息,通过oled显示光照值。 包括程序源码和原理图,程序源码注释详细需要的可以看下

刚玩STM32那会儿总想搞点有意思的传感器联动,最近用BH1750光照传感器配了个OLED屏,实测效果还不错。今天就带大家手搓这个光照监测小装置,从硬件连接到代码实现都会讲到,文末有完整工程自取。

先看硬件接线(原理图其实就几条线):BH1750和0.96寸OLED都是I2C设备,直接挂在同一组I2C总线上就行。STM32F103的PB6接SCL,PB7接SDA,记得给两个设备都加上上拉电阻(4.7K就行)。这里有个坑:BH1750的地址默认是0x23,如果遇到设备不响应记得用逻辑分析仪抓包确认。

上代码!先看I2C初始化的骚操作:

// 硬件I2C初始化
void I2C_Config(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    I2C_InitTypeDef I2C_InitStructure;
    
    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_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模式——必须用开漏!之前用推挽输出结果I2C总线直接卡死,折腾了半天才发现是这里的问题。

STM32和BH1750光照传感器和IIC总线通讯OLED显示程序源码,通过BH1750,光照传感器采集光照信息,通过oled显示光照值。 包括程序源码和原理图,程序源码注释详细需要的可以看下

BH1750的驱动代码有个小技巧,上电后要发个唤醒指令:

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
}

注意这里用的是0x23左移1位,因为I2C协议里地址位最后一位表示读写方向。连续模式下的数据读取更简单:

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;  // 根据手册转换公式
}

这里有个精度问题:BH1750原始数据单位是lux/1.2,所以最终要除以1.2。实测在室内灯光下数值在200-500lux之间,阳光下能到上万。

OLED显示部分用到了u8g2库,重点在数值刷新策略:

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);
    u8g2_SendBuffer(&u8g2);
    
    // 低功耗时可设置局部刷新
    if(lux > 1000) 
        u8g2_SetContrast(&u8g2, 255);  // 强光下提高亮度
    else
        u8g2_SetContrast(&u8g2, 120);
}

为了省电加了自动亮度调节——当光照足够时降低OLED背光。注意sprintf浮点数会占用较多资源,在资源紧张的单片机上可以考虑用整型运算。

主循环里控制采样频率很重要:

while(1)
{
    static uint32_t last = 0;
    if(HAL_GetTick() - last > 500)  // 500ms采样一次
    {
        float lux = BH1750_Read();
        OLED_Refresh(lux);
        last = HAL_GetTick();
        
        // 超过20000lux时闪屏警告
        if(lux > 20000) OLED_Blink(3);  
    }
    __WFI();  // 进入休眠省电
}

这里用到了WFI指令让CPU在空闲时休眠,实测整机电流从8mA降到了3mA左右。遇到强光照时的闪屏提示是个实用小功能,防止传感器过曝。

最后说几个踩过的坑:

  1. I2C上拉电阻不接或阻值太大会导致波形畸变
  2. BH1750的测量模式要选连续H分辨率模式(0x10)
  3. OLED的I2C地址可能因厂商不同有变化(常用0x3C或0x3D)
  4. 光照值突变时建议做滑动平均滤波

完整工程里包含了自适应I2C扫描、异常重连机制,实测在STM32F103C8T6上稳定运行。下次考虑加上光强阈值报警功能,用蜂鸣器做个智能光控装置应该挺有意思。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐