Implementing a Clock Dial on the ESP32 Handheld Device Using LVGL

Previously, we introduced how to flash the <span>LVGL-Micropython</span> firmware onto the Little Cat handheld device, showcasing effects such as displaying GIF images, bar graphs, line graphs, zooming and rotating images, and simple dashboards. This note will continue to share how to implement a clock dial based on <span>LVGL-Micropython</span>.

1. Development Board Introduction

This handheld device is a custom development board for the Xueersi children’s programming course, with the MCU being <span>ESP32-WROVER-B</span>, equipped with a case, battery, 1.8-inch LCD screen, 6 buttons, buzzer, light sensor, thermistor, etc., and supports external sensors.

For details, see ESP32 Development Board Under 50 Yuan, with Case, Battery, 1.8-inch LCD, Buttons, and Other Modules.

2. LVGL-Micropython Firmware

Referring to Compiling an LVGL-Micropython Firmware in Three Simple Steps, we compiled the <span>LVGL-Micropython</span> firmware suitable for the Little Cat handheld device.

Due to the complexity of the screen driver for LVGL-Micropython, we encapsulated it into a driver module <span>screen.py</span>. This module allows for easier initialization of the display, thereby simplifying the development process.

Download link: QQ group or https://gitee.com/py2012/xueersi-eps32-handheld-device

LVGL version: 9.2.2

Micropython version: 1.24.1

3. Example Code

In the previous article, Implementing a Simple Dashboard on the ESP32 Handheld Device Using LVGL, we found that the pointer exhibited tearing issues.

According to the official documentation of LVGL-Micropython, we modified the <span>screen.py</span> file to add a double buffering module. This improvement can significantly reduce display tearing and flickering issues. The specific modified code can be found in the <span>screen2.py</span> file on Gitee.

The reference code is as follows:

import screen2
import time
import lvgl as lv
from machine import Pin, ADC, Timer

# Initialize screen
scrn = lv.screen_active()
scrn.set_style_bg_color(lv.color_hex(0x000000), 0)

class AnalogClock:
    def __init__(self, parent):
        self.scale = None
        self.second_hand = None
        self.minute_hand = None
        self.hour_hand = None
        # Get current time
        now = time.localtime()
        self.hour = now[3] % 12  # Convert to 12-hour format
        self.minute = now[4]
        self.second = now[5]
        self.create_clock(parent)
        self.start_timer()

    def create_clock(self, parent):
        """Create analog clock component"""
        # Create clock face (maintain size 120x120)
        self.scale = lv.scale(parent)
        self.scale.set_size(120, 120)
        self.scale.set_mode(lv.scale.MODE.ROUND_INNER)
        # Set clock face style
        self.scale.set_style_bg_opa(lv.OPA._60, 0)
        self.scale.set_style_bg_color(lv.color_hex(0x222222), 0)
        self.scale.set_style_radius(lv.RADIUS_CIRCLE, 0)
        self.scale.set_style_clip_corner(True, 0)
        self.scale.center()
        # Configure scale system
        self.scale.set_label_show(True)
        hour_labels = ["12", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", None]
        self.scale.set_text_src(hour_labels)
        self.scale.set_style_text_font(lv.font_montserrat_8, 0)
        self.scale.set_total_tick_count(61)
        self.scale.set_major_tick_every(5)
        # Main tick style
        style_indicator = lv.style_t()
        style_indicator.init()
        style_indicator.set_text_color(lv.color_hex(0xFFFFFF))
        style_indicator.set_line_color(lv.color_hex(0xFFFFFF))
        style_indicator.set_length(3)
        style_indicator.set_line_width(2)
        self.scale.add_style(style_indicator, lv.PART.INDICATOR)
        # Minor tick style
        style_minor = lv.style_t()
        style_minor.init()
        style_minor.set_line_color(lv.color_hex(0xAAAAAA))
        style_minor.set_length(2)
        style_minor.set_line_width(1)
        self.scale.add_style(style_minor, lv.PART.ITEMS)
        # Clock face border style
        style_main = lv.style_t()
        style_main.init()
        style_main.set_arc_color(lv.color_hex(0x222222))
        style_main.set_arc_width(2)
        self.scale.add_style(style_main, lv.PART.MAIN)
        # Set range and angle (remain unchanged)
        self.scale.set_range(0, 60)
        self.scale.set_angle_range(360)
        self.scale.set_rotation(270)
        # Create second hand (white, length 45px, thin line)
        self.second_hand = lv.line(self.scale)
        self.second_hand.set_style_line_width(1, 0)  # Thinner line width
        self.second_hand.set_style_line_rounded(True, 0)
        self.second_hand.set_style_line_color(lv.color_hex(0xFFFFFF), 0)
        # Create minute hand (orange, length 40px)
        self.minute_hand = lv.line(self.scale)
        self.minute_hand.set_style_line_width(2, 0)
        self.minute_hand.set_style_line_rounded(True, 0)
        self.minute_hand.set_style_line_color(lv.color_hex(0xFFA500), 0)
        # Create hour hand (red, length 30px)
        self.hour_hand = lv.line(self.scale)
        self.hour_hand.set_style_line_width(3, 0)
        self.hour_hand.set_style_line_rounded(True, 0)
        self.hour_hand.set_style_line_color(lv.color_hex(0xFF0000), 0)
        # Add center point
        center = lv.obj(self.scale)
        center.set_size(8, 8)  # Slightly reduce center point size
        center.center()
        center.set_style_radius(lv.RADIUS_CIRCLE, 0)
        center.set_style_bg_color(lv.color_hex(0xFFD700), 0)
        center.set_style_bg_opa(lv.OPA.COVER, 0)
        self.update_hands()

    def update_hands(self):
        """Update all hand positions"""
        # Second hand (length 45px)
        lv.scale.set_line_needle_value(self.scale, self.second_hand, 50, self.second)
        # Minute hand (length 40px)
        lv.scale.set_line_needle_value(self.scale, self.minute_hand, 40, self.minute)
        # Hour hand (length 30px), considering minute offset
        hour_value = self.hour * 5 + (self.minute // 12)
        lv.scale.set_line_needle_value(self.scale, self.hour_hand, 30, hour_value)

    def timer_callback(self, timer):
        """Timer callback (updates every second)"""
        # Get current time
        now = time.localtime()
        self.hour = now[3] % 12
        self.minute = now[4]
        self.second = now[5]
        self.update_hands()

    def start_timer(self):
        """Start hardware timer (triggers every second)"""
        self.timer = Timer(-1)
        self.timer.init(period=1000, mode=Timer.PERIODIC, callback=self.timer_callback)

# Create clock instance
clock = AnalogClock(scrn)

# Main loop
while True:
    lv.timer_handler()
    time.sleep_ms(5)

Implementing a Clock Dial on the ESP32 Handheld Device Using LVGL

Recommended Reading:

  • Implementing Image Rotation and Scaling on the ESP32 Handheld Device Using LVGL
  • Implementing Line Graph Effects on the ESP32 Handheld Device Using LVGL
  • Implementing Temperature Meter Effects on the ESP32 Handheld Device Using LVGL
  • Practical ESP32C3: Implementing a Brick Breaker Game Using LVGL-Micropython
  • Practical ESP32C3: Touch Control to Adjust Screen Backlight Brightness Using LVGL-Micropython
  • Practical ESP32C3: Creating a Dynamic Temperature and Humidity Meter Using LVGL-Micropython
  • Practical ESP32C3: Creating a Simple Level Meter Using LVGL

  • Practical ESP32S3: Creating a Simple Music Player Using LVGL

  • Practical ESP32S3: Experiencing an AI Chatbot

  • Using AI Technology to Write Mini Games for the Little Cat Handheld Device (Part 2)

Implementing a Clock Dial on the ESP32 Handheld Device Using LVGL

Leave a Comment