1. 项目概述:当STM32遇上IIC与光照传感器
在嵌入式开发领域,IIC(Inter-Integrated Circuit)总线因其简洁的两线制结构和灵活的多主从配置,成为传感器通信的首选方案之一。这次我们要用STM32通过软件模拟IIC的方式驱动BH1750光照传感器,这不仅是学习通信协议的绝佳案例,更是理解底层时序的实战机会。
BH1750作为数字式环境光强度传感器,通过IIC接口输出16位精度的光照度数据(单位lx),无需额外AD转换电路。而软件模拟IIC相比硬件IIC的最大优势在于:
- 不依赖特定引脚,可灵活配置任意GPIO
- 规避硬件IIC可能存在的兼容性问题
- 深度掌握通信协议底层细节
提示:BH1750有连续测量和单次测量两种模式,功耗差异显著。在电池供电场景建议使用单次模式(One-time measurement)
2. 核心硬件与通信协议解析
2.1 BH1750传感器关键特性
| 参数 | 规格 |
|---|---|
| 测量范围 | 1-65535 lx |
| 分辨率 | 0.5 lx (高精度模式) |
| 供电电压 | 2.4V-3.6V |
| IIC地址 | 0x23(ADDR=L)/0x5C(ADDR=H) |
| 典型电流 | 120μA(连续测量模式) |
传感器内部寄存器结构精简,主要通过发送指令码控制工作模式:
c复制#define BH1750_POWER_DOWN 0x00 // 进入低功耗模式
#define BH1750_POWER_ON 0x01 // 唤醒传感器
#define BH1750_RESET 0x07 // 重置数据寄存器
#define BH1750_CONT_H_MODE 0x10 // 连续高分辨率模式
#define BH1750_ONE_H_MODE 0x20 // 单次高分辨率模式
2.2 IIC协议时序要点拆解
软件模拟IIC需要严格遵循的时序参数(以标准模式100kHz为例):
- 起始条件:SCL高电平时SDA出现下降沿
- 停止条件:SCL高电平时SDA出现上升沿
- 数据有效性:SCL高电平期间SDA必须保持稳定
- 应答周期:每个字节传输后接收方拉低SDA
注意:STM32的GPIO翻转速度远高于IIC总线速率,需插入适当延时满足时序要求。例如标准模式下SCL周期应≥10μs(对应100kHz)
3. 软件模拟IIC驱动实现
3.1 GPIO初始化配置
以STM32F103C8T6为例,配置PB6(SCL)、PB7(SDA)为开漏输出模式:
c复制void IIC_GPIO_Init(void) {
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOB_CLK_ENABLE();
// SCL配置
GPIO_InitStruct.Pin = GPIO_PIN_6;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD; // 开漏输出
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
// SDA配置(初始化为输入模式)
GPIO_InitStruct.Pin = GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
// 初始状态置高
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6|GPIO_PIN_7, GPIO_PIN_SET);
}
3.2 关键时序函数实现
起始信号生成函数:
c复制void IIC_Start(void) {
SDA_OUT(); // 切换为输出模式
IIC_SDA_HIGH();
IIC_SCL_HIGH();
delay_us(5); // 保持时间>4.7μs
IIC_SDA_LOW();
delay_us(5);
IIC_SCL_LOW(); // 钳住总线
}
字节发送函数(含应答检测):
c复制uint8_t IIC_Send_Byte(uint8_t byte) {
uint8_t i, ack;
SDA_OUT();
for(i=0; i<8; i++) {
IIC_SCL_LOW();
delay_us(2);
if(byte & 0x80) IIC_SDA_HIGH();
else IIC_SDA_LOW();
delay_us(2);
IIC_SCL_HIGH();
delay_us(5); // 确保高电平周期>4μs
byte <<= 1;
}
// 等待应答
IIC_SCL_LOW();
SDA_IN(); // 切换为输入检测ACK
delay_us(2);
IIC_SCL_HIGH();
delay_us(2);
ack = READ_SDA();
IIC_SCL_LOW();
SDA_OUT();
return ack; // 0:应答成功 1:无应答
}
4. BH1750驱动完整实现
4.1 传感器初始化流程
c复制void BH1750_Init(void) {
IIC_Start();
IIC_Send_Byte(BH1750_ADDR_WRITE); // 发送设备地址+写标志
IIC_Send_Byte(BH1750_POWER_ON); // 唤醒传感器
IIC_Stop();
IIC_Start();
IIC_Send_Byte(BH1750_ADDR_WRITE);
IIC_Send_Byte(BH1750_CONT_H_MODE); // 设置连续高精度模式
IIC_Stop();
HAL_Delay(180); // 等待首次测量完成
}
4.2 光照数据读取实现
c复制float BH1750_Read_Light(void) {
uint8_t buf[2];
uint16_t val = 0;
float lux;
IIC_Start();
IIC_Send_Byte(BH1750_ADDR_WRITE);
IIC_Send_Byte(BH1750_ONE_H_MODE); // 触发单次测量
IIC_Stop();
HAL_Delay(180); // 等待测量完成
IIC_Start();
IIC_Send_Byte(BH1750_ADDR_READ); // 发送设备地址+读标志
buf[0] = IIC_Read_Byte(1); // 读取高字节(发送ACK)
buf[1] = IIC_Read_Byte(0); // 读取低字节(发送NACK)
IIC_Stop();
val = (buf[0]<<8) | buf[1];
lux = val / 1.2; // 转换为lux值
return lux;
}
5. 调试技巧与性能优化
5.1 常见问题排查指南
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 读取值始终为0 | 1. 电源未接通 2. IIC地址错误 |
1. 检查VCC/GND连接 2. 确认ADDR引脚电平 |
| 数据波动大 | 1. 电源噪声 2. 未滤波 |
1. 增加0.1μF去耦电容 2. 软件均值滤波 |
| 通信超时 | 1. 上拉电阻过大 2. 时序不符 |
1. 使用4.7kΩ上拉 2. 用逻辑分析仪抓时序 |
5.2 软件优化策略
- 中断安全设计:
c复制void IIC_Write_Reg(uint8_t reg, uint8_t val) {
__disable_irq(); // 关闭中断保证时序完整
IIC_Start();
IIC_Send_Byte(DEV_ADDR);
IIC_Send_Byte(reg);
IIC_Send_Byte(val);
IIC_Stop();
__enable_irq();
}
- 动态延时调整:
c复制void IIC_Delay(uint8_t mode) {
if(mode == FAST_MODE) delay_us(1); // 400kHz模式
else delay_us(5); // 100kHz标准模式
}
- 错误重试机制:
c复制uint8_t IIC_Write_With_Retry(uint8_t addr, uint8_t data, uint8_t retry) {
while(retry--) {
if(IIC_Send_Byte(addr) == 0 && IIC_Send_Byte(data) == 0) {
return 0; // 成功
}
IIC_Stop();
HAL_Delay(1);
}
return 1; // 失败
}
6. 项目进阶与扩展
6.1 多传感器组网方案
通过IIC地址切换实现多个BH1750并联:
c复制#define BH1750_ADDR_LOW 0x23 // ADDR引脚接地
#define BH1750_ADDR_HIGH 0x5C // ADDR引脚接VCC
float Read_Multi_Sensors(void) {
float lux1, lux2;
// 读取第一个传感器
BH1750_Set_Address(BH1750_ADDR_LOW);
lux1 = BH1750_Read_Light();
// 读取第二个传感器
BH1750_Set_Address(BH1750_ADDR_HIGH);
lux2 = BH1750_Read_Light();
return (lux1 + lux2) / 2; // 返回平均值
}
6.2 低功耗设计技巧
- 间歇工作模式:
c复制void Power_Saving_Mode(void) {
BH1750_Send_Cmd(BH1750_POWER_DOWN);
HAL_Delay(1000); // 休眠1秒
BH1750_Send_Cmd(BH1750_POWER_ON);
BH1750_Send_Cmd(BH1750_ONE_H_MODE);
HAL_Delay(180); // 唤醒并测量
}
- 动态分辨率调整:
c复制void Auto_Adjust_Resolution(float lux) {
if(lux < 10) {
BH1750_Set_Mode(BH1750_CONT_H_MODE2); // 0.11lx分辨率
} else {
BH1750_Set_Mode(BH1750_CONT_H_MODE); // 1lx分辨率
}
}
在实际项目中,我发现软件模拟IIC最关��的三个要点是:时序严格匹配、异常处理完善、资源占用优化。通过合理设置GPIO速度等级和延时参数,可以兼顾稳定性和效率。对于需要频繁读写的场景,建议将关键函数放在RAM中执行以提升速度,具体可通过__attribute__((section(".RAMcode")))实现。
