哔哩哔哩视频效果链接:http://【毕业设计——STM32温湿度采集系统——手机APP】 https://www.bilibili.com/video/BV12i421Z7is/?share_source=copy_web&vd_source=d711fa8bef565aeec8a4d279df8b85aa ~~大家可以给我点波关注~~记得一键三连喔哦~~
一、项目描述:
本设计是基于stm32的无线温湿度检测系统设计实现对机房温湿度的自动化管理。首先STM32单片机通过温湿度传感器采集环境温湿度数据,并将数据显示在OLED显示屏上。其次STM32单片机对采集到的数据进行处理,包括计算、数据校验、异常检测等操作;接着将处理后的数据通过蓝牙通信模块发送到手机APP,手机APP接收STM32单片机发送的数据,并将数据显示在手机屏幕上,方便用户进行实时监控;之后通过手机APP对接收到的数据进行分析处理,以判断环境温湿度是否异常;最后当环境温湿度异常时,stm32报警模块就发出报警及LED灯就会亮,提醒用户和方便用户及时采取相应的措施以进一步改善温湿度环境,达到温湿度环境的安全和舒适性。
二、项目功能模块:
主要元器件:温湿度传感器DHT11,蜂鸣器,LED灯,面包板,stem32最小系统板,蓝牙HC05,电机等。
蜂鸣器:
原理图:
TB6612NG电机:
HC05蓝牙模块:
原理图:
三、功能代码:
Main.c配置:
GPIO_InitTypeDef GPIO_InitStructure;
uart1_init(115200);
DHT11_Init();
OLED_Init();
OLED_Clear();
Buzzer_Init();
Serial_Init();
Motor_Init();
OLED_ShowString(30, 0, "DHT11 ", 16); //输出字符
OLED_ShowCHinese(0, 4, 0); //
OLED_ShowCHinese(16, 4, 1); //
OLED_ShowString (32, 4, ":", 16); //显示温度
OLED_ShowString(48, 4, "--", 16);
OLED_ShowString (64, 4, ".", 16);
OLED_ShowCHinese(87, 4, 4); //
OLED_ShowString (93, 4, "C", 16);
OLED_ShowCHinese(0, 6, 2); //
OLED_ShowCHinese(16, 6, 3); //
OLED_ShowString (32, 6, ":", 16); //显示湿度
OLED_ShowString(48, 6, "--", 16);
OLED_ShowString(64, 6, ".", 16);
OLED_ShowString(87, 6, "%", 16);
SysTick_Init();
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //
while(1)
{
//先去中断判断read_dht11_finish
if( read_dht11_finish ) // read_dht11_finish == 1 or read_dht11_finish == -1
{
sprintf ( cStr, "\r\n\r\n读取DHT11成功!\r\n 温度:%d.%d℃,湿度:%d.%d%%\r\n",
DHT11_Data.temp_int, DHT11_Data.temp_deci ,DHT11_Data.humi_int, DHT11_Data.humi_deci );
printf ( "%s", cStr );
//打印读取 DHT11 温湿度信息
humi_int=DHT11_Data.humi_int;
humi_deci=DHT11_Data.humi_deci;
temp_int=DHT11_Data.temp_int;
temp_deci=DHT11_Data.temp_deci;
OLED_ShowNum (48, 4, temp_int, 2, 16); //显示温度
OLED_ShowNum (66, 4, temp_deci, 2, 16);
OLED_ShowNum (48, 6, humi_int, 2, 16);
OLED_ShowNum (66, 6, humi_deci, 2, 16); //显示湿度
read_dht11_finish = 0; // 清零标志位
}
}
OLED.c配置:
#include "oled.h"
#include "stdlib.h"
#include "oledfont.h"
#include "delay.h"
//起始信号
void I2C_Start(void)
{
OLED_SDIN_Set();
OLED_SCLK_Set();
OLED_SDIN_Clr();
OLED_SCLK_Clr();
}
//结束信号
void I2C_Stop(void)
{
OLED_SCLK_Set();
OLED_SDIN_Clr();
OLED_SDIN_Set();
}
//等待信号响应
void I2C_WaitAck(void) //测数据信号的电平
{
OLED_SCLK_Set();
OLED_SCLK_Clr();
}
//写入一个字节
void Send_Byte(u8 dat)
{
u8 i;
for(i = 0; i < 8; i++)
{
OLED_SCLK_Clr();//将时钟信号设置为低电平
if(dat & 0x80) //将dat的8位从最高位依次写入
{
OLED_SDIN_Set();
}
else
{
OLED_SDIN_Clr();
}
OLED_SCLK_Set();//将时钟信号设置为高电平
OLED_SCLK_Clr();//将时钟信号设置为低电平
dat <<= 1;
}
}
//发送一个字节
//向SSD1306写入一个字节。
//mode:数据/命令标志 0,表示命令;1,表示数据;
void OLED_WR_Byte(u8 dat, u8 mode)
{
I2C_Start();
Send_Byte(0x78);
I2C_WaitAck();
if(mode)
{
Send_Byte(0x40);
}
else
{
Send_Byte(0x00);
}
I2C_WaitAck();
Send_Byte(dat);
I2C_WaitAck();
I2C_Stop();
}
uint8_t CMD_Data[] =
{
0xAE, 0x00, 0x10, 0x40, 0xB0, 0x81, 0xFF, 0xA1, 0xA6, 0xA8, 0x3F,
0xC8, 0xD3, 0x00, 0xD5, 0x80, 0xD8, 0x05, 0xD9, 0xF1, 0xDA, 0x12,
0xD8, 0x30, 0x8D, 0x14, 0xAF
}; //初始化命令
/********************************************************************************
* @author: Luo Chen
* @date: 2022-07-01 15:15
* @brief: 写入初始化命令
* @return: None
********************************************************************************/
void Write_Cmd(void)
{
int i;
for (i = 0; i < 27; i++)
{
OLED_WR_Byte(CMD_Data[i], OLED_CMD); //SET DCDC命令
}
}
/********************************************************************************
* @author: Luo Chen
* @date: 2022-07-01 15:19
* @brief: 向设备写控制命令
* @param: cmd
* @return: None
********************************************************************************/
void OLED_WR_CMD(uint8_t cmd)
{
OLED_WR_Byte(cmd, OLED_CMD); //SET DCDC命令
}
/********************************************************************************
* @author: Luo Chen
* @date: 2022-07-01 15:35
* @brief: 写数据
* @param: data
* @return: None
********************************************************************************/
void OLED_WR_DATA(uint8_t data)
{
OLED_WR_Byte(data, OLED_DATA); //SET DCDC命令
}
/********************************************************************************
* @author: Luo Chen
* @date: 2022-07-01 15:32
* @brief: 清屏
* @param: None
* @return: None
********************************************************************************/
void OLED_Clear(void)
{
uint8_t i, n;
for (i = 0; i < 27; i++)
{
OLED_WR_CMD(0xb0 + i);
OLED_WR_CMD(0x00);
OLED_WR_CMD(0x10);
for (n = 0; n < 128; n++)
{
OLED_WR_DATA(0);
}
}
}
/********************************************************************************
* @author: Luo Chen
* @date: 2022-07-01 15:59
* @brief: 开启OLed显示
* @param: None
* @return: None
********************************************************************************/
void OLED_Display_On(void)
{
OLED_WR_CMD(0x8D); //SET DCDC设置电荷泵
OLED_WR_CMD(0x14); //DCDC ON 开启电荷泵
OLED_WR_CMD(0xAF); //DISPlay ON开启显示
}
/********************************************************************************
* @author: Luo Chen
* @date: 2022-07-01 16:36
* @brief: 关闭OLED显示
* @param:
* @return:
********************************************************************************/
void OLED_Display_Off(void)
{
OLED_WR_CMD(0x8D); //SET DCDC设置电荷泵
OLED_WR_CMD(0x10); //DCDC OFF 开启电荷泵
OLED_WR_CMD(0xAE); //DISPlay OFF开启显示
}
/********************************************************************************
* @author: Luo Chen
* @date: 2022-07-01 16:33
* @brief: 设置起始点坐标
* @param: x,y
* @return: None
********************************************************************************/
void OLED_Set_Pos(uint8_t x, uint8_t y)
{
OLED_WR_CMD(0xb0 + y);
OLED_WR_CMD(((x & 0xf0) >> 4) | 0x10);
OLED_WR_CMD(x & 0x0f);
}
void OLED_On(void)
{
uint8_t i, n;
for (i = 0; i < 8; i++)
{
OLED_WR_CMD(0xb0 + i); //设置页地址
OLED_WR_CMD(0x00); //设置显示位置-列低地址
OLED_WR_CMD(0x10); //设置显示位置-列高地址
for (n = 0; n < 128; n++)
{
OLED_WR_DATA(1);
}//刷新显示
}
}
/********************************************************************************
* @author: Luo Chen
* @date: 2022-07-03 15:35
* @brief: n为循环次数,得到值为m的n+1次方
* @param m
* @param n
* @return result
********************************************************************************/
unsigned int oled_pow(uint8_t m, uint8_t n)
{
unsigned int result = 1;
while (n--)result *= m;
return result;
}
//显示2个数字
//x,y :起点坐标
//len :数字的位数
//size:字体大小
//mode:模式 0,填充模式;1,叠加模式
//num:数值(0~4294967295);
void OLED_ShowNum(uint8_t x, uint8_t y, unsigned int num, uint8_t len, uint8_t size2)
{
uint8_t t, temp;
uint8_t enshow = 0;
for (t = 0; t < len; t++)
{
temp = (num / oled_pow(10, len - t - 1)) % 10;
if (enshow == 0 && t < (len - 1))
{
if (temp == 0)
{
OLED_ShowChar(x + (size2 / 2) * t, y, ' ', size2);
continue;
}
else enshow = 1;
}
OLED_ShowChar(x + (size2 / 2) * t, y, temp + '0', size2);
}
}
//在指定位置显示一个字符,包括部分字符
//x:0~127
//y:0~63
//mode:0,反白显示;1,正常显示
//size:选择字体 16/12
void OLED_ShowChar(uint8_t x, uint8_t y, uint8_t chr, uint8_t Char_Size)
{
unsigned char c = 0, i = 0;
c = chr - ' ';//得到偏移后的值
if (x > 128 - 1)
{
x = 0;
y = y + 2;
}
if (Char_Size == 16)
{
OLED_Set_Pos(x, y);
for (i = 0; i < 8; i++)
OLED_WR_DATA(F8X16[c * 16 + i]);
OLED_Set_Pos(x, y + 1);
for (i = 0; i < 8; i++)
OLED_WR_DATA(F8X16[c * 16 + i + 8]);
}
else
{
OLED_Set_Pos(x, y);
for (i = 0; i < 6; i++)
OLED_WR_DATA(F6x8[c][i]);
}
}
/********************************************************************************
* @author: Luo Chen
* @date: 2022-07-03 15:02
* @brief: 显示一个字符号串
* @param x
* @param y
* @param chr
* @param Char_Size
* @return: None
********************************************************************************/
void OLED_ShowString(uint8_t x, uint8_t y, uint8_t *chr, uint8_t Char_Size)
{
unsigned char j = 0;
while (chr[j] != '\0')
{
OLED_ShowChar(x, y, chr[j], Char_Size);
x += 8;
//如果--行显示的数字大于16就将行清零,y+2变成下一行
if (x > 120)
{
x = 0;
y += 2;
}
j++;
}
}
/********************************************************************************
* @author: Luo Chen
* @date: 2022-07-03 14:46
* @brief: 显示汉字 hzk 用取模软件得出的数组
* @param x,设置起始点x坐标
* @param y,设置起始点y坐标
* @param no,显示数组的第n个字
* @return:
********************************************************************************/
void OLED_ShowCHinese(uint8_t x, uint8_t y, uint8_t no)
{
uint8_t t, adder = 0;
OLED_Set_Pos(x, y);
//显示上面一部分
for (t = 0; t < 16; t++)
{
//Hzk[第几个数组][第几个值]
OLED_WR_DATA(Hzk[2 * no][t]);
adder += 1;
}
//显示下面一部分
OLED_Set_Pos(x, y + 1);
for (t = 0; t < 16; t++)
{
OLED_WR_DATA(Hzk[2 * no + 1][t]);
adder += 1;
}
}
/********************************************************************************
* @author: Luo Chen
* @date: 2022-07-01 15:38
* @brief: 初始化oled屏幕
* @param: None
* @return: None
********************************************************************************/
void OLED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd( OLED_CLK, ENABLE );
GPIO_InitStructure.GPIO_Pin = OLED_SCLK_Pin | OLED_SDIN_Pin;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(OLED_PORT, &GPIO_InitStructure);
Delay_ms(100);
Write_Cmd();
OLED_Clear();
}
DHT11.c配置:
#include "dht11.h"
#include "delay.h"
static void DHT11_GPIO_Config ( void );
static void DHT11_Mode_IPU ( void );
static void DHT11_Mode_Out_PP ( void );
static uint8_t DHT11_ReadByte ( void );
/**
* @brief DHT11 初始化函数
* @param 无
* @retval 无
*/
void DHT11_Init ( void )
{
DHT11_GPIO_Config ();
DHT11_Dout_1; // 拉高GPIOB10
}
/*
* 函数名:DHT11_GPIO_Config
* 描述 :配置DHT11用到的I/O口
* 输入 :无
* 输出 :无
*/
static void DHT11_GPIO_Config ( void )
{
/*定义一个GPIO_InitTypeDef类型的结构体*/
GPIO_InitTypeDef GPIO_InitStructure;
/*开启DHT11_Dout_GPIO_PORT的外设时钟*/
DHT11_Dout_SCK_APBxClock_FUN ( DHT11_Dout_GPIO_CLK, ENABLE );
/*选择要控制的DHT11_Dout_GPIO_PORT引脚*/
GPIO_InitStructure.GPIO_Pin = DHT11_Dout_GPIO_PIN;
/*设置引脚模式为通用推挽输出*/
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
/*设置引脚速率为50MHz */
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
/*调用库函数,初始化DHT11_Dout_GPIO_PORT*/
GPIO_Init ( DHT11_Dout_GPIO_PORT, &GPIO_InitStructure );
}
/*
* 函数名:DHT11_Mode_IPU
* 描述 :使DHT11-DATA引脚变为上拉输入模式
* 输入 :无
* 输出 :无
*/
static void DHT11_Mode_IPU(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/*选择要控制的DHT11_Dout_GPIO_PORT引脚*/
GPIO_InitStructure.GPIO_Pin = DHT11_Dout_GPIO_PIN;
/*设置引脚模式为浮空输入模式*/
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU ;
/*调用库函数,初始化DHT11_Dout_GPIO_PORT*/
GPIO_Init(DHT11_Dout_GPIO_PORT, &GPIO_InitStructure);
}
/*
* 函数名:DHT11_Mode_Out_PP
* 描述 :使DHT11-DATA引脚变为推挽输出模式
* 输入 :无
* 输出 :无
*/
static void DHT11_Mode_Out_PP(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/*选择要控制的DHT11_Dout_GPIO_PORT引脚*/
GPIO_InitStructure.GPIO_Pin = DHT11_Dout_GPIO_PIN;
/*设置引脚模式为通用推挽输出*/
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
/*设置引脚速率为50MHz */
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
/*调用库函数,初始化DHT11_Dout_GPIO_PORT*/
GPIO_Init(DHT11_Dout_GPIO_PORT, &GPIO_InitStructure);
}
static uint8_t DHT11_ReadByte(void)
{
uint8_t i, temp = 0;
for(i = 0; i < 8; i++)
{
/*每bit以50us低电平标置开始,轮询直到从机发出 的50us 低电平 结束*/
while(DHT11_Dout_IN() == Bit_RESET);
/*DHT11 会持续26~28us的高电平表示“0”,持续70us高电平表示“1”,
*通过检测 x us后的电平即可区别这两个状 ,x 即下面的延时
*/
Delay_us(40);
//如果40us后持续高电平
if(DHT11_Dout_IN() == Bit_SET)
{
//将高电平持续结束
while(DHT11_Dout_IN() == Bit_SET);
temp |= 0x01 << (7 - i); //把第7-i位置1,MSB先行
}
else // x us后为低电平表示数据“0”,将第7-i位置0
{
temp &= ~0x01 << (7 - i); //把第7-i位置0,MSB先行
}
}
return temp;
}
/*
* 函数名:DHT11_Read_TempAndHumidity
* 描述 :DTH11读取温湿度
* 一次完整的数据传输为40bit,高位先出
* 8bit 湿度整数 + 8bit 湿度小数 + 8bit 温度整数 + 8bit 温度小数 + 8bit 校验和
* 输入 :DHT11_Data
* 返回 :状态值
* 调用 :调用
*/
uint8_t DHT11_Read_TempAndHumidity(DHT11_Data_TypeDef *DHT11_Data)
{
/********************************************************************************
* 先主机为输出模式,拉低至少18ms的信号
* 然后主机拉高20~40us的信号
* 再主机(DHT11)转为输入模式,等待响应
* 先将响应低电平通过while过滤
* 再将响应高电平通过while过滤
* 最后通过DHT11_ReadByte函数读取数据
********************************************************************************/
//输出模式
DHT11_Mode_Out_PP();
//主机拉低
DHT11_Dout_0;
//延时18MS
Delay_ms(20);
//拉高
DHT11_Dout_1;
//延时30us
Delay_us(30);
/*主机设为输入 判断从机响应信号*/
DHT11_Mode_IPU();
/*判断从机是否有低电平响应信号 如不响应则跳出,响应则向下运行*/
if(DHT11_Dout_IN() == Bit_RESET)
{
/*轮询直到从机发出 的80us 低电平 响应信号结束*/
while(DHT11_Dout_IN() == Bit_RESET);
/*轮询直到从机发出的 80us 高电平 标置信号结束*/
while(DHT11_Dout_IN() == Bit_SET);
//8bit 湿度整数 + 8bit 湿度小数 + 8bit 温度整数 + 8bit 温度小数 + 8bit 校验和
DHT11_Data->humi_int = DHT11_ReadByte();
DHT11_Data->humi_deci = DHT11_ReadByte();
DHT11_Data->temp_int = DHT11_ReadByte();
DHT11_Data->temp_deci = DHT11_ReadByte();
DHT11_Data->check_sum = DHT11_ReadByte();
//读取结束将引脚改为输出模式
DHT11_Mode_Out_PP();
DHT11_Dout_1;
if(DHT11_Data->check_sum == DHT11_Data->humi_int + DHT11_Data->humi_deci + DHT11_Data->temp_int + DHT11_Data->temp_deci)
return SUCCESS;
else
return ERROR;
}
else
return ERROR;
}
电机驱动+蜂鸣器+LED灯闪烁
if(temp_int >threshold){
Buzzer_ON();
GPIO_ResetBits(GPIOA, GPIO_Pin_0);
Delay_ms(500);
GPIO_SetBits(GPIOA, GPIO_Pin_0);
Delay_ms(500);
Motor_SetSpeed(30);
}
else
{
Buzzer_OFF();
GPIO_SetBits(GPIOA, GPIO_Pin_0);
Motor_SetSpeed(0);