GY-302光感模块详解
在农业物联网中,有这么一个小模块,却发挥着巨大的作用,它就是光感模块。不要看它只是小小的一块,它背负着检测光照强度这一个巨大的任务,而光照是可以直接影响到农作物产量的一个因素。所以今天就让我们来好好学习这个小小的光感模块吧!
1. 源码下载及前置阅读
- STM32F103C8T6模板工程
链接:https://pan.baidu.com/s/1gJigWypYfwLo2cn2nP2DGg?pwd=yqya 提取码:yqya
- 本文的源码
链接:https://pan.baidu.com/s/1KfIo9vv0XnCHUegwmTUSrQ?pwd=62wm 提取码:62wm
如果你是嵌入式开发小白,那么建议你先读读下面几篇文章。
- 计算机概念理清,生动形象,通俗易懂:图解固件、驱动、软件的区别
- 了解不同的下载程序方法,为你的嵌入式开发提供更多选择:STM32下载程序的五种方法
- 嵌入式开发者的得力助手,强大的MDK仿真:手把手教你使用MDK仿真调试
往期教程,有兴趣的小伙伴可以看看。
- 蓝牙模块知识应用:小项目:蓝牙模块点亮RGB三色灯
- 简单好用,为你的智能语音项目提供核心支持:SU-03T语音控制模块详解
- 继电器模块的原理、功能和应用:继电器模块详解
2. GY-302光感模块介绍
本文将带你具体了解 GY-302 光感模块。
GY-302 光感模块的芯片是 BH1750FVI,BH1750FVI 的内部是由光敏二极管、运算放大器、ADC 采集、晶振等组成的,它的通信协议是 I2C。BH1750FVI 的工作原理是 PD 二极管通过光生伏特效应将输入光信号转换成电信号,经运算放大电路放大后,由 ADC 采集电压,然后通过逻辑电路转换成 16 位二进制数存储在内部的寄存器中(光照越强,光电流越大,电压就越大)。
这是一款我们最常见的 GY-302 光感模块,它长这样:
引脚说明:
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.4 | 3.0 | 3.6 | V |
I2C参考电压 | 1.65 | — | VCC | V |
特点:
- 采用 ROHM 原装 BH1750FVI 芯片;
- 数据范围:0-65535lx;
- 供电电源:3-5V;
- 尺寸:13.9mm×18.5mm;
- 传感器内置 16bitAD 转换器;
- 直接数字输出,省略复杂的计算;
- 不区分环境光源,接近于视觉灵敏度的分光特性;
- 可对广泛的亮度进行高精度测定;
- 模块内部包含通信电平转换。
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 指令集的特点:
- 易于理解和使用:采用了一种简单易懂的语法,使得开发人员能够轻松地学习和使用它。
- 强大的功能:提供了一系列功能强大的指令,可以完成各种复杂的任务,如运动控制、逻辑控制、数据处理等。
- 可扩展性:具有良好的可扩展性,可以通过添加新的指令和功能来满足不断发展的工业自动化需求。
- 兼容性:与多种硬件设备和软件平台兼容,使得用户可以在不同的设备和平台上使用相同的指令集。
- 安全性:采用了一系列安全机制,以确保工业自动化系统的安全和稳定运行。
GY-302指令集:
功能 | 十六进制指令 | 说明 |
---|---|---|
断电 | 0x00 | 无激活状态 |
通电 | 0x01 | 等待测量指令 |
重置 | 0x07 | 重置数字寄存器值(重置指令在断电模式下不起作用) |
连续 H 分辨率模式 | 0x10 | 在 11x 分辨率下开始测量(测量时间一般为 120ms) |
连续 H 分辨率模式2 | 0x11 | 在 0.51x 分辨率下开始测量(测量时间一般为 120ms) |
连续 L 分辨率模式 | 0x13 | 在 411x 分辨率下开始测量(测量时间一般为 16ms) |
一次 H 分辨率模式 | 0x20 | 在 11x 分辨率下开始测量(测量时间一般为 120ms,测量后自动设置为断电模式) |
一次 H 分辨率模式2 | 0x21 | 在 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-302 | STM32C8T6 | 0.96寸OLED屏幕模块 |
---|---|---|
VCC | VCC | VCC |
GND | GND | GND |
B8 | SCL | |
B9 | SDA | |
SCL | B10 | |
SDA | B11 | |
ADDR | G |
烧录的时候接线如下表,如果不会烧录的话可以看我之前的文章【STM32下载程序的五种方法】。
ST-Link V2 | STM32 |
---|---|
SWCLK | SWCLK |
SWDIO | SWDIO |
GND | GND |
3.3V | 3V3 |
接好如下图。开发板使用的是我们自绘的板子。大家也可以用自己的板子,只要是 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);