在农业物联网中,有这么一个小模块,却发挥着巨大的作用,它就是光感模块。不要看它只是小小的一块,它背负着检测光照强度这一个巨大的任务,而光照是可以直接影响到农作物产量的一个因素。所以今天就让我们来好好学习这个小小的光感模块吧!

1. 源码下载及前置阅读

本文首发:https://www.lxlinux.net/e/ ,欢迎关注!

本文所涉及的源码及安装包如下(由于平台限制,请点击以下链接阅读原文下载):

https://www.lxlinux.net/e/stm32/gy-302-tutorial.html

如果你是嵌入式开发小白,那么建议你先读读下面几篇文章。

往期教程,有兴趣的小伙伴可以看看。

作者简介
大家好,我是良许,博客里所有的文章皆为我的原创。
下面是我的一些个人介绍,欢迎交个朋友:
· 211工科硕士,国家奖学金获得者;
· 深耕嵌入式11年,前世界500强外企高级嵌入式工程师;
· 书籍《速学Linux作者》,机械工业出版社专家委员会成员;
· 全网60W粉丝,博客分享大量原创成体系文章,全网阅读量累计超4000万;
· 靠自媒体连续年入百万,靠自己买房买车。

我本科及硕士都是学机械,通过自学成功进入世界500强外企。我已经将自己的学习经验写成了一本电子书,超千人通过此书学习并转行成功。现在将这本电子书免费分享给大家,希望对你们有帮助:

电子书链接:https://www.lxlinux.net/1024.html

2. GY-302光感模块介绍

本文将带你具体了解 GY-302 光感模块。

GY-302 光感模块的芯片是 BH1750FVI,BH1750FVI 的内部是由光敏二极管、运算放大器、ADC 采集、晶振等组成的,它的通信协议是 I2C。BH1750FVI 的工作原理是 PD 二极管通过光生伏特效应将输入光信号转换成电信号,经运算放大电路放大后,由 ADC 采集电压,然后通过逻辑电路转换成 16 位二进制数存储在内部的寄存器中(光照越强,光电流越大,电压就越大)。

这是一款我们最常见的 GY-302 光感模块,它长这样:

<img src="https://lxlinux.superbed.verylink.top/item/659a9fa5871b83018a3db50b.png" style="zoom:50%;" />

引脚说明:

VCC:电源引脚,接正电(3V~5V);

GND:电源引脚,接负电;

SCL:时钟引脚,要接在单片机有 I2C-SCL 功能的引脚上;

SDA:数据引脚,要接在单片机有 I2C-SDA 功能的引脚上;

ADDR:接在 GND 或者 VCC 上(接在 GND 和 VCC 上的硬件地址不相同)。

设备参数:

参数额定值
电源电压4.5V
运行温度-40 ~ 85℃
储存温度-40 ~ 100℃
反向电流7mA
功率损耗260mW

运行条件:

参数最小值时间最大值单位
VCC电压2.43.03.6V
I2C参考电压1.65VCCV

特点:

  1. 采用 ROHM 原装 BH1750FVI 芯片;
  2. 数据范围:0-65535lx;
  3. 供电电源:3-5V;
  4. 尺寸:13.9mm×18.5mm;
  5. 传感器内置 16bitAD 转换器;
  6. 直接数字输出,省略复杂的计算;
  7. 不区分环境光源,接近于视觉灵敏度的分光特性;
  8. 可对广泛的亮度进行高精度测定;
  9. 模块内部包含通信电平转换。

3. IIC通信

GY-302 光感模块的通信协议是 I2C。

I2C 通信协议是一种串行通信总线,使用多主从架构,是由飞利浦(Philips)公司在 1980 年代初设计的一种串行、半双工的通信,主要用于近距离、低速的芯片之间的通信。I2C 总线有两根双向的信号线,一根数据线 SDA 用于收发数据,一根时钟线 SCL 用于通信双方时钟的同步。

不太懂 I2C 通信协议的小伙伴可以去看一下 0.96 寸 OLED 屏幕模块的文章,那里面有详细的教程。

【OLED链接】

4. GY-302指令集

GY-302 指令集是一种用于控制和操作工业自动化设备的编程语言。它由一系列指令组成,可以控制各种设备,如机器人、PLC(可编程逻辑控制器)、CNC(计算机数控)系统等。

GY-302 指令集的特点:

  1. 易于理解和使用:采用了一种简单易懂的语法,使得开发人员能够轻松地学习和使用它。
  2. 强大的功能:提供了一系列功能强大的指令,可以完成各种复杂的任务,如运动控制、逻辑控制、数据处理等。
  3. 可扩展性:具有良好的可扩展性,可以通过添加新的指令和功能来满足不断发展的工业自动化需求。
  4. 兼容性:与多种硬件设备和软件平台兼容,使得用户可以在不同的设备和平台上使用相同的指令集。
  5. 安全性:采用了一系列安全机制,以确保工业自动化系统的安全和稳定运行。

GY-302指令集:

功能十六进制指令说明
断电0x00无激活状态
通电0x01等待测量指令
重置0x07重置数字寄存器值(重置指令在断电模式下不起作用)
连续 H 分辨率模式0x10在 11x 分辨率下开始测量(测量时间一般为 120ms)
连续 H 分辨率模式20x11在 0.51x 分辨率下开始测量(测量时间一般为 120ms)
连续 L 分辨率模式0x13在 411x 分辨率下开始测量(测量时间一般为 16ms)
一次 H 分辨率模式0x20在 11x 分辨率下开始测量(测量时间一般为 120ms,测量后自动设置为断电模式)
一次 H 分辨率模式20x21在 0.51x 分辨率下开始测量(测量时间一般为 120ms,测量后自动设置为断电模式)
一次 L 分辨率模式0x23在 411x 分辨率下开始测量(测量时间一般为 16ms,测量后自动设置为断电模式)
改变测量时间(高位)01000_MT[7,6,5]改变测量时间(要参考“根据光学扇窗的影响调整测量结果”)
改变测量时间(低位)011_MT[4,3,2,1,0]改变测量时间(要参考“根据光学扇窗的影响调整测量结果”)

5. GY-302驱动代码

接着我们来学习如何使用 GY-302 光感模块。

本教程使用的硬件如下:

  • 光感模块:GY-302
  • 单片机:STM32F103C8T6
  • 串口:USB 转 TTL
  • 烧录器:ST-LINK V2

接线如下:

GY-302STM32C8T60.96寸OLED屏幕模块
VCCVCCVCC
GNDGNDGND
B8SCL
B9SDA
SCLB10
SDAB11
ADDRG

烧录的时候接线如下表,如果不会烧录的话可以看我之前的文章【STM32下载程序的五种方法】。

ST-Link V2STM32
SWCLKSWCLK
SWDIOSWDIO
GNDGND
3.3V3V3

接好如下图。开发板使用的是我们自绘的板子。大家也可以用自己的板子,只要是 STM32F103C8T6 主控芯片就行。

GY-302 同样是用 I2C 通信协议的,首先我们打开工程,新建一个 gy302.c 和 gy302.h。

先写一个引脚初始化的代码(本文使用 PB10 和 PB11),代码如下:

void gy302_IO_Init(void)
{
    GPIO_InitTypeDef GY302;

    GY302_I2C_SCL_SDA_CLK();                        //开启时钟
    GY302.Pin=GY302_I2C_SCL_PIN;                    //配置SCL引脚
    GY302.Mode=GPIO_MODE_OUTPUT_OD;                 //开漏输出
    GY302.Speed=GPIO_SPEED_FREQ_HIGH;               //高速
    HAL_GPIO_Init(GY302_I2C_SCL_SDA_GPIO, &GY302);

    GY302.Pin=GY302_I2C_SDA_PIN;                    //配置SDA引脚
    HAL_GPIO_Init(GY302_I2C_SCL_SDA_GPIO, &GY302);
}

接着我们需要写一个 I2C 的模拟信号,代码如下:

void GY302_Start(void)
{
    GY302_SDA_SET();                    //拉高数据线
    GY302_SCL_SET();                    //拉高时钟线
    delay_us(5);                 
    GY302_SDA_RESET();                  //产生下降沿
    delay_us(5);                
    GY302_SCL_RESET();                  //拉低时钟线
}

void GY302_Stop(void)
{
    GY302_SDA_RESET();                  //拉低数据线
    GY302_SCL_SET();                    //拉高时钟线
    delay_us(5);                 
    GY302_SDA_SET();                    //产生上升沿
    delay_us(5);
}  

void GY302_SendACK(int ack)
{
    if(ack == 1)                //写应答信号
            GY302_SDA_SET();
        else if(ack == 0)
            GY302_SDA_RESET();
        else
            return;
            
    GY302_SCL_SET();
    delay_us(5);
    GY302_SCL_RESET();
    delay_us(5);
}

uint32_t GY302_RecvACK(void)
{
    uint8_t mcy = 0;

    GY302_SCL_SET();                                //拉高时钟线
    delay_us(5);

    if(HAL_GPIO_ReadPin( GY302_I2C_SCL_SDA_GPIO, GY302_I2C_SDA_PIN ) == 1 )   //读应答信号
        mcy = 1 ;
    else
        mcy = 0 ;

    GY302_SCL_RESET();                              //拉低时钟线
    delay_us(5);

    return mcy;
}

I2C 通信准备完成之后,我们可以开始写指令操作了,首先需要定义一个写入数据的函数。我们对GY302发送命令时,要先发送器件地址+写入位,然后发送指令,代码如下:

void GY302_SendByte(uint8_t dat)
{
    uint8_t i;
 
    for (i=0; i<8; i++)                 //8位计数器
      {
        if( 0X80 & dat )
          GY302_SDA_SET();
        else
          GY302_SDA_RESET();

        dat <<= 1;
        GY302_SCL_SET();               //拉高时钟线
        delay_us(5);
        GY302_SCL_RESET();             //拉低时钟线
        delay_us(5);
      }
    GY302_RecvACK();
}

void GY302_Write_Cmd(uint8_t REG_Address)//REG_Address是要写入的指令
{
    GY302_Start();                  //起始信号
    GY302_SendByte(SlaveAddress);   //发送器件地址+写信号
    GY302_SendByte(REG_Address);    //写入指令,内部寄存器地址
    GY302_Stop();                   //结束信号
}

这样我们就可以将我们的指令通过 I2C 通信协议写入到 GY-302 光感模块当中了,接下来我们需要读取 GY-302 光感模块采集的数据。读取数据的时候,需要先发送器件地址+读入位,然后读取两字节数据,代码如下:

uint8_t GY302_RecvByte(void)
{
    uint8_t i, bit, dat = 0;

    GY302_SDA_SET();                        //准备读取数据
    for (i=0; i<8; i++)                     //8位计数器
    {
        dat <<= 1;
        GY302_SCL_SET();                    //拉高时钟线
        delay_us(5);

       if( SET == HAL_GPIO_ReadPin( GY302_I2C_SCL_SDA_GPIO, GY302_I2C_SDA_PIN ) )
            bit = 0x01;
       else
            bit = 0x00;
            
        dat |= bit;                         //读数据 
            
        GY302_SCL_RESET();                  //拉低时钟线
        delay_us(5);
    }

    return dat;
}

uint32_t GY30_Value(void)
{
    uint8_t i, BUF[3];
    uint16_t dis_data, Value_GY_30 = 0;
    GY302_Write_Cmd(0x01);               //上电
    GY302_Write_Cmd(0x10);               //设置为连续 H 分辨率模式
    delay_ms(100);                          //延时,很重要,为读取数据做准备

    GY302_Start();                          //起始信号
    GY302_SendByte(SlaveAddress+1);         //发送设备地址+读信号

     for (i=0; i<3; i++)                    //连续读取6个地址数据到BUF
    {
        BUF[i] = GY302_RecvByte();
        if (i == 3)
        {
            GY302_SendACK(1);               //最后一个数据需要回NOACK
        }
        else
        {
            GY302_SendACK(0);               //回应ACK
        }
    }
 
    GY302_Stop();                           //停止信号
    delay_us(5);
    dis_data = BUF[0];
    dis_data = ( dis_data<<8 ) + BUF[1];    //字节合成数据
    Value_GY_30 = (float)dis_data;
    return Value_GY_30;
}

完成数据的读取,剩下的就是初始化 GY-302 光感模块了,根据上文的指令集我们可以得出代码如下:

void GY302_Init(void)
{
    gy302_IO_Init();

    GY302_Start();                       //起始信号
    GY302_SendByte(SlaveAddress);        //发送设备地址+写信号
    GY302_SendByte(0x01);                //上电
    GY302_Stop();                        //停止信号
}

gy302.h 的代码如下:

#ifndef __GY302_H
#define __GY302_H

#include "stm32f1xx_hal.h"

#define GY302_I2C_SCL_SDA_CLK()     __HAL_RCC_GPIOB_CLK_ENABLE()
#define GY302_I2C_SCL_SDA_GPIO      GPIOB
#define GY302_I2C_SCL_PIN           GPIO_PIN_10
#define GY302_I2C_SDA_PIN           GPIO_PIN_11

#define GY302_SCL_RESET()       HAL_GPIO_WritePin(GY302_I2C_SCL_SDA_GPIO,GY302_I2C_SCL_PIN,GPIO_PIN_RESET)  //SCL
#define GY302_SCL_SET()         HAL_GPIO_WritePin(GY302_I2C_SCL_SDA_GPIO,GY302_I2C_SCL_PIN,GPIO_PIN_SET)

#define GY302_SDA_RESET()       HAL_GPIO_WritePin(GY302_I2C_SCL_SDA_GPIO,GY302_I2C_SDA_PIN,GPIO_PIN_RESET)  //SDA
#define GY302_SDA_SET()         HAL_GPIO_WritePin(GY302_I2C_SCL_SDA_GPIO,GY302_I2C_SDA_PIN,GPIO_PIN_SET)

#define SlaveAddress       0x46  //ADDR接GND时的器件地址

void GY302_Start(void);
void GY302_Stop(void);
void GY302_Init(void);
void GY302_SendACK(int ack);
uint32_t GY302_RecvACK(void);
void GY302_SendByte(uint8_t dat);
void GY302_Write_Cmd(uint8_t REG_Address);
uint8_t GY302_RecvByte(void);
uint32_t GY30_Value(void);


#endif

main.c 代码如下:

#include "sys.h"
#include "delay.h"
#include "oled.h"
#include "gy302.h"


int main(void)
{
    uint16_t Light = 0;
    uint8_t a,b,c,d,e;

    HAL_Init();                         /* 初始化HAL库 */
    stm32_clock_init(RCC_PLL_MUL9);     /* 设置时钟, 72Mhz */
    OLED_Init();                        /* oled初始化 */
    GY302_Init();                       /* GY-302初始化 */

    OLED_Fill(0x00);                    /* 清屏 */

    while(1)
    {
        Light = GY30_Value();                  //获取光照强度
        a = Light / 10000;
        b = (Light % 10000) / 1000;
        c = (Light % 1000) / 100;
        d = (Light % 100) / 10;
        e = Light % 10;

        OLED_Show_ChineseFont(0,0,0,16);
        OLED_Show_ChineseFont(16,0,1,16);
        OLED_Show_ChineseFont(32,0,2,16);
        OLED_Show_ChineseFont(48,0,3,16);
        OLED_ShowStr(64,0,":",16);
        OLED_Show_Char(0,2,a + '0',16);
        OLED_Show_Char(12,2,b + '0',16);
        OLED_Show_Char(24,2,c + '0',16);
        OLED_Show_Char(36,2,d + '0',16);
        OLED_Show_Char(48,2,e + '0',16);
        OLED_Show_Char(67,2,'l',16);
        OLED_Show_Char(75,2,'x',16);

        delay_ms(500);
    }
}

最终效果如下,这是正常状态:

打光状态下光照强度增加:

到这里我们就完成了 GY-302 光感模块的使用了,感谢各位看官的陪伴!

6. 小结

今天我们学习了 GY-302 光感模块,不仅为我们的技术锦上添花,还复习了有关于 I2C 通信的知识,真是一石二鸟!希望本文章能够为各位看官枯燥的项目学习中带来几分兴趣,感谢各位看官,peace and love!

另外,想进大厂的同学,一定要好好学算法,这是面试必备的。这里准备了一份 BAT 大佬总结的 LeetCode 刷题宝典,很多人靠它们进了大厂。

刷题 | LeetCode算法刷题神器,看完 BAT 随你挑!

有收获?希望老铁们来个三连击,给更多的人看到这篇文章

推荐阅读:

欢迎关注我的博客:良许嵌入式教程网,满满都是干货!


良许
1k 声望1.8k 粉丝