Maker.io main logo

Making a Retro Analog NTP Clock with Unihiker K10 - Arduino IDE Tutorial

2025-10-20 | By Mirko Pavleski

License: General Public License Real Time Clocks (RTCs) Arduino ESP32

Some time ago I presented you a way to use standard Arduino libraries on the Unihiker k10 development board. Specifically, the project consisted of using the TFT-eSPI library and an integrated sensor, with the result displayed on an analog meter. This time I will present you a project where I will test the Wi-Fi functionality as well as using the A and B buttons.

fg

And again I use a combination of "unihiker_k10.h" and other standard libraries. These buttons are connected to a pin on the I/O expansion chip and not directly to the microcontroller, which theoretically means that there is no way to use other libraries.

I decided to make an analog clock project that will be another example in my collection of unusual Arduino clocks.

 

This clock uses NTP (Network Time Protocol) to synchronize time over the internet. To demonstrate the functionality of the buttons, I expanded the code to display the time and date in digital format, as well as change the background colors. To make this cute clock project, we don't need any external components or soldering, so it's suitable for beginners. Let me briefly explain how to install support for Unihiker K10 in Arduino IDE. Let me remind you that the Arduino IDE version must be 1.8.19 or older.

We go to File-> Preferences-> Additional Boards Manager, and enter the given link:

https://downloadcd.dfrobot.com.cn/UNIHIKER/package_unihiker_index.json

fghg

Then in Tools-> Boards Manager we write Unihiker, and we install the support for Unihiker from DFRobot.

nmbn

Now we go to Tools -> Unihiker -> Unihiker K10.

 bnm

With this, the support for this development board in Arduino IDE is installed.

As for the code, I can say that it is not ideally optimized, but you can easily modify it according to your own needs and desires.

 vbn

At the beginning you need to enter your Wi-Fi credentials, the time zone you live in, and Daylight Saving Time. Then the code is divided into several separate, easy-to-understand functions in which you can change various parameters and, if you have the time and desire, create your own clock faces.

Now let's see how the device works in real conditions. Immediately after switching on, information about Wi-Fi availability appears, and if the connection is successful, an ellipsoidal analog clock with a yellow background appears on the display as defined in the code.

hgj

The clock contains a colored arrow for showing hours, an empty arrow for minutes, and a thin red arrow for seconds. The date is written in the lower part. Now by pressing the A button, the next screen appears, which displays the time and date in digital format. With the B button, we can change several previously defined colors for the clock background.

gggjgh

Let me just tell you that the main goal of this project was to present you with a way to use standard libraries on this largely AI-oriented development board, so I slightly neglected the functionality of the device by omitting the alarm function.

 nb

And finally, a short conclusion: This project successfully demonstrates that the Unihiker K10 board, despite being AI-oriented, can effectively utilize standard Arduino libraries for creating traditional electronics projects. By combining the board's native capabilities with Arduino's extensive library ecosystem, makers can expand their project possibilities while maintaining the simplicity and familiarity of the Arduino environment.

Copy Code
#include <WiFi.h>
#include "time.h"  // Built-in for NTP handling
#include <TFT_eSPI.h> // TFT_eSPI library
#include <math.h>  // For sin/cos calculations
#include "unihiker_k10.h"


UNIHIKER_K10 k10;

uint8_t screen_dir = 2;
Music music;  // For button functionality
bool showAnalog = true;  // To track which display is showing
bool first_digital = true;  // For digital display initialization

// Initialize display
TFT_eSPI tft = TFT_eSPI();

// Variables for digital display
int prev_hour = -1;
int prev_min = -1;
int prev_sec = -1;

uint16_t bgColors[] = {TFT_YELLOW, TFT_ORANGE, TFT_MAGENTA, TFT_OLIVE, TFT_WHITE};
int currentColorIndex = 0;
uint16_t currentBgColor = bgColors[0];


#define TFT_GREY 0x5AEB
#define TFT_LIGHTPINK 0xFDB8
#define TFT_GOLD 0xFEA0
#define TFT_LIGHTGREEN 0x9772
#define TFT_LIGHTSALMON 0xFD0F
#define TFT_ORANGE 0xFD20
#define TFT_MAGENTA 0xF81F
#define TFT_OLIVE 0x7BE0

// WiFi and NTP settings
const char* ssid = "*******";
const char* password = "*******";
const char* ntpServer = "pool.ntp.org";
long gmtOffset_sec = 3600;     // Timezone offset (UTC+1)
int daylightOffset_sec = 3600; // Daylight Saving Time

// Clock parameters
const int center_x = 160;      // Center of display
const int center_y = 120;      // Center of display
const int x_radius = 110;      // Horizontal radius
const int y_radius = 90;       // Vertical radius
const int ellipse_offset_x = 3;  // Ellipse offset right
const int ellipse_offset_y = -2; // Ellipse offset up
const int second_length = 85;
const int minute_length = 70;
const int hour_length = 50;
const int center_size = 5;
const int tick_size = 3;

// Previous hand positions
int prev_sx = center_x, prev_sy = center_y;
int prev_m1x, prev_m1y, prev_m2x, prev_m2y, prev_m3x, prev_m3y;
int prev_h1x, prev_h1y, prev_h2x, prev_h2y, prev_h3x, prev_h3y;

// Flag for first loop
bool first_loop = true;

// Function prototypes
void onButtonAPressed();
void onButtonBPressed();
void drawDigitalClock(struct tm timeinfo);

// Function to calculate elliptical points with offset
void getEllipsePoint(float angle, float* x, float* y) {
    *x = center_x + ellipse_offset_x + x_radius * sin(angle);
    *y = center_y + ellipse_offset_y - y_radius * cos(angle);
}

void drawDigitalClock(struct tm timeinfo) {
  if (first_digital) {
    tft.fillScreen(currentBgColor);
    
    // Draw outer frame (5 pixels thick)
    tft.fillRect(0, 0, 320, 5, TFT_DARKGREY);
    tft.fillRect(0, 235, 320, 5, TFT_DARKGREY);
    tft.fillRect(0, 0, 5, 240, TFT_DARKGREY);
    tft.fillRect(315, 0, 5, 240, TFT_DARKGREY);

    // Draw inner frame (2 pixels thick)
    tft.drawRoundRect(8, 8, 304, 2, 20, TFT_BLACK);
    tft.drawRoundRect(8, 230, 304, 2, 20, TFT_BLACK);
    tft.drawRoundRect(8, 8, 2, 224, 20, TFT_BLACK);
    tft.drawRoundRect(310, 8, 2, 224, 20, TFT_BLACK);

    first_digital = false;
  }

  // Set text properties for time
  tft.setTextSize(4);  // Size 4 for time
  tft.setTextColor(TFT_BLACK, currentBgColor);  // Red text on yellow background
  
  int current_hour = timeinfo.tm_hour;
  int current_min = timeinfo.tm_min;
  int current_sec = timeinfo.tm_sec;

  // Position for the time display - centered
  int x = 40;  // Adjusted for better centering
  int y = 80;  // Adjusted vertical position
  
  // Update hours if changed
  if (current_hour != prev_hour) {
    char hourStr[3];
    sprintf(hourStr, "%02d", current_hour);
    tft.fillRect(x, y, 70, 45, currentBgColor);  // Adjusted clear area
    tft.setCursor(x, y);
    tft.print(hourStr);
    prev_hour = current_hour;
  }
  
  // Always show colon
  tft.setCursor(x + 60, y);
  tft.print(":");

  // Update minutes if changed
  if (current_min != prev_min) {
    char minStr[3];
    sprintf(minStr, "%02d", current_min);
    tft.fillRect(x + 95, y, 70, 45, currentBgColor);  // Adjusted clear area
    tft.setCursor(x + 95, y);
    tft.print(minStr);
    prev_min = current_min;
  }

  // Always show colon
  tft.setCursor(x + 155, y);
  tft.print(":");

  // Update seconds if changed
  if (current_sec != prev_sec) {
    char secStr[3];
    sprintf(secStr, "%02d", current_sec);
    tft.fillRect(x + 190, y, 70, 45, currentBgColor);  // Adjusted clear area
    tft.setCursor(x + 190, y);
    tft.print(secStr);
    prev_sec = current_sec;
  }

  // Update date
  tft.setTextSize(3);  // Size 3 for date
  char dateStr[11];
  strftime(dateStr, sizeof(dateStr), "%Y-%m-%d", &timeinfo);
  
  // Center the date
  tft.fillRect(60, 150, 200, 30, currentBgColor);  // Clear previous date
  tft.setCursor(65, 150);  // Adjusted position for better centering
  tft.print(dateStr);
}
void onButtonAPressed() {
  showAnalog = !showAnalog;
  if (showAnalog) {
    // Instead of calling setup(), just redraw the analog clock
    tft.fillScreen(currentBgColor);  // Use current background color
    
    // Draw frames
    tft.fillRect(0, 0, 320, 5, TFT_DARKGREY);
    tft.fillRect(0, 235, 320, 5, TFT_DARKGREY);
    tft.fillRect(0, 0, 5, 240, TFT_DARKGREY);
    tft.fillRect(315, 0, 5, 240, TFT_DARKGREY);

    tft.drawRoundRect(8, 8, 304, 2, 20, TFT_BLACK);
    tft.drawRoundRect(8, 230, 304, 2, 20, TFT_BLACK);
    tft.drawRoundRect(8, 8, 2, 224, 20, TFT_BLACK);
    tft.drawRoundRect(310, 8, 2, 224, 20, TFT_BLACK);

    // Draw clock face
    tft.setTextSize(2);
    for (int i = 0; i < 60; i++) {
      float ang_rad = i * 6.0 * PI / 180.0;
      float tx, ty;
      getEllipsePoint(ang_rad, &tx, &ty);
      
      if (i % 5 != 0) {
        tft.fillCircle(tx, ty, 1, TFT_DARKGREY);
      }
    }

    for (int i = 1; i <= 12; i++) {
      float ang_rad = i * 30.0 * PI / 180.0;
      float tx, ty;
      getEllipsePoint(ang_rad, &tx, &ty);
      
      if (i % 3 == 0) {
        char num[3];
        sprintf(num, "%d", i);
        int text_offset_x, text_offset_y;
        
        if (i == 12) {
          text_offset_x = -12;
          text_offset_y = -6;
        } else if (i == 3) {
          text_offset_x = -4;
          text_offset_y = -6;
        } else if (i == 6) {
          text_offset_x = -6;
          text_offset_y = -6;
        } else if (i == 9) {
          text_offset_x = -6;
          text_offset_y = -6;
        }
        
        tft.setCursor(tx + text_offset_x, ty + text_offset_y);
        tft.print(num);
      } else {
        tft.fillCircle(tx, ty, tick_size, TFT_BLACK);
      }
    }
    tft.fillCircle(center_x, center_y, center_size, TFT_BLACK);
    first_loop = true;
  } else {
    // Reset digital clock variables
    prev_hour = -1;
    prev_min = -1;
    prev_sec = -1;
    first_digital = true;
  }
}

void onButtonBPressed() {
  // Change background color
  currentColorIndex = (currentColorIndex + 1) % 5;
  currentBgColor = bgColors[currentColorIndex];
  
  // Play three-tone ascending sequence
  music.playTone(262, 200);  // C4 (middle C)
  delay(250);  // Small delay between tones
  music.playTone(330, 200);  // E4
  delay(250);
  music.playTone(392, 200);  // G4
  
  // Redraw screen with new background color
  if (showAnalog) {
    onButtonAPressed();  // Redraw analog clock with new color
  } else {
    first_digital = true;  // Force complete redraw of digital display
  }
}

void setup() {
  Serial.begin(115200);

  k10.begin();
  k10.initScreen(screen_dir);
  
  // Add button callbacks
  k10.buttonA->setPressedCallback(onButtonAPressed);
  k10.buttonB->setPressedCallback(onButtonBPressed);

  // Initialize display
  tft.init();
  tft.setRotation(3);  // Landscape mode
  tft.fillScreen(currentBgColor);
  tft.setTextColor(TFT_BLACK, currentBgColor);

  // Connect to WiFi
  tft.setCursor(30, 100);
  tft.setTextSize(3);
  tft.print("Connecting...");
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    tft.fillScreen(currentBgColor);
    Serial.print(".");
  }
  Serial.println("WiFi connected");
  tft.setCursor(55, 100);
  tft.setTextSize(3);
  tft.print("Connected!");
  delay(2000);
  tft.fillScreen(currentBgColor);

  // Draw outer frame (5 pixels thick)
  tft.fillRect(0, 0, 320, 5, TFT_DARKGREY);
  tft.fillRect(0, 235, 320, 5, TFT_DARKGREY);
  tft.fillRect(0, 0, 5, 240, TFT_DARKGREY);
  tft.fillRect(315, 0, 5, 240, TFT_DARKGREY);

  // Draw inner frame (2 pixels thick)
  tft.drawRoundRect(8, 8, 304, 2, 20, TFT_BLACK);
  tft.drawRoundRect(8, 230, 304, 2, 20, TFT_BLACK);
  tft.drawRoundRect(8, 8, 2, 224, 20, TFT_BLACK);
  tft.drawRoundRect(310, 8, 2, 224, 20, TFT_BLACK);

  // Initialize NTP
  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);

  // Draw static clock face
  tft.setTextSize(2);
  
  // First draw all second markers (60 small dots)
  for (int i = 0; i < 60; i++) {
    float ang_rad = i * 6.0 * PI / 180.0;
    float tx, ty;
    getEllipsePoint(ang_rad, &tx, &ty);
    
    if (i % 5 != 0) {
      tft.fillCircle(tx, ty, 1, TFT_DARKGREY);
    }
  }

  // Then draw hour markers over the second markers
  for (int i = 1; i <= 12; i++) {
    float ang_rad = i * 30.0 * PI / 180.0;
    float tx, ty;
    getEllipsePoint(ang_rad, &tx, &ty);
    
    if (i % 3 == 0) {
      char num[3];
      sprintf(num, "%d", i);
      int text_offset_x, text_offset_y;
      
      if (i == 12) {
        text_offset_x = -12;
        text_offset_y = -6;
      } else if (i == 3) {
        text_offset_x = -4;
        text_offset_y = -6;
      } else if (i == 6) {
        text_offset_x = -6;
        text_offset_y = -6;
      } else if (i == 9) {
        text_offset_x = -6;
        text_offset_y = -6;
      }
      
      tft.setCursor(tx + text_offset_x, ty + text_offset_y);
      tft.print(num);
    } else {
      tft.fillCircle(tx, ty, tick_size, TFT_BLACK);
    }
  }
  tft.fillCircle(center_x, center_y, center_size, TFT_BLACK);
}

void loop() {
  struct tm timeinfo;
  if (getLocalTime(&timeinfo)) {
    if (showAnalog) {
      int sec = timeinfo.tm_sec;
      int min = timeinfo.tm_min;
      int hour = timeinfo.tm_hour;

      // Clear previous hands if not first loop
      if (!first_loop) {
        tft.drawTriangle(prev_h1x, prev_h1y, prev_h2x, prev_h2y, prev_h3x, prev_h3y, currentBgColor);
        tft.drawTriangle(prev_m1x, prev_m1y, prev_m2x, prev_m2y, prev_m3x, prev_m3y, currentBgColor);
        tft.drawLine(center_x, center_y, prev_sx, prev_sy, currentBgColor);
      }

      // Store current text settings
      uint8_t current_size = tft.textsize;
      uint8_t current_font = tft.textfont;

      // Display date with its own settings
      tft.setTextSize(1);
      tft.setTextFont(2);
      char dateStr[11];
      strftime(dateStr, sizeof(dateStr), "%Y-%m-%d", &timeinfo);
      tft.setCursor(130, 150);
      tft.print(dateStr);

      // Restore original text settings
      tft.setTextSize(current_size);
      tft.setTextFont(current_font);

      // Calculate new hour hand position
      float h_deg = (hour % 12) * 30.0 + min * 0.5 + sec * (0.5 / 60.0);
      float h_rad = h_deg * PI / 180.0;
      int hx = center_x + hour_length * sin(h_rad);
      int hy = center_y - hour_length * cos(h_rad);
      float hdx = sin(h_rad);
      float hdy = -cos(h_rad);
      float hpx = -hdy;
      float hpy = hdx;
      float base_half = 5.0;
      int p1x = center_x + base_half * hpx;
      int p1y = center_y + base_half * hpy;
      int p2x = center_x - base_half * hpx;
      int p2y = center_y - base_half * hpy;
      int p3x = hx;
      int p3y = hy;

      // Draw hour hand with cyan fill and black outline
      tft.fillTriangle(p1x, p1y, p2x, p2y, p3x, p3y, TFT_CYAN);
      tft.drawTriangle(p1x, p1y, p2x, p2y, p3x, p3y, TFT_BLACK);

      // Calculate new minute hand position
      float m_deg = min * 6.0 + sec * 0.1;
      float m_rad = m_deg * PI / 180.0;
      int mx = center_x + minute_length * sin(m_rad);
      int my = center_y - minute_length * cos(m_rad);
      float dx = sin(m_rad);
      float dy = -cos(m_rad);
      float px = -dy;
      float py = dx;
      float m_base_half = 5.0;
      int m1x = center_x + m_base_half * px;
      int m1y = center_y + m_base_half * py;
      int m2x = center_x - m_base_half * px;
      int m2y = center_y - m_base_half * py;
      int m3x = mx;
      int m3y = my;

      // Draw minute hand (outlined)
      tft.drawTriangle(m1x, m1y, m2x, m2y, m3x, m3y, TFT_BLACK);

      // Calculate new second hand position
      float s_deg = sec * 6.0;
      float s_rad = s_deg * PI / 180.0;
      int sx = center_x + second_length * sin(s_rad);
      int sy = center_y - second_length * cos(s_rad);

      // Draw second hand
      tft.drawLine(center_x, center_y, sx, sy, TFT_RED);

      // Draw center dot
      tft.fillCircle(center_x, center_y, center_size, TFT_BLACK);

      // Update previous positions
      prev_h1x = p1x; prev_h1y = p1y;
      prev_h2x = p2x; prev_h2y = p2y;
      prev_h3x = p3x; prev_h3y = p3y;
      prev_m1x = m1x; prev_m1y = m1y;
      prev_m2x = m2x; prev_m2y = m2y;
      prev_m3x = m3x; prev_m3y = m3y;
      prev_sx = sx; prev_sy = sy;

      first_loop = false;
    } else {
      drawDigitalClock(timeinfo);
    }
  } else {
    Serial.println("Failed to obtain time");
  }
  delay(1000);
}
Herst.-Teilenr. DFR0992-EN
UNIHIKER K10 AI EDUCATIONAL TOOL
DFRobot
Add all DigiKey Parts to Cart
Have questions or comments? Continue the conversation on TechForum, DigiKey's online community and technical resource.