WiFi 时钟

本文详细介绍了如何使用ESP8266和OLED屏幕制作一个简单的WiFi时钟,包括硬件准备、代码编写和网络时间同步。通过逐步教程,读者可以了解如何解决中文显示问题,以及如何实现时钟的实时更新和网络对时功能。
摘要由CSDN通过智能技术生成

更多内容,请访问我的网站:https://jiangge12.github.io/

WiFi 时钟有很多开源项目的。但是,成品往往代码一大篇,看起来有些上头。加上有些库和环境的版本变迁,编译报错排查起来很是费劲。于是从头捋一遍,一步一步的过程,容易上手:

准备工作:

a 零件:只需要用到 ESP8266 和 OLED 各一个。

b 开发环境:arduino_1.8.19 + ESP8266_3.0.2 ,这是我当前使用的,其他高一些的应该也行,没测试

c 显示布局:上图布局花了点心思,42是能够显示全的最大号字体,把“秒”放在冒号中间刚刚好。

制作步骤:

1. 首先需要保证OLED能够亮起来

我手里这块 ESP8266 购买时没焊接排针,自己模仿UNO焊接的排母,可以方便的插 OLED 和其他零件。

  

 如果已经焊了排针,下面这样用面包板对插也是很方便的。

注意代码里面是按上面的图插的,下图面包板只是演示变通的插接方法,真这么插需要修改相关语句。

下面的代码可以让OLED显示出一个静止的时钟,但是星期六三个汉字只有“期”能显示出来,这是因为U8G2库的原生中文字库很小,“星”、“六”这两字没有。

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

下面的代码 增加调用 u8g2.drawXBM ,以图片方式显示汉字, 这样显示部分调试就算完成了。

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

 2. 让时钟走起来

需要 TimeLib.h 这个库,时钟是从1970/1/1 00:00 开始走的,因为还没有添加“对时”代码(也叫授时)。关键字 Epoch 需要了解一下。

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

3. 没有对时怎么叫时钟? 先试试本地对时:

利用 __TIME__ 这个常量,把PC的当前时间写入到ESP8266,这时钟基本就像样了,一秒一秒走起来还是很准的。。。嗯,可不能掉电哦。因为。。。你可以拔掉线再插上试试看。

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

4. 网络对时

要做到断电时间不停摆,通常的思路是增加DS1302之类的带电池的RTC模块,比如电脑主板就是这样,有一个CR2032电池,保存时间和BIOS设置。

ESP8266 有 WiFi,玩法就不同了,可以便利的从网络获取时间,这也是智能手机几乎不需要考虑设置时钟的原因。

关键字 NTP 可以搜一下。

下面代码里,只需要修改 “WiFi.begin("SSID", "PASSWORD");” 这一行

整个代码里,有几十行都是OLED相关语句,还有几十行头文件,初始化这些固定操作,真正体现程序逻辑的是 loop 里面的3行,以及函数 getNtpTime 里面的2行, 理解起来应该很清晰了。

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

--------------------------------------------------------------------------------------------------------------------------------

2023-7-3 补充:

因为OLED很小,可以说寸土寸金。

这不,进入了夏季,显示器上粘一个能显示时间和室温的OLED显示屏 比 上面显示日期的实用些。这一次用400孔小面包板,能看出来I2C怎么连接的吗?

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值