STM32案例学习 GY-39环境监测传感器模块
硬件平台
- 野火STM32F1系列开发板
- 正点STM32F1系列开发板
- STM32F103ZET6核心板
- GY-39环境监测传感器模块
GY-39环境监测传感器模块
- GY-39 是一款低成本,气压,温湿度,光强度传感器模块。工作电压 3-5v,功耗小,安装方便。
- 其工作原理是,MCU 收集各种传感器数据,统一处理,直接输出计算后的结果。此模块,有两种方式读取数据,即**串口 UART(TTL 电平)**或者 IIC(2 线)。串口的波特率有 9600bps 与 115200bps,可配置,有连续,询问输出两种方式,可掉电保存设置。可适应不同的工作环境,与单片机及电脑连接。
- 模块另外可以设置单独传感器芯片工作模式,作为简单传感器模块,MCU 不参与数据处理工作。
- 参考资料网站https://www.gysensor.cn/air-gy39/
技术参数(传感器精度请参考芯片手册)
模块引脚声明
Pin1 | VCC | 电源+ (3v-5v) |
Pin2 | CT | 串口UART_TX / IIC_SCL |
Pin3 | DR | 串口UART_RX / IIC_SDA |
Pin4 | GND | 电源地 |
Pin5 | NC | 保留,不要连接 |
Pin6 | INT | max44009光强芯片中断 S1=0(接GND时启用) |
Pin7 | SDA | 芯片数据线S1=0(接GND时启用) |
Pin8 | SCL | 芯片时钟线S1=0(接GND时启用) |
PinA | S0 | 串口/MCU_IIC模式选择,接地为MCU_IIC模式 |
PinB | S1 | 仅使用传感器芯片选择 |
模块通信协议描述
- 串口通信
(1) 串口通信参数(默认波特率值 9600bps,可通过软件设定)
波特率:9600 bps 校验位:N 数据位:8 停止位:1
波特率:115200 bps 校验位:N 数据位:8 停止位:1
(2) 模块输出格式,每帧包含 8-13 个字节(十六进制):
① .Byte0: 0x5A 帧头标志
②. Byte1: 0x5A 帧头标志
③. Byte2: 0x15 本帧数据类型(参考含义说明)
④. Byte3: 0x04 数据量
⑤. Byte4: 0x00~0xFF 数据前高 8 位
⑤. Byte5: 0x00~0xFF 数据前低 8 位
⑥. Byte6: 0x00~0xFF 数据后高 8 位
⑦. Byte7: 0x00~0xFF 数据后低 8 位
⑧. Byte8: 0x00~0xFF 校验和(前面数据累加和,仅留低 8 位)
(3) Byte2 代表的含义说明
Byte2 | 0x15 | 0x45 | 0x55 |
说明 | 光照强度 | 温度,湿度,气压,海拔 | IIC地址 |
(4) 数据计算方法 ①光照强度计算方法(当 Byte2=0x15 时,数据:Byte4~Byte7) : Lux=(前高8位<<24) | (前低8位<<16) | (后高8位<<8) | 后低8位 单位lux 例:一帧数据
<5A- 5A- 15 -04- 00 -00- FE- 40- 0B >
Lux=(0x00<<24)|(0x00<<16)|(0xFE<<8)|0x40 Lux=Lux/100 =650.88 (lux) ②温度、气压、湿度、海拔,计算方法(当 Byte2=0x45 时): 温度:Byte4~Byte5 T=(高 8 位<<8)|低 8 位 T=T/100 单位℃ 气压:Byte6~Byte9 P=(前高 8 位<<24) | (前低 8 位<<16) | (后高 8 位<<8) | 后低 8 位 P=P/100 单位 pa 湿度:Byte10~Byte11 Hum=(高 8 位<<8)|低 8 位 Hum=Hum/100 百分制 海拔:Byte12~Byte13 H=(高 8 位<<8)|低 8 位 单位 m 例:一帧数据
< 5A -5A -45 -0A -0B -2D -00 -97 -C4 -3F -12- 77 -00- 9C- FA >
T=(0x0B<<8)|0x2D=2861 温度 T=2861/100=28.61 (℃) P=(0x00<<24)|(0x97<<16)|(C4<<8)|3F=9946175 气压 P=9946175/100=99461.75 (pa) Hum=(0x12<<8)| 77=4727 湿度 Hum=4727/100=47.27 (%) 海拔 H=(0x00<<8)|0x9c=156 (m)
(5) 命令字节,由外部控制器发送至 GY-39 模块(十六进制)
2. IIC通信
硬件测试
接线示意图
代码
#include "main.h"
void I2C_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* 使能与 I2C有关的时钟 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE );
/* PC3-I2C_SCL、PC5-I2C_SDA*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6| GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_Init(GPIOB, &GPIO_InitStructure);
SCL_H;
SDA_H;
}
void delay_1us(uint8_t x)//粗略延时,iic_40K
{
uint8_t i=20;
x=i*x;
while(x--);
}
////////IIC起始函数//////////
/*
IIC起始:当SCL处于高电平期间,SDA由高电平变成低电平出现一个下降沿,然后SCL拉低
*/
uint8_t I2C_Start(void)
{
SDA_H;
delay_1us(5); //延时保证时钟频率低于40K,以便从机识别
SCL_H;
delay_1us(5);//延时保证时钟频率低于40K,以便从机识别
if(!SDA_read) return 0;//SDA线为低电平则总线忙,退出
SDA_L; //SCL处于高电平的时候,SDA拉低
delay_1us(5);
if(SDA_read) return 0;//SDA线为高电平则总线出错,退出
SCL_L;
delay_1us(5);
return 1;
}
//**************************************
//IIC停止信号
/*
IIC停止:当SCL处于高电平期间,SDA由低电平变成高电平出现一个上升沿
*/
//**************************************
void I2C_Stop(void)
{
SDA_L;
SCL_L;
delay_1us(5);
SCL_H;
delay_1us(5);
SDA_H;//当SCL处于高电平期间,SDA由低电平变成高电平 //延时
}
//**************************************
//IIC发送应答信号
//入口参数:ack (0:ACK 1:NAK)
/*
应答:当从机接收到数据后,向主机发送一个低电平信号
先准备好SDA电平状态,在SCL高电平时,主机采样SDA
*/
//**************************************
void I2C_SendACK(uint8_t i)
{
if(1==i)
SDA_H; //准备好SDA电平状态,不应答
else
SDA_L; //准备好SDA电平状态,应答
SCL_H; //拉高时钟线
delay_1us(5); //延时
SCL_L ; //拉低时钟线
delay_1us(5);
}
///////等待从机应答////////
/*
当本机(主机)发送了一个数据后,等待从机应答
先释放SDA,让从机使用,然后采集SDA状态
*/
/////////////////
uint8_t I2C_WaitAck(void) //返回为:=1有ACK,=0无ACK
{
uint16_t i=0;
SDA_H; //释放SDA
SCL_H; //SCL拉高进行采样
while(SDA_read)//等待SDA拉低
{
i++; //等待计数
if(i==500)//超时跳出循环
break;
}
if(SDA_read)//再次判断SDA是否拉低
{
SCL_L;
return RESET;//从机应答失败,返回0
}
delay_1us(5);//延时保证时钟频率低于40K,
SCL_L;
delay_1us(5); //延时保证时钟频率低于40K,
return SET;//从机应答成功,返回1
}
//**************************************
//向IIC总线发送一个字节数据
/*
一个字节8bit,当SCL低电平时,准备好SDA,SCL高电平时,从机采样SDA
*/
//**************************************
void I2C_SendByte(uint8_t dat)
{
uint8_t i;
SCL_L;//SCL拉低,给SDA准备
for (i=0; i<8; i++) //8位计数器
{
if(dat&0x80)//SDA准备
SDA_H;
else
SDA_L;
SCL_H; //拉高时钟,给从机采样
delay_1us(5); //延时保持IIC时钟频率,也是给从机采样有充足时间
SCL_L; //拉低时钟,给SDA准备
delay_1us(5); //延时保持IIC时钟频率
dat <<= 1; //移出数据的最高位
}
}
//**************************************
//从IIC总线接收一个字节数据
//**************************************
uint8_t I2C_RecvByte()
{
uint8_t i;
uint8_t dat = 0;
SDA_H;//释放SDA,给从机使用
delay_1us(1); //延时给从机准备SDA时间
for (i=0; i<8; i++) //8位计数器
{
dat <<= 1;
SCL_H; //拉高时钟线,采样从机SDA
if(SDA_read) //读数据
dat |=0x01;
delay_1us(5); //延时保持IIC时钟频率
SCL_L; //拉低时钟线,处理接收到的数据
delay_1us(5); //延时给从机准备SDA时间
}
return dat;
}
//**************************************
//向IIC设备写入一个字节数据
//**************************************
uint8_t Single_WriteI2C_byte(uint8_t Slave_Address,uint8_t REG_Address,uint8_t data)
{
if(I2C_Start()==0) //起始信号
{I2C_Stop(); return RESET;}
I2C_SendByte(Slave_Address); //发送设备地址+写信号
if(!I2C_WaitAck()){I2C_Stop(); return RESET;}
I2C_SendByte(REG_Address); //内部寄存器地址,
if(!I2C_WaitAck()){I2C_Stop(); return RESET;}
I2C_SendByte(data); //内部寄存器数据,
if(!I2C_WaitAck()){I2C_Stop(); return RESET;}
I2C_Stop(); //发送停止信号
return SET;
}
//**************************************
//从IIC设备读取一个字节数据
//**************************************
uint8_t Single_ReadI2C(uint8_t Slave_Address,uint8_t REG_Address,uint8_t *REG_data,uint8_t length)
{
if(I2C_Start()==0) //起始信号
{I2C_Stop(); return RESET;}
I2C_SendByte(Slave_Address); //发送设备地址+写信号
if(!I2C_WaitAck()){I2C_Stop(); return RESET;}
I2C_SendByte(REG_Address); //发送存储单元地址
if(!I2C_WaitAck()){I2C_Stop(); return RESET;}
if(I2C_Start()==0) //起始信号
{I2C_Stop(); return RESET;}
I2C_SendByte(Slave_Address+1); //发送设备地址+读信号
if(!I2C_WaitAck()){I2C_Stop(); return RESET;}
while(length-1)
{
*REG_data++=I2C_RecvByte(); //读出寄存器数据
I2C_SendACK(0); //应答
length--;
}
*REG_data=I2C_RecvByte();
I2C_SendACK(1); //发送停止传输信号
I2C_Stop(); //停止信号
return SET;
}
#ifndef _GY39_H
#define _GY39_H
#include "main.h"
#define SCL_H GPIOB->BSRR = GPIO_Pin_6
#define SCL_L GPIOB->BRR = GPIO_Pin_6
#define SDA_H GPIOB->BSRR = GPIO_Pin_7
#define SDA_L GPIOB->BRR = GPIO_Pin_7
#define SCL_read GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_6)
#define SDA_read GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_7)
void I2C_GPIO_Config(void);
void I2C_Stop(void);
uint8_t Single_WriteI2C_byte(uint8_t Slave_Address,uint8_t REG_Address,uint8_t data);
uint8_t Single_ReadI2C(uint8_t Slave_Address,uint8_t REG_Address,uint8_t *REG_data,uint8_t length);
#endif
/**
************************************* Copyright ******************************
*
* FileName : main.c
* Version : v5.0
* Author : dele
* Date : 2023-07-19
******************************************************************************
*/
/*
STLink 连接 SWDIO PA13 SWCLK PA14 不要使用这两个端口
*/
//--------------------------------------------------------------------------------------------------
// 包含头文件 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
//--------------------------------------------------------------------------------------------------
#include "main.h"
//--------------------------------------------------------------------------------------------------
// 定义引用变量 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
//--------------------------------------------------------------------------------------------------
float gy_temp,gy_hum;
float aht_temp,aht_hum;//温湿度
//==================================================================================================
// 实现功能: 硬件初始化配置
// 函数说明: Hareware_Iint
// 函数备注:
//--------------------------------------------------------------------------------------------------
// | - | - | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
void Hareware_Iint(void)
{
delay_init();//延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
//设置中断优先级分组为组2:2位抢占优先级,2位响应优先级
USART1_Config(115200);//串口初始化波特率为115200
USART2_Config(115200);//ESP8266 通讯串口2 波特率为115200
printf("串口1配置 [OK] \r\n");
printf("串口2配置 [OK] \r\n");
LED_Init();//LED端口初始化
printf("LED_Init [OK] \r\n");
Beep_Init();//蜂鸣器初始化
printf("Beep_Init [OK] \r\n");
KEY_Init(); //按键初始化
printf("KEY_Init [OK] \r\n");
LCD_Init(); //液晶屏初始化
printf("LCD_Init [OK] \r\n");
I2C_GPIO_Config();
AHT_I2C_UserConfig();
printf("AHT_I2C_UserConfig [OK] \r\n");
//TIM1_Int_Init(1999,35999);//定时1s
//printf("TIM1_Int_Init [OK] \r\n");
W25QXX_Init();//W25QXX初始化
printf("W25QXX初始化 [OK] \r\n");
POINT_COLOR=RED;
GBK_Lib_Init();
printf("硬件GBK字库初始化 [OK] \r\n");
//硬件GBK字库初始化--(如果使用不带字库的液晶屏版本,此处可以屏蔽,不做字库初始化)
tp_dev.init();//触摸屏初始化
printf("触摸屏初始化 [OK] \r\n");
}
//==================================================================================================
// 实现功能: 传感器数据采集
// 函数说明: SensorGet_Data
// 函数备注:
//--------------------------------------------------------------------------------------------------
// | - | - | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
//==================================================================================================
typedef struct
{
uint32_t Press;
uint16_t Temperature;
uint16_t Humidity;
uint16_t Altitude;
} gy_module;
gy_module Bme={0,0,0,0};
//==================================================================================================
// 实现功能: 主函数
// 函数说明: main
// 函数备注:
//--------------------------------------------------------------------------------------------------
// | - | - | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
//==================================================================================================
int main(void)
{
Hareware_Iint();
printf("Hareware_Iint [OK] \r\n");
uint8_t raw_data[13]={0};
uint16_t data_16[2]={0};
uint32_t Lux;
while(1)
{
if(Single_ReadI2C(0xb6,0x04,raw_data,10))
{
Bme.Temperature=(raw_data[0]<<8)|raw_data[1];
data_16[0]=(((uint16_t)raw_data[2])<<8)|raw_data[3];
data_16[1]=(((uint16_t)raw_data[4])<<8)|raw_data[5];
Bme.Press=(((uint32_t)data_16[0])<<16)|data_16[1];
Bme.Humidity=(raw_data[6]<<8)|raw_data[7];
Bme.Altitude=(raw_data[8]<<8)|raw_data[9];
}
if(Single_ReadI2C(0xb6,0x00,raw_data,4))
data_16[0]=(((uint16_t)raw_data[0])<<8)|raw_data[1];
data_16[1]=(((uint16_t)raw_data[2])<<8)|raw_data[3];
Lux=(((uint32_t)data_16[0])<<16)|data_16[1];
gy_temp = Bme.Temperature/100;
gy_hum = Bme.Humidity/100;
printf("Temperature:%.2fDegC \r\n ",(float)Bme.Temperature/100);
printf("Prees:%.2fPa \r\n",(float)Bme.Press/100);
printf("Humidity: %.2f \r\n ",(float)Bme.Humidity/100);
printf("Altitude: %.2f m \r\n ",(float)Bme.Altitude);
printf("LightLux: %.2f lux \r\n ",(float)Lux/100);
delay_ms(200);
}
}