目录
一、前言
项目成品图片:
哔哩哔哩视频链接:
STM32智能晾衣架
(资料分享见文末)
二、项目简介
1.功能详解
基于STM32的智能晾衣架
功能如下:
- 环境监测:采集环境温湿度、光照强度、是否有雨/有人等等
- 自动模式:光照强度低于阈值、湿度高于阈值或者有雨,自动关闭晾衣架
- 手动模式:通过按键手动控制晾衣架的开关
- 定时模式:通过设置开关时间来对晾衣架进行定时控制
- 防盗功能:通过光电红外传感器检测是否有人,有人开启声光报警
- 阈值调节:按键可设置各项参数的阈值
- 时间显示:显示当前时间并且可通过按键调节时间
- APP控制:通过WIFI模块连接APP来接收环境参数数据与控制下发
2.主要器件
- STM32F103C8T6单片机
- OLED 屏幕
- DHT11温湿度传感器
- 光敏传感器
- 雨滴传感器
- 光电红外传感器
- ESP8266模块(WIFI)
- DS1302时钟模块
- 步进电机
- 有源蜂鸣器
- LED指示灯
三、原理图设计
四、PCB硬件设计
PCB图
五、程序设计
#include "sys.h"
#include "delay.h"
#include "adc.h"
#include "gpio.h"
#include "OLED_I2C.h"
#include "ds1302.h"
#include "timer.h"
#include "dht11.h"
#include "esp8266.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define AUTOMATIC 0 //自动模式
#define MANUAL 1 //手动模式
#define TIMING 2 //定时模式
#define angle 360 //电机旋转的角度
#define Coil_8B_A {A=1;B=0;C=0;D=0;}//A相通电,其他相断电
#define Coil_8B_AB {A=1;B=1;C=0;D=0;}//AB相通电,其他相断电
#define Coil_8B_B {A=0;B=1;C=0;D=0;}//B相通电,其他相断电
#define Coil_8B_BC {A=0;B=1;C=1;D=0;}//BC相通电,其他相断电
#define Coil_8B_C {A=0;B=0;C=1;D=0;}//C相通电,其他相断电
#define Coil_8B_CD {A=0;B=0;C=1;D=1;}//CD相通电,其他相断电
#define Coil_8B_D {A=0;B=0;C=0;D=1;}//D相通电,其他相断电
#define Coil_8B_DA {A=1;B=0;C=0;D=1;}//DA相通电,其他相断电
#define Coil_OFF {A=0;B=0;C=0;D=0;}//全部断电
extern const unsigned char BMP[];
char display[16];
unsigned char setn=0;//记录设置键按下的次数
unsigned char temperature=0;
unsigned char humidity=0;
unsigned char setHumiValue=75; //湿度设置值
unsigned int light=0;
unsigned char setLightValue=15; //光照设置值
unsigned char mode=0;
/*开启和关闭时间*/
unsigned char openTimeHour=7;
unsigned char openTimeMin=30;
unsigned char closeTimeHour=20;
unsigned char closeTimeMin=0;
u8 AntiTheftFlag = 0; //防盗模式
u8 SomebodyFlag = 0; //有人标志
u8 openFlag = 0;
u8 shuaxin = 0;
u8 sendFlag = 1;
u8 setHumiLight=0;
void displayMode(void)
{
unsigned char i=0;
if(mode == AUTOMATIC)
{
for(i=0;i<4;i++)OLED_ShowCN(i*16,0,i+0,1);//显示中文:自动模式
}
if(mode == MANUAL)
{
for(i=0;i<4;i++)OLED_ShowCN(i*16,0,i+4,1);//显示中文:手动模式
}
if(mode == TIMING)
{
for(i=0;i<4;i++)OLED_ShowCN(i*16,0,i+8,1);//显示中文:定时模式
}
if(AntiTheftFlag==0) //防盗模式
{
OLED_ShowCN(111,0,15,1); //显示关
}
else
{
OLED_ShowCN(111,0,14,1); //显示开
}
}
void displayState(void) //显示窗的状态
{
if(openFlag==0)
{
OLED_ShowCN(64,4,15,0);
}
else
{
OLED_ShowCN(64,4,14,0);
}
}
void InitDisplay(void) //初始化显示
{
unsigned char i=0;
for(i=0;i<2;i++)OLED_ShowCN(i*16+71,0,i+12,0);//显示中文:防盗
for(i=0;i<2;i++)OLED_ShowCN(i*16+0,4,i+17,0);//显示中文:状态
for(i=0;i<3;i++)OLED_ShowCN(i*16+0,6,i+19,0);//显示中文:光强度
OLED_ShowChar(103,0,':',2,0);
OLED_ShowChar(48,4,':',2,0);
OLED_ShowChar(48,6,':',2,0);
displayMode();
}
void displayTime(void) //显示时间
{
if(setn == 0)DS1302_DateRead(&SysDate); //读时间
OLED_ShowChar(0,2,SysDate.hour/10+'0',2,setn+1-1);
OLED_ShowChar(8,2,SysDate.hour%10+'0',2,setn+1-1);
OLED_ShowChar(16,2,':',2,0);
OLED_ShowChar(24,2,SysDate.min/10+'0',2,setn+1-2);
OLED_ShowChar(32,2,SysDate.min%10+'0',2,setn+1-2);
OLED_ShowChar(40,2,':',2,0);
OLED_ShowChar(48,2,SysDate.sec/10+'0',2,setn+1-3);
OLED_ShowChar(56,2,SysDate.sec%10+'0',2,setn+1-3);
}
void displayTempAndHumi(void) //显示温湿度
{
DHT11_Read_Data(&temperature,&humidity);
OLED_ShowChar(72,2,temperature/10+'0',2,0);
OLED_ShowChar(80,2,temperature%10+'0',2,0);
OLED_ShowCentigrade(88, 2);
OLED_ShowChar(103,2,humidity/10+'0',2,0);
OLED_ShowChar(111,2,humidity%10+'0',2,0);
OLED_ShowChar(119,2,'%',2,0);
}
void displayLight(void)//显示光强度
{
u16 test_adc=0;
/////////////获取光线值
test_adc = Get_Adc_Average(ADC_Channel_9,5);//读取通道9的5次AD平均值
light = 99-test_adc*99/4096;//转换成0-99百分比
light = light >= 99? 99: light;//最大只能到百分之99
OLED_ShowChar(60,6,light/10+'0',2,0);
OLED_ShowChar(68,6,light%10+'0',2,0);
OLED_ShowChar(76,6,'%',2,0);
}
void displaySetValue(void) //显示设置的值
{
if(setn == 4)
{
OLED_ShowChar(60,4,setHumiValue/10+'0',2,0);
OLED_ShowChar(68,4,setHumiValue%10+'0',2,0);
OLED_ShowChar(76,4,'%',2,0);
}
if(setn == 5)
{
OLED_ShowChar(60,4,setLightValue/10+'0',2,0);
OLED_ShowChar(68,4,setLightValue%10+'0',2,0);
OLED_ShowChar(76,4,'%',2,0);
}
if(setn >= 6)
{
OLED_ShowChar(78,4,openTimeHour/10+'0',2,setn+1-6);
OLED_ShowChar(86,4,openTimeHour%10+'0',2,setn+1-6);
OLED_ShowChar(94,4,':',2,0);
OLED_ShowChar(102,4,openTimeMin/10+'0',2,setn+1-7);
OLED_ShowChar(110,4,openTimeMin%10+'0',2,setn+1-7);
OLED_ShowChar(78,6,closeTimeHour/10+'0',2,setn+1-8);
OLED_ShowChar(86,6,closeTimeHour%10+'0',2,setn+1-8);
OLED_ShowChar(94,6,':',2,0);
OLED_ShowChar(102,6,closeTimeMin/10+'0',2,setn+1-9);
OLED_ShowChar(110,6,closeTimeMin%10+'0',2,setn+1-9);
}
}
void keyscan(void) //按键扫描
{
u8 i;
if(KEY1 == 0) //设置键
{
delay_ms(20);
if(KEY1 == 0)
{
while(KEY1 == 0);
setn ++;
if(setn == 4)
{
OLED_CLS(); //清屏
for(i=0;i<4;i++)OLED_ShowCN(i*16+32,0,i+22,0);//显示中文:设置湿度
}
if(setn == 5)
{
for(i=0;i<3;i++)OLED_ShowCN(i*16+64,0,i+19,0);//显示中文:光强度
}
if(setn == 6)
{
for(i=0;i<3;i++)OLED_ShowCN(i*16+64,0,i+26,0);//显示中文:时间段
for(i=0;i<4;i++)OLED_ShowCN(i*16+0,4,i+29,0);//显示中文:开启时间
for(i=0;i<4;i++)OLED_ShowCN(i*16+0,6,i+33,0);//显示中文:开启时间
OLED_ShowStr(62,4,": ",2,0);//显示冒号
OLED_ShowStr(62,6,": ",2,0);//显示冒号
}
displaySetValue();
if(setn > 9)
{
setn = 0;
OLED_CLS(); //清屏
InitDisplay();
}
}
}
if(KEY2 == 0) //加键
{
delay_ms(20);
if(KEY2 == 0)
{
while(KEY2 == 0);
if(setn == 0)
{
mode ++;
if(mode > TIMING)mode=AUTOMATIC;
displayMode();
}
if(setn == 1)//设置时
{
SysDate.hour ++;
if(SysDate.hour == 24)SysDate.hour=0;
DS1302_DateSet(&SysDate);//设置时间
}
if(setn == 2)//设置分
{
SysDate.min ++;
if(SysDate.min == 60)SysDate.min=0;
DS1302_DateSet(&SysDate);//设置时间
}
if(setn == 3)//设置秒
{
SysDate.sec ++;
if(SysDate.sec == 60)SysDate.sec=0;
DS1302_DateSet(&SysDate);//设置时间
}
if(setn == 4)//设置湿度
{
if(setHumiValue < 99)setHumiValue++;
displaySetValue();
setHumiLight=1;
}
if(setn == 5)//设置光线
{
if(setLightValue < 99)setLightValue++;
displaySetValue();
setHumiLight=1;
}
if(setn == 6)//设置开启时
{
openTimeHour ++;
if(openTimeHour == 24)openTimeHour=0;
displaySetValue();
}
if(setn == 7)//设置开启分
{
openTimeMin ++;
if(openTimeMin == 60)openTimeMin=0;
displaySetValue();
}
if(setn == 8)//设置关闭时
{
closeTimeHour ++;
if(closeTimeHour == 24)closeTimeHour=0;
displaySetValue();
}
if(setn == 9)//设置关闭分
{
closeTimeMin ++;
if(closeTimeMin == 60)closeTimeMin=0;
displaySetValue();
}
}
}
if(KEY3 == 0) //减键
{
delay_ms(20);
if(KEY3 == 0)
{
while(KEY3 == 0);
if(setn == 0)
{
AntiTheftFlag=!AntiTheftFlag;
displayMode();
}
if(setn == 1)//设置时
{
if(SysDate.hour == 0)SysDate.hour=24;
SysDate.hour --;
DS1302_DateSet(&SysDate);
}
if(setn == 2)//设置分
{
if(SysDate.min == 0)SysDate.min=60;
SysDate.min --;
DS1302_DateSet(&SysDate);
}
if(setn == 3)//设置秒
{
if(SysDate.sec == 0)SysDate.sec=60;
SysDate.sec --;
DS1302_DateSet(&SysDate);
}
if(setn == 4)//设置湿度
{
if(setHumiValue > 0)setHumiValue--;
displaySetValue();
setHumiLight=1;
}
if(setn == 5)//设置光线
{
if(setLightValue > 0)setLightValue--;
displaySetValue();
setHumiLight=1;
}
if(setn == 6)//设置开启时
{
if(openTimeHour == 0)openTimeHour=24;
openTimeHour --;
displaySetValue();
}
if(setn == 7)//设置开启分
{
if(openTimeMin == 0)openTimeMin=60;
openTimeMin --;
displaySetValue();
}
if(setn == 8)//设置关闭时
{
if(closeTimeHour == 0)closeTimeHour=24;
closeTimeHour --;
displaySetValue();
}
if(setn == 9)//设置关闭分
{
if(closeTimeMin == 0)closeTimeMin=60;
closeTimeMin --;
displaySetValue();
}
}
}
if(KEY4 == 0) //手动键
{
delay_ms(20);
if(KEY4 == 0)
{
while(KEY4 == 0);
if(mode == MANUAL)
{
openFlag = !openFlag;
}
}
}
}
void UsartSendReceiveData(void)
{
unsigned char *dataPtr = NULL;
char *str1=0,*str2=0,i;
int setValue=0;
char setvalue[3]={0};
char SEND_BUF[100];
char set_tmp[20];
dataPtr = ESP8266_GetIPD(0); //接收数据
if(dataPtr != NULL)
{
if(strstr((char *)dataPtr,"setHu:")!=NULL)
{
str1 = strstr((char *)dataPtr,"setHu:");
while(*str1 < '0' || *str1 > '9') //判断是不是0到9有效数字
{
str1 = str1 + 1;
delay_ms(10);
}
i = 0;
while(*str1 >= '0' && *str1 <= '9') //判断是不是0到9有效数字
{
setvalue[i] = *str1;
i ++; str1 ++;
if(*str1 == '\r')break; //换行符,直接退出while循环
delay_ms(10);
}
setvalue[i] = '\0'; //加上结尾符
setValue = atoi(setvalue);
if(setValue>=0 && setValue<=99)
{
setHumiValue = setValue; //设置的湿度值
displaySetValue();
}
}
if(strstr((char *)dataPtr,"setLi:")!=NULL)
{
str1 = strstr((char *)dataPtr,"setLi:");
while(*str1 < '0' || *str1 > '9') //判断是不是0到9有效数字
{
str1 = str1 + 1;
delay_ms(10);
}
i = 0;
while(*str1 >= '0' && *str1 <= '9') //判断是不是0到9有效数字
{
setvalue[i] = *str1;
i ++; str1 ++;
if(*str1 == '\r')break; //换行符,直接退出while循环
delay_ms(10);
}
setvalue[i] = '\0'; //加上结尾符
setValue = atoi(setvalue);
if(setValue>=0 && setValue<=99)
{
setLightValue = setValue; //设置的光线值
displaySetValue();
}
}
if(strstr((char *)dataPtr,"opentime")!=NULL)
{
BEEP = 1;
delay_ms(80);
BEEP = 0;
str2 = strstr((char *)dataPtr,"opentime");
while(*str2 < '0' || *str2 > '9') //判断是不是0到9有效数字
{
str2 = str2 + 1;
delay_ms(10);
}
i = 0;
while(*str2 >= '0' && *str2 <= '9') //判断是不是0到9有效数字
{
setvalue[i] = *str2;
i ++; str2 ++;
if(*str2 == ':')break;
delay_ms(10);
}
setvalue[i] = '\0'; //加上结尾符
setValue = atoi(setvalue);
if(setValue>=0 && setValue<=23)
{
openTimeHour = setValue; //开启时间的小时
displaySetValue();
}
str2 = str2 + 1;
i = 0;
while(*str2 >= '0' && *str2 <= '9') //判断是不是0到9有效数字
{
setvalue[i] = *str2;
i ++; str2 ++;
if(*str2 == ',')break;
delay_ms(10);
}
setvalue[i] = '\0'; //加上结尾符
setValue = atoi(setvalue);
if(setValue>=0 && setValue<=59)
{
openTimeMin = setValue; //开启时间的分钟
displaySetValue();
}
}
if(strstr((char *)dataPtr,"closetime")!=NULL)
{
str2 = strstr((char *)dataPtr,"closetime");
while(*str2 < '0' || *str2 > '9') //判断是不是0到9有效数字
{
str2 = str2 + 1;
delay_ms(10);
}
i = 0;
while(*str2 >= '0' && *str2 <= '9') //判断是不是0到9有效数字
{
setvalue[i] = *str2;
i ++; str2 ++;
if(*str2 == ':')break;
delay_ms(10);
}
setvalue[i] = '\0'; //加上结尾符
setValue = atoi(setvalue);
if(setValue>=0 && setValue<=23)
{
closeTimeHour = setValue; //关闭时间的小时
displaySetValue();
}
str2 = str2 + 1;
i = 0;
while(*str2 >= '0' && *str2 <= '9') //判断是不是0到9有效数字
{
setvalue[i] = *str2;
i ++; str2 ++;
if(*str2 == '\r')break; //换行符,直接退出while循环
delay_ms(10);
}
setvalue[i] = '\0'; //加上结尾符
setValue = atoi(setvalue);
if(setValue>=0 && setValue<=59)
{
closeTimeMin = setValue; //关闭时间的分钟
displaySetValue();
}
}
if(strstr((char *)dataPtr,"manualMode")!=NULL) //手动模式
{
BEEP = 1;
delay_ms(80);
BEEP = 0;
mode=MANUAL;
displayMode();
}
if(strstr((char *)dataPtr,"timingMode")!=NULL) //定时模式
{
BEEP = 1;
delay_ms(80);
BEEP = 0;
mode=TIMING;
displayMode();
}
if(strstr((char *)dataPtr,"autoMode")!=NULL) //自动模式
{
BEEP = 1;
delay_ms(80);
BEEP = 0;
mode=AUTOMATIC;
displayMode();
}
if(strstr((char *)dataPtr,"antiTheft")!=NULL) //防盗模式
{
BEEP = 1;
delay_ms(80);
BEEP = 0;
AntiTheftFlag=!AntiTheftFlag;
displayMode();
}
if(strstr((char *)dataPtr,"manualOpen")!=NULL) //手动开
{
if(mode == MANUAL)
{
BEEP = 1;
delay_ms(80);
BEEP = 0;
openFlag = 1;
}
}
if(strstr((char *)dataPtr,"manualClose")!=NULL) //手动关
{
if(mode == MANUAL)
{
BEEP = 1;
delay_ms(80);
BEEP = 0;
openFlag = 0;
}
}
ESP8266_Clear(); //清空缓存
}
if(sendFlag==1) //1秒钟上传一次数据
{
sendFlag = 0;
memset(SEND_BUF,0,sizeof(SEND_BUF)); //清空缓冲区
sprintf(SEND_BUF,"$temp:%d#,$humi:%d#,$light:%d#",temperature,humidity,light);
if(SomebodyFlag==1)
{
strcat(SEND_BUF,"somebody");
}
else
{
strcat(SEND_BUF,"nobody");
}
if(setHumiLight==1)
{
setHumiLight =0;
memset(set_tmp,0,sizeof(set_tmp)); //清空缓冲区
sprintf(set_tmp,"$setHumi:%d#",setHumiValue);
strcat(SEND_BUF,set_tmp);
memset(set_tmp,0,sizeof(set_tmp)); //清空缓冲区
sprintf(set_tmp,"$setLight:%d#",setLightValue);
strcat(SEND_BUF,set_tmp);
}
ESP8266_SendData((u8 *)SEND_BUF, strlen(SEND_BUF));
ESP8266_Clear();
}
}
int main(void)
{
delay_init(); //延时函数初始化
NVIC_Configuration(); //中断优先级配置
I2C_Configuration(); //IIC初始化
delay_ms(300);
DS1302_Init(&SysDate); //DS1302初始化
OLED_Init(); //OLED液晶初始化
Adc_Init();
OLED_CLS(); //清屏
KEY_GPIO_Init(); //按键引脚初始化
OLED_ShowStr(0, 2, " loading... ", 2,0);//显示加载中
ESP8266_Init(); //ESP8266初始化
MOTOR_GPIO_Init();
while(DHT11_Init())
{
OLED_ShowStr(0, 2, " DHT11 Error! ", 2,0);//显示DHT11错误!
delay_ms(500);
}
OLED_CLS(); //清屏
DS1302_DateRead(&SysDate);//读时间
InitDisplay();
TIM3_Init(99,719); //定时器初始化,定时1ms
//Tout = ((arr+1)*(psc+1))/Tclk ;
//Tclk:定时器输入频率(单位MHZ)
//Tout:定时器溢出时间(单位us)
while(1)
{
keyscan(); //按键扫描
if(setn < 4) //不在设置状态下
{
displayTime();
if(shuaxin == 1) //大概500ms刷新一下
{
shuaxin = 0;
displayTempAndHumi();
displayLight();
if(mode == AUTOMATIC) //在自动模式下
{
if(light<=setLightValue || humidity>=setHumiValue || !WATER) //自动模式下,检测到光线暗,湿度高,有雨都关窗
{
openFlag = 0;
}
else
{
openFlag = 1; //否则都开窗
}
}
if(mode == TIMING) //在定时模式下
{
/* 统一转换成分钟来计算 */
if(((openTimeHour*60+openTimeMin)<(SysDate.hour*60+SysDate.min+1))//在开启时间和关闭时间的中间段
&&((closeTimeHour*60+closeTimeMin)>(SysDate.hour*60+SysDate.min)))
{
if(!WATER)
openFlag = 0; //有雨关窗
else
openFlag = 1; //开窗
}
else
{
openFlag = 0; //关窗
}
}
}
displayState(); //显示窗的状态
if(AntiTheftFlag==1&&HW==0) //在防盗模式下检测到有人
{
if(SomebodyFlag==0)
{
OLED_DrawBMP(95,4,127,8,(unsigned char *)BMP); //图显示
BEEP = 1; //蜂鸣器报警
SomebodyFlag = 1;
}
}
else
{
if(SomebodyFlag==1)
{
OLED_ShowStr(95, 4, " ", 2,0);
OLED_ShowStr(95, 6, " ", 2,0);
BEEP = 0; //蜂鸣器关闭
SomebodyFlag = 0;
}
}
}
UsartSendReceiveData(); //串口发送接收数据
delay_ms(10);
}
}
void TIM3_IRQHandler(void)//定时器3中断服务程序,用于记录时间
{
static u16 timeCount1 = 300;
static u16 timeCount2 = 0;
static u8 index = 0;
static u16 pulse = 0;
static u16 count = 0;
if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) //检查指定的TIM中断发生与否:TIM 中断源
{
TIM_ClearITPendingBit(TIM3, TIM_IT_Update); //清除中断标志位
timeCount1 ++;
timeCount2 ++;
if(timeCount1 >= 500) //500ms
{
timeCount1 = 0;
shuaxin = 1;
}
if(timeCount2 >= 1000) //1000ms
{
timeCount2 = 0;
sendFlag = 1;
}
pulse = (int)(angle/5.625)*32; //计算电机旋转的时长
if(openFlag == 1) //步进电机正转
{
if(count < pulse)
{
switch(index)
{
case 0:Coil_8B_A; break;
case 1:Coil_8B_AB;break;
case 2:Coil_8B_B; break;
case 3:Coil_8B_BC;break;
case 4:Coil_8B_C; break;
case 5:Coil_8B_CD;break;
case 6:Coil_8B_D; break;
case 7:Coil_8B_DA;break;
}
index ++;
if(index == 8) index = 0;
count ++;
}
else
{
Coil_OFF ; //正转结束,电机停止
}
}
else //步进电机反转
{
if(count > 0)
{
switch(index)
{
case 0:Coil_8B_DA; break;
case 1:Coil_8B_D; break;
case 2:Coil_8B_CD; break;
case 3:Coil_8B_C; break;
case 4:Coil_8B_BC; break;
case 5:Coil_8B_B; break;
case 6:Coil_8B_AB; break;
case 7:Coil_8B_A; break;
}
index ++;
if(index == 8) index = 0;
count --;
}
else
{
Coil_OFF ; //反转结束,电机停止
}
}
}
}
六、实验效果
七、资料内容