更新日志:

2023年3月25日

  • 增加WEB OTA功能
  • 增加Web串口功能

    2023年3月24日

  • 上传代码
  • 修改注释
  • 改用web方式OTA

    时效性以github版本为准

实现功能

使用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编译通过的主要库版本:

PlatformIO编译通过的主要库版本:

  • Blynk:0.6.1
  • ArduinoJson:5.13.4
  • ESP8266板库:2.0.4、2.1.1、2.2.0
Platform espressif8266 @ 2.6.3 (required: espressif8266)
├── framework-arduinoespressif8266 @ 3.20704.0 (required: platformio/framework-arduinoespressif8266 @ ~3.20704.0)
├── tool-esptool @ 1.413.0 (required: platformio/tool-esptool @ <2)
├── tool-esptoolpy @ 1.30000.201119 (required: platformio/tool-esptoolpy @ ~1.30000.0)
├── tool-mklittlefs @ 1.203.210628 (required: platformio/tool-mklittlefs @ ~1.203.0)
├── tool-mkspiffs @ 1.200.0 (required: platformio/tool-mkspiffs @ ~1.200.0)
└── toolchain-xtensa @ 2.40802.200502 (required: platformio/toolchain-xtensa @ ~2.40802.0)

Libraries
├── Adafruit Unified Sensor @ 1.1.9 (required: adafruit/Adafruit Unified Sensor @ ^1.1.9)
├── Blynk @ 1.2.0 (required: blynkkk/Blynk @ ^1.2.0)
├── DHT sensor library @ 1.4.4 (required: adafruit/DHT sensor library @ ^1.4.4)
└── ESP8266 and ESP32 OLED driver for SSD1306 displays @ 4.4.0 (required: thingpulse/ESP8266 and ESP32 OLED driver for SSD1306 displays @ ^4.4.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

代码

代码时效性以github端为准

更新日志:


2023年3月24日

  • 上传代码
  • 修改注释
  • 改用web方式OTA

主程序

已过时

#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,
};//°

手机端效果图


一沙一世界,一花一天堂。君掌盛无边,刹那成永恒。