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.
- STM32 reads SHTC3 temp/humidity via hardware I²C: https://blog.zeruns.com/archives/692.html
- STM32F407 Std-Peripheral template with U8g2 ready: https://blog.zeruns.com/archives/722.html
- 0.96" OLED driver for STM32G4 (HAL, hw/sw I²C): https://blog.zeruns.com/archives/776.html
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
```
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.

## 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
- Open-sourced a 3-phase power monitor to easily track home power usage: https://blog.zeruns.com/archives/771.html
- STM32F407 Standard Peripheral Library template with U8g2 graphics library ported: https://blog.zeruns.com/archives/722.html
- WCH CH32V307VCT6 minimum system board open source: https://blog.zeruns.com/archives/726.html
- LM25118 auto buck-boost adjustable DC-DC power module: https://blog.zeruns.com/archives/727.html
- EG1164 high-power synchronous boost module open source, up to 97% efficiency: https://blog.zeruns.com/archives/730.html
- 4G environmental monitoring node (temperature, humidity, barometric pressure, etc.) based on Air700E, uploads to Alibaba Cloud IoT via MQTT: https://blog.zeruns.com/archives/747.html
- Smart electronic load based on CH32V307, open-sourced contest project: https://blog.zeruns.com/archives/785.html
Recommended Reading
- Cost-effective & Cheap VPS/Cloud Server Recommendations: https://blog.zeruns.com/archives/383.html
- Minecraft server setup tutorials: https://blog.zeruns.com/tag/mc/
- Ruideng RD6012P bench power supply quick unbox & review, 60V 12A CNC DC power supply: https://blog.zeruns.com/archives/740.html
- Bambu Lab P1SC 3D printer unboxing experience: https://blog.zeruns.com/archives/770.html
- Real-world comparison of capacitors & inductors from different brands and types (D, Q, ESR, X): https://blog.zeruns.com/archives/765.html
- Testing & teardown of a 120W charger bought for 2.6 RMB on Douyin Mall: https://blog.zeruns.com/archives/786.html





