How to make Digital & Analog Watch using SquareLine Studio and LVGL Arduino
Table of Contents
Description:
How to make Digital & Analog Watch using SquareLine Studio and LVGL Arduino- This is my second article on the MaTouch ESP32-S3 AMOLED with a 1.8” FT3168 touch display. And today, you guys are going to learn a lot of new things.
For example, how to display time and date on the screen in a simple way.
You will also learn how to work with multiple screens and switch between them.
I will show you how to create custom fonts, and then use those fonts to make a digital watch.
And not just a digital watch; we will also build an analog watch, which will teach you even more new concepts, like how to use image widgets and animate them. The hour, minute, and second hands you see; along with the watch background; are all image files.
Amazon Links:
Other Tools and Components:
ESP32 WiFi + Bluetooth Module (Recommended)
Arduino Nano USB C type (Recommended)
*Please Note: These are affiliate links. I may make a commission if you buy the components through these links. I would appreciate your support in this way!
Now, you might be wondering, this development board doesn’t even have an RTC, so how are we accessing and displaying the real-time clock and date?
Well, for that, I am using pool.ntp.org, and I will explain it in more detail later in the video.
So guys, here is my template code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
#include <lvgl.h> #include "Arduino_GFX_Library.h" #include "Arduino_DriveBus_Library.h" #include "pin_config.h" #include "ui.h" static const uint16_t screenWidth = 368; static const uint16_t screenHeight = 448; static lv_disp_draw_buf_t draw_buf; static lv_color_t buf[ screenWidth * screenHeight / 10 ]; Arduino_DataBus *bus = new Arduino_ESP32QSPI( LCD_CS /* CS */, LCD_SCLK /* SCK */, LCD_SDIO0 /* SDIO0 */, LCD_SDIO1 /* SDIO1 */, LCD_SDIO2 /* SDIO2 */, LCD_SDIO3 /* SDIO3 */); Arduino_GFX *gfx = new Arduino_SH8601(bus, LCD_RST /* RST */, 0 /* rotation */, false /* IPS */, LCD_WIDTH, LCD_HEIGHT); std::shared_ptr<Arduino_IIC_DriveBus> IIC_Bus = std::make_shared<Arduino_HWIIC>(IIC_SDA, IIC_SCL, &Wire); void Arduino_IIC_Touch_Interrupt(void); std::unique_ptr<Arduino_IIC> FT3168(new Arduino_FT3x68(IIC_Bus, FT3168_DEVICE_ADDRESS, DRIVEBUS_DEFAULT_VALUE, TP_INT, Arduino_IIC_Touch_Interrupt)); void Arduino_IIC_Touch_Interrupt(void) { FT3168->IIC_Interrupt_Flag = true; } #if LV_USE_LOG != 0 /* Serial debugging */ void my_print(const char * buf) { Serial.printf(buf); Serial.flush(); } #endif /* Display flushing */ void my_disp_flush( lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p ) { uint32_t w = ( area->x2 - area->x1 + 1 ); uint32_t h = ( area->y2 - area->y1 + 1 ); #if (LV_COLOR_16_SWAP != 0) gfx->draw16bitBeRGBBitmap(area->x1, area->y1, (uint16_t *)&color_p->full, w, h); #else gfx->draw16bitRGBBitmap(area->x1, area->y1, (uint16_t *)&color_p->full, w, h); #endif lv_disp_flush_ready( disp ); } /*Read the touchpad*/ void my_touchpad_read( lv_indev_drv_t * indev_driver, lv_indev_data_t * data ) { int32_t touchX = FT3168->IIC_Read_Device_Value(FT3168->Arduino_IIC_Touch::Value_Information::TOUCH_COORDINATE_X); int32_t touchY = FT3168->IIC_Read_Device_Value(FT3168->Arduino_IIC_Touch::Value_Information::TOUCH_COORDINATE_Y); if( FT3168->IIC_Interrupt_Flag == true) { FT3168->IIC_Interrupt_Flag = false; data->state = LV_INDEV_STATE_PR; /*Set the coordinates*/ data->point.x = touchX; data->point.y = touchY; USBSerial.print( "Data x " ); USBSerial.print( touchX ); USBSerial.print( "Data y " ); USBSerial.println( touchY ); char s[40]; sprintf(s, "%d", touchX); } else { data->state = LV_INDEV_STATE_REL; } } void setup() { USBSerial.begin( 115200 ); /* prepare for possible serial debug */ pinMode(LCD_EN, OUTPUT); digitalWrite(LCD_EN, HIGH); while (FT3168->begin() == false) { USBSerial.println("FT3168 initialization fail"); delay(2000); } USBSerial.println("FT3168 initialization successfully"); gfx->begin(); gfx->Display_Brightness(200); String LVGL_Arduino = "Hello Arduino! "; LVGL_Arduino += String('V') + lv_version_major() + "." + lv_version_minor() + "." + lv_version_patch(); USBSerial.println( LVGL_Arduino ); USBSerial.println( "I am LVGL_Arduino" ); lv_init(); #if LV_USE_LOG != 0 lv_log_register_print_cb( my_print ); /* register print function for debugging */ #endif FT3168->IIC_Write_Device_State(FT3168->Arduino_IIC_Touch::Device::TOUCH_POWER_MODE, FT3168->Arduino_IIC_Touch::Device_Mode::TOUCH_POWER_MONITOR); lv_disp_draw_buf_init( &draw_buf, buf, NULL, screenWidth * screenHeight / 10 ); /*Initialize the display*/ static lv_disp_drv_t disp_drv; lv_disp_drv_init( &disp_drv ); /*Change the following line to your display resolution*/ disp_drv.hor_res = screenWidth; disp_drv.ver_res = screenHeight; disp_drv.flush_cb = my_disp_flush; disp_drv.draw_buf = &draw_buf; lv_disp_drv_register( &disp_drv ); /*Initialize the (dummy) input device driver*/ static lv_indev_drv_t indev_drv; lv_indev_drv_init( &indev_drv ); indev_drv.type = LV_INDEV_TYPE_POINTER; indev_drv.read_cb = my_touchpad_read; lv_indev_drv_register( &indev_drv ); ui_init(); USBSerial.println( "Setup done" ); } void loop() { lv_timer_handler(); /* let the GUI do its work */ delay(5); } |
I have already discussed this template code in Part1 in detail.
Now, to access and display the time and date, I will need to modify this code.
Note: I highly recommend to download the complete Template folder from my Patreon page. Because there are so many other files, libraries, example codes, etc. You can also download the Digital and Analog watch project folders.
Accessing Time and Date through Internet:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
#include <lvgl.h> #include "Arduino_GFX_Library.h" #include "Arduino_DriveBus_Library.h" #include "pin_config.h" #include "ui.h" #include <HTTPClient.h> #include <WiFi.h> #include <time.h> const char* ssid = "fahad"; const char* password = "fahad123"; const char* ntpServer = "pool.ntp.org"; const long utcOffsetInSeconds = 18000; // UTC+5 const int daylightOffset_sec = 0; static const uint16_t screenWidth = 368; static const uint16_t screenHeight = 448; static lv_disp_draw_buf_t draw_buf; static lv_color_t buf[ screenWidth * screenHeight / 10 ]; Arduino_DataBus *bus = new Arduino_ESP32QSPI( LCD_CS, LCD_SCLK, LCD_SDIO0, LCD_SDIO1, LCD_SDIO2, LCD_SDIO3); Arduino_GFX *gfx = new Arduino_SH8601(bus, LCD_RST, 0, false, LCD_WIDTH, LCD_HEIGHT); std::shared_ptr<Arduino_IIC_DriveBus> IIC_Bus = std::make_shared<Arduino_HWIIC>(IIC_SDA, IIC_SCL, &Wire); void Arduino_IIC_Touch_Interrupt(void); std::unique_ptr<Arduino_IIC> FT3168(new Arduino_FT3x68(IIC_Bus, FT3168_DEVICE_ADDRESS, DRIVEBUS_DEFAULT_VALUE, TP_INT, Arduino_IIC_Touch_Interrupt)); void Arduino_IIC_Touch_Interrupt(void) { FT3168->IIC_Interrupt_Flag = true; } #if LV_USE_LOG != 0 void my_print(const char * buf) { Serial.printf(buf); Serial.flush(); } #endif void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p) { uint32_t w = ( area->x2 - area->x1 + 1 ); uint32_t h = ( area->y2 - area->y1 + 1 ); #if (LV_COLOR_16_SWAP != 0) gfx->draw16bitBeRGBBitmap(area->x1, area->y1, (uint16_t *)&color_p->full, w, h); #else gfx->draw16bitRGBBitmap(area->x1, area->y1, (uint16_t *)&color_p->full, w, h); #endif lv_disp_flush_ready(disp); } void my_touchpad_read(lv_indev_drv_t * indev_driver, lv_indev_data_t * data) { int32_t touchX = FT3168->IIC_Read_Device_Value(FT3168->Arduino_IIC_Touch::Value_Information::TOUCH_COORDINATE_X); int32_t touchY = FT3168->IIC_Read_Device_Value(FT3168->Arduino_IIC_Touch::Value_Information::TOUCH_COORDINATE_Y); if(FT3168->IIC_Interrupt_Flag == true) { FT3168->IIC_Interrupt_Flag = false; data->state = LV_INDEV_STATE_PR; data->point.x = touchX; data->point.y = touchY; Serial.printf("Data x %d, Data y %d\n", touchX, touchY); } else { data->state = LV_INDEV_STATE_REL; } } // Global time variables int currentHour = 0; int currentMinute = 0; int currentSecond = 0; int currentDay = 0; int currentMonth = 0; int currentYear = 0; int currentDayOfYear = 0; char currentDayName[10]; // E.g., "Monday" void setup() { Serial.begin(115200); pinMode(LCD_EN, OUTPUT); digitalWrite(LCD_EN, HIGH); while (FT3168->begin() == false) { Serial.println("FT3168 initialization fail"); delay(2000); } Serial.println("FT3168 initialization successfully"); gfx->begin(); gfx->Display_Brightness(200); Serial.printf("Hello Arduino! V%d.%d.%d\n", lv_version_major(), lv_version_minor(), lv_version_patch()); lv_init(); #if LV_USE_LOG != 0 lv_log_register_print_cb(my_print); #endif FT3168->IIC_Write_Device_State(FT3168->Arduino_IIC_Touch::Device::TOUCH_POWER_MODE, FT3168->Arduino_IIC_Touch::Device_Mode::TOUCH_POWER_MONITOR); lv_disp_draw_buf_init(&draw_buf, buf, NULL, screenWidth * screenHeight / 10); static lv_disp_drv_t disp_drv; lv_disp_drv_init(&disp_drv); disp_drv.hor_res = screenWidth; disp_drv.ver_res = screenHeight; disp_drv.flush_cb = my_disp_flush; disp_drv.draw_buf = &draw_buf; lv_disp_drv_register(&disp_drv); static lv_indev_drv_t indev_drv; lv_indev_drv_init(&indev_drv); indev_drv.type = LV_INDEV_TYPE_POINTER; indev_drv.read_cb = my_touchpad_read; lv_indev_drv_register(&indev_drv); ui_init(); Serial.println("Setup done"); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(" Connected!"); Serial.print("Configuring time from NTP..."); configTime(utcOffsetInSeconds, daylightOffset_sec, ntpServer); while (!time(nullptr)) { delay(500); Serial.print("."); } Serial.println(" Synced!"); } void loop() { time_t now = time(nullptr); struct tm timeinfo; localtime_r(&now, &timeinfo); // Save components into individual variables currentHour = timeinfo.tm_hour; currentMinute = timeinfo.tm_min; currentSecond = timeinfo.tm_sec; currentDay = timeinfo.tm_mday; currentMonth = timeinfo.tm_mon + 1; currentYear = timeinfo.tm_year + 1900; // NEW: Get day of week (e.g., Monday) strftime(currentDayName, sizeof(currentDayName), "%A", &timeinfo); // NEW: Get day of year (tm_yday ranges from 0 to 365) currentDayOfYear = timeinfo.tm_yday + 1; // Format time and date strings char timeStr[9]; char dateStr[32]; strftime(timeStr, sizeof(timeStr), "%H:%M:%S", &timeinfo); snprintf(dateStr, sizeof(dateStr), "%s, %02d-%02d-%04d", currentDayName, currentDay, currentMonth, currentYear); // Debug print Serial.printf("Time: %02d:%02d:%02d | Date: %s, %02d-%02d-%04d\n", currentHour, currentMinute, currentSecond, currentDayName, currentDay, currentMonth, currentYear); lv_timer_handler(); delay(5); } |
I have added these three header files
1 2 3 |
#include <HTTPClient.h> #include <WiFi.h> #include <time.h> |
And I have also included the Wi-Fi credentials
1 2 |
const char* ssid = "fahad"; const char* password = "fahad123"; |
SSID and password. Make sure to replace them with your own Wi-Fi details.
To make sure my ESP32 gets the accurate time, first, I need to tell it where to find it on the internet.
1 2 3 |
const char* ntpServer = "pool.ntp.org"; const long utcOffsetInSeconds = 18000; // UTC+5 const int daylightOffset_sec = 0; |
For that, I am using “pool.ntp.org”, which is a standard address for Network Time Protocol servers; that’s what this ntpServer line sets up. Now, the time we get from the internet is usually standard UTC time, but I want the display to show the local time for Pakistan (PKT), which is 5 hours ahead. So, I need to tell the system that offset in seconds; 5 hours works out to 18,000 seconds, which is the value I have assigned to utcOffsetInSeconds. Lastly, since Pakistan doesn’t currently observe Daylight Saving Time, I have set the daylightOffset_sec to 0, ensuring the time doesn’t automatically adjust for summer hours.
Some global variables for time and date.
1 2 3 4 5 6 7 |
int currentHour = 0; int currentMinute = 0; int currentSecond = 0; int currentDay = 0; int currentMonth = 0; int currentYear = 0; int currentDayOfYear = 0; |
After synchronizing time from the NTP server
1 2 3 4 5 6 7 8 |
Serial.print("Configuring time from NTP..."); configTime(utcOffsetInSeconds, daylightOffset_sec, ntpServer); while (!time(nullptr)) { delay(500); Serial.print("."); } Serial.println(" Synced!"); |
I break down the time information into individual components for easier use throughout the project. I extract the current hour, minute, and second, as well as the current day, month, and year.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// Save components into individual variables currentHour = timeinfo.tm_hour; currentMinute = timeinfo.tm_min; currentSecond = timeinfo.tm_sec; currentDay = timeinfo.tm_mday; currentMonth = timeinfo.tm_mon + 1; currentYear = timeinfo.tm_year + 1900; // NEW: Get day of week (e.g., Monday) strftime(currentDayName, sizeof(currentDayName), "%A", &timeinfo); // NEW: Get day of year (tm_yday ranges from 0 to 365) currentDayOfYear = timeinfo.tm_yday + 1; |
After that, I have saved the time and date information into timeStr and dateStr. And then I am printing it to the Serial Monitor.
1 2 3 4 5 6 7 8 9 |
// Format time and date strings char timeStr[9]; char dateStr[32]; strftime(timeStr, sizeof(timeStr), "%H:%M:%S", &timeinfo); snprintf(dateStr, sizeof(dateStr), "%s, %02d-%02d-%04d", currentDayName, currentDay, currentMonth, currentYear); // Debug print Serial.printf("Time: %02d:%02d:%02d | Date: %s, %02d-%02d-%04d\n", currentHour, currentMinute, currentSecond, currentDayName, currentDay, currentMonth, currentYear); |
Great! We have successfully accessed the current time and date.
Now we need to display it on the screen. For that, let’s open the SquareLine Studio project.
In Part 1, we ended the article at this point. Now, to display the time and date, we need two labels. I will use this label to display the time. So first, let’s rename this label to lblTime.
Next, add another label and name it lblDate.
Then, update the texts of the labels to Time and Date.
Now, save the project and export the UI files.
After that, go to the UI Files folder, copy all the generated files, and paste them into the same folder where your Arduino .ino file is located. I have already explained this in the previous video and article.
Anyway, now open the main Arduino .ino file.
If you open the ui_Screen1.c file, you will see a lot of styling code has been generated for both the Time and Date labels.
We will use these two labels to display the actual time and date.
Add the following two lines of codes in the Arduino loop().
1 2 |
lv_label_set_text(ui_lblTime, timeStr); lv_label_set_text(ui_lblDate, dateStr); |
Complete Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 |
#include <lvgl.h> #include "Arduino_GFX_Library.h" #include "Arduino_DriveBus_Library.h" #include "pin_config.h" #include "ui.h" #include <HTTPClient.h> #include <WiFi.h> #include <time.h> const char* ssid = "fahad"; const char* password = "fahad123"; const char* ntpServer = "pool.ntp.org"; const long utcOffsetInSeconds = 18000; // UTC+5 const int daylightOffset_sec = 0; static const uint16_t screenWidth = 368; static const uint16_t screenHeight = 448; static lv_disp_draw_buf_t draw_buf; static lv_color_t buf[ screenWidth * screenHeight / 10 ]; Arduino_DataBus *bus = new Arduino_ESP32QSPI( LCD_CS, LCD_SCLK, LCD_SDIO0, LCD_SDIO1, LCD_SDIO2, LCD_SDIO3); Arduino_GFX *gfx = new Arduino_SH8601(bus, LCD_RST, 0, false, LCD_WIDTH, LCD_HEIGHT); std::shared_ptr<Arduino_IIC_DriveBus> IIC_Bus = std::make_shared<Arduino_HWIIC>(IIC_SDA, IIC_SCL, &Wire); void Arduino_IIC_Touch_Interrupt(void); std::unique_ptr<Arduino_IIC> FT3168(new Arduino_FT3x68(IIC_Bus, FT3168_DEVICE_ADDRESS, DRIVEBUS_DEFAULT_VALUE, TP_INT, Arduino_IIC_Touch_Interrupt)); void Arduino_IIC_Touch_Interrupt(void) { FT3168->IIC_Interrupt_Flag = true; } #if LV_USE_LOG != 0 void my_print(const char * buf) { Serial.printf(buf); Serial.flush(); } #endif void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p) { uint32_t w = ( area->x2 - area->x1 + 1 ); uint32_t h = ( area->y2 - area->y1 + 1 ); #if (LV_COLOR_16_SWAP != 0) gfx->draw16bitBeRGBBitmap(area->x1, area->y1, (uint16_t *)&color_p->full, w, h); #else gfx->draw16bitRGBBitmap(area->x1, area->y1, (uint16_t *)&color_p->full, w, h); #endif lv_disp_flush_ready(disp); } void my_touchpad_read(lv_indev_drv_t * indev_driver, lv_indev_data_t * data) { int32_t touchX = FT3168->IIC_Read_Device_Value(FT3168->Arduino_IIC_Touch::Value_Information::TOUCH_COORDINATE_X); int32_t touchY = FT3168->IIC_Read_Device_Value(FT3168->Arduino_IIC_Touch::Value_Information::TOUCH_COORDINATE_Y); if(FT3168->IIC_Interrupt_Flag == true) { FT3168->IIC_Interrupt_Flag = false; data->state = LV_INDEV_STATE_PR; data->point.x = touchX; data->point.y = touchY; Serial.printf("Data x %d, Data y %d\n", touchX, touchY); } else { data->state = LV_INDEV_STATE_REL; } } // Global time variables int currentHour = 0; int currentMinute = 0; int currentSecond = 0; int currentDay = 0; int currentMonth = 0; int currentYear = 0; int currentDayOfYear = 0; char currentDayName[10]; // E.g., "Monday" void setup() { Serial.begin(115200); pinMode(LCD_EN, OUTPUT); digitalWrite(LCD_EN, HIGH); while (FT3168->begin() == false) { Serial.println("FT3168 initialization fail"); delay(2000); } Serial.println("FT3168 initialization successfully"); gfx->begin(); gfx->Display_Brightness(200); Serial.printf("Hello Arduino! V%d.%d.%d\n", lv_version_major(), lv_version_minor(), lv_version_patch()); lv_init(); #if LV_USE_LOG != 0 lv_log_register_print_cb(my_print); #endif FT3168->IIC_Write_Device_State(FT3168->Arduino_IIC_Touch::Device::TOUCH_POWER_MODE, FT3168->Arduino_IIC_Touch::Device_Mode::TOUCH_POWER_MONITOR); lv_disp_draw_buf_init(&draw_buf, buf, NULL, screenWidth * screenHeight / 10); static lv_disp_drv_t disp_drv; lv_disp_drv_init(&disp_drv); disp_drv.hor_res = screenWidth; disp_drv.ver_res = screenHeight; disp_drv.flush_cb = my_disp_flush; disp_drv.draw_buf = &draw_buf; lv_disp_drv_register(&disp_drv); static lv_indev_drv_t indev_drv; lv_indev_drv_init(&indev_drv); indev_drv.type = LV_INDEV_TYPE_POINTER; indev_drv.read_cb = my_touchpad_read; lv_indev_drv_register(&indev_drv); ui_init(); Serial.println("Setup done"); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(" Connected!"); Serial.print("Configuring time from NTP..."); configTime(utcOffsetInSeconds, daylightOffset_sec, ntpServer); while (!time(nullptr)) { delay(500); Serial.print("."); } Serial.println(" Synced!"); } void loop() { time_t now = time(nullptr); struct tm timeinfo; localtime_r(&now, &timeinfo); // Save components into individual variables currentHour = timeinfo.tm_hour; currentMinute = timeinfo.tm_min; currentSecond = timeinfo.tm_sec; currentDay = timeinfo.tm_mday; currentMonth = timeinfo.tm_mon + 1; currentYear = timeinfo.tm_year + 1900; // NEW: Get day of week (e.g., Monday) strftime(currentDayName, sizeof(currentDayName), "%A", &timeinfo); // NEW: Get day of year (tm_yday ranges from 0 to 365) currentDayOfYear = timeinfo.tm_yday + 1; // Format time and date strings char timeStr[9]; char dateStr[32]; strftime(timeStr, sizeof(timeStr), "%H:%M:%S", &timeinfo); snprintf(dateStr, sizeof(dateStr), "%s, %02d-%02d-%04d", currentDayName, currentDay, currentMonth, currentYear); // Debug print Serial.printf("Time: %02d:%02d:%02d | Date: %s, %02d-%02d-%04d\n", currentHour, currentMinute, currentSecond, currentDayName, currentDay, currentMonth, currentYear); lv_label_set_text(ui_lblTime, timeStr); lv_label_set_text(ui_lblDate, dateStr); lv_timer_handler(); delay(5); } |
All the other files are already generated for you by the SquareLine Studio. If you still face any issues, simply go to my Patreon page and download the entire project folder.
Let’s upload the code.
This was the simplest method to display the time and date. Next, we will build a Digital Watch, and after that, an Analog Watch.
To make the Digital Watch, we will use custom fonts. I have already downloaded seven-segment font. You can download and use any font you like. Personally, I prefer the segment-style font, so I will use this.
Digital Watch SquareLine Studio:
So, let’s start by adding a second screen.
On this screen, we will display the hours, minutes, and seconds in a slightly different style.
So, let’s go ahead and import the Seven Segment font by clicking on “ADD FILE INTO ASSETS.”
As you can see, our Seven Segment.ttf font has been successfully added to the assets.
Now, using this font, we will create three different fonts.
To do this, go to the Font tab and let’s name it font_hour.
After that, we need to select the font size; you can choose any size you prefer.
For hours, I want to use a larger font size.
250 should be fine for now. Then click on the Create button.
As you can see, our first font has been successfully created. When the font is created you will see two more font files will appear in the Assets along with the Seven Segment.ttf file.
Later on, if you want, you can change the font size.
In the same way, you need to create two more fonts for the minutes and seconds. In my case, I selected different sizes. For the seconds I selected 45 and for the minutes I selected 150.
Now all three fonts are ready.
Let’s go ahead and add three labels and assign each one the font we created. For the quick setup please watch video tutorial given at the end of this article.
After assigning the fonts and setting the positions. I also added a 4th label for displaying the Date.
For this label, I used a a built-in font “as you can see on the right side” so that you get an idea of how to use different fonts at the same time.
Now, let’s save the project and export the UI files.
Go to the ui_files folder, copy all the generated files, and paste them into the same folder where your main Arduino .ino file is located.
This time, you can see that it has also loaded all three fonts that we created for hours, minutes, and seconds.
Digital Watch Complete Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 |
#include <lvgl.h> #include "Arduino_GFX_Library.h" #include "Arduino_DriveBus_Library.h" #include "pin_config.h" #include "ui.h" #include <HTTPClient.h> #include <WiFi.h> #include <time.h> const char* ssid = "fahad"; const char* password = "fahad123"; const char* ntpServer = "pool.ntp.org"; const long utcOffsetInSeconds = 18000; // UTC+5 const int daylightOffset_sec = 0; static const uint16_t screenWidth = 368; static const uint16_t screenHeight = 448; static lv_disp_draw_buf_t draw_buf; static lv_color_t buf[ screenWidth * screenHeight / 10 ]; Arduino_DataBus *bus = new Arduino_ESP32QSPI( LCD_CS, LCD_SCLK, LCD_SDIO0, LCD_SDIO1, LCD_SDIO2, LCD_SDIO3); Arduino_GFX *gfx = new Arduino_SH8601(bus, LCD_RST, 0, false, LCD_WIDTH, LCD_HEIGHT); std::shared_ptr<Arduino_IIC_DriveBus> IIC_Bus = std::make_shared<Arduino_HWIIC>(IIC_SDA, IIC_SCL, &Wire); void Arduino_IIC_Touch_Interrupt(void); std::unique_ptr<Arduino_IIC> FT3168(new Arduino_FT3x68(IIC_Bus, FT3168_DEVICE_ADDRESS, DRIVEBUS_DEFAULT_VALUE, TP_INT, Arduino_IIC_Touch_Interrupt)); void Arduino_IIC_Touch_Interrupt(void) { FT3168->IIC_Interrupt_Flag = true; } #if LV_USE_LOG != 0 void my_print(const char * buf) { Serial.printf(buf); Serial.flush(); } #endif void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p) { uint32_t w = ( area->x2 - area->x1 + 1 ); uint32_t h = ( area->y2 - area->y1 + 1 ); #if (LV_COLOR_16_SWAP != 0) gfx->draw16bitBeRGBBitmap(area->x1, area->y1, (uint16_t *)&color_p->full, w, h); #else gfx->draw16bitRGBBitmap(area->x1, area->y1, (uint16_t *)&color_p->full, w, h); #endif lv_disp_flush_ready(disp); } void my_touchpad_read(lv_indev_drv_t * indev_driver, lv_indev_data_t * data) { int32_t touchX = FT3168->IIC_Read_Device_Value(FT3168->Arduino_IIC_Touch::Value_Information::TOUCH_COORDINATE_X); int32_t touchY = FT3168->IIC_Read_Device_Value(FT3168->Arduino_IIC_Touch::Value_Information::TOUCH_COORDINATE_Y); if(FT3168->IIC_Interrupt_Flag == true) { FT3168->IIC_Interrupt_Flag = false; data->state = LV_INDEV_STATE_PR; data->point.x = touchX; data->point.y = touchY; Serial.printf("Data x %d, Data y %d\n", touchX, touchY); } else { data->state = LV_INDEV_STATE_REL; } } // Global time variables int currentHour = 0; int currentMinute = 0; int currentSecond = 0; int currentDay = 0; int currentMonth = 0; int currentYear = 0; int currentDayOfYear = 0; char currentDayName[10]; // E.g., "Monday" void setup() { Serial.begin(115200); pinMode(LCD_EN, OUTPUT); digitalWrite(LCD_EN, HIGH); while (FT3168->begin() == false) { Serial.println("FT3168 initialization fail"); delay(2000); } Serial.println("FT3168 initialization successfully"); gfx->begin(); gfx->Display_Brightness(200); Serial.printf("Hello Arduino! V%d.%d.%d\n", lv_version_major(), lv_version_minor(), lv_version_patch()); lv_init(); #if LV_USE_LOG != 0 lv_log_register_print_cb(my_print); #endif FT3168->IIC_Write_Device_State(FT3168->Arduino_IIC_Touch::Device::TOUCH_POWER_MODE, FT3168->Arduino_IIC_Touch::Device_Mode::TOUCH_POWER_MONITOR); lv_disp_draw_buf_init(&draw_buf, buf, NULL, screenWidth * screenHeight / 10); static lv_disp_drv_t disp_drv; lv_disp_drv_init(&disp_drv); disp_drv.hor_res = screenWidth; disp_drv.ver_res = screenHeight; disp_drv.flush_cb = my_disp_flush; disp_drv.draw_buf = &draw_buf; lv_disp_drv_register(&disp_drv); static lv_indev_drv_t indev_drv; lv_indev_drv_init(&indev_drv); indev_drv.type = LV_INDEV_TYPE_POINTER; indev_drv.read_cb = my_touchpad_read; lv_indev_drv_register(&indev_drv); ui_init(); Serial.println("Setup done"); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(" Connected!"); Serial.print("Configuring time from NTP..."); configTime(utcOffsetInSeconds, daylightOffset_sec, ntpServer); while (!time(nullptr)) { delay(500); Serial.print("."); } Serial.println(" Synced!"); } void loop() { time_t now = time(nullptr); struct tm timeinfo; localtime_r(&now, &timeinfo); // Save components into individual variables currentHour = timeinfo.tm_hour; currentMinute = timeinfo.tm_min; currentSecond = timeinfo.tm_sec; currentDay = timeinfo.tm_mday; currentMonth = timeinfo.tm_mon + 1; currentYear = timeinfo.tm_year + 1900; // NEW: Get day of week (e.g., Monday) strftime(currentDayName, sizeof(currentDayName), "%A", &timeinfo); // NEW: Get day of year (tm_yday ranges from 0 to 365) currentDayOfYear = timeinfo.tm_yday + 1; // Format time and date strings char timeStr[9]; char dateStr[32]; strftime(timeStr, sizeof(timeStr), "%H:%M:%S", &timeinfo); snprintf(dateStr, sizeof(dateStr), "%s, %02d-%02d-%04d", currentDayName, currentDay, currentMonth, currentYear); // Debug print Serial.printf("Time: %02d:%02d:%02d | Date: %s, %02d-%02d-%04d\n", currentHour, currentMinute, currentSecond, currentDayName, currentDay, currentMonth, currentYear); lv_label_set_text(ui_lblTime, timeStr); lv_label_set_text(ui_lblDate, dateStr); // Convert integers to strings char hourStr[3]; char minuteStr[3]; char secondStr[3]; sprintf(hourStr, "%02d", currentHour); sprintf(minuteStr, "%02d", currentMinute); sprintf(secondStr, "%02d", currentSecond); // Set labels lv_label_set_text(ui_lblhour, hourStr); lv_label_set_text(ui_lblminutes, minuteStr); lv_label_set_text(ui_lblseconds, secondStr); lv_label_set_text(ui_Label4,dateStr ); lv_timer_handler(); delay(5); } |
This code converts integer time values (hour, minute, second) into two-digit strings using sprintf.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// Convert integers to strings char hourStr[3]; char minuteStr[3]; char secondStr[3]; sprintf(hourStr, "%02d", currentHour); sprintf(minuteStr, "%02d", currentMinute); sprintf(secondStr, "%02d", currentSecond); |
It then updates three LVGL label widgets (ui_lblhour, ui_lblminutes, ui_lblseconds) to display the formatted time values.
1 2 3 4 5 6 7 |
// Set labels lv_label_set_text(ui_lblhour, hourStr); lv_label_set_text(ui_lblminutes, minuteStr); lv_label_set_text(ui_lblseconds, secondStr); |
And this instruction prints the Date.
1 |
lv_label_set_text(ui_Label4,dateStr ); |
And I just remembered; I haven’t applied screen gestures yet.
So let’s go back to SquareLine Studio and apply the screen gestures; otherwise, we won’t be able to switch between the two screens.
- Select Screen1, then click on “Add Event.”
- Set the Trigger Type to “Gesture Left.”
- Set the Action Type to “Change Screen.”
- Click the “Add” button.
- Select Screen2.
Now select Screen2 and follow the same steps. This time, choose “Gesture Right” as the trigger and set Screen1 as the target.
Now, let’s save the project and export the UI files.
After that, let’s go ahead and upload the program.
Our digital watch is ready, and it looks so cool!
You saw how, even without an RTC module, you can easily access and use the date and time from the internet.
Analog Watch:
To create an analog watch, I have designed a simple watch face in Photoshop.
Along with that, I have also created the hour hand, minute hand, and second hand.
I have saved all four elements as separate .png images. You can download the watch face and project folder from the Patreon page.
So, let’s open SquareLine Studio and start building our analog watch.
Analog Watch in SquareLine Studio:
For the step-by-step explanation, watch the video tutorial given at the end of this article.
Analog Watch Complete code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 |
#include <lvgl.h> #include "Arduino_GFX_Library.h" #include "Arduino_DriveBus_Library.h" #include "pin_config.h" #include "ui.h" #include <HTTPClient.h> #include <WiFi.h> #include <time.h> const char* ssid = "fahad"; const char* password = "fahad123"; const char* ntpServer = "pool.ntp.org"; const long utcOffsetInSeconds = 18000; // UTC+5 const int daylightOffset_sec = 0; static const uint16_t screenWidth = 368; static const uint16_t screenHeight = 448; static lv_disp_draw_buf_t draw_buf; static lv_color_t buf[ screenWidth * screenHeight / 10 ]; Arduino_DataBus *bus = new Arduino_ESP32QSPI( LCD_CS, LCD_SCLK, LCD_SDIO0, LCD_SDIO1, LCD_SDIO2, LCD_SDIO3); Arduino_GFX *gfx = new Arduino_SH8601(bus, LCD_RST, 0, false, LCD_WIDTH, LCD_HEIGHT); std::shared_ptr<Arduino_IIC_DriveBus> IIC_Bus = std::make_shared<Arduino_HWIIC>(IIC_SDA, IIC_SCL, &Wire); void Arduino_IIC_Touch_Interrupt(void); std::unique_ptr<Arduino_IIC> FT3168(new Arduino_FT3x68(IIC_Bus, FT3168_DEVICE_ADDRESS, DRIVEBUS_DEFAULT_VALUE, TP_INT, Arduino_IIC_Touch_Interrupt)); void Arduino_IIC_Touch_Interrupt(void) { FT3168->IIC_Interrupt_Flag = true; } #if LV_USE_LOG != 0 void my_print(const char * buf) { Serial.printf(buf); Serial.flush(); } #endif void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p) { uint32_t w = ( area->x2 - area->x1 + 1 ); uint32_t h = ( area->y2 - area->y1 + 1 ); #if (LV_COLOR_16_SWAP != 0) gfx->draw16bitBeRGBBitmap(area->x1, area->y1, (uint16_t *)&color_p->full, w, h); #else gfx->draw16bitRGBBitmap(area->x1, area->y1, (uint16_t *)&color_p->full, w, h); #endif lv_disp_flush_ready(disp); } void my_touchpad_read(lv_indev_drv_t * indev_driver, lv_indev_data_t * data) { int32_t touchX = FT3168->IIC_Read_Device_Value(FT3168->Arduino_IIC_Touch::Value_Information::TOUCH_COORDINATE_X); int32_t touchY = FT3168->IIC_Read_Device_Value(FT3168->Arduino_IIC_Touch::Value_Information::TOUCH_COORDINATE_Y); if(FT3168->IIC_Interrupt_Flag == true) { FT3168->IIC_Interrupt_Flag = false; data->state = LV_INDEV_STATE_PR; data->point.x = touchX; data->point.y = touchY; Serial.printf("Data x %d, Data y %d\n", touchX, touchY); } else { data->state = LV_INDEV_STATE_REL; } } // Global time variables int currentHour = 0; int currentMinute = 0; int currentSecond = 0; int currentDay = 0; int currentMonth = 0; int currentYear = 0; int currentDayOfYear = 0; char currentDayName[10]; // E.g., "Monday" void setup() { Serial.begin(115200); pinMode(LCD_EN, OUTPUT); digitalWrite(LCD_EN, HIGH); while (FT3168->begin() == false) { Serial.println("FT3168 initialization fail"); delay(2000); } Serial.println("FT3168 initialization successfully"); gfx->begin(); gfx->Display_Brightness(200); Serial.printf("Hello Arduino! V%d.%d.%d\n", lv_version_major(), lv_version_minor(), lv_version_patch()); lv_init(); #if LV_USE_LOG != 0 lv_log_register_print_cb(my_print); #endif FT3168->IIC_Write_Device_State(FT3168->Arduino_IIC_Touch::Device::TOUCH_POWER_MODE, FT3168->Arduino_IIC_Touch::Device_Mode::TOUCH_POWER_MONITOR); lv_disp_draw_buf_init(&draw_buf, buf, NULL, screenWidth * screenHeight / 10); static lv_disp_drv_t disp_drv; lv_disp_drv_init(&disp_drv); disp_drv.hor_res = screenWidth; disp_drv.ver_res = screenHeight; disp_drv.flush_cb = my_disp_flush; disp_drv.draw_buf = &draw_buf; lv_disp_drv_register(&disp_drv); static lv_indev_drv_t indev_drv; lv_indev_drv_init(&indev_drv); indev_drv.type = LV_INDEV_TYPE_POINTER; indev_drv.read_cb = my_touchpad_read; lv_indev_drv_register(&indev_drv); ui_init(); Serial.println("Setup done"); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(" Connected!"); Serial.print("Configuring time from NTP..."); configTime(utcOffsetInSeconds, daylightOffset_sec, ntpServer); while (!time(nullptr)) { delay(500); Serial.print("."); } Serial.println(" Synced!"); } void loop() { time_t now = time(nullptr); struct tm timeinfo; localtime_r(&now, &timeinfo); // Save components into individual variables currentHour = timeinfo.tm_hour; currentMinute = timeinfo.tm_min; currentSecond = timeinfo.tm_sec; currentDay = timeinfo.tm_mday; currentMonth = timeinfo.tm_mon + 1; currentYear = timeinfo.tm_year + 1900; // NEW: Get day of week (e.g., Monday) strftime(currentDayName, sizeof(currentDayName), "%A", &timeinfo); // NEW: Get day of year (tm_yday ranges from 0 to 365) currentDayOfYear = timeinfo.tm_yday + 1; // Format time and date strings char timeStr[9]; char dateStr[32]; strftime(timeStr, sizeof(timeStr), "%H:%M:%S", &timeinfo); snprintf(dateStr, sizeof(dateStr), "%s, %02d-%02d-%04d", currentDayName, currentDay, currentMonth, currentYear); // Debug print Serial.printf("Time: %02d:%02d:%02d | Date: %s, %02d-%02d-%04d\n", currentHour, currentMinute, currentSecond, currentDayName, currentDay, currentMonth, currentYear); lv_label_set_text(ui_lblTime, timeStr); lv_label_set_text(ui_lblDate, dateStr); // Convert integers to strings char hourStr[3]; char minuteStr[3]; char secondStr[3]; sprintf(hourStr, "%02d", currentHour); sprintf(minuteStr, "%02d", currentMinute); sprintf(secondStr, "%02d", currentSecond); // Set labels lv_label_set_text(ui_lblhour, hourStr); lv_label_set_text(ui_lblminutes, minuteStr); lv_label_set_text(ui_lblseconds, secondStr); lv_label_set_text(ui_Label4,dateStr ); //Initializing the alarm page from the RTC lv_img_set_angle(ui_Image1, currentHour * 322 + currentMinute / 12 * 60); lv_img_set_angle(ui_Image2, (currentMinute-9) * 60); lv_label_set_text(ui_lblanalogsecs, secondStr); //lv_img_set_angle(ui_Image3, currentSecond); lv_timer_handler(); delay(5); } |
You only need these three lines of code; and that’s it,
1 2 3 |
lv_img_set_angle(ui_Image1, currentHour * 322 + currentMinute / 12 * 60); lv_img_set_angle(ui_Image2, (currentMinute-9) * 60); lv_label_set_text(ui_lblanalogsecs, secondStr); |
Your analog watch is ready!
You may have noticed that I made a small change:
I have removed the seconds hand. Instead, I am now displaying the seconds inside the red rectangle. By the way, you can also display date in that Red rectangle.
So that’s all for now.
Support me on Patreon for more videos and articles.
Watch Video Tutorial: