天天看點

基于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)

繼續閱讀