上手GY-30(BH1750)光强度传感器和相关程序代码

最近在写比赛的文档的时候,写到了BH1750的参数之类的,于是想着想都想了,不如写下来玩玩。
emmmm,这边用的是STM32F103C8T6的芯片的标准库,其他MCU也大同小异,如果是用hal库就更好了,无需管GPIO口初始化那堆零碎的事情了。

模块和芯片

而BH1750是一款数字型的光强传感器片上集成芯片,采用标准I2C总线协议与MCU进行链接。

GY-30模块的实质是BH1750,只是把外围诸如滤波和电容之类的电路整合进去了而已,其实都是用的BH1750芯片。

BH1750内部电路是由:光敏二极管、运算放大器、AD转换器等组成。光敏二极管通过光伏效应接收光信号产生电信号,经过运算放大后,由AD转换器采集电压数据并转换为数字信号,然后储存在寄存器之中。BH1750支持完全的I2C协议,使用I2C总线发送特定的控制位,即可读取光强度数据,亦可以修改BH1750的采集模式。

BH1750内部

引脚定义

BH1750支持且仅支持完整的I2C总线,采用四线传输方式,但由于BH1750模块本身可以从硬件对其设备地址进行修改,所以还多一个修改位。

名称 说明
VCC 接入 3.3V 电源正极
SCL I2C时钟同步总线
SDA I2C实际数据总线
ADDR I2C设备地址切换线;低电平:0x23,高电平:0x5C
GND 接地或者电源负极

I2C协议

前面说到,BH1750模块支持完整的I2C总线协议。I2C总线是由飞利浦公司推出的串行、半双工总线,它是总线通讯,可以在总线上最高挂载112个设备(含保留地址则为127个设备)。

I2C总线是一种多主机总线,连接在 I2C总线上的器件分为主机和从机。 主机有权发起和结束一次通信,从机只能被动呼叫。每个I2C设备有一个唯一的7bit地址,由于需要告知从机是读取还是写入,所以在实际使用中会增加一个bit作为控制,实际使用中是8个bit地址。BH1750的通讯过程

首先我们看数据手册,可以发现BH1750的数据收发分为两个8bit合共16个bit的数据(不算ACK)。数据结构于英文文档之下表如下:

在实际使用中,Slave Address与R/W数据可以一起传输,即7bit+1bit = 8bit

BH1750之I2C数据结构

以Continuously H模式为例

传输路径跟其他I2C设备一样,发送Slave Address → 等ACK应答 → 发送Opecode数据 → 等ACK应答。

第一步:发送上电指令 (0x01)

假设ADDR为低电平,BH1750设备地址为0x23 + R0 = 0x46。数据结构为:0x46 WaitACK 0x01 WaitACK

第二步:发送测量模式指令 (0x10)

根据图片上和第一步的例子,我们假定ADDR低电平且采用连续H分辨率测量的模式(0x10)

数据结构为:0x46 WaitACK 0x10 WaitAC

第三步:等测量和读数据

手册上对每个测量模式的时间有说,连续H分辨率需要的时间最长,平均为120ms,但是为了数据最新,最好是隔200ms读取一次。值得一提的是,寄存器数据不会清零,因此亦可以不延迟直接读取,只是有点误差。

现在,是读取模式,与之前的0x23 + R0 = 0x46的地址不同,读取模式是0x23 + W1 = 0x47的地址。

因此数据结构为:0x47 WaitACK 读出的数据 WaitACK

第四步:计算实际光照值

前面读出来的值,显然的是BH1750内部传感器的值,并不是真实的光照强度值,根据手册,我们需要转换一下。

Clip_2024-06-17_14-41-30

H模式: 光照强度 = 1 / 1.2 * (寄存器值[15:0] * 分辨率)单位是lx

H2模式:光照强度 = 1 / 1.2 * (寄存器值[15:0] * 分辨率) / 2单位是lx

BH1750的指令和测量模式

所有指令

名称 地址 注释
断电 Power Down 0000_0000 无激活状态 No active state.
上电 Power On 0000_0001 通电等待测量指令 Waiting for measurement command.
重置 Reset 0000_0111 重置寄存器的所有值 Reset Data register value. Reset command is not acceptable in Power Down mode.
连续H分辨率模式 Continuously H-Resolution Mode 0001_0000 在1lx分辨率下开始连续测量 Start measurement at 1lx resolution. Measurement Time is typically 120ms.
连续H分辨率模式2 Continuously H-Resolution Mode2 0001_0001 在0.5lx分辨率下开始连续测量 Start measurement at 0.5lx resolution. Measurement Time is typically 120ms.
连续L分辨率模式 Continuously L-Resolution Mode 0001_0011 在4lx分辨率下开始连续测量 Start measurement at 4lx resolution. Measurement Time is typically 16ms.
一次H分辨率模式 One Time H-Resolution Mode 0010_0000 在1lx分辨率下开始测量一次,完成后进入断电模式 Start measurement at 1lx resolution. Measurement Time is typically 120ms. It is automatically set to Power Down mode after measurement.
一次H分辨率模式2 One Time H-Resolution Mode2 0010_0001 在0.5lx分辨率下开始测量一次,完成后进入断电模式 Start measurement at 0.5lx resolution. Measurement Time is typically 120ms. It is automatically set to Power Down mode after measurement.
一次L分辨率模式 One Time L-Resolution Mode 0010_0011 在4lx分辨率下开始测量一次,完成后进入断电模式 Start measurement at 4lx resolution. Measurement Time is typically 16ms. It is automatically set to Power Down mode after measurement.
Change Measurement time ( High bit ) 01000_MT[7,6,5] Change measurement time. ※ Please refer “adjust measurement result for influence of optical window.”
Change Masurement time ( Low bit ) 011_MT[4,3,2,1,0] Change measurement time. ※ Please refer “adjust measurement result for influence of optical window.”

各类测量模式

测量模式 精度 测量时间 注释
一次H分辨率模式 1 lx 120ms 进行一次测量,测量完成后会切换到断电模式
一次H分辨率模式2 0.5 lx 120ms 进行一次测量,测量完成后会切换到断电模式
一次L分辨率模式 4 lx 16ms 进行一次测量,测量完成后会切换到断电模式
连续H分辨率模式 1 lx 120ms 会自动进行连续测量,无需重复配置
连续H分辨率模式2 0.5 lx 120ms 会自动进行连续测量,无需重复配置
连续L分辨率模式 4 lx 16ms 会自动进行连续测量,无需重复配置

编程和代码部分

下面的编程我以stm32为例,I2C和BH1750都是标准协议,其实换成其他单片机或者系统,程序也是基本一样的,不同的只是GPIO口的不同。

这边用的就还是软件模拟I2C的方式,没有用硬件I2C。

1. 头文件定义部分

#ifndef __BH1750_H__
#define __BH1750_H__
#include "stm32f10x.h"
#include "Delay.h"

/**
* author: 御枫林下 on Tue, 28 May 2024
* brief: 宏定义GOIO引脚
* param: void
* return: void
**/
#define BH1750_I2C_GPIO_Port GPIOB
#define BH1750_I2C_SCL_GPIOPin GPIO_Pin_6
#define BH1750_I2C_SDA_GPIOPin GPIO_Pin_7
#define BH1750_I2C_SCL(x) GPIO_WriteBit(BH1750_I2C_GPIO_Port, BH1750_I2C_SCL_GPIOPin, (x))
#define BH1750_I2C_SDA(x) GPIO_WriteBit(BH1750_I2C_GPIO_Port, BH1750_I2C_SDA_GPIOPin, (x))
#define BH1750_I2C_SDARead() GPIO_ReadInputDataBit(BH1750_I2C_GPIO_Port, BH1750_I2C_SDA_GPIOPin)
#define HIGH Bit_SET
#define LOW Bit_RESET

/**
* author: 御枫林下 on Tue, 28 May 2024
* brief: 宏定义BH1750相关地址
**/
#define BH1750_ADDRESS 0x46
#define BH1750_CMD_AddWrite 0x46
#define BH1750_CMD_AddRead 0x47
#define BH1750_CMD_PowOn 0x01
#define BH1750_CMD_ModeH1 0x10

/**
* author: 御枫林下 on Wed, 21 May 2024
* brief: 过滤需要用到的函数
* note: 不在此列的函数,无法在外部调用。为了保证函数在本体的可用性,在源文件内会重新定义一次。
**/
void BH1750_Init(void);
uint16_t BH1750_FluxValue(void);
uint16_t BH1750_ReadData(void);

#endif

2. BH1750部分

#include "BH1750.h"

/**
* author: 御枫林下 on Wed, 21 May 2024
* brief: 定义所有函数
**/
static void BH1750_I2C_GPIOInit(void);
void BH1750_I2C_Start(void);
void BH1750_I2C_Stop(void);
void BH1750_I2C_SendByte(uint8_t Byte);
uint8_t BH1750_I2C_ReadByte(void);
uint8_t BH1750_I2C_WaitAck(void);
uint16_t BH1750_FluxValue(void);
void BH1750_Init(void);

/**
* author: 御枫林下 on Wed, 21 May 2024
* brief: GPIO初始化函数
* param: void
* return: void
**/
static void BH1750_I2C_GPIOInit(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = BH1750_I2C_SCL_GPIOPin | BH1750_I2C_SDA_GPIOPin;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(BH1750_I2C_GPIO_Port, &GPIO_InitStructure);

BH1750_I2C_SDA(HIGH);
BH1750_I2C_SCL(HIGH);
}

void BH1750_I2C_Start(void)
{
BH1750_I2C_SDA(HIGH);
BH1750_I2C_SCL(HIGH);
Delay_us(10);
BH1750_I2C_SDA(LOW);
Delay_us(10);
BH1750_I2C_SCL(LOW);
Delay_us(10);
}

void BH1750_I2C_Stop(void)
{
BH1750_I2C_SCL(LOW);
BH1750_I2C_SDA(LOW);
Delay_us(10);
BH1750_I2C_SCL(HIGH);
Delay_us(10);
BH1750_I2C_SDA(HIGH);
Delay_us(10);
}

void BH1750_I2C_SendByte(uint8_t Byte)
{
for (uint8_t i = 0; i < 8; i++)
{
BH1750_I2C_SDA(Byte & (0x80 >> i) ? HIGH : LOW);
BH1750_I2C_SCL(HIGH);
Delay_us(10);
BH1750_I2C_SCL(LOW);
Delay_us(10);
}
}

uint8_t BH1750_I2C_ReadByte(void)
{
uint8_t byte = 0x00;
BH1750_I2C_SDA(HIGH);
Delay_us(10);
for (uint8_t i = 0; i < 8; i++)
{
BH1750_I2C_SCL(HIGH);
Delay_us(10);
if (BH1750_I2C_SDARead())
{
byte |= (0x80 >> i);
}
BH1750_I2C_SCL(LOW);
Delay_us(10);
}
return byte;
}

uint8_t BH1750_I2C_WaitAck(void)
{
uint8_t AckTag = 0;
BH1750_I2C_SDA(HIGH);
BH1750_I2C_SCL(HIGH);
Delay_us(10);
if (BH1750_I2C_SDARead())
{
AckTag = 1;
}
BH1750_I2C_SCL(LOW);
Delay_us(10);
return AckTag;
}

void BH1750_I2C_SendAck(void)
{
BH1750_I2C_SDA(LOW);
Delay_us(10);
BH1750_I2C_SCL(HIGH);
Delay_us(10);
BH1750_I2C_SCL(LOW);
Delay_us(10);
}

void BH1750_I2C_SendNAck(void)
{
BH1750_I2C_SDA(HIGH);
Delay_us(10);
BH1750_I2C_SCL(HIGH);
Delay_us(10);
BH1750_I2C_SCL(LOW);
Delay_us(10);
}

uint8_t BH1750_SendData(uint8_t data)
{
BH1750_I2C_Start();
BH1750_I2C_SendByte(BH1750_CMD_AddWrite);
if (BH1750_I2C_WaitAck())
{
BH1750_I2C_Stop();
return 1;
}
BH1750_I2C_SendByte(data);
if (BH1750_I2C_WaitAck())
{
BH1750_I2C_Stop();
return 2;
}
BH1750_I2C_Stop();
return 0;
}

uint16_t BH1750_ReadData(void)
{
uint8_t MSB, LSB;
BH1750_I2C_Start();
BH1750_I2C_SendByte(BH1750_CMD_AddRead);
if (BH1750_I2C_WaitAck())
{
BH1750_I2C_Stop();
return 0;
}
MSB = BH1750_I2C_ReadByte();
BH1750_I2C_SendAck();
LSB = BH1750_I2C_ReadByte();
BH1750_I2C_SendNAck();
BH1750_I2C_Stop();
return (MSB << 8) | LSB;
}

/**
* author: 御枫林下 on Wed, 21 May 2024
* brief: BH1750读取并转换光照值函数
* param: void
* return: float temp 返回Flux单位的光照值
* note: 需要先调用BH1750_Init()函数初始化整个BH1750后,才能调用.
**/
uint16_t BH1750_FluxValue(void)
{
uint16_t temp = 0;
uint16_t raw_data = BH1750_ReadData();
temp = raw_data / 1.2;
return temp;
}

void BH1750_Init(void)
{
BH1750_I2C_GPIOInit();
BH1750_SendData(BH1750_CMD_PowOn);
BH1750_SendData(BH1750_CMD_ModeH1);
}

3. Delay函数

#include "Delay.h"
/**
* @brief 微秒级延时
* @param xus 延时时长,范围:0~233015
* @retval 无
*/
void Delay_us(uint32_t xus)
{
SysTick->LOAD = 72 * xus; //设置定时器重装值
SysTick->VAL = 0x00; //清空当前计数值
SysTick->CTRL = 0x00000005; //设置时钟源为HCLK,启动定时器
while(!(SysTick->CTRL & 0x00010000)); //等待计数到0
SysTick->CTRL = 0x00000004; //关闭定时器
}

/**
* @brief 毫秒级延时
* @param xms 延时时长,范围:0~4294967295
* @retval 无
*/
void Delay_ms(uint32_t xms)
{
while(xms--)
{
Delay_us(1000);
}
}
/**
* @brief 秒级延时
* @param xs 延时时长,范围:0~4294967295
* @retval 无
*/
void Delay_s(uint32_t xs)
{
while(xs--)
{
Delay_ms(1000);
}
}

4. 主函数

#include "stm32f10x.h" // Device header
#include "USART1.h"
#include "BH1750.h"

int main()
{
USART1_Init();
BH1750_Init();

while(1)
{
USART1_Printf("BH1750: %d \r", BH1750_FluxValue());
Delay_ms(500);
}
}

读取的时候直接调用BH1750_FluxValue()函数即可以直接读取出数值。