DFRobot出品的Beetle ESP32-C3,个头非常小巧,但是功能不弱,非常方便进行一些有趣的小玩意的开发。
这个项目,就是将Beetle ESP32-C3与OLED SSD1306结合,制作一个基于互联网的时钟。
通常,我们要制作一个能够稳定运行的时钟,往往需要RTC来确保时钟信息准确。
但这个基于Beetle ESP32-C3的互联网时钟,通过网络时间服务器进行对时,就算运行一万年(如果能够运行),显示的时间也不会有误差。
步骤1 硬件材料
要实现这个基于Beetle ESP32-C3的互联网时钟,所需要的材料非常少,具体如下:
步骤2 硬件连线
硬件连线机器简单,实际上,只用将SPI接口的OLED直接插接到Beetle ESP32-C3母座上即可:
步骤3 原理说明
基于Beetle ESP32-C3的互联网时钟,其时间信息,来源于通过网络获取的NTP时间。
网络时间协议NTP(Network Time Protocol)是TCP/IP协议族里面的一个应用层协议,用来使客户端和服务器之间进行时钟同步,提供高精准度的时间校正。NTP服务器从权威时钟源(例如原子钟、GPS)接收精确的协调世界时UTC,客户端再从服务器请求和接收时间。
NTP基于UDP报文进行传输,NTP就是用来使网络中的各个主机时钟同步的一种协议,他把主机的时钟同步到协调世界时UTC,其精度在LAN网络内可达1毫秒内,在WAN网络上可以达到几十毫秒内。
因此,只要在一定的时间内,进行NTP时间同步,那么设备上的时间,就基本上是没有误差的。
所以这个互联网时钟,就应用了NTP时间同步,因此就算运行一万年(如果能够运行),显示的时间也不会有误差。
步骤4 代码编写
/* 基于NTP服务器的互联网时钟
*/
#include <Arduino.h>
#include <stdlib.h>
#include <SPI.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 // 高度
// 接口类型:1-I2C、2-SPI
#define OLED_TYPE 2
// Adafruit_SSD1306显示对象
#if OLED_TYPE == 1
// OLED I2C定义
#define OLED_RESET -1 // RESET
#define SCREEN_ADDRESS 0x3C // I2C地址
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
#endif
#if OLED_TYPE == 2
// SPI
#define OLED_CLK 0
#define OLED_MOSI 1
#define OLED_RESET 4
#define OLED_DC 6
#define OLED_CS 5
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT,(int)OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS);
#endif
// U8G2_FOR_ADAFRUIT_GFX显示对象
U8G2_FOR_ADAFRUIT_GFX u8g2_for_adafruit_gfx;
// WiFi定义
#define WIFI_SSID "OpenBSD"
#define WIFI_PASS "********"
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);
// Serial.println(buff);
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 OLED_TYPE == 1
if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
Serial.println(F("SSD1306 allocation failed"));
for (;;)
; // 循环
} else {
Serial.println(F("SSD1306 allocation ok"));
}
#endif
#if OLED_TYPE == 2
if (!display.begin(SSD1306_SWITCHCAPVCC)) {
Serial.println(F("SSD1306 allocation failed"));
for (;;)
; // 循环
} else {
Serial.println(F("SSD1306 allocation ok"));
}
#endif
// 显示默认 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(12, 12); // 显示坐标
u8g2_for_adafruit_gfx.print("Net Clock@NTP");
showtime(); // 显示时间
// 显示
display.display();
}
}
// 延时
delay(50);
}
要运行上面的代码,仅需要修改如下的部分,设置好WiFi连接,就能够正常运行:
// WiFi定义
#define WIFI_SSID "OpenBSD"
#define WIFI_PASS "********"
在上述代码中,使用了如下的模块:
* SPI:硬件库* TimeLib:时间库
* WiFi:无线连接库
* WiFiMulti多WiFi配置连接库
* WiFiUdp:WiFi UDP通讯库
* Adafruit_SSD1306:SSD1306显示屏操作库
* U8g2_for_Adafruit_GFX:基于U8g2的字符显示库
另外,可以通过 OLED_TYPE 来配置使用I2C接口还是SPI接口。
代码中的网络时间处理部分,主要为getNtpTime()和sendNTPpacket(),用于向时间服务器发送时间同步信息,收到后在内存中记录,作为当前时间使用。
然后,在loop()中,根据校对的时间自动计时,并显示到屏幕上。
步骤5 实际效果
rzyzzxw2023.01.16
高级
花生编程2023.01.11
不错666
花生编程2023.01.11
厉害厉害
三春牛-创客2023.01.02
OLED是不是容易烧屏啊?感觉用起来成本高,不如做个LCD的
HonestQiao2023.01.06
https://www.dfrobot.com.cn/goods-3628.html 用这个
三春牛-创客2023.01.02
厉害厉害