WiFi 时钟有很多开源项目的。但是,成品往往代码一大篇,看起来有些上头。加上有些库和环境的版本变迁,编译报错排查起来很是费劲。于是从头捋一遍,一步一步的过程,容易上手:
准备工作:
a 零件:只需要用到 ESP8266 和 OLED 各一个。
b 开发环境:arduino_1.8.19 + ESP8266_3.0.2 ,这是我当前使用的,其他高一些的应该也行,没测试
制作步骤:
1. 首先需要保证OLED能够亮起来
#include <U8g2lib.h>
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(/*默认R0,R2为旋转180°*/U8G2_R2, /* reset=*/ U8X8_PIN_NONE, /* clock=*/ 4, /* data=*/ 0);
void setup(){
pinMode(16,OUTPUT);digitalWrite(16,HIGH); // OLED供电
pinMode(5,OUTPUT);digitalWrite(5,LOW); // OLED供电
Serial.begin(115200);
u8g2.begin();
u8g2.enableUTF8Print(); // 启用中文显示,但库不全
oledClockDisplay();
}
void loop(){
}
void oledClockDisplay(){
u8g2.clearBuffer();
u8g2.setCursor(0, 47);u8g2.setFont(u8g2_font_logisoso42_tr);u8g2.print("18:56");
u8g2.setCursor(54,35);u8g2.setFont(u8g2_font_unifont_t_chinese2);u8g2.print("34");
u8g2.setCursor(0, 64);u8g2.setFont(u8g2_font_unifont_t_chinese2);u8g2.print("2023/04/01");
u8g2.print("星期六");
u8g2.sendBuffer();
}
上面的代码可以让OLED显示出一个静止的时钟,但是星期六三个汉字只有“期”能显示出来,这是因为U8G2库的原生中文字库很小,“星”、“六”这两字没有。
这个布局花了点心思,42号时能够显示全的最大号字体,把“秒”放在冒号中间,需要读秒也能看到。
#include <U8g2lib.h>
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(/*默认R0,R2为旋转180°*/U8G2_R2, /* reset=*/ U8X8_PIN_NONE, /* clock=*/ 4, /* data=*/ 0);
const unsigned char /*星*/ xing[] U8X8_PROGMEM = {
0x00, 0x00, 0xF8, 0x0F, 0x08, 0x08, 0xF8, 0x0F, 0x08, 0x08, 0xF8, 0x0F, 0x80, 0x00, 0x88, 0x00,
0xF8, 0x1F, 0x84, 0x00, 0x82, 0x00, 0xF8, 0x0F, 0x80, 0x00, 0x80, 0x00, 0xFE, 0x3F, 0x00, 0x00
};
const unsigned char /*六*/ liu[] U8X8_PROGMEM = {
0x40, 0x00, 0x80, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0xFF, 0x7F, 0x00, 0x00, 0x00, 0x00,
0x20, 0x02, 0x20, 0x04, 0x10, 0x08, 0x10, 0x10, 0x08, 0x10, 0x04, 0x20, 0x02, 0x20, 0x00, 0x00
};
void setup(){
pinMode(16,OUTPUT);digitalWrite(16,HIGH); // OLED供电
pinMode(5,OUTPUT);digitalWrite(5,LOW); // OLED供电
Serial.begin(115200);
u8g2.begin();
u8g2.enableUTF8Print(); // 启用中文显示,但库不全
oledClockDisplay();
}
void loop(){
}
void oledClockDisplay(){
u8g2.clearBuffer();
u8g2.setCursor(0, 47);u8g2.setFont(u8g2_font_logisoso42_tr);u8g2.print("18:56");
u8g2.setCursor(54,35);u8g2.setFont(u8g2_font_unifont_t_chinese2);u8g2.print("34");
u8g2.setCursor(0, 64);u8g2.setFont(u8g2_font_unifont_t_chinese2);u8g2.print("2023/04/01");
u8g2.drawXBM(80, 49, 16, 16, xing);
u8g2.setCursor(95, 63);u8g2.print("期");
u8g2.drawXBM(111, 49, 16, 16, liu);
u8g2.sendBuffer();
}
u8g2.drawXBM 以图片方式显示汉字, 这样显示部分调试就算完成了。
2. 让时钟走起来
这时需要用到 TimeLib.h 这个库
#include <U8g2lib.h>
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(/*默认R0,R2为旋转180°*/U8G2_R2, /* reset=*/ U8X8_PIN_NONE, /* clock=*/ 4, /* data=*/ 0);
const unsigned char /*星*/ xing[] U8X8_PROGMEM = {
0x00, 0x00, 0xF8, 0x0F, 0x08, 0x08, 0xF8, 0x0F, 0x08, 0x08, 0xF8, 0x0F, 0x80, 0x00, 0x88, 0x00,
0xF8, 0x1F, 0x84, 0x00, 0x82, 0x00, 0xF8, 0x0F, 0x80, 0x00, 0x80, 0x00, 0xFE, 0x3F, 0x00, 0x00
};
const unsigned char /*六*/ liu[] U8X8_PROGMEM = {
0x40, 0x00, 0x80, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0xFF, 0x7F, 0x00, 0x00, 0x00, 0x00,
0x20, 0x02, 0x20, 0x04, 0x10, 0x08, 0x10, 0x10, 0x08, 0x10, 0x04, 0x20, 0x02, 0x20, 0x00, 0x00
};
#include <TimeLib.h>
time_t currentDisplayTime = 0;
void setup(){
pinMode(16,OUTPUT);digitalWrite(16,HIGH); // OLED供电
pinMode(5,OUTPUT);digitalWrite(5,LOW); // OLED供电
Serial.begin(115200);
u8g2.begin();
u8g2.enableUTF8Print(); // 启用中文显示,但库不全
oledClockDisplay();
}
void loop(){
if (now()!= currentDisplayTime){
currentDisplayTime = now();
oledClockDisplay();
}
}
void oledClockDisplay(){
int years = year();
int months = month();
int days = day();
int hours = hour();
int minutes = minute();
int seconds = second();
int weekdays = weekday();
Serial.printf("%d/%d/%d %d:%d:%d Weekday:%d\n", years, months, days, hours, minutes, seconds, weekdays);
String currentTime = "";
if (hours < 10)currentTime += 0;currentTime += hours;currentTime += ":";
if (minutes < 10)currentTime += 0;currentTime += minutes;currentTime += ":";
String currentDate = "";
currentDate += years;currentDate += "/";
if (months < 10)currentDate += 0;currentDate += months;currentDate += "/";
if (days < 10)currentDate += 0;currentDate += days;
String s=""; if (seconds < 10)s += 0;s += seconds; // 单独把秒字符串拿出来以小字体显示在冒号处
u8g2.clearBuffer();
u8g2.setCursor(0, 47);u8g2.setFont(u8g2_font_logisoso42_tr);u8g2.print(currentTime);
u8g2.setCursor(54,35);u8g2.setFont(u8g2_font_unifont_t_chinese2);u8g2.print(s);
u8g2.setCursor(0, 64);u8g2.setFont(u8g2_font_unifont_t_chinese2);u8g2.print(currentDate);
u8g2.drawXBM(80, 49, 16, 16, xing);
u8g2.setCursor(95, 63);u8g2.print("期");
if (weekdays == 1)u8g2.print("日");
else if (weekdays == 2)u8g2.print("一");
else if (weekdays == 3)u8g2.print("二");
else if (weekdays == 4)u8g2.print("三");
else if (weekdays == 5)u8g2.print("四");
else if (weekdays == 6)u8g2.print("五");
else if (weekdays == 7)u8g2.drawXBM(111, 49, 16, 16, liu);
u8g2.sendBuffer();
}
因为还没有添加对时代码,这个时钟是从1970/1/1 00:00 开始走的。
下面 利用 __TIME__ 这个常量,把PC的时钟写入到ESP8266,只要不断电,这个时钟还算不错,但要做到断电时间不停摆,需要DS1302之类的带电池的RTC模块, 不过ESP8266有WiFi,能从网络获取NTP时间。
#include <U8g2lib.h>
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(/*默认R0,R2为旋转180°*/U8G2_R2, /* reset=*/ U8X8_PIN_NONE, /* clock=*/ 4, /* data=*/ 0);
const unsigned char /*星*/ xing[] U8X8_PROGMEM = {
0x00, 0x00, 0xF8, 0x0F, 0x08, 0x08, 0xF8, 0x0F, 0x08, 0x08, 0xF8, 0x0F, 0x80, 0x00, 0x88, 0x00,
0xF8, 0x1F, 0x84, 0x00, 0x82, 0x00, 0xF8, 0x0F, 0x80, 0x00, 0x80, 0x00, 0xFE, 0x3F, 0x00, 0x00
};
const unsigned char /*六*/ liu[] U8X8_PROGMEM = {
0x40, 0x00, 0x80, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0xFF, 0x7F, 0x00, 0x00, 0x00, 0x00,
0x20, 0x02, 0x20, 0x04, 0x10, 0x08, 0x10, 0x10, 0x08, 0x10, 0x04, 0x20, 0x02, 0x20, 0x00, 0x00
};
#include <TimeLib.h>
time_t currentDisplayTime = 0;
void setup(){
pinMode(16,OUTPUT);digitalWrite(16,HIGH); // OLED供电
pinMode(5,OUTPUT);digitalWrite(5,LOW); // OLED供电
Serial.begin(115200);
u8g2.begin();
u8g2.enableUTF8Print(); // 启用中文显示,但库不全
oledClockDisplay();
int yy = 2023; // 年月日可以从常量 __DATE__ 中获得,这里暂不处理,只处理时间,以演示 setTime 用法,后续用网络同步即可
int mm = 4;
int dd = 19;
int HH = (int(__TIME__[0])-48)*10 + int(__TIME__[1])-48; // 0 的 ascii 48
int MM = (int(__TIME__[3])-48)*10 + int(__TIME__[4])-48;
int SS = (int(__TIME__[6])-48)*10 + int(__TIME__[7])-48;
setTime(mktime(yy,mm,dd,HH,MM,SS)+30);
//setTime(1357041600); // 从1970-1-1开始至今的秒数 1357041600 = Jan 1 2013
//setTime(mktime(2023,4,19,17,42,00)); // 从日期计算秒数
}
void loop(){
if (now()!= currentDisplayTime){
currentDisplayTime = now();
Serial.println(currentDisplayTime); // 输出为 1681868855 这样的 Epoch 时间 https://www.epochconverter.com/
oledClockDisplay();
}
}
void oledClockDisplay(){
int years = year();
int months = month();
int days = day();
int hours = hour();
int minutes = minute();
int seconds = second();
int weekdays = weekday();
Serial.printf("%d/%d/%d %d:%d:%d Weekday:%d\n", years, months, days, hours, minutes, seconds, weekdays);
String currentTime = "";
if (hours < 10)currentTime += 0;currentTime += hours;currentTime += ":";
if (minutes < 10)currentTime += 0;currentTime += minutes;currentTime += ":";
String currentDate = "";
currentDate += years;currentDate += "/";
if (months < 10)currentDate += 0;currentDate += months;currentDate += "/";
if (days < 10)currentDate += 0;currentDate += days;
String s=""; if (seconds < 10)s += 0;s += seconds; // 单独把秒字符串拿出来以小字体显示在冒号处
u8g2.clearBuffer();
u8g2.setCursor(0, 47);u8g2.setFont(u8g2_font_logisoso42_tr);u8g2.print(currentTime);
u8g2.setCursor(54,35);u8g2.setFont(u8g2_font_unifont_t_chinese2);u8g2.print(s);
u8g2.setCursor(0, 64);u8g2.setFont(u8g2_font_unifont_t_chinese2);u8g2.print(currentDate);
u8g2.drawXBM(80, 49, 16, 16, xing);
u8g2.setCursor(95, 63);u8g2.print("期");
if (weekdays == 1)u8g2.print("日");
else if (weekdays == 2)u8g2.print("一");
else if (weekdays == 3)u8g2.print("二");
else if (weekdays == 4)u8g2.print("三");
else if (weekdays == 5)u8g2.print("四");
else if (weekdays == 6)u8g2.print("五");
else if (weekdays == 7)u8g2.drawXBM(111, 49, 16, 16, liu);
u8g2.sendBuffer();
}
/* ----------------- 当前到1970-1-1 的秒数 https://blog.csdn.net/weixin_46935110/article/details/124325951 ------------*/
long mktime (int year, int mon, int day, int hour, int min, int sec){
if (0 >= (int) (mon -= 2)){mon += 12;year -= 1;}
return ((((unsigned long) (year/4 - year/100 + year/400 + 367*mon/12 + day) + year*365 - 719499)*24 + hour)*60 + min)*60 + sec;
}
3. 网络对时
先直接上代码,注意需要修改 “WiFi.begin("SSID", "PASSWORD");” 这一行
用到了 NTPClient.h 这个库,在线对时的代码很简洁了。
整个代码里,几十行都是显示相关语句,还有几十行头文件,初始化这些固定操作,真正体现程序逻辑的是 loop 里面的3行,以及函数 getNtpTime 里面的4行, 理解起来应该很清晰了。
#include <U8g2lib.h>
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(/*默认R0,R2为旋转180°*/U8G2_R2, /* reset=*/ U8X8_PIN_NONE, /* clock=*/ 4, /* data=*/ 0);
#include <TimeLib.h>
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include <NTPClient.h>
WiFiUDP Udp;
NTPClient timeClient(Udp, "cn.pool.ntp.org"); //ntp1.aliyun.com
time_t currentDisplayTime = 0;
const unsigned char /*星*/ xing[] U8X8_PROGMEM = {
0x00, 0x00, 0xF8, 0x0F, 0x08, 0x08, 0xF8, 0x0F, 0x08, 0x08, 0xF8, 0x0F, 0x80, 0x00, 0x88, 0x00,
0xF8, 0x1F, 0x84, 0x00, 0x82, 0x00, 0xF8, 0x0F, 0x80, 0x00, 0x80, 0x00, 0xFE, 0x3F, 0x00, 0x00
};
const unsigned char /*六*/ liu[] U8X8_PROGMEM = {
0x40, 0x00, 0x80, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0xFF, 0x7F, 0x00, 0x00, 0x00, 0x00,
0x20, 0x02, 0x20, 0x04, 0x10, 0x08, 0x10, 0x10, 0x08, 0x10, 0x04, 0x20, 0x02, 0x20, 0x00, 0x00
};
void setup(){
pinMode(16,OUTPUT);digitalWrite(16,HIGH); // OLED供电
pinMode(5,OUTPUT);digitalWrite(5,LOW); // OLED供电
Serial.begin(115200);
u8g2.begin();
u8g2.enableUTF8Print(); // 启用中文显示,但库不全
WiFi.begin("SSID", "PASSWORD");
Serial.print("\n\n\nConnecting WiFi"); // ESP8266复位有很多乱码,换几行再显示
while (WiFi.status()!= WL_CONNECTED){delay(1000);Serial.print(".");}
Udp.begin(8888); // UDP 侦听端口,任意指定
setSyncProvider(getNtpTime);
setSyncInterval(60); // NTP网络同步间隔时间,单位秒
}
void loop(){
if (now()!= currentDisplayTime){
currentDisplayTime = now();
oledClockDisplay();
}
}
/*---------------- 刷新显示 ------------------*/
void oledClockDisplay(){
int years = year();
int months = month();
int days = day();
int hours = hour();
int minutes = minute();
int seconds = second();
int weekdays = weekday();
Serial.printf("%d/%d/%d %d:%d:%d Weekday:%d\n", years, months, days, hours, minutes, seconds, weekdays);
String currentTime = "";
if (hours < 10)currentTime += 0;currentTime += hours;currentTime += ":";
if (minutes < 10)currentTime += 0;currentTime += minutes;currentTime += ":";
String currentDate = "";
currentDate += years;currentDate += "/";
if (months < 10)currentDate += 0;currentDate += months;currentDate += "/";
if (days < 10)currentDate += 0;currentDate += days;
String s=""; if (seconds < 10)s += 0;s += seconds; // 单独把秒字符串拿出来以小字体显示在冒号处
u8g2.clearBuffer();
u8g2.setCursor(0, 47);u8g2.setFont(u8g2_font_logisoso42_tr);u8g2.print(currentTime);
u8g2.setCursor(54,35);u8g2.setFont(u8g2_font_unifont_t_chinese2);u8g2.print(s);
u8g2.setCursor(0, 64);u8g2.setFont(u8g2_font_unifont_t_chinese2);u8g2.print(currentDate);
u8g2.drawXBM(80, 49, 16, 16, xing);
u8g2.setCursor(95, 63);u8g2.print("期");
if (weekdays == 1)u8g2.print("日");
else if (weekdays == 2)u8g2.print("一");
else if (weekdays == 3)u8g2.print("二");
else if (weekdays == 4)u8g2.print("三");
else if (weekdays == 5)u8g2.print("四");
else if (weekdays == 6)u8g2.print("五");
else if (weekdays == 7)u8g2.drawXBM(111, 49, 16, 16, liu);
u8g2.sendBuffer();
}
/*---------------- NTP 代码 ------------------*/
time_t getNtpTime(){
Serial.print("\nSync...");
if(timeClient.update())Serial.println("Success!"); // 串口打印同步成功与否 0 失败 1 成功
else Serial.println("Failed!");
return(timeClient.getEpochTime()+28800); // GMT+8, 3600*8
}