实现功能
使用DHT22+ESP8266+2个OLED显示屏,接入到Blynk实现在手机端和设备通过一个屏幕端试试显示温湿度的同时,加入NTP自动校时使第二个屏幕显示时间和日期.这里使用的外壳是3D打印当然,你也可以自己做一个外壳,模型的链接下面回放出,有需要的可以去淘宝找店家打印一个.由于我的外壳没有空间加按钮开关了,所以也就没有整合WIFImanager的功能,需要的可以看看我上一编制作气象仪的文章.
具体功能:
- 显示温湿度
- NTP自动校准时间
- 手机端实时显示温湿度数据
- 手机端实时显示网络信息
- 手机端使用按钮控制屏幕的开启/关闭
- 手机端设置屏幕的定时开启/关闭
- OTA更新固件
材料准备
- ESP8266 - X 1(D1mini/NodeMCU也行)
- DHT22温湿度传感器 - X 1(也可以是DHT11,可选已加电阻的,不带电阻的需要自己加一个10K的电阻)
- 0.96'OLED屏幕 - X 2(SSD1306主控)
- 3D打印机 - X 1(可选,外壳模型点这里下载)
传感器用到的程序库
在IDE内都可以搜到
需要注意的编译环境(亲测高版本会出错,未解决)
ArduinoIDE编译通过的主要库版本:
- ESP8266板库:2.4.2
- Blynk版本:0.6.1
- IDE版本:1.8.10
- ArduinoJson:5.13.3
PlatformIO编译通过的主要库版本:
- Blynk:0.6.1
- ArduinoJson:5.13.4
- ESP8266板库:2.0.4、2.1.1、2.2.0
接线
- 如果你的DHT22是没有自带电阻的就必须加个10kΩ电阻,电阻一端接到单片机的3.3,另一端接到DHT的OUT,不然读数及有可能不正常
- DHT22/OLED都可以接到5V,具体按你到手的为准,市面上多数支持3.3-5V
设备 | 引脚 | ||||||
---|---|---|---|---|---|---|---|
ESP8266 | 3.3 | GND | D2 | D3 | D4 | D5 | D6 |
DHT22 | Vcc | GND | OUT | ||||
OLED1 | Vcc | GND | SDA | SCL | |||
OLED2 | Vcc | GND | SDA | SCL | |||
10K电阻 | s1 | s2 |
设备端代码
(如果手机端网页显示不正常,点击左上角三个点点)
主程序
#define BLYNK_PRINT Serial
//#include <Arduino.h> //PlatformIO编译需要
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <BlynkSimpleEsp8266.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>
#include <Adafruit_Sensor.h>
#include <DHT.h>
#include <DHT_U.h>
#include <time.h>
#include <Wire.h>
#include "SSD1306Wire.h"
//新建一个名为images.h的头文件,即用于存放函数和全局变量的文件,这里用了存放一个人WIFI图标和特殊符号- °
#include "images.h"
// 使用Wire库初始化OLED显示
SSD1306Wire display(0x3c, D4, D3); //SCL-D4,SDA-D4
SSD1306Wire display2(0x3c, D6, D5);
char auth[] = "你的Blynk TokenKEY";
char ssid[] = "你的WIFI-SSD";
char pass[] = "你的WIFI-密码";
int timezone = 8 * 3600; //设置时区
int dst = 0; //夏令时
#define DHTPIN D2
#define DHTTYPE DHT22 //如果是DHT22则改成 #define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);
BlynkTimer timer;
//void drawImageDemo();//PlatformIO编译需要
//void sendSensor(); //PlatformIO编译需要
void sendSensor()
{
float h = dht.readHumidity();
float t = dht.readTemperature();
Blynk.virtualWrite(V0, t);
Blynk.virtualWrite(V1, h);
}
//手机端设置一个开关插件,虚拟引脚为V5,设为开关模式,你也可以根据这个引脚在新建一个定时器,来控制定时开关屏幕
BLYNK_WRITE(V5) {
if (param.asInt() == 0) {
display.displayOff();
display2.displayOff();
} else {
display.displayOn();
display2.displayOn();
}
}
//链接APP时,同步手机上开关的状态
BLYNK_CONNECTED() {
Blynk.syncVirtual(V5);
}
void setup()
{
// Debug console
Serial.begin(115200);
//这里使用的是微软的NTP服务器,你可以自己搜索
configTime(timezone, dst, "time.windows.com");
display.init();
display2.init();
display.setI2cAutoInit(true);
display2.setI2cAutoInit(true);
display.flipScreenVertically();
display.setFont(ArialMT_Plain_10);
display.setTextAlignment(TEXT_ALIGN_LEFT);
display2.flipScreenVertically();
display2.setFont(ArialMT_Plain_10);
display2.setTextAlignment(TEXT_ALIGN_LEFT);
ArduinoOTA.setHostname("DHT双显示屏温度计");
ArduinoOTA.onStart([]() {
String type;
if (ArduinoOTA.getCommand() == U_FLASH) {
type = "sketch";
} else { // U_SPIFFS
type = "filesystem";
}
// NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
Serial.println("Start updating " + type);
});
ArduinoOTA.onEnd([]() {
Serial.println("\nEnd");
});
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
});
ArduinoOTA.onError([](ota_error_t error) {
Serial.printf("Error[%u]: ", error);
if (error == OTA_AUTH_ERROR) {
Serial.println("Auth Failed");
} else if (error == OTA_BEGIN_ERROR) {
Serial.println("Begin Failed");
} else if (error == OTA_CONNECT_ERROR) {
Serial.println("Connect Failed");
} else if (error == OTA_RECEIVE_ERROR) {
Serial.println("Receive Failed");
} else if (error == OTA_END_ERROR) {
Serial.println("End Failed");
}
});
ArduinoOTA.begin();
dht.begin();
Blynk.begin(auth, ssid, pass, "你的域名", 8080);
timer.setInterval(1000L, sendSensor);
//先显示一下开机信息
drawImageDemo();
}
void loop()
{
time_t now = time(nullptr);
struct tm* p_tm = localtime(&now);
float h = dht.readHumidity();
float t = dht.readTemperature(); //dht.readTemperature(true)
display.clear();
display.setFont(ArialMT_Plain_24);
//这里我写了两种代码写法,时间时普通写法,日期时简化写法
//if语句用于:当时/分/秒为个位时,在数字前补一个0,只是为了显示美观
if ( p_tm->tm_hour < 10) {
display.drawString(15, 0, "0" + String(p_tm->tm_hour) + ":");
} else {
display.drawString(15, 0, String(p_tm->tm_hour) + ":");
}
if ( p_tm->tm_min < 10) {
display.drawString(47, 0, "0" + String(p_tm->tm_min) + ":");
} else {
display.drawString(47, 0, String(p_tm->tm_min) + ":");
}
//if语句用于:当 时/分/秒为个位时,在数字前补一个0,只是为了显示美观
if ( p_tm->tm_sec < 10) {
display.drawString(81, 0, "0" + String(p_tm->tm_sec));
} else {
display.drawString(81, 0, String(p_tm->tm_sec));
}
//if语句用于:当 日和月为个位时,在数字前补一个0
if (p_tm->tm_mday < 10) {
display.drawString(0, 35, String(p_tm->tm_year + 1900) + "-" + "0" + String(p_tm->tm_mon + 1) + "-" + "0" + String(p_tm->tm_mday));
} else {
display.drawString(0, 35, String(p_tm->tm_year + 1900) + " -" + String(p_tm->tm_mon + 1) + " -" + String(p_tm->tm_mday));
}
display.display();
//第二个屏幕显示温湿度的代码
display2.clear();
//设置字体大小,可选10 16 24三种规格
display2.setFont(ArialMT_Plain_24);
display2.drawString(15, 0, "T: " + String(t, 1));
//在像素坐标:94, 0上显示摄氏度符号 " ° "
display2.drawXbm(94, 0, str_1_width, str_1_height, str_1);
display2.drawString(99, 0, "C");
display2.drawString(15, 35, "H: " + String(h, 1) + " % " );
display2.display();
ArduinoOTA.handle();
Blynk.run();
timer.run();
Blynk.virtualWrite(V2, "IP地址: ", WiFi.localIP().toString());
Blynk.virtualWrite(V3, "MAC地址: ", WiFi.macAddress());
Blynk.virtualWrite(V4, "RSSI: ", WiFi.RSSI(), " ", "SSID: ", WiFi.SSID());
//延时一秒刷新一次,同步时钟每秒的变化
delay(1000);
}
//用于开机启动画面的函数
void drawImageDemo() {
// 需要自定义图像的请去:http://blog.squix.org/2015/05/esp8266-nodemcu-how-to-create-xbm.html
//第二屏显示一个WIFI LOGO
display2.clear();
display2.drawXbm(35, 0, WiFi_Logo_width, WiFi_Logo_height, WiFi_Logo_bits);
display2.drawString(0, 40, "RSSI: " + String(WiFi.RSSI()) + " dB");
display2.drawString(0, 50, "MAC: " + String(WiFi.macAddress()));
display2.display();
//第一个屏幕显示IP/HOST名字/SSID
display.clear();
display.setFont(ArialMT_Plain_10);
display.drawString(0, 0, "IP: " + String(WiFi.localIP().toString()));
display.drawString(0, 20, "Hostname: " + String(WiFi.hostname()));
display.drawString(0, 40, "SSId: " + String(WiFi.SSID()));
display.display();
//延时十秒
delay(10000);
}
images.h头文件
//设置WIFI LOGO的长和款,命名为:WiFi_Logo_bits
#define WiFi_Logo_width 60
#define WiFi_Logo_height 36
const uint8_t WiFi_Logo_bits[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFF, 0x07, 0x00, 0x00, 0x00,
0x00, 0x00, 0xE0, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xFF,
0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0xFF, 0x00, 0x00, 0x00,
0x00, 0x00, 0xFE, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF,
0xFF, 0x03, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00,
0x00, 0xFF, 0xFF, 0xFF, 0x07, 0xC0, 0x83, 0x01, 0x80, 0xFF, 0xFF, 0xFF,
0x01, 0x00, 0x07, 0x00, 0xC0, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x0C, 0x00,
0xC0, 0xFF, 0xFF, 0x7C, 0x00, 0x60, 0x0C, 0x00, 0xC0, 0x31, 0x46, 0x7C,
0xFC, 0x77, 0x08, 0x00, 0xE0, 0x23, 0xC6, 0x3C, 0xFC, 0x67, 0x18, 0x00,
0xE0, 0x23, 0xE4, 0x3F, 0x1C, 0x00, 0x18, 0x00, 0xE0, 0x23, 0x60, 0x3C,
0x1C, 0x70, 0x18, 0x00, 0xE0, 0x03, 0x60, 0x3C, 0x1C, 0x70, 0x18, 0x00,
0xE0, 0x07, 0x60, 0x3C, 0xFC, 0x73, 0x18, 0x00, 0xE0, 0x87, 0x70, 0x3C,
0xFC, 0x73, 0x18, 0x00, 0xE0, 0x87, 0x70, 0x3C, 0x1C, 0x70, 0x18, 0x00,
0xE0, 0x87, 0x70, 0x3C, 0x1C, 0x70, 0x18, 0x00, 0xE0, 0x8F, 0x71, 0x3C,
0x1C, 0x70, 0x18, 0x00, 0xC0, 0xFF, 0xFF, 0x3F, 0x00, 0x00, 0x08, 0x00,
0xC0, 0xFF, 0xFF, 0x1F, 0x00, 0x00, 0x0C, 0x00, 0x80, 0xFF, 0xFF, 0x1F,
0x00, 0x00, 0x06, 0x00, 0x80, 0xFF, 0xFF, 0x0F, 0x00, 0x00, 0x07, 0x00,
0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0xF8, 0xFF, 0xFF,
0xFF, 0x7F, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0xFF, 0x01, 0x00, 0x00,
0x00, 0x00, 0xFC, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xFF,
0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x1F, 0x00, 0x00, 0x00,
0x00, 0x00, 0x80, 0xFF, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
//设置数组大小
#define str_1_width 16
#define str_1_height 16
//符号: ° 的数组
static const unsigned char PROGMEM str_1[] = {
0x00, 0x00, 0x18, 0x00, 0x24, 0x00, 0x24, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};//°