[经验分享] 【2022得捷创新设计大赛】ESP32上基于NTP服务的互联网时钟

HonestQiao   2022-11-2 16:05 楼主

很多时候,我们使用ESP32的时候,没有RTC时钟芯片辅助,那么就需要一个外部时钟源,来提供准确的时间,以便进行对时。

经过一番了解,可以使用NTP服务器来进行对时。

 

最终完成的实际代码如下:

/* 基于NTP服务器的互联网时钟

*/

 

#include <Arduino.h>

#include <stdlib.h>

 

// 时间库

#include <TimeLib.h>

 

// WiFi库

#include <WiFi.h>

#include <WiFiMulti.h>

#include <WiFiUdp.h>

#include <Wire.h>

 

// OLED库

#include <Adafruit_SSD1306.h>

#include <U8g2_for_Adafruit_GFX.h>

 

// OLED定义

// 屏幕大小定义

#define SCREEN_WIDTH 128 // 宽度

#define SCREEN_HEIGHT 64 // 高度

 

// OLED I2C定义

#define OLED_RESET -1 // RESET

#define SCREEN_ADDRESS 0x3C // I2C地址

 

// Adafruit_SSD1306显示对象

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

 

// U8G2_FOR_ADAFRUIT_GFX显示对象

U8G2_FOR_ADAFRUIT_GFX u8g2_for_adafruit_gfx;

 

// WiFi定义

#define WIFI_SSID "OpenBSD"

#define WIFI_PASS "13581882013"

 

WiFiMulti WiFiMulti;

 

// NTP定义

static const char ntpServerName[] = "ntp.aliyun.com";

const int timeZone = 8; // 时区

 

// 通讯数据包定义

const int NTP_PACKET_SIZE = 48;

byte packetBuffer[NTP_PACKET_SIZE];

 

// UDP定义

WiFiUDP Udp;

unsigned int localPort = 8888;

 

// NTP方法和对象定义

time_t getNtpTime();

 

// 时间显示状态

time_t prevDisplay = 0;

 

/**

* showtime 显示时间

**/

void showtime() {

    int Hour = hour();

    int Min = minute();

    int Sec = second();

    int HourHigh, HourLow, MinHigh, MinLow, SecHigh, SecLow;

 

    char buff[50];

    sprintf(buff, "%02d:%02d:%02d", Hour, Min, Sec);

    u8g2_for_adafruit_gfx.setFont(u8g2_font_fur20_tn); // 中文字体

    u8g2_for_adafruit_gfx.setCursor(8, 52); // start writing at this position

    u8g2_for_adafruit_gfx.print(buff);

}

 

/**

* getNtpTime NTP对时

**/

time_t getNtpTime() {

    IPAddress ntpServerIP; // NTP 服务器地址

 

    while (Udp.parsePacket() > 0)

        ; // 丢弃任何先前接收的数据包

 

    // 发起NTP请求

    Serial.println("Transmit NTP Request");

    WiFi.hostByName(ntpServerName, ntpServerIP); // 获取IP

    Serial.print(ntpServerName);

    Serial.print(": ");

    Serial.println(ntpServerIP);

 

    sendNTPpacket(ntpServerIP); // 发送数据包

 

    uint32_t beginWait = millis();

    while (millis() - beginWait < 1500) {

        int size = Udp.parsePacket();

    if (size >= NTP_PACKET_SIZE) {

        Serial.println("Receive NTP Response");

    Udp.read(packetBuffer, NTP_PACKET_SIZE); // 读取数据包

    unsigned long secsSince1900;

 

    // 转换数据

    secsSince1900 = (unsigned long)packetBuffer[40] << 24;

    secsSince1900 |= (unsigned long)packetBuffer[41] << 16;

    secsSince1900 |= (unsigned long)packetBuffer[42] << 8;

    secsSince1900 |= (unsigned long)packetBuffer[43];

 

    // 返回数据

    return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;

}

}

Serial.println("No NTP Response :-(");

 

return 0;

}

 

/**

* sendNTPpacket 发送NTP数据包

**/

void sendNTPpacket(IPAddress &address) {

    // 清理buff

    memset(packetBuffer, 0, NTP_PACKET_SIZE);

 

    // 生成请求数据

    packetBuffer[0] = 0b11100011; // LI, Version, Mode

    packetBuffer[1] = 0; // Stratum, or type of clock

    packetBuffer[2] = 6; // Polling Interval

    packetBuffer[3] = 0xEC; // Peer Clock Precision

    // 8 bytes of zero for Root Delay & Root Dispersion

    packetBuffer[12] = 49;

    packetBuffer[13] = 0x4E;

    packetBuffer[14] = 49;

    packetBuffer[15] = 52;

 

    // 发送NTP数据包

    Udp.beginPacket(address, 123);

    Udp.write(packetBuffer, NTP_PACKET_SIZE);

    Udp.endPacket();

}

 

void setup() {

    // 串口初始化

    Serial.begin(115200);

 

    // 通讯初始化

    Wire.begin();

 

    // OLED初始化

    Serial.println(F("SSD1306 init:"));

    if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {

        Serial.println(F("SSD1306 allocation failed"));

        for (;;)

        ; // 循环

    } else {

        Serial.println(F("SSD1306 allocation ok"));

    }

 

    // 显示默认 Adafruit splash screen

    display.display();

    delay(1000);

 

    // 关联u8g8到Adafruit GFX

    u8g2_for_adafruit_gfx.begin(display);

 

    // WiFi连接处理

    WiFiMulti.addAP(WIFI_SSID, WIFI_PASS);

    while (WiFiMulti.run() != WL_CONNECTED) {

            Serial.print(".");

            delay(500);

    }


 

    // 开启UDP

    Serial.println("Starting UDP");

    Udp.begin(localPort);

    Serial.print("Local port: ");

    Serial.println(localPort);

    Serial.println("waiting for sync");

    // 设置NTP对时

    setSyncProvider(getNtpTime);

    setSyncInterval(300);

 

    // 延时

    delay(500);

}

 

void loop() {

    // 显示时间

    if (timeStatus() != timeNotSet) { // 检查更新最新时间

        if (now() != prevDisplay) {

            prevDisplay = now();

 

            // 清除显示缓存

            display.clearDisplay();

            // 画框

            display.drawRoundRect(0, 0, display.width() - 1, 16, 1, SSD1306_WHITE); // 状态栏

            display.drawRoundRect(0, 16, display.width() - 1, display.height() - 16, 1, SSD1306_WHITE); // 内容区

 

            u8g2_for_adafruit_gfx.setFontDirection(0); // 显示方向:从左到右(默认)

            u8g2_for_adafruit_gfx.setForegroundColor(WHITE); // 应用Adafruit GFX颜色

 

            u8g2_for_adafruit_gfx.setFont(u8g2_font_unifont_t_chinese2); // 中文字体

            u8g2_for_adafruit_gfx.setCursor(25, 12); // 显示坐标

            u8g2_for_adafruit_gfx.print("Net Clock");

 

            showtime(); // 显示时间

            // 显示

            display.display();

        }

    }

 

    // 延时

    delay(50);

}

 

以上代码在Arduino中进行开发;

在上述代码中:

  • 使用了Time模块,用于进行时间的设置和读取。
  • 使用了WiFi模块,用于联网
  • 使用了Adafruit_SSD1306模块,用于操控OLED(SSD1306)
  • 使用了U8g2_for_Adafruit_GFX模块,用于接偶heU8g2和Adafruit_SSD1306

 

在setup()初始化阶段:

  • 初始化显示屏OLED
  • 初始化WiFi连接
  • 初始化NTP对时

在loop()主循环阶段:

  • 检查当前时间
  • 如果时间与之前记住的时间不同,则显示时间到屏幕,并基础当前时间

 

另外,几个主要的函数功能:

  • showtime()函数,用于时间的显示
  • getNtpTime()函数,用于NTP对时

  • sendNTPpacket()函数,用于发送NTP数据包

实际测试验证中,使用了一块ESP32-E的开发板,一块SSD 1306 128x64 的OLED,具体效果如下:

image.png  

视频效果如下:

359_1667375440

 

回复评论 (2)

Leader Qiao is niubility

点赞  2022-11-2 22:52

详细

点赞  2022-11-2 23:23
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复