![](http://chrisxs.top/wp-content/uploads/2020/05/ZSA@PC1I4LQ321OH26D4.png)

这次做的版本是上次这个时钟的简化版,由于不介入云平台,所以代码十分简单.这个的关闭屏幕方式我采用了使用轻触式开关的做法,按一下按钮即可关闭屏幕,再按一下则开启.如果通电开机前时按着开关不放,则进入重置状态.这次的校时同样是使用NTP校时,整个流程很简短,萌新都可以做,十分简单.

材料准备

材料名称 数量
ESP8266 1个
0.96'OLED 1个
常开开关 1个
3D打印外壳 1个
线 若干

外壳是可选的,点击这里下载模具文件 ,我这里是用了带四角轻触式开关开孔那个模具(见下图),OLED屏幕是使用SSD1306主控的.

硬件接线

ESP8266 OLED 开关
3.3V S1
5V
GND
D1 SCL
D2 SDA
D3 S2


接好线后的效果图(点击图片放大)

注: D3是同时作为屏幕开关和WIFI复位的用途.

软件准备

OLED用到的程序库:ThingPulse OLED SSD1306

需要注意的编译环境(亲测高版本会出错,未解决),全部库都可以在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

代码

主代码

#include <Arduino.h>
#include <FS.h>

#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <ESP8266WebServer.h>

#include <WiFiUdp.h>
#include <ArduinoOTA.h>
#include <time.h>
#include <WiFiManager.h>
#include <DNSServer.h>
#include <ArduinoJson.h> 

#include <Wire.h>
#include "SSD1306Wire.h"
#include "images.h"

SSD1306Wire  display(0x3c, D2, D1);
const int  SetPin = D3;

int SetPinState = LOW;
boolean toggle = LOW;
byte click = 0;        // 开关信号的改变次数,预设为 0

int timezone = 8 * 3600; //设置时区
int dst = 0;
bool shouldSaveConfig = false;

char blynk_token[34] = "";

void saveConfigCallback ()
{
  Serial.println("Should save config");
  shouldSaveConfig = true;
}

void setup()
{
  Serial.begin(115200);

  pinMode(SetPin, OUTPUT);
  int SetPinState = digitalRead(SetPin);
  configTime(timezone, dst, "time.windows.com");

  display.init();
  display.setI2cAutoInit(true);
  display.flipScreenVertically();
  display.setFont(ArialMT_Plain_10);
  display.setTextAlignment(TEXT_ALIGN_LEFT);
  display.clear();

  //读取FS json的配置
  Serial.println("mounting FS...");
  if (SPIFFS.begin()) {
    Serial.println("mounted file system");
    if (SPIFFS.exists("/config.json")) {
      //文件存在,读取和加载
      Serial.println("reading config file");
      File configFile = SPIFFS.open("/config.json", "r");
      if (configFile) {
        Serial.println("opened config file");
        size_t size = configFile.size();
        // 分配缓冲区以存储文件的内容。
        std::unique_ptr<char[]> buf(new char[size]);
        configFile.readBytes(buf.get(), size);
        DynamicJsonBuffer jsonBuffer;
        JsonObject& json = jsonBuffer.parseObject(buf.get());
        json.printTo(Serial);
        if (json.success()) {
          Serial.println("\nparsed json");
          strcpy(blynk_token, json["blynk_token"]);
        } else {
          Serial.println("failed to load json config");
        }
        configFile.close();
      }
    }
  } else {
    Serial.println("failed to mount FS");
  }
  //结束

  // 要配置的额外参数(可以是全局的,也可以只是在设置中)
  // 连接后,parameter.getValue()将获得配置的值//After connecting, parameter.getValue() will get you the configured value
  // id/name placeholder/prompt 的默认长度
  WiFiManagerParameter custom_blynk_token("blynk", "链接WIFI后请手动重启一次", blynk_token, 34);

  //WiFiManager
  //本地初始化。一旦业务完成,就没有必要保留它
  WiFiManager wifiManager;

  //设置配置保存通知回调
  wifiManager.setSaveConfigCallback(saveConfigCallback);

  //在这里添加所有参数
  wifiManager.addParameter(&custom_blynk_token);

  //重置设置 - 用于测试
  //wifiManager.resetSettings();

  //设置重置按钮的功能
  if (SetPinState == HIGH)
  {
    Serial.println("Getting Reset ESP Wifi-Setting.......");
    display.setFont(ArialMT_Plain_10);
    display.clear();
    display.drawXbm(35, 0, WiFi_Logo_width, WiFi_Logo_height, WiFi_Logo_bits);
    display.drawString(0, 40, "RESET mode activated .");
    display.drawString(0, 50, "Please wait for reboot !");
    display.display();
    wifiManager.resetSettings();
    delay(5000);
    Serial.println("Formatting FS......");
    SPIFFS.format();
    delay(5000);
    Serial.println("Done Reboot In 5 seconds");
    display.setFont(ArialMT_Plain_16);
    display.clear();
    display.drawString(5, 25, "Reboot in 5 Sec !");
    display.display();
    delay(1000);

    display.clear();
    display.drawString(5, 25, "Reboot in 4 Sec !");
    display.display();
    delay(1000);

    display.clear();
    display.drawString(5, 25, "Reboot in 3 Sec !");
    display.display();
    delay(1000);

    display.clear();
    display.drawString(5, 25, "Reboot in 2 Sec !");
    display.display();
    delay(1000);

    display.clear();
    display.drawString(5, 25, "Reboot in 1 Sec !");
    display.display();
    delay(1000);

    ESP.restart();
  }

  //获取ssid并传递并尝试连接
  //如果它没有连接,它将启动具有指定名称的访问点,"AutoConnectAP",并进入等待配置的阻塞循环
  if (!wifiManager.autoConnect("OLED时钟", "")) {
    Serial.println("failed to connect and hit timeout");
    delay(3000);
    //重置并重试,或者让它深入睡眠
    ESP.reset();
    delay(5000);
  }

  //如果已经连接
  Serial.println("connected...yeey :)");
  //读取更新的参数
  strcpy(blynk_token, custom_blynk_token.getValue());
  //j将自定义参数保存到FS
  if (shouldSaveConfig) {
    Serial.println("saving config");
    DynamicJsonBuffer jsonBuffer;
    JsonObject& json = jsonBuffer.createObject();
    json["blynk_token"] = blynk_token;
    File configFile = SPIFFS.open("/config.json", "w");
    if (!configFile) {
      Serial.println("failed to open config file for writing");
    }
    json.printTo(Serial);
    json.printTo(configFile);
    configFile.close();
    //end save
  }
  Serial.println("local ip");
  Serial.println(WiFi.localIP());

  ArduinoOTA.setHostname("OLED时钟");
  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();
  drawImageDemo();
}

void loop()
{
  time_t now = time(nullptr);

  struct tm* p_tm = localtime(&now);

  display.clear();
  display.setFont(ArialMT_Plain_24);

  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 ( 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 (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();

  ArduinoOTA.handle();

  int b1 = digitalRead(SetPin);

  if (b1 != SetPinState) {     // 如果和之前的开关值不同...
    delay(20);               // 等待 20 毫秒
    int b2 = digitalRead(SetPin);   // 再读取一次开关值

    if (b1 == b2) {    // 确认两次开关值是否一致
      SetPinState = b1; // 储存开关的状态
      click ++;       // 增加信号变化次数
    }
  }

  if (click == 2) {    // 如果开关状态改变两次
    click = 0;         // 状态次数归零
    toggle = !toggle;            // 取相反值
    Serial.println(toggle);
  }
  if (toggle == 0) {
    display.displayOn();
  } else {
    display.displayOff();
  }
}

void drawImageDemo() {//用于开机时候显示LOGO和网络信息的函数
  display.setFont(ArialMT_Plain_10);
  display.clear();
  display.drawXbm(35, 0, WiFi_Logo_width, WiFi_Logo_height, WiFi_Logo_bits);
  display.drawString(0, 40, "IP: " + String(WiFi.localIP().toString()));
  display.drawString(0, 50, "SSID: " + String(WiFi.SSID()));
  display.display();
  delay(5000);
  display.setFont(ArialMT_Plain_10);
  display.clear();
  display.drawString(0, 0, "Hostname: " + String(WiFi.hostname()));
  display.drawString(0, 10, "RSSI: " + String(WiFi.RSSI()) + " dB");
  display.drawString(0, 20, "MAC: " + String(WiFi.macAddress()));
  display.drawString(0, 35, "Done ! WiFi Connected .");
  display.display();
  delay(5000);
}

转成C组的图片

新建一个名为images.h的文件.

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

完成后演示




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