天天看点

基于STM32设计的智能插座+人体感应灯(ESP8266+人体感应+手机APP)

一、环境介绍

MCU: STM32F103C8T6

程序开发IDE: keil5

STM32程序风格:  采用寄存器方式开发,注释齐全,执行效率高,方便移植

手机APP:  采用QT设计,程序支持跨平台编译运行(Android、IOS、Windows、Linux都可以编译运行,对应平台上QT的环境搭建,之前博客已经发了文章讲解)

硬件包含:  SRM32F103C8T6最小系统板、红外热释电人体感应模块、DHT11温湿度传感器、0.96寸单色OLED显示屏、ESP8266、继电器、RGB大功率白灯.

完整工程源码下载地址(包含手机APP源码、Windows系统上位机源码、STM32工程、下载工具、原理图):  

https://download.csdn.net/download/xiaolong1126626497/19702853
基于STM32设计的智能插座+人体感应灯(ESP8266+人体感应+手机APP)
基于STM32设计的智能插座+人体感应灯(ESP8266+人体感应+手机APP)
基于STM32设计的智能插座+人体感应灯(ESP8266+人体感应+手机APP)
基于STM32设计的智能插座+人体感应灯(ESP8266+人体感应+手机APP)

二、功能介绍

这是基于STM32设计的智能插座+人体感应灯。

硬件包含:  

1. SRM32F103C8T6最小系统板:  基础的系统板,引出了所有IO口

2. 红外热释电人体感应模块: 用来检测人体

3. DHT11温湿度传感器: 检测环境的温度、湿度

4. 0.96寸单色OLED显示屏 : 显示状态信息。比如: WIFI状态、RTC时钟、插座状态、温湿度值

5. ESP8266: 用来与手机APP之间通信

6. 继电器:  模拟插座开关

7. RGB大功率白灯: 模拟正常的灯泡

支持的功能如下:

1. 使用热释电人体感应模块检测人体,检测到人体自动开灯,30秒(时间可以根据要求调整)没有检测到人体就自动关灯。

2. 检测环境温湿度,使用OLED显示屏在界面上实时显示出来。 如果环境温度高于阀值,强制关闭插座、如果湿度高于阀值,也会强制关闭插座;防止火灾隐患。  温度最高阀值设置为: 30°,湿度阀值为80%, 这些都可以根据设计要求调整。

  并且RGB灯也会根据不同的温度阀值亮不同颜色的灯。 比如: 温度高于30°亮红色、温度20°黄色 、温度10°青色

3. 设置ESP8266WIFI模块为AP模式(路由器模式),手机或者电脑可以连接到ESP8266.搭建局域网。

4. 设计手机APP和电脑客户端软件,可以实时显示收到的温湿度数据(3秒上传一次).可以显示历史. 点击手机APP上的按钮,可以用来控制插座开关。

5. OLED一共有4个页面。 RTC实时时钟显示页面、温湿度显示页面、智能插座开关状态页面、WIFI热点信息页面

6. OLED显示屏的第一页是实时时钟页面,时间可以通过手机APP来校准。 在手机APP上有一个RTC校准按钮,点击一下就可以校准设备上的时间。

三、使用的相关硬件介绍

3.1 DTH11 温湿度传感器

基于STM32设计的智能插座+人体感应灯(ESP8266+人体感应+手机APP)

 DHT11数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器,它应用专用的数字模块采集技术和温湿度传感技术,确保产品具有极高的可靠性和卓越的长期稳定性。传感器包括一个电阻式感湿元件和一个NTC测温元件,并与一个高性能8位单片机相连接。因此该产品具有品质卓越、超快响应、抗干扰能力强、性价比极高等优点。每个DHT11传感器都在极为精确的湿度校验室中进行校准。校准系数以程序的形式存在OTP内存中,传感器内部在检测信号的处理过程中要调用这些校准系数。单线制串行接口,使系统集成变得简易快捷。超小的体积、极低的功耗,使其成为该类应用中,在苛刻应用场合的最佳选择。产品为4针单排引脚封装,连接方便。

3.2  热释电传感器

基于STM32设计的智能插座+人体感应灯(ESP8266+人体感应+手机APP)
基于STM32设计的智能插座+人体感应灯(ESP8266+人体感应+手机APP)

热释电红外传感器在结构上引入场效应管,其目的在于完成阻抗变换。由于热释电元输出的是电荷信号,并不能直接使用,因而需要用电阻将其转换为电压形式。故引入的N沟道结型场效应管应接成共漏形式来完成阻抗变换。热释电红外传感器由传感探测元、干涉滤光片和场效应管匹配器三部分组成。设计时应将高热电材料制成一定厚度的薄片,并在它的两面镀上金属电极,然后加电对其进行极化,这样便制成了热释电探测元。

热释电红外传感器的外形如上图所示。其可以检测人体发出的红外线信号,并将其转换成电信号输出。传感器顶部的长方形窗口加有滤光片,可以使人体发出的9~10μm波长的红外线通过,而其它波长的红外线被滤除,这样便提高了抗干扰能。热释电红外传感器由滤光片、热释电探测元和前置放大器组成,补偿型热释电传感器还带有温度补偿元件,图所示为热释电传感器的内部结构。为防止外部环境对传感器输出信号的干扰,上述元件被真空封装在一个金属营内。热释电传感器的滤光片为带通滤光片,它封装在传感器壳体的顶端,使特定波长的红外辐射选择性地通过,到达热释电探测元+在其截止范围外的红外辐射则不能通过。

   热释电探测元是热释电传感器的核心元件,它是在热释电晶体的两面镀上金属电极后,加电极化制成,相当于一个以热释电晶体为电介质的平板电容器。当它受到非恒定强度的红外光照射时,产生的温度变化导致其表面电极的电荷密度发生改变,从而产生热释电电流。

前置放大器由一个高内阻的场效应管源极跟随器构成,通过阻抗变换,将热释电探测元微弱的电流信号转换为有用的电压信号输出。

3.3 ESP8266串口WIFI模块

基于STM32设计的智能插座+人体感应灯(ESP8266+人体感应+手机APP)

ESP8266系列无线模块是高性价比WIFI SOC模组,该系列模块支持标准的IEEE802.11b/g/n协议,内置完整的TCP/IP协议栈。用户可以使用该系列模块为现有的设备添加联网功能,也可以构建独立的网络控制器。

能卓越

ESP8266EX 芯片内置超低功耗 Tensilica L106 32 位 RISC 处理器,CPU 时钟速度最⾼可达 160 MHz,⽀持实时操作系统 (RTOS) 和 Wi-Fi 协议栈,可将⾼达 80% 的处理能⼒应用于编程和开发。

高度集成

ESP8266 芯片高度集成天线开关、射频巴伦、功率放大器、低噪声接收放大器、滤波器等射频模块。模组尺寸小巧,尤其适用于空间受限的产品设计。

认证齐全

RF 认证:SRRC、FCC、CE-RED、KCC、TELEC/MIC、IC 和 NCC 认证;

环保认证:RoHS、REACH;

可靠性认证:HTOL、HTSL、μHAST、TCT、ESD。

丰富的产品应用

ESP8266 模组既可以通过 ESP-AT 指令固件,为外部主机 MCU 提供 Wi-Fi 连接功能;也可以作为独立 Wi-Fi MCU 运行,用户通过基于 RTOS 的 SDK 开发带 Wi-Fi 连接功能的产品。用户可以轻松实现开箱即用的云连接、低功耗运行模式,以及包括 WPA3 在内的 Wi-Fi 安全支持等功能。

3.4 OLED显示屏

基于STM32设计的智能插座+人体感应灯(ESP8266+人体感应+手机APP)

OLED显示屏是利用有机电自发光二极管制成的显示屏。由于同时具备自发光有机电激发光二极管,不需背光源、对比度高、厚度薄、视角广、反应速度快、可用于挠曲性面板、使用温度范围广、构造及制程较简单等优异之特性,被认为是下一代的平面显示器新兴应用技术。

有机发光二极管 (OLED)显示器越来越普遍,在手机、媒体播放器及小型入门级电视等产品中最为显著。不同于标准的液晶显示器,OLED 像素是由电流源所驱动。若要了解 OLED 电源供应如何及为何会影响显示器画质,必须先了解 OLED 显示器技术及电源供应需求。本文将说明最新的 OLED 显示器技术,并探讨主要的电源供应需求及解决方案,另外也介绍专为 OLED 电源供应需求而提出的创新性电源供应架构。

背板技术造就软性显示器  高分辨率彩色主动式矩阵有机发光二极管 (AMOLED) 显示器需要采用主动式矩阵背板,此背板使用主动式开关进行各像素的开关。液晶 (LC) 显示器非晶硅制程已臻成熟,可供应低成本的主动式矩阵背板,并且可用于 OLED。许多公司正针对软性显示器开发有机薄膜晶体管 (OTFT) 背板制程,此一制程也可用于 OLED 显示器,以实现全彩软性显示器的推出。不论是标准或软性 OLED,都需要运用相同的电源供应及驱动技术。若要了解 OLED 技术、功能及其与电源供应之间的互动,必须深入剖析这项技术本身。OLED 显示器是一种自体发光显示器技术,完全不需要任何背光。OLED 采用的材质属于化学结构适用的有机材质。  OLED 技术需要电流控制驱动方法  OLED 具有与标准发光二极管 (LED) 相当类似的电气特性,亮度均取决于 LED 电流。若要开启和关闭 OLED 并控制 OLED 电流,需要使用薄膜晶体管 (TFT)的控制电路。

OLED为自发光材料,不需用到背光板,同时视角广、画质均匀、反应速度快、较易彩色化、用简单驱动电路即可达到发光、制程简单、可制作成挠曲式面板,符合轻薄短小的原则,应用范围属于中小尺寸面板。

显示方面:主动发光、视角范围大;响应速度快,图像稳定;亮度高、色彩丰富、分辨率高。

工作条件:驱动电压低、能耗低,可与太阳能电池、集成电路等相匹配。

适应性广:采用玻璃衬底可实现大面积平板显示;如用柔性材料做衬底,能制成可折叠的显示器。由于OLED是全固态、非真空器件,具有抗震荡、耐低温(-40℃)等特性,在军事方面也有十分重要的应用,如用作坦克、飞机等现代化武器的显示终端。

3.5 LED大功率灯模块

基于STM32设计的智能插座+人体感应灯(ESP8266+人体感应+手机APP)

LED灯是一块电致发光的半导体材料芯片,用银胶或白胶固化到支架上,然后用银线或金线连接芯片和电路板,四周用环氧树脂密封,起到保护内部芯线的作用,最后安装外壳,所以 LED 灯的抗震性能好。

LED(Light Emitting Diode),发光二极管,是一种能够将电能转化为可见光的固态的半导体器件,它可以直接把电转化为光。LED的心脏是一个半导体的晶片,晶片的一端附在一个支架上,一端是负极,另一端连接电源的正极,使整个晶片被环氧树脂封装起来。

半导体晶片由两部分组成,一部分是P型半导体,在它里面空穴占主导地位,另一端是N型半导体,在这边主要是电子。但这两种半导体连接起来的时候,它们之间就形成一个P-N结。当电流通过导线作用于这个晶片的时候,电子就会被推向P区,在P区里电子跟空穴复合,然后就会以光子的形式发出能量,这就是LED灯发光的原理。而光的波长也就是光的颜色,是由形成P-N结的材料决定的。

LED可以直接发出红、黄、蓝、绿、青、橙、紫、白色的光。

3.6 STM32F103C8T6最小系统板

基于STM32设计的智能插座+人体感应灯(ESP8266+人体感应+手机APP)

STM32F系列属于中低端的32位ARM微控制器,该系列芯片是意法半导体(ST)公司出品,其内核是Cortex-M3。

该系列芯片按片内Flash的大小可分为三大类:小容量(16K和32K)、中容量(64K和128K)、大容量(256K、384K和512K)。

芯片集成定时器Timer,CAN,ADC,SPI,I2C,USB,UART等多种外设功能。

内核

--ARM 32位的Cortex-M3

--最高72MHz工作频率,在存储器的0等待周期访问时可达1.25DMips/MHZ(DhrystONe2.1)

--单周期乘法和硬件除法

存储器

--从16K到512K字节的闪存程序存储器(STM32F103XXXX中的第二个X表示FLASH容量,其中:“4”=16K,“6”=32K,“8”=64K,B=128K,C=256K,D=384K,E=512K)

--最大64K字节的SRAM

电源管理

--2.0-3.6V供电和I/O引脚

--上电/断电复位(POR/PDR)、可编程电压监测器(PVD)

--4-16MHZ晶振

--内嵌经出厂调校的8MHz的RC振荡器

--内嵌带校准的40KHz的RC振荡器

--产生CPU时钟的PLL

--带校准的32KHz的RC振荡器

低功耗

--睡眠、停机和待机模式

--Vbat为RTC和后备寄存器供电

模数转换器

--2个12位模数转换器,1us转换时间(多达16个输入通道)

--转换范围:0至3.6V

--双采样和保持功能

--温度传感器

DMA

--2个DMA控制器,共12个DMA通道:DMA1有7个通道,DMA2有5个通道

--支持的外设:定时器、ADC、SPI、USB、IIC和UART

--多达112个快速I/O端口(仅Z系列有超过100个引脚)

--26/37/51/80/112个I/O口,所有I/O口一块映像到16个外部中断;几乎所有的端口均可容忍5V信号

调试模式

--串行单线调试(SWD)和JTAG接口

--多达8个定时器

--3个16位定时器,每个定时器有多达4个用于输入捕获/输出比较/PWM或脉冲计数的通道和增量编码器输入

--1个16位带死区控制和紧急刹车,用于电机控制的PWM高级控制定时器

--2个看门狗定时器(独立的和窗口型的)

--系统时间定时器:24位自减型计数器

--多达9个通信接口:

2个I2C接口(支持SMBus/PMBus)

3个USART接口(支持ISO7816接口,LIN,IrDA接口和调制解调控制)

2个SPI接口(18M位/秒)

CAN接口(2.0B主动)

USB 2.0全速接口

计算单元

CRC计算单元,96位的新批唯一代码

封装

ECOPACK封装

3.7 杜邦线

基于STM32设计的智能插座+人体感应灯(ESP8266+人体感应+手机APP)
3.8 继电器
基于STM32设计的智能插座+人体感应灯(ESP8266+人体感应+手机APP)
基于STM32设计的智能插座+人体感应灯(ESP8266+人体感应+手机APP)
基于STM32设计的智能插座+人体感应灯(ESP8266+人体感应+手机APP)
四、STM32核心代码
基于STM32设计的智能插座+人体感应灯(ESP8266+人体感应+手机APP)
4.1  STM32:  main.c 

#include "stm32f10x.h"
#include "beep.h"
#include "delay.h"
#include "led.h"
#include "key.h"
#include "sys.h"
#include "usart.h"
#include <string.h>
#include <stdlib.h>
#include "exti.h"
#include "timer.h"
#include "rtc.h"
#include "wdg.h"
#include "oled.h"
#include "fontdata.h"
#include "adc.h"
#include "FunctionConfig.h"
#include "dht11.h"
#include "HumanDetection.h"
#include "esp8266.h"
 
/*
函数功能: 绘制时钟表盘框架
*/
void DrawTimeFrame(void)
{
    u8 i;
    OLED_Circle(32,32,31);//画外圆
    OLED_Circle(32,32,1); //画中心圆
    //画刻度
    for(i=0;i<60;i++)
    {
        if(i%5==0)OLED_DrawAngleLine(32,32,6*i,31,3,1);
    }
    OLED_RefreshGRAM();  //刷新数据到OLED屏幕
}
 
/*
函数功能: 更新时间框架显示,在RTC中断里调用
*/
char TimeBuff[20];
void Update_FrameShow(void)
{
    /*1. 绘制秒针、分针、时针*/
    OLED_DrawAngleLine2(32,32,rtc_clock.sec*6-6-90,27,0);//清除之前的秒针
    OLED_DrawAngleLine2(32,32,rtc_clock.sec*6-90,27,1); //画秒针
    
    OLED_DrawAngleLine2(32,32,rtc_clock.min*6-6-90,24,0);
    OLED_DrawAngleLine2(32,32,rtc_clock.min*6-90,24,1);
    
    OLED_DrawAngleLine2(32,32,rtc_clock.hour*30-6-90,21,0);
    OLED_DrawAngleLine2(32,32,rtc_clock.hour*30-90,21,1);
    
    //绘制电子钟时间
    sprintf(TimeBuff,"%d",rtc_clock.year);
    OLED_ShowString(65,16*0,16,TimeBuff);  //年份字符串
    OLED_ShowChineseFont(66+32,16*0,16,4); //显示年
    
    sprintf(TimeBuff,"%d/%d",rtc_clock.mon,rtc_clock.day);
    OLED_ShowString(75,16*1,16,TimeBuff); //月
    
    if(rtc_clock.sec==0)OLED_ShowString(65,16*2,16,"        "); //清除多余的数据
    sprintf(TimeBuff,"%d:%d:%d",rtc_clock.hour,rtc_clock.min,rtc_clock.sec);
    OLED_ShowString(65,16*2,16,TimeBuff); //秒
    
    //显示星期
    OLED_ShowChineseFont(70,16*3,16,5); //星
    OLED_ShowChineseFont(70+16,16*3,16,6); //期
    OLED_ShowChineseFont(70+32,16*3,16,rtc_clock.week+7); //具体的值
}
 
 
/*
函数功能: 温湿度显示
*/
void ShowTemperatureAndHumidity(u8 temp,u8 humi)
{
    sprintf(TimeBuff,"T: %d",temp);
    OLED_ShowString(40,16*1,16,TimeBuff); 
    sprintf(TimeBuff,"H: %d%%",humi);
    OLED_ShowString(40,16*2,16,TimeBuff); 
}
 
/*
函数功能: OLED所有显示页面信息初始化
*/
u8 ESP8266_Stat=0;              //存放ESP8266状态  1 OK ,0 error
u8 ESP8266_WIFI_AP_SSID[10];  //存放WIFI的名称   
#define STM32_96BIT_UID (0x1FFFF7E8) //STM32内部96位唯一芯片标识符寄存器地址
 
/*
函数功能: ESP8266 WIFI 显示页面
*/
char ESP8266_PwdShow[20];
char ESP8266_IP_PortAddr[30];  //存放ESP8266 IP地址与端口号地址
u8 Save_ESP8266_SendCmd[30];  //保存WIFI发送的命令
u8 Save_ESP8266_SendData[50];  //保存WIFI发送的数据
 
void ESP8266_ShowPageTable(void)
{
    if(ESP8266_Stat)OLED_ShowString(0,16*0,16,"WIFI STAT:ERROR");
    else OLED_ShowString(0,16*0,16,"WIFI STAT:OK");
 
    //显示字符串
    //OLED_ShowString(0,2,(u8*)"                "); //清除一行的显示
    memset((u8*)ESP8266_PwdShow,0,20); //清空内存
    sprintf((char*)ESP8266_PwdShow,"WIFI:%s",ESP8266_WIFI_AP_SSID);
    ESP8266_PwdShow[15]='\0';
    OLED_ShowString(0,16*1,16,ESP8266_PwdShow); 
    memset((u8*)ESP8266_PwdShow,0,20); //清空内存
 
    sprintf((char*)ESP8266_PwdShow,"PWD:%s",wifiap_password);
    OLED_ShowString(0,16*2,16,ESP8266_PwdShow);
    OLED_ShowString(0,16*3,16,"192.168.4.1:8089");      
}
 
 
void OledShowPageTableInit(void)
{
    u8 data[100],i;
    u32 stm32_uid=0;
    u8 *uid;
 
/*1. ESP8266 WIFI相关信息初始化*/
 
    OLED_ShowString(0,2,16,"             "); //清空一行的显示
    OLED_ShowString(0,2,16,"WifiInit...");      //显示页面提示信息
 
    /*1.1 设置WIFI AP模式 */
    if(ESP8266_SendCmd("AT+CWMODE=2\r\n","OK",50))
    {
            ESP8266_Stat=1;  //OK
    }
    else
    {
            ESP8266_Stat=0;  //ERROR
    }
    
    /*1.2 重启模块 */
    ESP8266_SendCmd("AT+RST\r\n","OK",20);
    
    /*1.3 延时3S等待重启成功*/
    DelayMs(1000);            
    DelayMs(1000);
    DelayMs(1000);
    
    //得到WIFI的名称
    uid=(u8*)STM32_96BIT_UID; //转为指针为1个字节
    for(i=0;i<96;i++)
    {   
        stm32_uid+=*uid++; //计算校验和,得到WIFI数字编号
    }
    printf("stm32_uid=%d\r\n",stm32_uid);
    sprintf((char*)data,"%d",stm32_uid);
    strcpy((char*)ESP8266_WIFI_AP_SSID,"wbyq_");
    strcat((char*)ESP8266_WIFI_AP_SSID,(char*)data);
    printf("请用设备连接WIFI热点:%s,%s,%s\r\n",(u8*)ESP8266_WIFI_AP_SSID,(u8*)wifiap_encryption,(u8*)wifiap_password);
    
    /*1.4 配置模块AP模式无线参数*/
    memset(data,0,100); //清空数组
    sprintf((char*)data,"AT+CWSAP=\"%s\",\"%s\",1,4\r\n",ESP8266_WIFI_AP_SSID,wifiap_password); 
    ESP8266_SendCmd(data,"OK",1000);
 
    /*1.5  设置多连接模式:0单连接,1多连接(服务器模式必须开启)*/
    ESP8266_SendCmd("AT+CIPMUX=1\r\n","OK",20); 
    
    /*1.6 开启Server模式(0,关闭;1,打开),端口号为portnum */
    memset(data,0,100); //清空数组
    sprintf((char*)data,"AT+CIPSERVER=1,%s\r\n",(u8*)portnum);
    ESP8266_SendCmd(data,"OK",50);
    
    /*1.7 获取当前模块的IP*/
    ESP8266_GetWanip((u8*)ESP8266_IP_PortAddr);
    strcat(ESP8266_IP_PortAddr,":");
    strcat(ESP8266_IP_PortAddr,portnum);
    printf("IP地址:%s\r\n",ESP8266_IP_PortAddr);
    
    OLED_ShowString(0,2,16,"              "); //清空一行的显示
    OLED_ShowString(0,2,16,"WifiInitOk");   //显示页面提示信息
}
 
 
/*
函数功能: 显示开关状态
*/
void Show_Switch(int state)
{
    //清屏
    OLED_Clear(0);
    
    if(state)
    {
        sprintf(TimeBuff,"Socket:ON");
        OLED_ShowString(20,16*2,16,TimeBuff); 
    }   
    else
    {
        sprintf(TimeBuff,"Socket:OFF");
        OLED_ShowString(20,16*2,16,TimeBuff); 
    }
}
 
//int main1(void)
//{
//    HumanDetection_Init(); //热释电模块初始化
//    UsartInit(USART1,72,115200);//串口1的初始化
//    LED_Init();  //初始化LED
//    while(1)
//    {
//    //热释电状态  为真表示有人
//        if(HumanState)
//        {
//            LED1=0; //开灯
//        }
//        else  //为假
//        {
//            LED1=1; //关灯
//        }
//        printf("%d\n",HumanState);
//    }
//}
 
 
int main(void)
{
    u8 rlen;
    char *p;
    u8 temp;  //温度
    u8 humi;  //湿度
    u8 stat;
    u8 key_val;
    u32 TimeCnt=0;
    u32 wifi_TimeCnt=0;
    u16 temp_data; //温度数据
    u8 page_cnt=0; //显示的页面
     char *time;
    u8 socket_state=0;
    
    UsartInit(USART1,72,115200);//串口1的初始化
    
    BEEP_Init(); //初始化蜂鸣器
    LED_Init();  //初始化LED
    KEY_Init();  //按键初始化
    
     printf("正在初始化OLED...\r\n");  
    OLED_Init(0xc8,0xa1); //OLED显示屏初始化--正常显示
    
    //OLED_Init(0xc0,0xa0); //OLED显示屏初始化--翻转显示
 
    OLED_Clear(0x00);                       //清屏    
    
   
    UsartInit(USART3,36,115200); //WIFI的波特率为115200
    Timer2Init(72,10000);          //10ms中断一次,辅助串口3接收数据--WIFI数据
    
    
    printf("正在初始化ESP8266..\r\n");  
    //ESP8266初始化
    OledShowPageTableInit();
     //清屏
    OLED_Clear(0);
      
    printf("正在初始化RTC...\r\n");         
    RTC_Init(); //RTC初始化
 
    DrawTimeFrame();                        //画时钟框架
    
    USART3_RX_STA=0; //清空串口的接收标志位
    USART3_RX_CNT=0; //清空计数器
        
     printf("初始化DHT11...\r\n");   
    DHT11_Init(); //初始化DHT11
     printf("热释电模块初始化...\r\n");   
    HumanDetection_Init(); //热释电模块初始化
    
    //OLED_Clear(0);
    
    printf("开始进入while(1)\r\n");    
    while(1)
    {   
        key_val=KEY_GetValue();
        if(key_val)
        {
            page_cnt++;
            printf("page_cnt:%d\r\n",page_cnt);
            
             //清屏
             OLED_Clear(0);
            
             //时钟页面
            if(page_cnt==0)
            {
                DrawTimeFrame();                        //画时钟框架
                RTC->CRH|=1<<0; //开启秒中断
            }
            //温湿度页面
            else if(page_cnt==1)
            {
                RTC->CRH&=~(1<<0); //关闭秒中断
                //温湿度
                ShowTemperatureAndHumidity(temp,humi);
            }
            //ESP8266显示
            else if(page_cnt==2)
            {
                ESP8266_ShowPageTable();
            }
            else if(page_cnt==3)
            {
                 Show_Switch(socket_state);
            }
            else
            {
                DrawTimeFrame();                        //画时钟框架
                RTC->CRH|=1<<0; //开启秒中断
                page_cnt=0;
            }    
        }
        
        //时间记录
        DelayMs(10);
        TimeCnt++;
        wifi_TimeCnt++;
        if(TimeCnt>=100) //1000毫秒一次
        {
             TimeCnt=0;
             
             //读取温湿度数据
             DHT11_Read_Data(&temp,&humi);
            
            //湿度大于90就关闭开关
            if(humi>90)
            {
                socket_state=0;
                if(page_cnt==3)
                {
                     Show_Switch(socket_state);
                }
            }
            
            //温湿度页面
            if(page_cnt==1)
            {
                //温湿度
                ShowTemperatureAndHumidity(temp,humi);
            }
        }
        
        if(wifi_TimeCnt>=300) //3000毫秒一次
        {
            wifi_TimeCnt=0;
            //温湿度1秒上传一次
            sprintf((char*)Save_ESP8266_SendData,"#%d,%d",temp,humi);  //拼接数据
            sprintf((char*)Save_ESP8266_SendCmd,"AT+CIPSEND=0,%d\r\n",strlen((char*)Save_ESP8266_SendData));
            ESP8266_SendCmd(Save_ESP8266_SendCmd,(u8*)"OK",200); //WIFI设置发送数据长度
            ESP8266_SendData(Save_ESP8266_SendData,(u8*)"OK",100); //WIFI发送数据
        }
        
        //热释电状态  为真表示有人
        if(HumanState)
        {
            LED1=0; //开灯
        }
        else  //为假
        {
            LED1=1; //关灯
        }
        
        /*轮询扫描数据*/
        if(USART3_RX_STA)         //WIFI 接收到一次数据了
        { 
            rlen=USART3_RX_CNT; //得到本次接收到的数据长度
            USART3_RX_BUF[rlen]='\0';         //添加结束符 
        //  printf("接收的数据: %s\r\n",USART3_RX_BUF);  //发送到串口   
 
            {
                /*判断是否收到客户端发来的数据  */
                p=strstr((char*)USART3_RX_BUF,"+IPD");
                if(p!=NULL) //正常数据格式: +IPD,0,7:LED1_ON    +IPD,0表示第0个客户端   7:LED1_ON表示数据长度与数据
                {
                        /*解析上位机发来的数据*/
                        p=strstr((char*)USART3_RX_BUF,":");
                        if(p!=NULL)
                        {
                                p+=1; //向后偏移1个字节
                                if(*p=='*')  //设置RTC时间
                                {
                                        p+=1; //向后偏移,指向正确的时间
                                        time=p;
                                        rtc_clock.year=(time[0]-48)*1000+(time[1]-48)*100+(time[2]-48)*10+(time[3]-48)*1;
                                        rtc_clock.mon=(time[4]-48)*10+(time[5]-48)*1;
                                        rtc_clock.day=(time[6]-48)*10+(time[7]-48)*1;
                                        rtc_clock.hour=(time[8]-48)*10+(time[9]-48)*1;
                                        rtc_clock.min=(time[10]-48)*10+(time[11]-48)*1;
                                        rtc_clock.sec=(time[12]-48)*10+(time[13]-48)*1;
                                        RTC_SetTime(rtc_clock.year,rtc_clock.mon,rtc_clock.day,rtc_clock.hour,rtc_clock.min,rtc_clock.sec);
 
                                        if(page_cnt==0)
                                        {
                                            OLED_Clear(0); //OLED清屏
                                            DrawTimeFrame();//画时钟框架   
                                        }                                    
                                }
                                else if(strcmp(p,"LED1_ON")==0)
                                {
                                    socket_state=1;
                                }
                                else if(strcmp(p,"LED1_OFF")==0)
                                {
                                    socket_state=0;
                                }
                                
                                if(page_cnt==3)
                                {
                                     Show_Switch(socket_state);
                                }
                        }
                }
          }
            USART3_RX_STA=0;
            USART3_RX_CNT=0;
        }
    }        
}
       

4.2  STM32:  rtc.c

#include "rtc.h"
 
//定义RTC标准结构体
struct RTC_CLOCK rtc_clock; 
 
/*
函数功能: RTC初始化函数
*/
void RTC_Init(void)
{
        //检查是不是第一次配置时钟
    u8 temp=0;
    if(BKP->DR1!=0X5051)//之前使用的不是LSE
    {    
        RCC->APB1ENR|=1<<28;     //使能电源时钟       
        RCC->APB1ENR|=1<<27;     //使能备份时钟       
        PWR->CR|=1<<8;           //取消备份区写保护
        RCC->BDCR|=1<<16;        //备份区域软复位     
        RCC->BDCR&=~(1<<16);     //备份区域软复位结束         
        RCC->BDCR|=1<<0;         //开启外部低速振荡器 
        while((!(RCC->BDCR&0X02))&&temp<250)//等待外部时钟就绪   
        {
            temp++;
            DelayMs(10);
        };
        
        if(temp>=250)  //return 1;//初始化时钟失败,晶振有问题      
        {
            
            RCC->CSR|=1<<0; //开启外部低速振荡器 
            while(!(RCC->CSR&(1<<1)));//外部低速振荡器 
            RCC->BDCR|=2<<8; //LSI作为RTC时钟 
            BKP->DR1=0X5050; //标记使用LSI作为RTC时钟
        }
        else 
        {
            RCC->BDCR|=1<<8; //LSE作为RTC时钟 
            BKP->DR1=0X5051; //标记使用LSE作为RTC时钟
        }
        RCC->BDCR|=1<<15;//RTC时钟使能  
        
        while(!(RTC->CRL&(1<<5)));//等待RTC寄存器操作完成     
        while(!(RTC->CRL&(1<<3)));//等待RTC寄存器同步  
        RTC->CRH|=0X01;           //允许秒中断
        while(!(RTC->CRL&(1<<5)));//等待RTC寄存器操作完成     
        RTC->CRL|=1<<4;           //允许配置     
        RTC->PRLH=0X0000;
        RTC->PRLL=32767;          //时钟周期设置(有待观察,看是否跑慢了?)理论值:32767   
        RTC_SetTime(2021,4,25,20,36,20);  //设置时间      
        RTC->CRL&=~(1<<4);           //配置更新
        while(!(RTC->CRL&(1<<5)));   //等待RTC寄存器操作完成                                           
        printf("FIRST TIME\n");
    }else//系统继续计时
    {
        while(!(RTC->CRL&(1<<3)));//等待RTC寄存器同步  
        RTC->CRH|=0X01;           //允许秒中断
        while(!(RTC->CRL&(1<<5)));//等待RTC寄存器操作完成
        printf("OK\n");
    }                             
    STM32_NVIC_SetPriority(RTC_IRQn,2,2); //优先级   
    
}
 
extern void Update_FrameShow(void);
/*
函数功能: RTC闹钟中断服务函数
*/
void RTC_IRQHandler(void)
{
      u32 SecCnt;
        if(RTC->CRL&1<<0)
        {
                SecCnt=RTC->CNTH<<16;//获取高位
                SecCnt|=RTC->CNTL;   //获取低位
                RTC_GetTime(SecCnt); //转换标准时间
                RTC_GetWeek(SecCnt);
             // printf("%d-%d-%d %d:%d:%d week:%d\n",rtc_clock.year,rtc_clock.mon,rtc_clock.day,rtc_clock.hour,rtc_clock.min,rtc_clock.sec,rtc_clock.week);
                Update_FrameShow(); //更新显示
                RTC->CRL&=~(1<<0); //清除秒中断标志位
        }
        
        if(RTC->CRL&1<<1)
        {
//              printf("闹钟时间到达!....\n");
//            BEEP=1;
//            DelayMs(500);
//              BEEP=0;
                RTC->CRL&=~(1<<1); //清除闹钟中断标志位
        }
}
 
 
 
//闰年的月份
static int mon_r[12]={31,29,31,30,31,30,31,31,30,31,30,31};
//平年的月份
static int mon_p[12]={31,28,31,30,31,30,31,31,30,31,30,31};
 
 
/*
函数功能: 设置RTC时间
函数形参:
    u32 year;   2018
      u32 mon;     8
      u32 day;
        u32 hour;
        u32 min;
      u32 sec;
*/
void RTC_SetTime(u32 year,u32 mon,u32 day,u32 hour,u32 min,u32 sec)
{
        u32 i;
      u32 SecCnt=0; //总秒数
        /*1. 累加已经过去的年份*/
        for(i=2017;i<year;i++)  //基准年份:20170101000000
      {
                if(RTC_GetYearState(i))
                {
                        SecCnt+=366*24*60*60; //闰年一年的秒数
                }
                else
                {
                        SecCnt+=365*24*60*60; //平年一年的秒数
                }
        }
        /*2. 累加过去的月份*/
        for(i=0;i<mon-1;i++)
        {
              if(RTC_GetYearState(year))
                {
                        SecCnt+=mon_r[i]*24*60*60; //闰年一月的秒数
                }
                else
                {
                        SecCnt+=mon_p[i]*24*60*60; //平年一月的秒数
                }   
        }
        
        /*3. 累加过去的天数*/
        SecCnt+=(day-1)*24*60*60;
        
        /*4. 累加过去小时*/
        SecCnt+=hour*60*60;
        
        /*5. 累加过去的分钟*/
        SecCnt+=min*60;
        
        /*6. 累加过去的秒*/
        SecCnt+=sec;
        
        /*7. 设置RTC时间*/
        //设置时钟
    RCC->APB1ENR|=1<<28;//使能电源时钟
    RCC->APB1ENR|=1<<27;//使能备份时钟
    PWR->CR|=1<<8;    //取消备份区写保护
    //上面三步是必须的!
    RTC->CRL|=1<<4;   //允许配置 
    RTC->CNTL=SecCnt&0xffff;
    RTC->CNTH=SecCnt>>16;
    RTC->CRL&=~(1<<4);//配置更新
    while(!(RTC->CRL&(1<<5)));//等待RTC寄存器操作完成 
}
 
 
/*
函数功能: 获取RTC时间
函数参数: u32 sec 秒单位时间
*/
void RTC_GetTime(u32 sec)
{
        u32 i;
        rtc_clock.year=2017; //基准年份
        
      /*1. 计算当前的年份*/
      while(1)
        {
              if(RTC_GetYearState(rtc_clock.year))
                {
                        if(sec>=366*24*60*60) //够一年
                        {
                                sec-=366*24*60*60;
                                rtc_clock.year++;
                        }
                        else break;
                }
                else
                {
                        if(sec>=365*24*60*60) //够一年
                        {
                                sec-=365*24*60*60;
                                rtc_clock.year++;
                        }
                        else break;
                }
        }
        
        /*2. 计算当前的月份*/
        rtc_clock.mon=1;
        for(i=0;i<12;i++)
        {
                if(RTC_GetYearState(rtc_clock.year))
                {
                        if(sec>=mon_r[i]*24*60*60)
                        {
                             sec-=mon_r[i]*24*60*60;
                             rtc_clock.mon++;
                        }
                        else break;     
                }
                else
                {
                        if(sec>=mon_p[i]*24*60*60)
                        {
                             sec-=mon_p[i]*24*60*60;
                             rtc_clock.mon++;
                        }
                        else break; 
                }
        }
        
        /*3. 计算当前的天数*/
        rtc_clock.day=1;
        while(1)
        {
                if(sec>=24*60*60)
                {
                        sec-=24*60*60;
                        rtc_clock.day++;
                }
                else break;
        }
        
        /*4. 计算当前的小时*/
        rtc_clock.hour=0;
        while(1)
        {
                if(sec>=60*60)
                {
                        sec-=60*60;
                        rtc_clock.hour++;
                }
                else break;
        }
        
        /*5. 计算当前的分钟*/
        rtc_clock.min=0;
        while(1)
        {
                if(sec>=60)
                {
                        sec-=60;
                        rtc_clock.min++;
                }
                else break;
        }
        
        /*6. 计算当前的秒*/
        rtc_clock.sec=sec;
}
 
 
/*
函数功能: 判断年份是否是平年、闰年
返回值  : 0表示平年 1表示闰年
*/
u8 RTC_GetYearState(u32 year)
{
     if((year%4==0&&year%100!=0)||year%400==0)
     {
         return 1;
     }
     return 0;
}
 
 
/*
函数功能: 获取星期
*/
void RTC_GetWeek(u32 sec)
{
    u32 day1=sec/(60*60*24); //将秒单位时间转为天数
    switch(day1%7)
    {
        case 0:
            rtc_clock.week=0;
            break;
        case 1:
            rtc_clock.week=1;
            break;
        case 2:
            rtc_clock.week=2;
            break;
        case 3:
            rtc_clock.week=3;
            break;
        case 4:
            rtc_clock.week=4;
            break;
        case 5:
            rtc_clock.week=5;
            break;
        case 6:
            rtc_clock.week=6;
            break;
    }
}
       

4.3 ESP8266.c

#include "esp8266.h"
 
/*
函数功能:向ESP82668266发送命令
函数参数:
                cmd:发送的命令字符串
                ack:期待的应答结果,如果为空,则表示不需要等待应答
                waittime:等待时间(单位:10ms)
返 回 值:
                 0,发送成功(得到了期待的应答结果)
         1,发送失败
*/
u8 ESP8266_SendCmd(u8 *cmd,u8 *ack,u16 waittime)
{
    u8 res=0; 
    USART3_RX_STA=0;
    USART3_RX_CNT=0;
    UsartStringSend(USART3,cmd);//发送命令
    if(ack&&waittime)       //需要等待应答
    {
        while(--waittime)   //等待倒计时
        {
            DelayMs(10);
            if(USART3_RX_STA)//接收到期待的应答结果
            {
                if(ESP8266_CheckCmd(ack))
                {
                    res=0;
                    //printf("cmd->ack:%s,%s\r\n",cmd,(u8*)ack);
                    break;//得到有效数据 
                }
                USART3_RX_STA=0;
                USART3_RX_CNT=0;
            } 
        }
        if(waittime==0)res=1; 
    }
    return res;
}
 
 
/*
函数功能:ESP8266发送命令后,检测接收到的应答
函数参数:str:期待的应答结果
返 回 值:0,没有得到期待的应答结果
                 其他,期待应答结果的位置(str的位置)
*/
u8* ESP8266_CheckCmd(u8 *str)
{
    char *strx=0;
    if(USART3_RX_STA)  //接收到一次数据了
    { 
        USART3_RX_BUF[USART3_RX_CNT]=0;//添加结束符
        strx=strstr((const char*)USART3_RX_BUF,(const char*)str); //查找是否应答成功
        //printf("RX=%s",USART3_RX_BUF);
    }
    return (u8*)strx;
}
 
/*
函数功能:向ESP8266发送指定数据
函数参数:
                data:发送的数据(不需要添加回车)
                ack:期待的应答结果,如果为空,则表示不需要等待应答
                waittime:等待时间(单位:10ms)
返 回 值:0,发送成功(得到了期待的应答结果)luojian
*/
u8 ESP8266_SendData(u8 *data,u8 *ack,u16 waittime)
{
    u8 res=0; 
    USART3_RX_STA=0;
    UsartStringSend(USART3,data);//发送数据
    if(ack&&waittime)       //需要等待应答
    {
        while(--waittime)   //等待倒计时
        {
            DelayMs(10);
            if(USART3_RX_STA)//接收到期待的应答结果
            {
                if(ESP8266_CheckCmd(ack))break;//得到有效数据 
                USART3_RX_STA=0;
                USART3_RX_CNT=0;
            } 
        }
        if(waittime==0)res=1; 
    }
    return res;
}
    
    
 
/*
函数功能:ESP8266退出透传模式
返 回 值:0,退出成功;
         1,退出失败
*/
u8 ESP8266_QuitTrans(void)
{
    while((USART3->SR&0X40)==0);    //等待发送空
    USART3->DR='+';      
    DelayMs(15);                    //大于串口组帧时间(10ms)
    while((USART3->SR&0X40)==0);    //等待发送空
    USART3->DR='+';      
    DelayMs(15);                    //大于串口组帧时间(10ms)
    while((USART3->SR&0X40)==0);    //等待发送空
    USART3->DR='+';      
    DelayMs(500);                   //等待500ms
    return ESP8266_SendCmd("AT\r\n","OK",20);//退出透传判断.
}
 
/*
函数功能:获取ESP8266模块的连接状态
返 回 值:0,未连接;1,连接成功.
*/
u8 ESP8266_ConstaCheck(void)
{
    u8 *p;
    u8 res;
    if(ESP8266_QuitTrans())return 0;              //退出透传 
    ESP8266_SendCmd("AT+CIPSTATUS\r\n",":",50); //发送AT+CIPSTATUS指令,查询连接状态
    p=ESP8266_CheckCmd("+CIPSTATUS\r\n:"); 
    res=*p;                                                                 //得到连接状态    
    return res;
}
 
/*
函数功能:获取ip地址
函数参数:ipbuf:ip地址输出缓存区
*/
void ESP8266_GetWanip(u8* ipbuf)
{
      u8 *p,*p1;
        if(ESP8266_SendCmd("AT+CIFSR\r\n","OK",50))//获取WAN IP地址失败
        {
            ipbuf[0]=0;
            return;
        }       
        p=ESP8266_CheckCmd("\"");
        p1=(u8*)strstr((const char*)(p+1),"\"");
        *p1=0;
        sprintf((char*)ipbuf,"%s",p+1); 
}
       

五、QT设计的上位机代码:  Android手机APP+Windows系统上位机

基于STM32设计的智能插座+人体感应灯(ESP8266+人体感应+手机APP)
基于STM32设计的智能插座+人体感应灯(ESP8266+人体感应+手机APP)

继续阅读