STM32F407-based LVGL project template (MSP3526 display), includes FreeRTOS and bare-metal versions

Successfully ported the LVGL graphics library on STM32F407; LCD model is MSP3526. To switch to another panel you only need to adapt the LCD driver. The project ships in both FreeRTOS and bare-metal flavors. The display is driven through SPI+DMA.

Panel specs: 3.5", 320×480, controller ST7796, SPI interface; capacitive-touch controller FT6336U, I²C interface.

With the MCU over-clocked, full-screen refresh reaches ~9 FPS; partial updates exceed 30 FPS—pretty decent for SPI, I’d say.

EE/MCU QQ group: 2169025065

Demo shots

Video demo: https://www.bilibili.com/video/BV1Ni421S7ta/






About LVGL

LVGL (Light and Versatile Graphics Library) is a free, open-source graphics library that provides everything you need to create embedded GUIs with easy-to-use widgets, beautiful visual effects and a small memory footprint.

Key features:

  • Rich, modular widgets: buttons, charts, lists, sliders, images, etc.
  • Advanced graphics engine: animations, anti-aliasing, opacity, smooth scrolling, layer blending.
  • Multiple input devices: touch, keyboard, encoder, push-buttons.
  • Multi-display support.
  • Hardware-agnostic—runs on any display.
  • Highly configurable (min. 64 kB Flash, 16 kB RAM).
  • UTF-8 multi-language: Chinese, Japanese, Korean, Arabic …
  • CSS-like layouts (Flexbox, Grid).
  • OS, external memory & HW acceleration support (built-in STM32 DMA2D, NXP PXP, VGLite).
  • Smooth rendering even with a single frame buffer.
  • Written in C, C++ compatible.
  • MicroPython bindings available.
  • PC simulator for no-hardware development.
  • Extensive examples and online/offline docs.
  • MIT licensed.

Minimum requirements:

  • 16/32/64-bit MCU or MPU
  • 16 MHz clock recommended

  • Flash/ROM: >64 kB for core, >180 kB recommended
  • RAM:
    – static ~2 kB (depends on widgets)
    – stack >2 kB (>8 kB recommended)
    – heap >2 kB (>16 kB recommended when many objects used); set via LV_MEM_SIZE in lv_conf.h
    – display buffer >“horizontal-resolution” pixels (≥10×horiz. res. recommended)
    – one frame buffer in MCU or external controller
  • C99 or newer compiler

Official site: https://lvgl.io/

Wiring

LCD Pin MCU/Board Function
GND GND Ground
VCC 5 V / 3.3 V Power
LCD_CS PE6 SPI chip-select
LCD_RST PC1 Reset
LCD_RS PC0 Command / data select
SDI(MOSI) PB5 SPI data (master out)
SCK PB3 SPI clock
LED 3.3 V Back-light (can wire to GPIO for SW control)
SDO(MISO) PB4 SPI data (master in)
CTP_SCL PB8 Touch controller I²C clock
CTP_RST PB7 Touch controller reset
CTP_SDA PB9 Touch controller I²C data
CTP_INT PB6 Touch controller interrupt

Note: on my board PB4 is not on the side headers but in the JTAG connector.

Docs & downloads

Online LCD wiki: https://url.zeruns.com/68x3Y
LCD docs mirror-1: https://www.123pan.com/s/2Y9Djv-rZevH.html
LCD docs mirror-2: https://url.zeruns.com/gzBO4

Bare-metal project mirror-1: https://url.zeruns.com/X242k
Bare-metal project mirror-2: https://pan.baidu.com/s/1vAhHijYd_aWvRr3_c1_WtA?pwd=ry4g

FreeRTOS project mirror-1: https://www.123pan.com/s/2Y9Djv-CzevH.html
FreeRTOS project mirror-2: https://url.zeruns.com/0iOHF

Please give it a Star!

Gitee (main): https://gitee.com/zeruns/STM32F407_LVGL_Template_MSP3526
GitHub: https://github.com/zeruns/STM32F407_LVGL_Template_MSP3526

The latest commits on Gitee/GitHub use FreeRTOS; the bare-metal version is tagged v0.1.3 under Releases.

Generated with STM32CubeMX, developed with Keil5 + VS Code + EIDE.
Porting followed the Atom (正点原子) LVGL tutorial.

Atom LVGL video course mirror-1: https://www.123pan.com/s/2Y9Djv-0ZevH.html
Atom LVGL video course mirror-2: https://www.alipan.com/s/Pd6TDfT2rBL

Usage

Clone, compile, download—done.

To hide FPS & memory gauges, change these macros in lv_conf.h from 1 to 0:

/*1: Show CPU usage and FPS count*/
#define LV_USE_PERF_MONITOR 1
#if LV_USE_PERF_MONITOR
    #define LV_USE_PERF_MONITOR_POS LV_ALIGN_BOTTOM_RIGHT
#endif

/*1: Show the used memory and the memory fragmentation
 * Requires LV_MEM_CUSTOM = 0*/
#define LV_USE_MEM_MONITOR 1
#if LV_USE_MEM_MONITOR
    #define LV_USE_MEM_MONITOR_POS LV_ALIGN_BOTTOM_LEFT
#endif
```![](upload://gM3vy1FSbkp0rfvCP6rg7bj5HDX.png)

If you are using the FreeRTOS version, write your code in `freertos.c`; you can create threads and semaphores with STM32CubeMX and then generate code—all code is placed in the designated positions according to the specification, so regenerating will not overwrite your own code. For the bare-metal version, simply write your code directly in `main.c`.

To disable the demo program, just comment out the code shown in the image below.

![](upload://rnrQUS1XVqXJTepVovvXcJek7T2.png)

## Where to Buy Components

- STM32F407VET6 development board: [https://s.click.taobao.com/sQSMWmt](https://s.click.taobao.com/t?e=m%3D2%26s%3DGxUVgIOWBWVw4vFB6t2Z2ueEDrYVVa64g3vZOarmkFi53hKxp7mNFrMfIvbtZ%2F%2B2IT%2BVhjqUP7X0JlhLk0Jl4QTquP0kWxBLBDnvz6xo38xspWc9%2BCL4bTGF1ceZMhPo8mL8HhJ3EdVrH4ks4QyiY4z4rjZDGVMAhscfsB2%2FyzZJq71CBMBeP%2F1SarTXhIOTsgIpc1WFZiJNubylQlnZt0ft88oCh4qlKjZ0Zrpn9cYR7kNHbGZ5w15bTaW0%2FWfaQBd0yVbMw3IeTeL3FeYp0owmLWBgEm80VKxcI130SjETn2JhMaQNtYWLhXkoGmYygFktLIh%2BAXuBHaXkFVFVVsYOae24fhW0&union_lens=lensId%3APUB%401716477927%40212abf30_0d16_18fa60ea164_1890%40025BlJPZdLaOG4oFPxX7gKUY%40eyJmbG9vcklkIjo4MDY3NCwiic3BtQiiI6Il9wb3J0YWxfdjJfcGFnZXNfcHJvbW9fZ29vZHNfaW5kZXhfaHRtIiiwiic3JjRmxvb3JJZCI6IjgwNjc0In0ie%3BtkScm%3AselectionPlaza_site_4358%3Bscm%3A1007.30148.329090.pub_search-item_5a7fc953-12f7-46f5-8abf-f0725f8174cf_)
- Daplink downloader: [https://s.click.taobao.com/9kMKWmt](https://s.click.taobao.com/t?e=m%3D2%26s%3DTHTCnVvsTHtw4vFB6t2Z2ueEDrYVVa64YUrQeSeIhnK53hKxp7mNFrMfIvbtZ%2F%2B2mFKI%2FmDcMIH0JlhLk0Jl4QTquP0kWxBLBDnvz6xo38xspWc9%2BCL4bTGF1ceZMhPo8mL8HhJ3EdVrH4ks4QyiY4z4rjZDGVMAFBcM8wkWlGmBPeR%2FUFCetKLWMw3EOEsyJN2owMjhufwDudUsQ2T%2Bdnc9abdYtRqDTzYdoB%2FDOjstFZhpb%2ByhLzPbHWUbis%2BWsZGiFV6vs3eSWi1ViPhRlULEkqTedE399KEV1g6mN9AKChukhDzWey1fmH6DK4UhxgxdTc00KD8%3D&union_lens=lensId%3APUB%401716478245%4021673e1b_0d06_18fa6137a23_e4cb%40023uxCV3Edv0RnAmphuKflFP%40eyJmbG9vcklkIjo4MDY3NCwiic3BtQiiI6Il9wb3J0YWxfdjJfcGFnZXNfcHJvbW9fZ29vZHNfaW5kZXhfaHRtIiiwiic3JjRmxvb3JJZCI6IjgwNjc0In0ie%3BtkScm%3AselectionPlaza_site_4358%3Bscm%3A1007.30148.329090.pub_search-item_be2f7d85-fcee-40ff-96c5-d9d5bd472c5d_)
- MSP3526 screen: [https://s.click.taobao.com/i1Z29nt](https://s.click.taobao.com/t?e=m%3D2%26s%3DepCXgq%2FZmWBw4vFB6t2Z2ueEDrYVVa64g3vZOarmkFi53hKxp7mNFrMfIvbtZ%2F%2B2tJl3z4Kcvjj0JlhLk0Jl4QTquP0kWxBLBDnvz6xo38xspWc9%2BCL4bTGF1ceZMhPo8mL8HhJ3EdVrH4ks4QyiY4z4rjZDGVMA%2FEM5jbZlb7P9R7R4oT9ML5ESMSIvSnZOMN7NXhFCiynJvjSktgLu1nmUrf%2BRsjyAm8kA6wY1r21y2p9AgG%2F91VmfPAhbcEJheBtNII99o%2FtnjWeMZ%2B39PZGZ9wPRcXV%2BQ%2FMlsmagC3ST4aDEgnPNca8ZkUY0DmW2xiXvDf8DaRs%3D&skuId=5223178287593&union_lens=lensId%3APUB%401716478528%40212c5298_0d1d_18fa617cd1e_3501%40034rs5qkRnldNGhC5eLF4gZ4%40eyJmbG9vcklkIjo4NTQ2Nywiic3BtQiiI6Il9wb3J0YWxfdjJfcGFnZXNfcHJvbW9fZ29vZHNfZGV0YWlsX2h0bSIsInNyY0Zsb29ySWQiiOiiI4MDY3NCJ9%3BtkScm%3AselectionPlaza_site_4358%3Bscm%3A1007.30148.329090.pub_search-item_18bdbb55-0a4c-494e-96fc-a14c1b4c6732_)
- Dupont wires: [https://s.click.taobao.com/W1wHWmt](https://s.click.taobao.com/t?e=m%3D2%26s%3DD%2BKEsm2G8bhw4vFB6t2Z2ueEDrYVVa64g3vZOarmkFi53hKxp7mNFrMfIvbtZ%2F%2B2Sn8%2BqdSFAFT0JlhLk0Jl4QTquP0kWxBLBDnvz6xo38xspWc9%2BCL4bTGF1ceZMhPo8mL8HhJ3EdVrH4ks4QyiY4z4rjZDGVMAhscfsB2%2FyzZJq71CBMBeP%2F1SarTXhIOTsgIpc1WFZiJNubylQlnZt5Tr%2BZsTRXxVoTwQXkQy%2BZ5FeGSAzHUbnuYhpSNeyz6v9H5aeGRpz8mogVv1SEjhEYwmLWBgEm80VKxcI130SjETn2JhMaQNtYWLhXkoGmYyO0%2FufYOLJc1EEap0MMy2i8YOae24fhW0&union_lens=lensId%3APUB%401716478612%402104b45c_0cd2_18fa619123a_652e%40023dY61JLu6wYjo297E4L0ip%40eyJmbG9vcklkIjo4MDY3NCwiic3BtQiiI6Il9wb3J0YWxfdjJfcGFnZXNfcHJvbW9fZ29vZHNfaW5kZXhfaHRtIiiwiic3JjRmxvb3JJZCI6IjgwNjc0In0ie%3BtkScm%3AselectionPlaza_site_4358%3Bscm%3A1007.30148.329090.pub_search-item_c09a2639-b75e-40a9-8886-2b13dfb05f6a_)
- STM32F407VGT6 development board: [https://s.click.taobao.com/VjC29nt](https://s.click.taobao.com/t?e=m%3D2%26s%3DimgWkSQh79Jw4vFB6t2Z2ueEDrYVVa64g3vZOarmkFi53hKxp7mNFrMfIvbtZ%2F%2B2VCC9Q%2BPYiL70JlhLk0Jl4QTquP0kWxBLBDnvz6xo38xspWc9%2BCL4bTGF1ceZMhPo8mL8HhJ3EdVrH4ks4QyiY4z4rjZDGVMAhscfsB2%2FyzZJq71CBMBeP%2F1SarTXhIOTsgIpc1WFZiJNubylQlnZt1PgRVjPwS2ItjXwer7KgrNYvdwnwfvUuaclSgSzfliuRVQ2zHzYrnKVyphVinEJT4wmLWBgEm80VKxcI130SjETn2JhMaQNtYWLhXkoGmYy0ZHJIUNbntFzUAbdwYp3usYOae24fhW0&union_lens=lensId%3APUB%401716478657%4021547cbb_0d71_18fa619c373_1476%40027J4l8QPFtC3F2VYygWUUdV%40eyJmbG9vcklkIjo4MDY3NCwiic3BtQiiI6Il9wb3J0YWxfdjJfcGFnZXNfcHJvbW9fZ29vZHNfaW5kZXhfaHRtIiiwiic3JjRmxvb3JJZCI6IjgwNjc0In0ie%3BtkScm%3AselectionPlaza_site_4358%3Bscm%3A1007.30148.329090.pub_search-item_a456d2bb-5c50-4eb6-a55f-0e6f4ff87eed_)

## Key Code Snippet

The main thing is to call the following LCD-fill function inside the `disp_flush` function in `lv_port_disp.c`. The LCD driver is modified from the official example provided by the screen vendor.

```c
/**
 * @brief Fill a specified rectangular area of the LCD screen
 * Fill the specified LCD area with color values from the LVGL library.
 * @param sx Start X coordinate
 * @param sy Start Y coordinate
 * @param ex End X coordinate
 * @param ey End Y coordinate
 * @param color_p Pointer to the color to be filled
 */
void LCD_Fill_LVGL(uint16_t sx, uint16_t sy, uint16_t ex, uint16_t ey, lv_color_t *color_p)
{
	uint32_t i, j;
	uint16_t width = ex - sx + 1;	 // Calculate the width of the fill area
	uint16_t height = ey - sy + 1;	 // Calculate the height of the fill area
	uint32_t Pixel = width * height; // Calculate the number of pixels in the fill area
	LCD_SetWindows(sx, sy, ex, ey);	 // Set the LCD display window to the specified area

//	for (i = 0; i < height; i++)
//	{
//		for (j = 0; j < width; j++){
//			Lcd_WriteData_16Bit(color_p->full); // Write data
//		}
//	}

// Data split value, used for sending data in batches
#define data_split 3000

	uint8_t data[Pixel * 2]; // Create an array to store color data

	for (i = 0; i < Pixel; i++)
	{
		// Convert 16-bit color data into two 8-bit data
		data[i * 2] = (color_p->full) >> 8;			 // Get high 8 bits
		data[i * 2 + 1] = (uint8_t)(color_p->full); // Get low 8 bits
		color_p++;									 // Point to next color value

		// If data volume exceeds 10000, send in batches
		if (Pixel > 10000)
		{
			if ((i + 1) % data_split == 0)
			{
				if ((i + 1) == data_split)
				{
					Lcd_WriteData_DMA(data, data_split * 2); // Send data via DMA
				}
				else
				{
					while (HAL_SPI_GetState(LCD_SPI) != HAL_SPI_STATE_READY);								   // Wait until SPI bus is idle
					uint8_t *temp = &data[((uint16_t)((i + 1) / data_split) - 1) * data_split * 2]; // Get remaining data
					Lcd_WriteData_DMA(temp, data_split * 2);										   // Send data via DMA
				}
			}
			else if (((i + 1) % data_split > 0) && ((i + 1) > data_split) && (i == (Pixel - 1)))
			{
				if ((uint16_t)((i + 1) / data_split) == 1)
				{
					while (HAL_SPI_GetState(LCD_SPI) != HAL_SPI_STATE_READY)
						;												   // Wait until SPI bus is idle
					uint8_t *temp = &data[data_split * 2];				   // Get remaining data
					Lcd_WriteData_DMA(temp, ((i + 1) % data_split) * 2); // Send data via DMA
				}
				else
				{
					while (HAL_SPI_GetState(LCD_SPI) != HAL_SPI_STATE_READY)
						;																			   // Wait until SPI bus is idle
					uint8_t *temp = &data[(uint16_t)((i + 1) / data_split) * data_split * 2]; // Get remaining data
					Lcd_WriteData_DMA(temp, ((i + 1) % data_split) * 2);
				}
			}
		}
	}

	if (Pixel <= 10000)
	{
		// If data to send is less than 10000*2 bytes, send all at once
		Lcd_WriteData_DMA(data, Pixel * 2);
	}

	LCD_SetWindows(0, 0, lcddev.width - 1, lcddev.height - 1); // Restore window to full screen
}

Other Open-Source Projects Recommended

Recommended Reading

1 Like