这次做的版本是上次这个时钟的简化版,由于不介入云平台,所以代码十分简单.这个的关闭屏幕方式我采用了使用轻触式开关的做法,按一下按钮即可关闭屏幕,再按一下则开启.如果通电开机前时按着开关不放,则进入重置状态.这次的校时同样是使用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++
转成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, | |
}; |
C++
完成后演示
正文完
发表至: 技术
2020-05-28