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 总线 | 外设连接 | 备注 |
|---|---|---|---|
| PB6 | SCL | BH1750_SCL | 外接 4.7KΩ 上拉电阻到 3.3V |
| PB7 | SDA | BH1750_SDA | 外接 4.7KΩ 上拉电阻到 3.3V |
| 3.3V | VCC | BH1750_VCC | BH1750 工作电压 3.3V |
| GND | GND | BH1750_GND | 共地 |
| GND | ADDR | BH1750_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 | 单次测量,测量后断电 | 120ms | 1 lx |
| 0x13 | 高分辨率模式 2 | 单次测量,测量后断电 | 120ms | 0.5 lx |
| 0x23 | 低分辨率模式 | 单次测量,测量后断电 | 16ms | 4 lx |
| 0x21 | 高分辨率模式 1(连续) | 持续测量,测量后不断电 | 120ms | 1 lx |
| 0x31 | 高分辨率模式 2(连续) | 持续测量,测量后不断电 | 120ms | 0.5 lx |
| 0x10 | 低分辨率模式(连续) | 持续测量,测量后不断电 | 16ms | 4 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_ON和BH1750_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 闪存、触摸屏)!