> 文章列表 > WiFi 时钟

WiFi 时钟

WiFi 时钟

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 48int 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
}