Introduction
A few days ago, I heard one of my friends say that he was going to move to a new house. So, I thought about giving him some gifts. I just got a new version of the Firebeetle ESP32 controller from DFRobot. The size of the new version is smaller than the old one, and it uses a type-c port.
Then, I decided to make a network clock. This production will be very simple, getting the time from the internet via the wifi of the ESP32, and then you need to find a screen that displays the time. There are many options for the screen, LCD1602 or LED monochrome dot matrix for splicing. I accidentally saw a VFD fluorescent screen before, it was green-blue when it was lit, and it glowed with white light, which makes the whole screen a mottled beauty. So, I am going to use it here.




Build the shell model.

Use 3D printing to print out the shell. The cover on the back of the shell uses a 2mm acrylic sheet.

Fix the VFD and ESP inside the shell with hot melt glue.

Apply a little glue and close the lid.

Burn Program
Download two library files here first.
Time Library: https://github.com/PaulStoffregen/Time
Timezone Library: https://github.com/JChristensen/Timezone
Then change the wifi and password in setup () in the code at the end of the article to your own, and click burn.
#include <Arduino.h> #include <HardwareSerial.h> #include<stdlib.h> #include <TimeLib.h> #include <WiFi.h> #include <WiFiMulti.h> #include <WiFiUdp.h> #ifdef ARDUINO_AVR_UNO SoftwareSerial Serial1( 2, 5 ); //RX, TX #endif static const char ntpServerName[] = "ntp.aliyun.com"; const int timeZone = 8; WiFiUDP Udp; unsigned int localPort = 8888; time_t getNtpTime(); WiFiMulti WiFiMulti; uint8_t din = 25; // DA uint8_t clk = 26; // CK uint8_t cs = 14; // CS uint8_t Reset = 13; // RS uint8_t en = 21; // EN void write_6302(unsigned char w_data) { unsigned char i; for (i = 0; i < 8; i++) { digitalWrite(clk, LOW); delayMicroseconds(5); if ( (w_data & 0x01) == 0x01) { digitalWrite(din, HIGH); delay(1); } else { digitalWrite(din, LOW); delay(1); } w_data >>= 1; digitalWrite(clk, HIGH); delayMicroseconds(5); } } void VFD_cmd(unsigned char command) { digitalWrite(cs, LOW); delayMicroseconds(5); write_6302(command); digitalWrite(cs, HIGH); delayMicroseconds(5); } void S1201_show(void) { digitalWrite(cs, LOW);//开始传输 delayMicroseconds(5); write_6302(0xe8); //地址寄存器起始位置 digitalWrite(cs, HIGH); //停止传输 delayMicroseconds(5); } void VFD_init() { //SET HOW MANY digtal numbers digitalWrite(cs, LOW); delayMicroseconds(5); write_6302(0xe0); delayMicroseconds(5); write_6302(0x07);//8 digtal digitalWrite(cs, HIGH); delayMicroseconds(5); //set bright digitalWrite(cs, LOW); delayMicroseconds(5); write_6302(0xe4); delayMicroseconds(5); write_6302(0xff);//leve 255 max digitalWrite(cs, HIGH); delayMicroseconds(5); } /****************************** 在指定位置打印一个字符(用户自定义,所有CG-ROM中的) x:0~11;chr:要显示的字符编码 *******************************/ void S1201_WriteOneChar(unsigned char x, unsigned char chr) { digitalWrite(cs, LOW); //开始传输 delayMicroseconds(5); write_6302(0x20 + x); //地址寄存器起始位置 delayMicroseconds(5); write_6302(chr + 0x30); digitalWrite(cs, HIGH); //停止传输 delayMicroseconds(5); S1201_show(); } /****************************** 在指定位置打印字符串 (仅适用于英文,标点,数字) x:0~11;str:要显示的字符串 *******************************/ void S1201_WriteStr(unsigned char x, char *str) { digitalWrite(cs, LOW); //开始传输 delayMicroseconds(5); write_6302(0x20 + x); //地址寄存器起始位置 delayMicroseconds(5); while (*str) { write_6302(*str); //ascii与对应字符表转换 str++; } digitalWrite(cs, HIGH); //停止传输 delayMicroseconds(5); S1201_show(); } void setup() { Serial.begin( 115200 ); delay(10); pinMode(en, OUTPUT); pinMode(clk, OUTPUT); pinMode(din, OUTPUT); pinMode(cs, OUTPUT); pinMode(Reset, OUTPUT); digitalWrite(en, HIGH); digitalWrite(Reset, LOW); delayMicroseconds(5); digitalWrite(Reset, HIGH); VFD_init(); WiFiMulti.addAP("wifi", "password"); while(WiFiMulti.run() != WL_CONNECTED) { Serial.print("."); delay(500); } S1201_WriteStr(0, "wifi..."); delay(2000); Serial.print( "IP number assigned by DHCP is " ); Serial.println( WiFi.localIP() ); Serial.println( "Starting UDP" ); Udp.begin( localPort ); Serial.print( "Local port: " ); //Serial.println( Udp.localPort() ); Serial.println( "waiting for sync" ); setSyncProvider( getNtpTime ); setSyncInterval( 300 ); } time_t prevDisplay = 0; void loop() { if( timeStatus() != timeNotSet ) { if( now() != prevDisplay ) { prevDisplay = now(); showtime(); } } } void showtime() { int Hour = hour(); int Min = minute(); int Sec = second(); int HourHigh,HourLow,MinHigh,MinLow,SecHigh,SecLow; Serial.print(Hour); Serial.print(":"); Serial.print(Min); Serial.print(":"); Serial.println(Sec); HourHigh=Hour/10; if(HourHigh){ S1201_WriteOneChar(0, HourHigh); } else{ S1201_WriteOneChar(0, ' '-0x30); } HourLow=Hour%10; S1201_WriteOneChar(1, HourLow); S1201_WriteOneChar(2, ':'-0x30); MinHigh=Min/10; S1201_WriteOneChar(3, MinHigh); MinLow=Min%10; S1201_WriteOneChar(4, MinLow); S1201_WriteOneChar(5, ':'-0x30); SecHigh=Sec/10; S1201_WriteOneChar(6, SecHigh); SecLow=Sec%10; S1201_WriteOneChar(7, SecLow); } const int NTP_PACKET_SIZE = 48; byte packetBuffer[NTP_PACKET_SIZE]; time_t getNtpTime() { IPAddress ntpServerIP; // NTP server's ip address while( Udp.parsePacket() > 0 ) ; // discard any previously received packets Serial.println( "Transmit NTP Request" ); // get a random server from the pool WiFi.hostByName( ntpServerName, ntpServerIP ); 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 ); // read packet into the buffer unsigned long secsSince1900; // convert four bytes starting at location 40 to a long integer 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; // return 0 if unable to get the time } // send an NTP request to the time server at the given address void sendNTPpacket( IPAddress &address ) { // set all bytes in the buffer to 0 memset( packetBuffer, 0, NTP_PACKET_SIZE ); // Initialize values needed to form NTP request // (see URL above for details on the packets) 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; // all NTP fields have been given values, now // you can send a packet requesting a timestamp: Udp.beginPacket( address, 123 ); //NTP requests are to port 123 Udp.write( packetBuffer, NTP_PACKET_SIZE ); Udp.endPacket(); }

Done. Personally, I love the display effect of this VFD very much. If you use a brown acrylic for the faceplate, it will have a display effect close to white. But I like the beauty of being able to see the internal structure, which looks complicated but organized.

