STM32 进阶封神之路(三十):IIC 通信深度实战 —— 软件模拟 IIC + 光照传感器(BH1750)全解析(底层时序 + 代码落地)

0 阅读19分钟

STM32 进阶封神之路(三十):IIC 通信深度实战 —— 软件模拟 IIC + 光照传感器(BH1750)全解析(底层时序 + 代码落地)

上一篇我们完成了 OneNET 物模型 MQTT 的工业级实现,这一篇聚焦嵌入式通信中高频使用的IIC(I2C)协议—— 通过软件模拟 IIC 总线,驱动 BH1750 光照传感器,实现光照强度采集。

IIC 协议以 “两根线控制多设备” 的优势,广泛应用于传感器、EEPROM、OLED 等外设,而软件模拟 IIC 无需依赖 STM32 硬件 IIC 外设,灵活性更高、移植性更强,是嵌入式开发的核心技能。本文从 IIC 协议底层时序、软件模拟实现、BH1750 传感器驱动、数据采集与校准,到实战排障,全程超详细拆解,帮你彻底掌握软件 IIC + 光照采集的全流程!

一、IIC 协议核心认知:为什么它是 “多设备通信利器”?

1. IIC 协议核心定位与优势

IIC(Inter-Integrated Circuit)是飞利浦(NXP)推出的两线式串行总线,仅需 SDA(串行数据线)和 SCL(串行时钟线)两根线,即可实现主控制器与多个从设备的双向通信,核心优势如下:

  • 布线简单:仅需 2 根线,支持多主多从模式,减少 PCB 板布线难度;
  • 资源占用少:STM32 无需启用硬件 IIC 外设,仅用两个 GPIO 即可模拟;
  • 远距离通信:支持最长 10 米通信(低速模式),适配工业场景;
  • 多设备兼容:同一总线可挂载多个 IIC 设备(通过从地址区分),如同时连接 BH1750(光照)、AT24C02(EEPROM)、OLED 屏幕。

2. IIC 协议核心概念(必掌握)

(1)总线组成
  • SDA(Serial Data) :双向串行数据线,用于传输数据和地址;
  • SCL(Serial Clock) :串行时钟线,由主设备(STM32)产生,同步数据传输;
  • 上拉电阻:SDA 和 SCL 必须外接 4.7KΩ~10KΩ 上拉电阻,默认状态为高电平(IIC 总线的空闲状态);
  • 主设备(Master) :发起通信、产生时钟、控制通信流程的设备(如 STM32);
  • 从设备(Slave) :响应主设备指令、被动传输数据的设备(如 BH1750)。
(2)从地址(Slave Address)

IIC 从设备都有唯一的 7 位或 10 位从地址,用于主设备识别总线上的目标设备。BH1750 的默认 7 位从地址为0x46(ADDR 引脚接 GND 时)或0x23(ADDR 引脚接 VCC 时),实战中需根据硬件接线确认。

(3)通信速率

IIC 支持三种通信速率,BH1750 适配所有速率,实战中推荐使用标准速率:

表格

速率等级速率范围适用场景
标准模式(Standard-mode)100Kbps大多数 IIC 设备,兼容性好
快速模式(Fast-mode)400Kbps高速通信场景(如 OLED 屏幕)
高速模式(High-speed mode)3.4Mbps高端设备,需硬件支持

3. IIC 协议核心时序(软件模拟的基础)

IIC 通信的所有操作(起始信号、数据传输、应答信号、停止信号)都基于严格的时序,软件模拟的本质就是通过 GPIO 电平翻转,精准复现这些时序。

(1)起始信号(Start Condition)

主设备发起通信的标志,时序要求:

  • SCL 保持高电平时,SDA 从高电平拉低(下降沿);
  • 起始信号后,总线进入忙状态,从设备开始监听地址。
(2)停止信号(Stop Condition)

主设备结束通信的标志,时序要求:

  • SCL 保持高电平时,SDA 从低电平拉高(上升沿);
  • 停止信号后,总线进入空闲状态。
(3)数据传输时序
  • 数据在 SCL 的高电平期间被从设备采样,因此 SDA 电平必须在 SCL 低电平期间变化;
  • 每次传输 8 位数据(1 字节),传输完成后从设备发送应答信号(ACK)或非应答信号(NACK)。
(4)应答信号(ACK)与非应答信号(NACK)
  • ACK:从设备接收完 8 位数据后,在第 9 个 SCL 时钟周期将 SDA 拉低,表示数据接收成功;
  • NACK:从设备接收完 8 位数据后,SDA 保持高电平,表示数据接收失败或无更多数据传输(主设备读取最后一字节时需发送 NACK)。

二、软件模拟 IIC 底层实现(STM32 GPIO 模拟)

软件模拟 IIC 的核心是通过 GPIO 的输入 / 输出模式切换,复现 IIC 时序。以下基于 STM32F103C8T6,选择 PB6(SCL)和 PB7(SDA)作为模拟 IIC 引脚,实现通用软件 IIC 驱动。

1. 硬件准备与 GPIO 配置

(1)硬件接线

表格

STM32 引脚IIC 总线外设连接备注
PB6SCLBH1750_SCL外接 4.7KΩ 上拉电阻到 3.3V
PB7SDABH1750_SDA外接 4.7KΩ 上拉电阻到 3.3V
3.3VVCCBH1750_VCCBH1750 工作电压 3.3V
GNDGNDBH1750_GND共地
GNDADDRBH1750_ADDR从地址设为 0x46(可接 VCC 改为 0x23)
(2)GPIO 初始化代码

c

运行

#include "stm32f10x.h"
#include "delay.h"

// 软件IIC引脚定义
#define IIC_SCL_PIN    GPIO_Pin_6
#define IIC_SDA_PIN    GPIO_Pin_7
#define IIC_GPIO_PORT  GPIOB
#define IIC_GPIO_CLK   RCC_APB2Periph_GPIOB

// 引脚电平操作宏定义(简化代码)
#define IIC_SCL_HIGH()  GPIO_SetBits(IIC_GPIO_PORT, IIC_SCL_PIN)  // SCL拉高
#define IIC_SCL_LOW()   GPIO_ResetBits(IIC_GPIO_PORT, IIC_SCL_PIN) // SCL拉低
#define IIC_SDA_HIGH()  GPIO_SetBits(IIC_GPIO_PORT, IIC_SDA_PIN)  // SDA拉高
#define IIC_SDA_LOW()   GPIO_ResetBits(IIC_GPIO_PORT, IIC_SDA_PIN) // SDA拉低
#define IIC_SDA_READ()  GPIO_ReadInputDataBit(IIC_GPIO_PORT, IIC_SDA_PIN) // 读取SDA电平

// GPIO初始化(SCL和SDA初始化为高电平)
void IIC_Init(void) {
    GPIO_InitTypeDef GPIO_InitStruct;

    // 使能GPIO时钟
    RCC_APB2PeriphClockCmd(IIC_GPIO_CLK, ENABLE);

    // 配置SCL和SDA为推挽输出(初始高电平)
    GPIO_InitStruct.GPIO_Pin = IIC_SCL_PIN | IIC_SDA_PIN;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;  // 推挽输出
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(IIC_GPIO_PORT, &GPIO_InitStruct);

    // 初始状态:SCL和SDA均为高电平(总线空闲)
    IIC_SCL_HIGH();
    IIC_SDA_HIGH();
    delay_ms(1); // 稳定总线
}

2. 核心时序函数实现

(1)起始信号(Start)

c

运行

// 发送IIC起始信号
void IIC_Start(void) {
    // SDA先拉高,再拉低(SCL保持高电平)
    IIC_SDA_HIGH();
    IIC_SCL_HIGH();
    delay_us(4); // 时序延时(根据速率调整,标准模式推荐≥4.7μs)

    IIC_SDA_LOW();
    delay_us(4); // 起始信号保持时间≥4μs

    IIC_SCL_LOW(); // SCL拉低,准备传输数据
}
(2)停止信号(Stop)

c

运行

// 发送IIC停止信号
void IIC_Stop(void) {
    // SDA先拉低,再拉高(SCL保持高电平)
    IIC_SDA_LOW();
    IIC_SCL_HIGH();
    delay_us(4); // 时序延时≥4μs

    IIC_SDA_HIGH();
    delay_us(4); // 停止信号保持时间≥4μs,释放总线
}
(3)应答信号(ACK)与非应答信号(NACK)

c

运行

// 主设备发送应答信号(ACK:0)
void IIC_Send_ACK(void) {
    IIC_SCL_LOW(); // SCL拉低,准备发送应答
    IIC_SDA_LOW(); // ACK:SDA拉低
    delay_us(2);

    IIC_SCL_HIGH(); // SCL拉高,从设备采样
    delay_us(4);

    IIC_SCL_LOW(); // SCL拉低,应答完成
}

// 主设备发送非应答信号(NACK:1)
void IIC_Send_NACK(void) {
    IIC_SCL_LOW(); // SCL拉低,准备发送非应答
    IIC_SDA_HIGH(); // NACK:SDA拉高
    delay_us(2);

    IIC_SCL_HIGH(); // SCL拉高,从设备采样
    delay_us(4);

    IIC_SCL_LOW(); // SCL拉低,非应答完成
}
(4)等待从设备应答(ACK)

c

运行

// 等待从设备发送应答信号,返回1=收到ACK,0=未收到ACK
uint8_t IIC_Wait_ACK(void) {
    uint8_t ack_flag = 1; // 初始化为未收到ACK
    IIC_SDA_HIGH(); // 释放SDA,由从设备控制电平
    delay_us(2);

    IIC_SCL_HIGH(); // SCL拉高,读取SDA电平
    delay_us(2);

    // 检测SDA电平,低电平表示收到ACK
    if (IIC_SDA_READ() == 0) {
        ack_flag = 0; // 收到ACK
    }

    IIC_SCL_LOW(); // SCL拉低,结束应答检测
    delay_us(2);
    return ack_flag;
}
(5)发送 1 字节数据

c

运行

// 主设备发送1字节数据(data:要发送的字节)
void IIC_Send_Byte(uint8_t data) {
    uint8_t i;
    IIC_SCL_LOW(); // SCL拉低,准备发送数据

    for (i = 0; i < 8; i++) {
        // 从最高位(bit7)开始传输
        if (data & 0x80) {
            IIC_SDA_HIGH();
        } else {
            IIC_SDA_LOW();
        }
        data <<= 1; // 左移一位,准备下一位数据
        delay_us(2);

        IIC_SCL_HIGH(); // SCL拉高,从设备采样
        delay_us(4);

        IIC_SCL_LOW(); // SCL拉低,准备传输下一位
        delay_us(2);
    }
}
(6)接收 1 字节数据

c

运行

// 主设备接收1字节数据,ack=1时发送ACK,ack=0时发送NACK
uint8_t IIC_Receive_Byte(uint8_t ack) {
    uint8_t i, data = 0;
    IIC_SDA_HIGH(); // 释放SDA,由从设备发送数据
    IIC_SCL_LOW(); // SCL拉低,准备接收数据

    for (i = 0; i < 8; i++) {
        data <<= 1; // 左移一位,接收下一位数据
        IIC_SCL_HIGH(); // SCL拉高,读取SDA电平
        delay_us(4);

        if (IIC_SDA_READ() == 1) {
            data |= 0x01; // 读取当前位数据
        }

        IIC_SCL_LOW(); // SCL拉低,准备接收下一位
        delay_us(2);
    }

    // 接收完成后,发送ACK或NACK
    if (ack == 1) {
        IIC_Send_ACK();
    } else {
        IIC_Send_NACK();
    }

    return data;
}

3. 软件 IIC 通用 API(适配所有 IIC 设备)

基于核心时序函数,封装 IIC 设备的读写 API,方便后续驱动 BH1750 等外设:

c

运行

// 向IIC从设备指定寄存器写入数据
// slave_addr:从设备地址(7位)
// reg_addr:寄存器地址
// data:要写入的数据
void IIC_Write_Reg(uint8_t slave_addr, uint8_t reg_addr, uint8_t data) {
    IIC_Start(); // 起始信号

    // 发送从设备地址+写命令(最低位0表示写)
    IIC_Send_Byte((slave_addr << 1) | 0x00);
    if (IIC_Wait_ACK() != 0) {
        IIC_Stop();
        return; // 未收到ACK,通信失败
    }

    // 发送寄存器地址
    IIC_Send_Byte(reg_addr);
    IIC_Wait_ACK();

    // 发送数据
    IIC_Send_Byte(data);
    IIC_Wait_ACK();

    IIC_Stop(); // 停止信号
    delay_ms(1); // 稳定总线
}

// 从IIC从设备指定寄存器读取数据
// slave_addr:从设备地址(7位)
// reg_addr:寄存器地址
// len:要读取的数据长度
// buf:存储读取数据的缓冲区
void IIC_Read_Reg(uint8_t slave_addr, uint8_t reg_addr, uint8_t len, uint8_t *buf) {
    uint8_t i;
    IIC_Start(); // 起始信号

    // 发送从设备地址+写命令(先写寄存器地址)
    IIC_Send_Byte((slave_addr << 1) | 0x00);
    if (IIC_Wait_ACK() != 0) {
        IIC_Stop();
        return;
    }

    // 发送寄存器地址
    IIC_Send_Byte(reg_addr);
    IIC_Wait_ACK();

    IIC_Start(); // 重新发送起始信号(切换为读模式)

    // 发送从设备地址+读命令(最低位1表示读)
    IIC_Send_Byte((slave_addr << 1) | 0x01);
    if (IIC_Wait_ACK() != 0) {
        IIC_Stop();
        return;
    }

    // 读取len字节数据,最后一字节发送NACK
    for (i = 0; i < len; i++) {
        if (i == len - 1) {
            buf[i] = IIC_Receive_Byte(0); // 最后一字节,发送NACK
        } else {
            buf[i] = IIC_Receive_Byte(1); // 非最后一字节,发送ACK
        }
    }

    IIC_Stop(); // 停止信号
    delay_ms(1);
}

三、BH1750 光照传感器驱动实现

BH1750 是一款数字式光照强度传感器,支持 IIC 接口,测量范围 1~65535 lx(勒克斯),精度 ±20%,无需校准即可使用,广泛应用于智能照明、环境监测等场景。

1. BH1750 核心参数与寄存器

(1)核心参数
  • 测量范围:1~65535 lx;
  • 分辨率:1 lx(高分辨率模式);
  • 响应时间:120ms(默认模式);
  • 供电电压:2.4V~3.6V(推荐 3.3V);
  • IIC 从地址:0x23(ADDR=VCC)或 0x46(ADDR=GND)。
(2)核心控制寄存器(指令)

BH1750 通过发送指令控制工作模式,常用指令如下:

表格

指令代码指令名称工作模式测量时间分辨率
0x01断电模式低功耗,不测量--
0x03高分辨率模式 1单次测量,测量后断电120ms1 lx
0x13高分辨率模式 2单次测量,测量后断电120ms0.5 lx
0x23低分辨率模式单次测量,测量后断电16ms4 lx
0x21高分辨率模式 1(连续)持续测量,测量后不断电120ms1 lx
0x31高分辨率模式 2(连续)持续测量,测量后不断电120ms0.5 lx
0x10低分辨率模式(连续)持续测量,测量后不断电16ms4 lx

实战中推荐使用高分辨率模式 1(0x03) ,兼顾精度和功耗。

2. BH1750 驱动代码实现

c

运行

#include "bh1750.h"
#include "iic.h"
#include "delay.h"

// BH1750从地址(根据硬件接线修改,此处为ADDR=GND)
#define BH1750_SLAVE_ADDR  0x46

// BH1750指令定义
#define BH1750_POWER_DOWN  0x00 // 断电模式
#define BH1750_POWER_ON     0x01 // 上电模式(等待指令)
#define BH1750_HIGH_RES1    0x03 // 高分辨率模式1(单次)
#define BH1750_HIGH_RES2    0x13 // 高分辨率模式2(单次)
#define BH1750_LOW_RES      0x23 // 低分辨率模式(单次)

// BH1750初始化(上电+设置工作模式)
void BH1750_Init(void) {
    IIC_Init(); // 初始化软件IIC
    delay_ms(10);

    // 发送上电指令
    IIC_Start();
    IIC_Send_Byte((BH1750_SLAVE_ADDR << 1) | 0x00); // 写模式
    if (IIC_Wait_ACK() != 0) {
        IIC_Stop();
        printf("BH1750上电失败!\r\n");
        return;
    }
    IIC_Send_Byte(BH1750_POWER_ON);
    IIC_Wait_ACK();
    IIC_Stop();
    delay_ms(10);

    // 设置高分辨率模式1
    IIC_Start();
    IIC_Send_Byte((BH1750_SLAVE_ADDR << 1) | 0x00);
    IIC_Wait_ACK();
    IIC_Send_Byte(BH1750_HIGH_RES1);
    IIC_Wait_ACK();
    IIC_Stop();
    printf("BH1750初始化成功!\r\n");
}

// 读取BH1750光照强度(单位:lx)
float BH1750_Read_Lux(void) {
    uint8_t data_buf[2]; // BH1750输出2字节数据
    uint16_t raw_data = 0;
    float lux = 0.0;

    // 等待测量完成(高分辨率模式需要120ms)
    delay_ms(120);

    // 读取2字节数据(从BH1750数据寄存器)
    IIC_Read_Reg(BH1750_SLAVE_ADDR, 0x00, 2, data_buf);

    // 拼接数据(BH1750数据为16位,高字节在前,低字节在后)
    raw_data = (data_buf[0] << 8) | data_buf[1];

    // 计算光照强度(高分辨率模式1:lux = 原始数据 / 1.2)
    lux = raw_data / 1.2;

    // 测量完成后,发送断电指令(降低功耗)
    IIC_Start();
    IIC_Send_Byte((BH1750_SLAVE_ADDR << 1) | 0x00);
    IIC_Wait_ACK();
    IIC_Send_Byte(BH1750_POWER_DOWN);
    IIC_Wait_ACK();
    IIC_Stop();

    return lux;
}

3. 驱动关键说明

  • 数据格式:BH1750 的光照数据以 16 位二进制形式输出,高字节(data_buf [0])和低字节(data_buf [1])拼接后得到原始数据;
  • 强度计算:高分辨率模式 1 下,光照强度(lx)= 原始数据 / 1.2,高分辨率模式 2 下为原始数据 / 2.4;
  • 功耗优化:单次测量完成后,发送断电指令(0x00),降低传感器功耗,适合电池供电场景。

四、实战整合:光照强度采集 + 串口打印

1. 主函数实现

c

运行

#include "stm32f10x.h"
#include "usart.h"
#include "bh1750.h"
#include "delay.h"

int main(void) {
    float lux_value = 0.0;

    // 初始化系统时钟(72MHz)
    SystemInit();
    // 初始化串口1(115200bps,用于打印数据)
    USART1_Init(115200);
    // 初始化BH1750光照传感器
    BH1750_Init();

    printf("STM32软件IIC+BH1750光照采集系统\r\n");
    printf("=======================================\r\n\r\n");

    while (1) {
        // 读取光照强度
        lux_value = BH1750_Read_Lux();

        // 打印结果(保留1位小数)
        printf("当前光照强度:%.1f lx\r\n", lux_value);

        // 根据光照强度分级提示
        if (lux_value < 10) {
            printf("光照状态:极弱(适合夜间照明)\r\n");
        } else if (lux_value < 100) {
            printf("光照状态:弱(适合室内昏暗环境)\r\n");
        } else if (lux_value < 1000) {
            printf("光照状态:中等(适合室内正常环境)\r\n");
        } else if (lux_value < 10000) {
            printf("光照状态:强(适合室外阴天)\r\n");
        } else {
            printf("光照状态:极强(适合室外晴天)\r\n");
        }

        printf("---------------------------------------\r\n\r\n");
        delay_ms(2000); // 每2秒采集一次
    }
}

2. 串口打印效果

plaintext

STM32软件IIC+BH1750光照采集系统
=======================================

BH1750初始化成功!
当前光照强度:320.5 lx
光照状态:中等(适合室内正常环境)
---------------------------------------

当前光照强度:8500.3 lx
光照状态:强(适合室外阴天)
---------------------------------------

当前光照强度:12000.7 lx
光照状态:极强(适合室外晴天)
---------------------------------------

五、软件 IIC+BH1750 常见问题与避坑指南

1. BH1750 初始化失败(未收到 ACK)

  • 原因 1:硬件接线错误(SDA/SCL 接反、未接共地、上拉电阻缺失);解决:重新检查接线,确保 SDA 接 PB7、SCL 接 PB6,必须接共地和 4.7KΩ 上拉电阻;
  • 原因 2:从地址错误(ADDR 引脚接线与代码定义不一致);解决:若 ADDR 接 VCC,代码中从地址改为0x23;接 GND 则为0x46
  • 原因 3:传感器供电不足(电压低于 2.4V);解决:确保传感器供电为 3.3V,避免与其他高功耗设备共电源;
  • 原因 4:时序延时过短(未满足 IIC 时序要求);解决:增大delay_us延时(如从 2μs 改为 4μs),尤其是标准模式下。

2. 读取光照强度为 0 或固定值

  • 原因 1:传感器未进入测量模式(指令发送错误);解决:检查 BH1750 指令代码,确保初始化时发送了BH1750_POWER_ONBH1750_HIGH_RES1指令;
  • 原因 2:测量时间不足(未等待 120ms);解决:读取数据前必须延时≥120ms(高分辨率模式),否则传感器未完成测量;
  • 原因 3:数据拼接错误(高字节和低字节顺序颠倒);解决:确保raw_data = (data_buf[0] << 8) | data_buf[1],BH1750 是高字节在前;
  • 原因 4:传感器被遮挡(无光照进入);解决:移除传感器表面遮挡物,确保光照能正常入射。

3. 光照强度波动过大

  • 原因 1:测量模式选择错误(低分辨率模式);解决:切换为高分辨率模式 1(0x03)或模式 2(0x13),提高测量精度;
  • 原因 2:环境光照本身波动(如室内灯光闪烁);解决:多次采样取平均(如连续读取 3 次,取平均值);
  • 原因 3:电源纹波干扰;解决:在传感器 VCC 和 GND 之间并联 0.1μF 陶瓷电容,减少电源干扰。

4. 软件 IIC 通信不稳定(偶尔失败)

  • 原因 1:GPIO 输出模式未配置为推挽输出;解决:确保 IIC 引脚配置为GPIO_Mode_Out_PP,而非开漏输出;
  • 原因 2:延时函数不精准(系统时钟与延时不匹配);解决:根据 STM32 主频校准delay_us函数(如 72MHz 主频下,1μs≈9 个空循环);
  • 原因 3:总线冲突(同一 IIC 总线挂载多个设备);解决:确保每个从设备的地址唯一,通信时避免同时操作多个设备;
  • 原因 4:SDA/SCL 引脚被其他外设占用;解决:选择未使用的 GPIO 引脚(如 PB6、PB7),避免与 USART、ADC 等外设冲突。

六、进阶优化:提升系统稳定性与实用性

1. 多次采样平均滤波

为减少光照强度波动,可增加多次采样取平均的功能:

c

运行

// 多次采样取平均(n为采样次数)
float BH1750_Read_Lux_Avg(uint8_t n) {
    float sum = 0.0;
    uint8_t i;
    for (i = 0; i < n; i++) {
        sum += BH1750_Read_Lux();
        delay_ms(50);
    }
    return sum / n;
}

2. 中断触发采集

结合定时器中断,实现定时采集,避免主循环阻塞:

c

运行

// 定时器2初始化(1秒中断一次)
void TIM2_Init(void) {
    TIM_TimeBaseInitTypeDef TIM_InitStruct;
    NVIC_InitTypeDef NVIC_InitStruct;

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

    TIM_InitStruct.TIM_Period = 9999;
    TIM_InitStruct.TIM_Prescaler = 7199;
    TIM_InitStruct.TIM_ClockDivision = 0;
    TIM_InitStruct.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM2, &TIM_InitStruct);

    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);

    NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn;
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStruct);

    TIM_Cmd(TIM2, ENABLE);
}

// 定时器2中断服务函数(1秒采集一次)
void TIM2_IRQHandler(void) {
    if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) {
        lux_avg = BH1750_Read_Lux_Avg(3); // 3次采样平均
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    }
}

3. 多 IIC 设备共存(扩展应用)

在同一软件 IIC 总线上挂载 AT24C02(EEPROM),实现光照数据存储:

c

运行

// AT24C02写入光照数据
void AT24C02_Write_Lux(uint16_t addr, float lux) {
    uint8_t data[4];
    // 将float转换为4字节存储
    memcpy(data, &lux, 4);
    IIC_Write_Reg(AT24C02_SLAVE_ADDR, addr, data[0]);
    IIC_Write_Reg(AT24C02_SLAVE_ADDR, addr+1, data[1]);
    IIC_Write_Reg(AT24C02_SLAVE_ADDR, addr+2, data[2]);
    IIC_Write_Reg(AT24C02_SLAVE_ADDR, addr+3, data[3]);
}

// AT24C02读取光照数据
float AT24C02_Read_Lux(uint16_t addr) {
    uint8_t data[4];
    float lux;
    IIC_Read_Reg(AT24C02_SLAVE_ADDR, addr, 4, data);
    memcpy(&lux, data, 4);
    return lux;
}

七、总结:软件 IIC+BH1750 核心要点与进阶方向

1. 核心要点回顾

  • 软件 IIC 本质:通过 GPIO 电平翻转复现 IIC 时序,关键是起始 / 停止信号、应答机制的精准实现;
  • BH1750 驱动核心:正确发送上电、测量指令,拼接 16 位数据并转换为光照强度;
  • 避坑关键:硬件接线(上拉电阻 + 共地)、从地址匹配、时序延时充足、测量时间等待;
  • 实用技巧:多次采样平均、中断触发采集、多 IIC 设备共存,提升系统稳定性和扩展性。

2. 进阶学习方向

  • 硬件 IIC 驱动:学习 STM32 硬件 IIC 外设配置,对比软件模拟的优缺点;
  • 低功耗优化:结合 STM32 低功耗模式,实现传感器定时唤醒采集,延长电池续航;
  • 无线传输:将光照数据通过 ESP8266 上传到 OneNET 云平台,实现远程监控;
  • 多传感器融合:在同一 IIC 总线挂载 BH1750(光照)、BME280(温湿度气压),实现多环境参数采集;
  • 阈值告警:设置光照强度阈值,超标时通过 LED 或蜂鸣器触发告警。

掌握软件 IIC+BH1750 后,你已具备 IIC 协议的底层开发能力,可轻松驱动各类 IIC 外设。下一篇我们将学习 STM32 的 SPI 通信协议,聚焦高速数据传输场景(如 SPI 闪存、触摸屏)!