STM32中的I2C协议与BH1750光照强度传感器详解
BH1750接线及效果
本文章主要介绍I2C协议和固件库硬件I2C实现读取BH1750光照强度传感器
效果演示:

OLED显示的Sun值为BH1750传感器读取到的数值
接线方式
用杜邦线连接板子与OLED显示屏:
用杜邦线连接板子与OLED显示屏:
I2C介绍(什么是i2c)
I2C作为STM32的重要通信外设,有着以下特点:
- IIC(Inter-Integrated Circuit)是 IIC Bus 简称,中文叫集成电路总线。
- IIC使用两根信号线进行通信:一根时钟线SCL,一根数据线SDA。
- 由飞利浦公司开发的一种通用数据总线。
- 一种同步半双工的串行通信总线。
- 支持使用多主多从的架构。
I2C基本时序(i2c的六个基本时序状态)
I2C的每个传输指令由六个基本时序组成,每个传输指令都由起始时序、发送一个字节、接受从机应答位和终止时序组成,所以理解了I2C基本时序也就大致理解了I2C的传输流程。
起始时序

起始条件:SCL高电平期间,SDA从高电平切换到低电平
该时序的作用是向从机发送起始信号,表明主机要发送数据或指令
终止时序

终止条件:SCL高电平期间,SDA从低电平切换到高电平
该时序的作用是向从机发送停止信号,表明主机要结束当前的发送
发送一个字节

SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后释放SCL(SCL为高电平),从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节
接收一个字节

SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后释放SCL(SCL为高电平),主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA)
发送应答

主机在接收完一个字节之后,在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答
接收应答

主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)
I2C传输时序(基于7位地址)
指定地址读

- 该命令由基本时序单元拼接而成。
- 发送起始时序->发送设备地址->接收设备应答位->发送设备寄存器地址->接收设备应答位->发送写入数据->接收设备应答位->发送停止时序
- 发送设备地址一般为7位地址+一位读写位,一共8位。最后一位为读写位,0表示读,1表示写
- 设备地址和设备寄存器地址请参考对应设备的数据手册
当前地址读

- 该命令由基本时序单元拼接而成。
- 发送起始时序->发送设备地址->接收设备应答位->发送写入数据->接收设备应答位->发送停止时序
- 发送设备地址一般为7位地址+一位读写位,一共8位。最后一位为读写位,0表示读,1表示写
- 使用当前地址读功能会调用目标设备的地址指针,该指针存储在设备的寄存器中,该指针指向设备寄存器的某个地址。当被调用后指针会自加1,例如当前指向0x09,调用后会指向0x0A。
- 设备地址和设备寄存器地址请参考对应设备的数据手册
指定地址写

- 该命令由基本时序单元拼接而成。
- 发送起始时序->发送设备地址->接收设备应答位->发送设备寄存器地址->接收设备应答位->发送起始时序->发送写入数据->接收设备应答位->发送停止时序
- 发送设备地址一般为7位地址+一位读写位,一共8位。最后一位为读写位,0表示读,1表示写
- 注意:在发送完寄存器地址后需要重新发送一次起始时序。
- 在读取完一个字节后如果不发送停止时序,则可以持续读取设备数据
- 设备地址和设备寄存器地址请参考对应设备的数据手册
I2C标志位顺序
发送顺序

根据上图我们可以写出I2C的伪代码发送流程:
I2C发送启动标志;
While(等待标志位EV5);
I2C发送从机地址;
While(等待标志位EV6);
I2C发送从机寄存器地址;
While(等待标志位EV8);
I2C发送数据;
While(等待标志位EV8_2);
I2C发送停止标志;
注意:
接收顺序

根据上图我们可以写出I2C的伪代码接收流程:
I2C发送启动标志;
While(等待标志位EV5);
I2C发送从机地址;
While(等待标志位EV6);
I2C发送寄存器地址;
While(等待标志位EV8_2);
I2C重新发送启动标志;
While(等待标志位EV5);
I2C发送从机地址;
While(等待标志位EV6);
I2C发送停止标志;
While(等待标志位EV7);
I2C接收数据;
注意:
BH1750介绍
产品介绍
BH1750FVI 是一种用于两线式串行总线接口的数字型光强度传感器集成电路。这种集成电路可以根据收集的光线强度数据来调整液晶或者键盘背景灯的亮度。利用它的高分辨率可以探测较大范围的光强度变化。( 1lx-65535lx)
产品特点
1. 支持 I2C BUS 接口(f/s Mode Support)。
2. 接近视觉灵敏度的光谱灵敏度特性(峰值灵敏度波长典型值:560nm)。
3. 输出对应亮度的数字值。
4. 对应广泛的输入光范围(相当于 1-65535lx)。
5. 通过降低功率功能,实现低电流化。
6. 通过 50Hz/60Hz 除光噪音功能实现稳定的测定
7. 支持 1.8V 逻辑输入接口。
8. 无需其他外部件。
9. 光源依赖性弱(白炽灯,荧光灯,卤素灯,白光 LED,日光灯)。
10. 有两种可选的 I2C slave 地址。
11. 可调的测量结果影响较大的因素为光入口大小。
12. 使用这种功能能计算 1.1 lx 到 100000 lx 马克斯/分钟的范围。
13. 最小误差变动在± 20%。
14. 受红外线影响很小
BH1750测量程序步骤

BH1750操作码
|
指令 |
功能代码 |
注释 |
|
断电 |
0000_0000 |
无激活状态。 |
|
通电 |
0000_0001 |
等待测量指令。 |
|
重置 |
0000_0111 |
重置数字寄存器值,重置指令在断电模式下不起作用。 |
|
连续H分辨率模式 |
0001_0000 |
在1lx分辨率下开始测量。 测量时间一般为120ms。 |
|
连续H分辨率模式2 |
0001_0001 |
在0.5lx分辨率下开始测量。 测量时间一般为120ms。 |
|
连续L分辨率模式 |
0001_0011 |
在41lx分辨率下开始测量。 测量时间一般为16ms。 |
|
一次H分辨率模式 |
0010_0000 |
在1lx分辨率下开始测量。 测量时间一般为120ms。 测量后自动设置为断电模式。 |
|
一次H分辨率模式2 |
0010_0001 |
在0.5lx分辨率下开始测量。 测量时间一般为120ms。 测量后自动设置为断电模式。 |
|
一次L分辨率模式 |
0010_0011 |
在41lx分辨率下开始测量。 测量时间一般为16ms。 测量后自动设置为断电模式。 |
|
改变测量时间( 高位 ) |
01000_MT[7,6,5] |
改变测量时间 ※ 请参考“根据光学扇窗的影响调整测量结果。” |
|
改变测量时间( 低位 ) |
011_MT[4,3,2,1,0] |
改变测量时间 ※ 请参考“根据光学扇窗的影响调整测量结果。” |
注意:以上操作码位二进制,在使用时应转换为十六进制
BH1750时序


注意:
读取的数据值需要进行进制转换和计算
BH1750驱动流程解析

BH1750硬件代码
bh1750.h
#ifndef __BH1750_H
#define __BH1750_H
//bh1750引脚定义
#define BH1750_PROT GPIOB
#define BH1750_SCL GPIO_Pin_10
#define BH1750_SDA GPIO_Pin_11
#define BH1750_ADDRESS 0x46 //bh1750设备地址
#define BH1750_START 0x01 //bh1750启动指令
#define BH1750_STOP 0x00 //bh1750停止指令
#define BH1750_RESET 0x07 //bh1750重置指令
#define BH1750_H_MODE 0x10 //bh1750连续 H 分辨率模式指令
#define BH1750_H_MODE2 0x11 //bh1750连续 H 分辨率模式2指令
#define BH1750_L_MODE 0x13 //bh1750连续 L 分辨率模式指令
void BH1750_Init(void);
void BH1750_WriteByte(uint8_t RegAddress,uint8_t Data);
uint16_t BH1750_ReadData(uint8_t RegAddress);
#endif
bh1750.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "bh1750.h"
/**
* 函 数:BH1750初始化函数
* 参 数:无
* 返 回 值:无
*/
void BH1750_Init(void)
{
//开启GPIOB 和I2C2外设时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2,ENABLE);
//GPIOB结构体初始化
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; //开漏输出
GPIO_InitStructure.GPIO_Pin = BH1750_SCL | BH1750_SDA;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(BH1750_PROT,&GPIO_InitStructure);
//I2C2结构体初始化
I2C_InitTypeDef I2C_InitStructure;
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable; //使能应答位
I2C_InitStructure.I2C_ClockSpeed = 100000; //I2C时钟速度,最大400KHz
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; //时钟占空比2:1,在400kHz时有效
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C; //I2C主机模式
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; //STM32作为从机响应7位地址
I2C_InitStructure.I2C_OwnAddress1 = 0x00; //指定STM32作为从机时的地址
I2C_Init(I2C2,&I2C_InitStructure);
I2C_Cmd(I2C2,ENABLE);
}
/**
* 函 数:BH1750等待事件
* 参 数:同I2C_CheckEvent
* 返 回 值:无
*/
void BH1750_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{
uint32_t Timeout= 10000; //给定超时计数时间
while (I2C_CheckEvent(I2Cx, I2C_EVENT) != SUCCESS) //循环等待指定事件
{
Timeout --; //等待时,计数值自减
if (Timeout == 0) //自减到0后,等待超时
{
/*超时的错误处理代码,可以添加到此处*/
break; //跳出等待,不等了
}
}
}
/**
* 函 数:BH1750写一个字节函数
* 参 数:RegAddress 从机地址值
* Data 发送的字节数据
* 返 回 值:无
*/
void BH1750_WriteByte(uint8_t RegAddress,uint8_t Data)
{
I2C_GenerateSTART(I2C2, ENABLE); //硬件I2C生成起始条件
BH1750_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT); //等待EV5
I2C_Send7bitAddress(I2C2, BH1750_ADDRESS, I2C_Direction_Transmitter); //硬件I2C发送从机地址,方向为发送
BH1750_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED); //等待EV6
I2C_SendData(I2C2, RegAddress); //硬件I2C发送寄存器地址
BH1750_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING); //等待EV8
I2C_SendData(I2C2, Data); //硬件I2C发送数据
BH1750_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED); //等待EV8_2
I2C_GenerateSTOP(I2C2, ENABLE); //硬件I2C生成终止条件
}
/**
* 函 数:读取BH1750数据函数
* 参 数:RegAddress 从机地址值
* 返 回 值:Data 读取到的BH1750光强数据
*/
uint16_t BH1750_ReadData(uint8_t RegAddress)
{
uint16_t Data;
I2C_GenerateSTART(I2C2, ENABLE); //硬件I2C生成起始条件
BH1750_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT); //等待EV5
I2C_Send7bitAddress(I2C2, BH1750_ADDRESS, I2C_Direction_Transmitter); //硬件I2C发送从机地址,方向为发送
BH1750_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED); //等待EV6
I2C_SendData(I2C2, RegAddress); //硬件I2C发送寄存器地址
BH1750_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED); //等待EV8_2
I2C_GenerateSTART(I2C2, ENABLE); //硬件I2C生成重复起始条件
BH1750_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT); //等待EV5
I2C_Send7bitAddress(I2C2, BH1750_ADDRESS, I2C_Direction_Receiver); //硬件I2C发送从机地址,方向为接收
BH1750_WaitEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED); //等待EV6
BH1750_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED); //等待EV7
Data = I2C_ReceiveData(I2C2) << 8; //接收数据寄存器
I2C_AcknowledgeConfig(I2C2, DISABLE); //在接收最后一个字节之前提前将应答失能
I2C_GenerateSTOP(I2C2, ENABLE); //在接收最后一个字节之前提前申请停止条件
BH1750_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED); //等待EV7
Data |= I2C_ReceiveData(I2C2); //接收数据寄存器
I2C_AcknowledgeConfig(I2C2, ENABLE); //将应答恢复为使能,为了不影响后续可能产生的读取多字节操作
return Data;
}
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "bh1750.h"
void LED_Init(void);
uint16_t raw_light=0; //光强原始数据
float light_lux=0; //光强转换进制后的数据
int main(void)
{
OLED_Init();
BH1750_Init();
LED_Init();
OLED_Clear();
OLED_ShowString(1,1,"Sun:");
while (1)
{
BH1750_WriteByte(BH1750_START,0x00); //启动bh1750
BH1750_WriteByte(BH1750_H_MODE,0x00); //选择H模式
Delay_ms(180); //延迟180ms读取数据
raw_light = BH1750_ReadData(0x00)/1.2f; //读取数据并进行数据转换
light_lux = raw_light; //转换成int型数据
OLED_ShowNum(1,5,light_lux,5); //显示数据
if(light_lux < 20)
//点亮GPIOC PIN13
GPIO_WriteBit(GPIOC,GPIO_Pin_13,0);
else
GPIO_WriteBit(GPIOC,GPIO_Pin_13,1);
}
}
//初始化GPIOC
void LED_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
GPIO_InitTypeDef GPIO_InitStrcuture;
GPIO_InitStrcuture.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStrcuture.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStrcuture.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC,&GPIO_InitStrcuture);
}
作者:StrayFatCat