STM32F103使用GY-30(BH1750)光照强度传感器

GY-30(BH1750)

GY-30是一款采用了ROHM-BH1750FVI芯片的数字输出的感光模块。简而言之,BH1750是一个芯片,而GY-30则是基于这个芯片的一个感光模块。

具体来说,GY-30可以测量的光照度范围为0~65535勒克斯,具有±20%的最小误差变动和1勒克斯的分辨度。它是一个五针脚模块,包括VCC、SCL、SDA、ADDR、GND五个引脚,其中VCC接电源正极,电压范围在3~5V之间;GND接地;SCL为时钟线;SDA为数据线;ADDR为寄存器的地址引脚。

长的下面这个模样。

它使用的是I2C通信协议,关于如何发出I2C可以参考我之前的文章。

【STM32F103】I2C通信协议&SHT20温湿度传感器-CSDN博客文章浏览阅读342次,点赞9次,收藏5次。I2C是Inter IC BUS=IIC=I²C=I2C,一般我们读作“挨方C”。简述一下I2C,是只需要两根通信线就能实现多主多从半双工的串行通信协议。传输速度会偏慢一点点,一般是100Kbps,是属于标准模式。另外还有快速模式,400Kbps;高速模式3.4Mbps;超快速模式5Mbps(后两种没接触过)。分别是SCL和SDA。SCL是Serial Clock,也就是统一时间的。SDA是Serial Data,也就是传输数据的。https://blog.csdn.net/m0_63235356/article/details/135734887?spm=1001.2014.3001.5501里面软件模拟I2C的时序函数可以直接拿来用。

我们就只需要关注给GY-30发送什么数据才能得到光照强度就行了。

我们首先要做的就是查看芯片相关的文档。

上面是BH1750芯片手册中的指令列表。

看不懂没关系,我也看不懂,但是我们可以简单地翻译一下。

翻译得不咋地,但是我们依稀可以知道我们需要的命令有哪些。

首先第二个0x01打开电源,这个毫无疑问,我们一开始使用GY-30的时候肯定是要传输这个命令的。

接下来一堆都是设置模式的,我们也可以猜的出来这些就是指定我们采样光照强度的模式。

这一堆模式里分为两种,一种是连续模式,另一种是一次模式。

这个我们也能猜的出来,如果是连续模式,那么只需要发送一次命令即可,GY-30会不断地连续采集数据,而一次模式也就是我们每次要取数据之前都需要发送一次采集命令。所以我们等等选择的是连续模式。

一次和连续模式中又分为了三种,低(L)分辨,高(H)分辨和高分辨2,区别就在于分辨率分别是4lx,1lx,0.5lx以及采样的时间,我们这边就是折中一下,等等选择高分辨1模式。

那么接下来我们可以参考一下手册里提供的时序图了。

从上图可以得知,BH1750的从机地址为0100011,如果是要写命令的话,那么地址是0x46,如果是要读数据的话,那么地址是0x47。

我们首先需要先给GY-30发送上电命令,所以是先用I2C发送0x46,再发送0x01。

 下面关于I2C的代码(Z_I2C开头的函数)参考我之前的文章,本文开头有链接。

    Z_I2C_Start();
    Z_I2C_SendByte(0x46);
    if(Z_I2C_ReveiceACK()!=0) return 0;
    Z_I2C_SendByte(0x01);
    if(Z_I2C_ReveiceACK()!=0) return 0;
    Z_I2C_End();

接下来我们发送命令,指定BH1750为高分辨连续采集模式(指令为0x10),并且根据手册中的说法,发送完这个命令之后,我们需要等待最多180ms,我们这边凑个整,延时个200ms。

    Z_I2C_Start();
    Z_I2C_SendByte(0x46);
    if(Z_I2C_ReveiceACK()!=0) return 0;
    Z_I2C_SendByte(0x10);
    if(Z_I2C_ReveiceACK()!=0) return 0;
    Z_I2C_End();
    Delay_ms(200);

上述代码完成之后,我们就可以开始读取数据了。

    uint16_t Light=0;
    Z_I2C_Start();
    Z_I2C_SendByte(0x47);
    if(Z_I2C_ReveiceACK()!=0) return 0;
    Light|=Z_I2C_ReveiceByte();
    Light<<=8;
    Z_I2C_SendACK(0);
    Light|=Z_I2C_ReveiceByte();
    Z_I2C_SendACK(1);
    Z_I2C_End();

这样,Light这个变量存放的就是GY-30发送来的数据了,根据手册里的例子,这个值还需要/1.2才是光照强度数据(lx)

完整的代码如下。

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"

#define SCL_Pin GPIO_Pin_0
#define SDA_Pin GPIO_Pin_1

//以下部分为之前文章中软件I2C的代码,可以直接用

void Z_I2C_Init(void){
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    
    GPIO_InitTypeDef itd;
    itd.GPIO_Mode=GPIO_Mode_Out_OD;        
    itd.GPIO_Pin=SCL_Pin|SDA_Pin;    
    itd.GPIO_Speed=GPIO_Speed_50MHz;                   
    GPIO_Init(GPIOA,&itd);
 
    GPIO_WriteBit(GPIOA,SCL_Pin,Bit_SET);       //SCL和SDA默认都是高电平
    GPIO_WriteBit(GPIOA,SDA_Pin,Bit_SET);       //因此初始化后设为高电平
}
    
void Z_I2C_SetSCL(uint8_t signal){
    if(signal==1) GPIO_WriteBit(GPIOA,SCL_Pin,Bit_SET);
    else GPIO_WriteBit(GPIOA,SCL_Pin,Bit_RESET);
    Delay_us(5);                    //防止电平翻转过快,因此加上延时
}
 
void Z_I2C_SetSDA(uint8_t signal){
    if(signal==1) GPIO_WriteBit(GPIOA,SDA_Pin,Bit_SET);
    else GPIO_WriteBit(GPIOA,SDA_Pin,Bit_RESET);
    Delay_us(5);
}
 
uint8_t Z_I2C_GetSDA(void){
    return GPIO_ReadInputDataBit(GPIOA,SDA_Pin);
}
 
void Z_I2C_Start(void){
    Z_I2C_SetSDA(1);
    Z_I2C_SetSCL(1);
    Z_I2C_SetSDA(0);
    Z_I2C_SetSCL(0);
}
 
void Z_I2C_End(){
    Z_I2C_SetSDA(0);
    Z_I2C_SetSCL(1);
    Z_I2C_SetSDA(1);
}
 
void Z_I2C_SendByte(uint8_t byte){
    Z_I2C_SetSCL(0);
    for(int i=0;i<8;++i){
        if((byte&0x80)==0) Z_I2C_SetSDA(0);
        else Z_I2C_SetSDA(1);
        byte<<=1;
        Z_I2C_SetSCL(1);
        Z_I2C_SetSCL(0);
    }
}
 
uint8_t Z_I2C_ReveiceByte(){
    uint8_t data=0x00;
    Z_I2C_SetSDA(1);
    for(int i=0;i<8;++i){
        Z_I2C_SetSCL(1);
        if(Z_I2C_GetSDA()==1) data|=(0x80>>i);
        Z_I2C_SetSCL(0);
    }
    return data;
}
 
void Z_I2C_SendACK(uint8_t ack){
    if(ack==0) Z_I2C_SetSDA(0);
    else Z_I2C_SetSDA(1);
    Z_I2C_SetSCL(1);
    Z_I2C_SetSCL(0);
}
 
uint8_t Z_I2C_ReveiceACK(){
    Z_I2C_SetSDA(1);
    Z_I2C_SetSCL(1);
    uint8_t ack=Z_I2C_GetSDA();
    Z_I2C_SetSCL(0);
    return ack;
}

uint16_t Z_GY30_GetData(void){
   
    Z_I2C_Start();
    Z_I2C_SendByte(0x46);
    if(Z_I2C_ReveiceACK()!=0) return 0;
    Z_I2C_SendByte(0x01);
    if(Z_I2C_ReveiceACK()!=0) return 0;
    Z_I2C_End();
    
    Z_I2C_Start();
    Z_I2C_SendByte(0x46);
    if(Z_I2C_ReveiceACK()!=0) return 0;
    Z_I2C_SendByte(0x10);
    if(Z_I2C_ReveiceACK()!=0) return 0;
    Z_I2C_End();
    Delay_ms(200);
    
    uint16_t Light=0;
    Z_I2C_Start();
    Z_I2C_SendByte(0x47);
    if(Z_I2C_ReveiceACK()!=0) return 0;
    Light|=Z_I2C_ReveiceByte();
    Light<<=8;
    Z_I2C_SendACK(0);
    Light|=Z_I2C_ReveiceByte();
    Z_I2C_SendACK(1);
    Z_I2C_End();
    
    return Light;
}

int main(void){
    OLED_Init();
    Z_I2C_Init();
    
    while(1){
        OLED_ShowNum(1,1,Z_GY30_GetData(),6);
        Delay_ms(50);
    }
}

按理说我们发送一次指定高分辨连续采集模式就可以了,之后直接等待180ms之后读取数据就行,但是我试了一下,采集的数据极其不稳定,因为最终还是上面的代码,每次读取GY-30的数据的时候都发一次指令。

效果如下。

作者:折途想要敲代码

物联沃分享整理
物联沃-IOTWORD物联网 » STM32F103使用GY-30(BH1750)光照强度传感器

发表评论