Home STM32 HAL How to Interface GC9A01 Round Display with STM32 Using SPI + LVGL Integration

Interface GC9A01 Round Display with STM32

Learn how to interface the GC9A01 round TFT display with STM32 via SPI and implement LVGL for rich embedded GUIs. Step-by-step guide to wiring, code, and rendering graphics with LVGL. The project is available for download at the end of the post.

Interface GC9A01 Round Display with STM32

This tutorial will cover how to interface the GC9A01 round TFT display with STM32 via SPI peripheral, with or without using the DMA. We will also see how to implement the LVGL into our project and use the Squareline Studio to build a basic UI for the round LCD display. In the next tutorial on this topic, we will display an actual analog watchface on this LCD. The clock will update with the data from the STM32 RTC peripheral.

Recommended Resources:

Since this tutorial is going to cover the LVGL integration into the project. You should take a look at the STM32 LVGL integration tutorials covered in the past.

Introducing GC9A01 Round LCD Display

The GC9A01 is a high-performance, compact round TFT LCD display driver commonly used with a 1.28-inch circular screen. Unlike traditional rectangular displays, this module features a fully round active area, making it ideal for modern embedded applications where visual design and user experience play a significant role. The most common resolution supported by this display is 240×240 pixels, which, despite the small size, provides sharp and detailed graphics due to its high pixel density.

The display is built using IPS (In-Plane Switching) technology, which offers wide viewing angles and accurate color reproduction, ensuring that the content remains clear and vibrant even when viewed from different angles. It supports 16-bit or 18-bit color depth, allowing up to 262,144 colors, which is more than sufficient for rich GUI elements like icons, charts, meters, and images.

Some of its features are:

  • Display Shape: Perfectly round display (typically 1.28” in diameter)
  • Resolution: 240×240 pixels with 1:1 aspect ratio, allowing high pixel density for clear visuals
  • Color Depth: 65K (16-bit) or 262K (18-bit) color support, enabling rich, vibrant color rendering
  • Interface: Supports SPI interface, which is simple to integrate with most microcontrollers
GC9A01 round LCD Display

Applications in Embedded Projects:

  • Smartwatches and fitness trackers
  • Industrial meters and dials (e.g., circular progress indicators)
  • Custom HMIs for appliances
  • Retro gaming devices with circular screens
  • Medical instruments with compact graphical displays
  • User interfaces in robotics or drones

Hardware Requirements

In this project, I will be using the WeAct Studio STM32H5-based custom development board. This board features 1MB (1024KB) of Flash memory and 640KB of SRAM, providing a balanced combination of storage and runtime memory. These specifications make it well-suited for memory-constrained applications, such as driving graphic displays where efficient memory management is crucial for smooth performance.
You are free to use any other development board, just make sure it has enough Flash (>=512KB) to handle the LVGL library.

I am using the 1.28 Inch GC9A01 Round TFT LCD Display Module available on ControllersTech Shop.

WIRING DIAGRAM

Below is the image showing the connection between GC9A01 round LCD and STM32.

GC9A01 Connection with STM32

As you can see in the image, the wiring is as follows:

GC9A01 PinFunctionSTM32H5 Pin
VCCPower Supply3.3V
GNDGroundGND
SCLSPI ClockPA5
SDASPI Data (MOSI)PA7
DCData/CommandPA4
CSChip SelectPA6
RSTResetPA3

STM32 CUBEMX CONFIGURATION

As I mentioned I am going to use the STM32H562RGT6 for this project. We will start with the clock configuration first.

Clock Configuration

Below is the image showing the STM32 Clock configuration for this project.

STM32 SPI Clock Configuration

The system is clocked by the 8MHz HSE crystal and we will run it at maximum 250MHz. I have also adjusted the PLL1Q factor to bring the SPI clock to 50MHz.

SPI Configuration

Parameters

Below is the image showing SPI1 configuration.

STM32 SPI Configuration

The SPI1 is configured in Half Duplex Mode. This mode is enough for the communication as the display does not transmits anything back. The parameter configuration is as follows:

  • The DATA SIZE is set to 8 Bits with MSB sent first.
  • The Prescaler is set to 4, which brings down the SPI clock to 12.5MHz. This frequency is suitable to drive a display.
  • The CPOL is set to Low and CPHA is set to 1 Edge.

DMA Configuration

You can also enable the DMA for better performance. Below is the image showing the DMA configuration for the SPI.

STM32 SPI DMA Configuration
  • Since we are only using Half Duplex mode, using SPI DMA in TX mode is enough.
  • The Data Direction should be from memory to peripheral.
  • Set the DMA mode to Normal and Data width to Byte.

Pin Configuration

Below is the image showing the pin configuration for this project.

STM32 SPI Pin Configuration

The pins here are configured as per the Wiring Diagram. Pins PA5 and PA7 are SPI1 SCK and MOSI pins respectively. The rest of the pins are configured as output pins and they are renamed as per their functions.

Add the GC9A01 Library

We will first add the GC9A01.c and GC9A01.h file into our project. You can find these files in the project downloadable at the end of this post.
Copy the GC9A01.c file in the Src directory and GC9A01.h in the Inc directory. The final project structure is shown below.

GC9A01 Project Structure

We do not need to modify anything in the GC9A01.c file but make sure to configure the parameters in the User Configuration section of the GC9A01.h file.

// ==== USER CONFIGURATIONS ====
#define GC9A01_SPI           hspi1
#define GC9A01_SPI_TIMEOUT   100
#define USE_DMA              0  // Enable/disable DMA

#define GC9A01_CS_PORT       GPIOA
#define GC9A01_CS_PIN        GPIO_PIN_6

#define GC9A01_DC_PORT       GPIOA
#define GC9A01_DC_PIN        GPIO_PIN_4

#define GC9A01_RST_PORT      GPIOA
#define GC9A01_RST_PIN       GPIO_PIN_3
  • Assign the correct SPI instance to the GC9A01_SPI.
  • If you want to use the DMA for SPI transfers, enable the USE_DMA flag.
  • Finally configure the correct pins and their ports for the CS, DC and RST.

Integrating LVGL Graphic Library

Add and configure the LVGL Library

Download the LVGL version 9.2 from their Github. The reason to use the older version is that we are going to use the Squareline Studio for the UI development and it still use the version 9.2 as the newest version.

After extracting the zip file, rename it to lvgl. Create a new folder inside the Drivers folder and name it lvgl. Copy the extracted lvgl folder to the Drivers->lvgl as shown below.

STM32 LVGL integration

Now copy the lv_conf_template.h from inside the lvgl(extracted) folder, paste it beside the lvgl folder and rename it to lv_conf.h. This is shown in the image below.

STM32 LVGL integration

Open this lv_conf.h file and change the #if 0 to #if 1 to enable the content of this file.

STM32 LVGL Configuration

Right click on the project and open Properties.

open the c/c++ Build -> Settings -> MCU GCC Compiler -> Include Paths. Here click the add button to include the lvgl path to the project.

STM32 LVGL integration

Select the Workspace in the pop up window, locate the lvgl folder we just added and click ok to add the path.

STM32 LVGL integration

Connect Display driver to LVGL

The LVGL display port files are located inside the lvgl->examples->porting folder. Copy the lv_port_disp_template.c from this folder to the Src folder and lv_port_disp_template.h file into the Inc folder.

Rename these files to lv_port_disp.c and lv_port_disp.h as shown in the image below.

LVGL Display Library files

Display configuration

Open the lv_port_disp.h file and define the display resolution inside it.

/*********************
 *      DEFINES
 *********************/
#define MY_DISP_HOR_RES    240
#define MY_DISP_VER_RES    240

The 1.28″ GC9A01 display has a resolution of 240×240 px, hence I have defined it here. That is all the changes we need to make in the lv_port_disp.h file.

Initialize display driver

The display driver will be connected to the lvgl using the functions available in the lv_port_disp.c file. Let’s take a look at the function lv_port_disp_init(). This function will initialise the display hardware and the display drivers for the LVGL.

lv_display_t * myDisplay = NULL;

void lv_port_disp_init(void)
{
    /*-------------------------
     * Initialize your display
     * -----------------------*/
    disp_init();

    /*------------------------------------
     * Create a display and set a flush_cb
     * -----------------------------------*/
    lv_display_t * disp = lv_display_create(MY_DISP_HOR_RES, MY_DISP_VER_RES);
    lv_display_set_flush_cb(disp, disp_flush);

    myDisplay = disp;

    /* Example 2
     * Two buffers for partial rendering
     * In flush_cb DMA or similar hardware should be used to update the display in the background.*/
    LV_ATTRIBUTE_MEM_ALIGN
    static uint8_t buf_2_1[MY_DISP_HOR_RES * 10 * BYTE_PER_PIXEL];

    LV_ATTRIBUTE_MEM_ALIGN
    static uint8_t buf_2_2[MY_DISP_HOR_RES * 10 * BYTE_PER_PIXEL];
    lv_display_set_buffers(disp, buf_2_1, buf_2_2, sizeof(buf_2_1), LV_DISPLAY_RENDER_MODE_PARTIAL);
}

The function disp_init() will initialise the display hardware. Here we need to initialise the GC9A01 as shown below.

/*Initialize your display and the required peripherals.*/
static void disp_init(void)
{
    GC9A01_Init();
}

Next the LVGL will create a display using the function lv_display_create(). The created display will be stored in the disp pointer, which we will assign to our globally defined display pointer (myDisplay). The function lv_display_set_flush_cb is used to assign the callback for the flushing process.
Here we will use Two buffers for partial rendering. You can view more details about the two buffer strategy in the LVGL Docs.

Flushing the data

The function disp_flush is called by the LVGL to flush the content of the buffer to the display. Below is the implementation of this function.

static void disp_flush(lv_display_t * disp_drv, const lv_area_t * area, uint8_t * px_map)
{
    if(disp_flush_enabled) {

    	lv_draw_sw_rgb565_swap(px_map, ((area->x2 - area->x1 +1)*(area->y2 - area->y1 +1)));
    	GC9A01_Flush(px_map, area->x1, area->y1, area->x2, area->y2);
    }
}

Inside this function we will first swap the color data endianness by calling the function lv_draw_sw_rgb565_swap. It is needed for the SPI based displays, but if the colors on your display are not rendering accurately, you can comment out this function.

Next we will call the GC9A01_Flush to flush the LVGL color data to the display. This function is defined in the GC9A01 library file, specially for this purpose.

Flush Ready function

Once the flushing is complete, we need to inform LVGL about the same. GC9A01 library file has a function GC9A01_FlushReady, which is called when the data transfer to the SPI is complete. This function is defined as weak, so we can call it again in the lv_port_disp.c file.

void GC9A01_FlushReady(void)
{
    /*IMPORTANT!!!
     *Inform the graphics library that you are ready with the flushing*/
	lv_display_flush_ready(myDisplay);
}

Inside this function we will call another function lv_display_flush_ready to inform LVGL that flushing is complete and it can send new data.

The Final setup

Open the interrupt source file (stm32xx_it.c) and add the following in the systick handler.

#include "lvgl/lvgl.h"  // To be added
.....
void SysTick_Handler(void)
{
  /* USER CODE BEGIN SysTick_IRQn 0 */
	lv_tick_inc(1);  // To be added
  /* USER CODE END SysTick_IRQn 0 */
  HAL_IncTick();
  /* USER CODE BEGIN SysTick_IRQn 1 */

  /* USER CODE END SysTick_IRQn 1 */
}

Here we will first include the lvgl/lvgl.h and then add the lv_tick_inc(1) inside the systick handler. This will increment the LVGL internal tick counter by 1, every 1millisecond.

Inside the main function, initialise the library and the display driver.

lv_init(); //Initialise LVGL UI library
lv_port_disp_init();  // intializse the display drivers

Next add the LVGL timer handler in the while loop.

  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
	  lv_timer_handler();  // to be added
	  HAL_Delay(5);  // to be added
  }

This completes the LVGL implementation. Now we will run some example to see if everything is working fine on the display.

THE CODE

To test the display working and LVGL implementation, we will run one of the animation examples provided by LVGL itself. Below is the main function running the example code.

/* USER CODE BEGIN Includes */
#include "lv_port_disp.h"
#include "lvgl/examples/anim/lv_example_anim.h"

int main()
{
  ....
  lv_init();
  lv_port_disp_init();
  lv_example_anim_2();
  while (1)
  {
	  lv_timer_handler();  
	  HAL_Delay(5);  
  }
}

We need to include the lv_example_anim.h file, which is inside the folder lvgl/examples/anim.
Inside the main function after initialising the dislay driver, call the function lv_example_anim_2() to run the animation example 2.

RESULT

Below is the gif showing the output of the above example animation code.

GC9A01 Working with LVGL

As you can see the ball animation is running exactly as the example provided in the LVGL docs. The ball colour is Red, which means that the colours are rendering accurately on the display.

VIDEO TUTORIAL

Watch the video below to see complete process of adding libraries, integrating LVGL and using Squareline Studio.

Check out the Video Below

PROJECT DOWNLOAD

Info

You can help with the development by DONATING Below.
To download the project, click the DOWNLOAD button.

You May Also Like

Subscribe
Notify of

0 Comments
Newest
Oldest Most Voted
Inline Feedbacks
View all comments
keyboard_arrow_up