Home ▸ STM32 Tutorials ▸ Analog Clock on GC9A01 with STM32

Analog Clock on GC9A01 Round Display with STM32, LVGL and RTC

Part 1 of this series connected the GC9A01 to an STM32 over SPI and confirmed that LVGL could draw on the 240×240 round screen with a bouncing ball. This part builds a real application on top of that setup: an analog clock with three image-based hands, where each hand rotates around its own pivot point and the time comes from the internal RTC of the STM32.

The tutorial covers how to design the clock face in SquareLine Studio, how to measure the pivot point of each hand in GIMP, and how to export the generated LVGL code into STM32CubeIDE. It also shows how to configure the STM32 RTC so that the time survives a reset by using a backup register, and how to write the updateClock() function that converts the RTC hours, minutes, and seconds into rotation values for lv_img_set_angle(). The complete CubeIDE project is available for download at the end of the post.

This post continues directly from Part 1: GC9A01 SPI and LVGL Integration, which covers the wiring, the SPI and DMA setup in CubeMX, the LVGL installation, and the display flush callback. The STM32 internal RTC tutorial gives useful background on the RTC peripheral, and the STM32 LVGL tutorial series lists all the related LVGL posts in one place.

What Is SquareLine Studio for LVGL

SquareLine Studio is a visual design tool made for LVGL. Instead of writing the position and style of every widget in C, you place the elements on a canvas, set their properties in a side panel, check the layout in a live preview, and export the result as LVGL source files. The export contains ui.c, ui.h, and the C files for the image assets, and this saves a lot of time that would otherwise go into adjusting pixel positions by hand.

SquareLine Studio on Round Displays

SquareLine Studio lets you set the canvas to any resolution and mark it as circular. The 240×240 GC9A01 panel is then shown as a circle in the preview, and any widget that extends past the circle is clipped the same way it will be on the real display. Image widgets can be given an exact pivot point for rotation, which is important for a clock because a hand that is one pixel away from the center visibly wobbles as it turns.


SquareLine Studio Key Features

  • The editor places buttons, sliders, images, arcs, labels, and gauges with drag and drop, so no layout code is needed.
  • The live canvas preview shows the rotation, alignment, and layering of the widgets as they will appear on the target display.
  • The code export produces ui.c and ui.h files that are compatible with LVGL 9.2.
  • Image widgets have a configurable pivot point, which lets a clock hand rotate around the center of the dial and not around a corner of the image.
  • The project can be set to any screen size, including circular and square panels.

GC9A01 Analog Clock Project Requirements

ComponentDetails
MCU boardWeAct Studio STM32H562RGT6 (at least 512 KB Flash, 640 KB SRAM)
Display1.28 inch GC9A01 round TFT LCD, 240×240 pixels
IDESTM32CubeIDE
Graphics libraryLVGL 9.2
UI design toolSquareLine Studio
Image editorGIMP

Any STM32 board with at least 512 KB of Flash, 20 KB of free SRAM, and a hardware SPI peripheral can run this project. The LVGL display pipeline from Part 1 must already be working before you start this tutorial.


Design the Analog Clock UI in SquareLine Studio

Create a New SquareLine Studio Project for GC9A01

SquareLine Studio Create tab showing LVGL 9.2 selected with 240x240 pixel resolution circular display and 16-bit color for GC9A01

Open SquareLine Studio and create a new project with the following settings:

  • Framework: LVGL 9.2, which must match the LVGL version in the CubeIDE project from Part 1.
  • Template: Eclipse with SDL, which allows testing on a PC.
  • Resolution: 240 × 240 pixels.
  • Color depth: 16 bits (RGB565).
  • Display shape: Circular.
  • LVGL version: 9.2.2, which was the latest 9.2 release when this tutorial was written.

Click Create to open the canvas. It shows a 240×240 circular preview, and the area outside the circle represents the part of the screen that is not visible.


Import the Clock Face and Hand Images

SquareLine Studio UI editor showing clock face background and hour minute second hand PNG images added to Assets and placed on circular canvas

The clock needs four PNG images, and each one should have a transparent background:

  • The clock face, which is the dial background with the hour markers.
  • The second hand, which is a thin needle.
  • The minute hand, which is a wider needle.
  • The hour hand, which is the shortest needle.

You can take these images from any royalty-free graphics site. In SquareLine Studio, open Assets, choose Import, and add all four files. Drag each image from Assets onto the canvas, set the widget to the Image type, enable Anti-alias, and place all three hands at the center of the clock face. At this stage the hands overlap at the center of the 240×240 canvas.


Find the Pivot Coordinates with GIMP

Every hand image has a pivot point, which is the pixel where the hand is pinned to the center of the clock. For a second hand stored in a 240×240 image, this pixel is near the bottom center of the needle and not at a corner of the image.

Open each hand image in GIMP and move the mouse over the point where the hand attaches to the center of the clock. The x and y coordinates of the cursor are shown in the status bar at the bottom left of the window. Write these values down for each hand.

GIMP image editor showing the second clock hand PNG with mouse cursor positioned at the pivot point and x y coordinates displayed in the status bar
Second’s Hand
GIMP image editor showing the hour clock hand PNG with pivot point coordinates visible in the bottom left status bar for use in SquareLine Studio
Hour’s Hand
GIMP image editor showing the minute clock hand PNG with pivot point pixel coordinates displayed in the status bar
Minute’s Hand

You will enter these values in the Pivot X and Pivot Y fields of the matching image widget in SquareLine Studio. When the values are correct, each hand turns around the center of the clock, and when they are wrong, the hand swings around the wrong point.


Configure the Second Hand

SquareLine Studio properties panel for the second hand image widget showing Pivot X and Pivot Y values set from GIMP and rotation offset of 1800

Select the second hand widget on the canvas and open its properties panel. Enter the pivot coordinates that you noted in GIMP. Then type a few values into the Angle field to test the rotation. The hand should turn around the center of the clock, and if it moves away from the center, the GIMP coordinates need to be checked again.

The rotation of the second hand follows these rules:

  • LVGL measures angles in tenths of a degree, so a full turn is 3600 units.
  • The hand makes one full turn in 60 seconds, so it advances 60 units for every second (seconds × 60).
  • The offset of +1800 turns the hand by 180 degrees. This value corrects the resting orientation of the image used in this tutorial, so that the hand points to 12 o’clock at zero seconds.

Configure the Minute Hand

SquareLine Studio properties panel for the minute hand image widget showing pivot coordinates and negative 550 rotation offset for CCW alignment to 12 o clock

Select the minute hand widget and enter the pivot coordinates measured in GIMP for the minute hand image. The rotation of this hand follows these rules:

  • The hand advances 60 units for every minute (minutes × 60).
  • The offset of −550 aligns the hand with 12 o’clock at minute zero. The value is negative because the image points slightly clockwise of the vertical, so a counter-clockwise correction is needed.
  • The current seconds value is added to the angle, which makes the hand move steadily and not jump to the next minute mark once every 60 seconds.

Configure the Hour Hand

SquareLine Studio properties panel for the hour hand image widget showing pivot coordinates from GIMP and 620 rotation offset for 12 o clock alignment

Select the hour hand widget and enter the pivot coordinates measured in GIMP for the hour hand image. The rotation of this hand follows these rules:

  • One hour covers 30 degrees, which is 300 LVGL units (hours × 300).
  • The offset of +620 aligns the hand with 12 o’clock at hour zero.
  • The current minutes value multiplied by 5 is added to the angle. One hour is 300 units, so every minute adds 5 units, and this lets the hour hand move gradually through each hour and not jump from one marker to the next.

Export the UI Files to STM32CubeIDE

SquareLine Studio Project Settings panel showing export path set to UI folder inside STM32CubeIDE project and LVGL include path set to lvgl/lvgl.h

Open File, then Project Settings, and set the following two values:

  • Export path: a folder named UI inside your STM32CubeIDE project directory.
  • LVGL include path: lvgl/lvgl.h, which must match the include structure used in Part 1.
SquareLine Studio Export UI Files dialog showing generated ui.c ui.h and image asset files being exported to the UI project folder

Click Export and choose Export UI Files. SquareLine Studio writes ui.c, ui.h, and the C files for the image assets into the UI folder.

STM32CubeIDE project Explorer showing the exported UI folder containing SquareLine Studio generated files added alongside Src Inc and Drivers

Make sure the UI folder sits in the project directory next to Src, Inc, and Drivers. Then add the UI folder to the compiler include paths in the CubeIDE project properties, using the same steps that were used for the LVGL folder in Part 1.


STM32 RTC Configuration for the Clock

The SPI, DMA, and LVGL settings are already in place from Part 1, so this section only covers the RTC peripheral.

Select LSI as the RTC Clock Source

STM32CubeMX RCC configuration showing LSI internal oscillator selected as RTC clock source running at 32 kHz for STM32H562

The RTC needs a low-frequency clock that is independent of the main system clock. There are two choices:

  • LSI is the internal 32 kHz RC oscillator. It needs no external parts, but its accuracy is only about ±5%. This tutorial uses LSI.
  • LSE is an external 32.768 kHz crystal connected to PC14 and PC15. It is much more accurate over time, so it is the better choice if your board has the crystal fitted.

In CubeMX, enable the RTC peripheral and select LSI as the clock source in the RCC settings.


Set the RTC to 24-Hour Binary Format

STM32CubeMX RTC parameter configuration showing 24-hour binary format with PREDIV_A 127 and PREDIV_S 249 for 1 Hz ck_spre output

Apply these RTC parameters:

  • Hour format: 24-hour.
  • Data format: Binary, which is easier to use in the rotation formulas than BCD.
  • Time and date: any placeholder values, because the setTime() function in the code replaces them.

Calculate the Prescaler for a 1 Hz ck_spre

The ck_spre signal of the RTC must run at exactly 1 Hz so that the seconds counter advances in real time. The prescalers divide the RTC clock down to 1 Hz according to this formula:

ck_spre = RTCCLK / ((PREDIV_A + 1) × (PREDIV_S + 1))

With PREDIV_A set to 127 and PREDIV_S set to 249, the result is:

ck_spre = 32,000 / (128 × 250) = 32,000 / 32,000 = 1 Hz

Enter these values in the RTC configuration panel. CubeMX may fill them in automatically when LSI is selected, but it is still worth checking them before you generate the code.


STM32 RTC Time Setting with Backup Register

Comment Out the Time Setting in MX_RTC_Init

CubeMX generates an MX_RTC_Init() function that writes the placeholder time and date into the RTC. If this block stays active, every reset of the MCU overwrites the running time, and the clock returns to the placeholder time after each power cycle.

STM32CubeIDE code editor showing the HAL_RTC_SetTime block inside MX_RTC_Init commented out to prevent RTC time reset on every MCU restart

Open main.c, find MX_RTC_Init(), and comment out only the HAL_RTC_SetTime() block. The rest of the function must stay, because the RTC peripheral still needs to be initialized. The setTime() function in the next section takes over the job of setting the time, and it includes a check that prevents the time from being overwritten after the first run.


The setTime Function

void setTime(int hour, int min, int sec)
{
    if (HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR0) != 0x4321)
    {
        RTC_TimeTypeDef sTime = {0};
        sTime.Hours   = hour;
        sTime.Minutes = min;
        sTime.Seconds = sec;
        sTime.DayLightSaving  = RTC_DAYLIGHTSAVING_NONE;
        sTime.StoreOperation  = RTC_STOREOPERATION_RESET;

        if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN) != HAL_OK)
        {
            Error_Handler();
        }

        HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR0, 0x4321);
    }
}

The STM32 has a set of backup registers, from RTC_BKP_DR0 to RTC_BKP_DR31, that keep their values across resets and, as long as the VBAT pin is powered, across power cycles. The check in setTime() uses the first of these registers as a flag:

  1. After the first start, RTC_BKP_DR0 holds its default value of zero and not 0x4321. The condition is true, so the function writes the requested hours, minutes, and seconds to the RTC and then stores 0x4321 in the register.
  2. After every later reset, RTC_BKP_DR0 already holds 0x4321. The condition is false, so the function does nothing and the running time is kept.

The value 0x4321 is only a marker. Any non-zero value works as long as the same value is used in both places.


Rotate Clock Hands with lv_img_set_angle

The updateClock Function

void updateClock(void)
{
    RTC_TimeTypeDef gTime;
    RTC_DateTypeDef gDate;

    HAL_RTC_GetTime(&hrtc, &gTime, RTC_FORMAT_BIN);
    HAL_RTC_GetDate(&hrtc, &gDate, RTC_FORMAT_BIN); // required to unlock shadow registers

    int16_t rotn;

    // Second hand
    rotn = (gTime.Seconds * 60) + 1800;
    lv_img_set_angle(ui_Image2, rotn);

    // Minute hand
    rotn = (gTime.Minutes * 60) - 550 + gTime.Seconds;
    lv_img_set_angle(ui_Image5, rotn);

    // Hour hand
    rotn = (gTime.Hours * 300) + 620 + (gTime.Minutes * 5);
    lv_img_set_angle(ui_Image4, rotn);
}

The call to HAL_RTC_GetDate() has to follow every call to HAL_RTC_GetTime(). The RTC uses shadow registers, and the time and date values are only latched into them after a specific read sequence. HAL expects the date to be read after the time in order to finish that sequence and release the registers. If the date is not read, the next call to HAL_RTC_GetTime() can return an old value.


Second Hand Rotation Formula

rotn = (gTime.Seconds * 60) + 1800;
lv_img_set_angle(ui_Image2, rotn);

The function lv_img_set_angle() takes the angle in tenths of a degree, so 3600 equals 360 degrees. A full turn in 60 seconds means that each second adds 3600 ÷ 60 = 60 units. The offset of +1800 is a 180 degree correction for the orientation of the image used here. The table below shows the angle and the resulting hand position at four points on the dial.

SecondsCalculationAngle (LVGL units)Hand position
00×60 + 1800180012 o’clock
1515×60 + 180027003 o’clock
3030×60 + 180036006 o’clock
4545×60 + 180045009 o’clock

Minute Hand Rotation Formula

rotn = (gTime.Minutes * 60) - 550 + gTime.Seconds;
lv_img_set_angle(ui_Image5, rotn);

Each minute adds 60 units, and the offset of −550 corrects the resting orientation of the minute hand image so that it points to 12 o’clock. The seconds value adds one more unit per second, and this keeps the hand in constant motion. Without it, the hand would stay still for 59 seconds and then jump to the next minute mark.


Hour Hand Rotation Formula

rotn = (gTime.Hours * 300) + 620 + (gTime.Minutes * 5);
lv_img_set_angle(ui_Image4, rotn);

Each hour adds 300 units, which is 30 degrees at 10 units per degree. The offset of +620 aligns the image with 12 o’clock. The minutes value multiplied by 5 spreads the 300 units of an hour over 60 minutes, so the hand moves slowly through each hour and does not jump between hour marks.


STM32 Analog Clock main() Code

#include "lv_port_disp.h"
#include "ui/ui.h"

int main(void)
{
    // ... HAL_Init, SystemClock_Config, MX_SPI1_Init,
    //     MX_GPIO_Init, MX_RTC_Init, MX_DMA_Init ...

    lv_init();
    lv_port_disp_init();   // initialize GC9A01 + connect to LVGL

    ui_init();             // initialize SquareLine Studio UI
    setTime(12, 12, 0);    // set RTC to 12:12:00 (first run only)

    while (1)
    {
        updateClock();         // read RTC and rotate clock hands
        lv_timer_handler();    // process LVGL events and flush display
        HAL_Delay(5);
    }
}

The order of the initialization calls matters. lv_init() must come first, lv_port_disp_init() second, and ui_init() third. If ui_init() runs before the display port is set up, the program fails because the display that the widgets attach to does not exist yet.

The updateClock() function runs on every pass through the loop. With a 5 ms delay this is roughly 200 times per second, which is far more often than the 1 Hz seconds hand requires. The extra calls do no harm, because the RTC value only changes once per second and lv_img_set_angle() only updates data in memory.


GC9A01 Analog Clock Result

Animated GIF showing an analog clock face running on the GC9A01 1.28 inch round display at 12:12 with the second hand visibly ticking every second

The clock starts at the programmed time of 12:12:00. The second hand moves once per second in steps of 6 degrees, and the minute hand moves continuously. The hour hand sits between the 12 and 1 markers, which matches 12 minutes past the hour.


Conclusion

Building an analog clock with the GC9A01 round display and LVGL 9.2 is a great way to explore custom graphics, animations, and real-time data visualization on the STM32H562. By combining SquareLine Studio for the UI design, an RTC for timekeeping, and LVGL for rendering, you can create a smooth, responsive clock interface that can be extended with additional features.

The same techniques can be applied to other embedded GUI projects, including gauges, dials, and animated indicators. If you want to explore more LVGL features on STM32, check out these tutorials:

Try adapting the clock project to your own application, experiment with different UI designs, and build something unique with LVGL on STM32.


Download Project Here

Download the CubeMX project files and HAL source code — all tested on real hardware.

If you find this useful, you can support us with a Donation as well.



About the Author
Arun Rawat
Arun Rawat
Embedded Systems Engineer · Founder, ControllersTech

Arun is an embedded systems engineer with 10+ years of experience in STM32, ESP32, and AVR microcontrollers. He created ControllersTech to share practical tutorials on embedded software, HAL drivers, RTOS, and hardware design — grounded in real industrial automation experience.

Subscribe
Notify of

0 Comments
Newest
Oldest Most Voted
×

Don’t Miss Future STM32 Tutorials

Join thousands of developers getting free guides, code examples, and updates.