LCD Development Guide_Rev1.0
1 Revision History
Version |
Date |
Author |
Reviewer |
Changes |
|---|---|---|---|---|
1.0 |
2023-12-7 |
Chenhz |
zlc |
Initial document |
1.1 |
2024-03-25 |
sxx |
Renamed document |
|
1.2 |
2024-03-28 |
Chenhz |
Added SPI interface parameter description, added refresh API description, updated API descriptions, updated pre-built LCD list |
|
1.3 |
2024-11-15 |
zw |
Updated document format, removed demo |
|
1.4 |
2026-04-29 |
zxq |
Updated based on latest code from separated base package |
2 Introduction
This document describes the LCD interface APIs for the LTE-EC71X. The API declarations are located in LSDK/components/kernel/lierda_api/liot_lcd/liot_lcd.h.
The LTE-EC71X series modules support LCD display output.
2.1 LCD Interface Overview
The LTE-EC71X series modules support driving an LCD through multiple interfaces (currently LSPI and SPI are available). The LSPI interface is specifically optimized for LCD driving, supporting up to 51 MHz, and supports 3-line-SPI and 2-data-lane modes to meet various LCD development requirements.
The LSPI interface is multiplexed with USP1 and USP2. For details, refer to:
Please check the DingTalk document attachment: Lierda NT26-FCN OpenCPU Pin Multiplexing Table.xlsx.
When USP1 is configured as Camera and USP2 as LSPI, accelerated camera preview on the LCD screen is supported.
2.2 LCD Display Coordinate Definition
The LCD supports rotated display. To facilitate development, the OpenCPU LCD API defines the LCD orientation coordinates as follows:
In the LCD default state, the horizontal pixel count is defined as width and the vertical pixel count as height. These values must be set in the LCD driver (see details below).
In the current display orientation, the horizontal direction is defined as the X-axis and the vertical direction as the Y-axis. The origin is at the top-left corner of the screen (may vary by screen).
Portrait mode:
Landscape mode:
3 API Function Overview
Function |
Description |
|---|---|
liot_lcd_init() |
LCD initialization |
liot_lcd_clear_screen() |
Full-screen refresh |
liot_lcd_draw_point() |
Draw a point |
liot_lcd_draw_line() |
Draw a line |
liot_lcd_draw_rectangle() |
Draw a rectangle |
liot_lcd_draw_circle() |
Draw a circle |
liot_lcd_write() |
Display an image |
liot_lcd_set_brightness() |
Set backlight brightness |
liot_lcd_display_on() |
Turn on display |
liot_lcd_display_off() |
Turn off display |
liot_lcd_sleep_in() |
Enter sleep mode |
liot_lcd_sleep_out() |
Exit sleep mode |
4 Type Definitions
4.1 liot_lcd_errcode_e
Error codes returned by LCD API calls.
Declaration
typedef enum {
LIOT_LCD_OK = 0,
LIOT_LCD_ERROR,
LIOT_LCD_NO_MEM,
LIOT_LCD_INVALID_PARAM,
LIOT_LCD_INVALID_HANDLE,
LIOT_LCD_INVALID_INTERFACE,
LIOT_LCD_LOCATION_OVERFLOW,
}liot_lcd_errcode_e;
Values
LIOT_LCD_OK: Success
LIOT_LCD_ERROR: Unknown error
LIOT_LCD_NO_MEM: Insufficient memory
LIOT_LCD_INVALID_PARAM: Invalid parameter
LIOT_LCD_INVALID_HANDLE: Invalid handle
LIOT_LCD_INVALID_INTERFACE: Invalid interface
LIOT_LCD_LOCATION_OVERFLOW: Coordinate out of bounds
5 API Function Details
5.1 liot_lcd_init
Initializes the LCD screen.
Declaration
liot_lcd_handle_t liot_lcd_init(liot_lcd_config_t *config);
Parameters
config: [in] LCD configuration parameters
Return Value
liot_lcd_handle_t — LCD handle
5.1.1 liot_lcd_config_t
LCD configuration structure definition
typedef liot_hal_lcd_config_t liot_lcd_config_t;
typedef struct{
liot_hal_lcd_interface_t interface;
liot_hal_lcdDev_t *lcdDev;
}liot_hal_lcd_config_t;
Parameters
Type |
Parameter |
Description |
|---|---|---|
liot_hal_lcd_interface_t |
interface |
LCD interface configuration |
liot_hal_lcdDev_t |
lcdDev |
LCD driver; the LCD driver interface is set here |
5.1.2 liot_lcd_interface_t
LCD interface configuration structure definition
typedef struct{
liot_hal_lcd_interface_type_e type;
union
{
liot_hal_lcd_interface_lspi_t lspi;
liot_hal_lcd_interface_spi_t spi;
liot_hal_lcd_interface_i2c_t i2c;
liot_hal_lcd_interface_8080_t l8080;
liot_hal_lcd_interface_8080_t l6800;
};
liot_hal_lcd_blk_t blk;
int8_t rst_pin;
}liot_hal_lcd_interface_t;
Parameters
Type |
Parameter |
Description |
|---|---|---|
liot_hal_lcd_interface_type_e |
interface |
LCD interface type |
liot_hal_lcd_interface_lspi_t |
lspi |
LSPI configuration |
liot_hal_lcd_interface_spi_t |
spi |
SPI configuration (supported, not yet complete) |
liot_hal_lcd_interface_i2c_t |
i2c |
I2C configuration (not yet supported) |
liot_hal_lcd_interface_8080_t |
l8080 |
8080 configuration (not yet supported) |
liot_hal_lcd_interface_6800_t |
l6800 |
6800 configuration (not yet supported) |
liot_hal_lcd_blk_t |
blk |
Backlight BLK configuration |
int8_t |
rst_pin |
LCD reset pin |
5.1.3 liot_lcd_interface_lspi_t
LSPI interface configuration structure definition
typedef struct{
liot_lspi_port_e num;
bool lcd_3_line_spi;
bool lcd_2_data_lane;
liot_hal_lspi_busspeed_e speed;
bool sync;
liot_lspi_cs_e cs;
liot_hal_lcd_event_cb cb;
}liot_hal_lcd_interface_lspi_t;
Parameters
Type |
Parameter |
Description |
|---|---|---|
liot_lspi_port_e |
num |
LSPI port number |
bool |
lcd_3_line_spi |
Enable 3-line SPI mode; affects whether the DCX pin is used |
bool |
lcd_2_data_lane |
Enable 2-data-lane mode; requires DCX pin to be enabled |
liot_hal_lspi_busspeed_e |
speed |
LSPI bus speed |
bool |
sync |
Enable synchronous mode (true) or asynchronous mode (false) |
liot_lspi_cs_e |
cs |
LSPI CS pin; specific pin support for EC718M |
liot_hal_lcd_event_cb |
cb |
LSPI event callback; triggered once per LSPI transfer completion |
5.1.4 liot_lcd_interface_spi_t
SPI interface configuration structure definition
typedef struct{
liot_spi_port_e num;
liot_spi_cpol_pol_e cpol;
liot_spi_cpha_pol_e cpha;
int8_t lcd_dc;
int8_t cs;
liot_spi_clk_e speed;
bool dma_en;
liot_hal_lcd_event_cb cb;
}liot_hal_lcd_interface_spi_t;
Parameters
Type |
Parameter |
Description |
|---|---|---|
liot_spi_port_e |
num |
SPI port number |
liot_spi_cpol_pol_e |
cpol |
SPI clock polarity |
liot_spi_cpha_pol_e |
cpha |
SPI clock phase |
int8_t |
lcd_dc |
LCD command/data pin |
cs |
cs |
SPI chip select pin |
liot_spi_clk_e |
speed |
SPI bus speed |
bool |
dma_en |
Enable SPI DMA (not yet complete) |
liot_hal_lcd_event_cb |
cb |
SPI event callback; triggered once per SPI+DMA transfer completion |
5.1.5 liot_lcd_blk_t
LCD backlight BLK configuration structure definition
typedef struct{
liot_hal_lcd_blk_type_e type;
int8_t pin;
liot_pwm_sel_e pwm_num;
}liot_hal_lcd_blk_t;
Parameters
Type |
Parameter |
Description |
|---|---|---|
liot_hal_lcd_blk_type_e |
type |
Backlight BLK type |
int8_t |
pin |
Backlight BLK pin |
liot_pwm_sel_e |
pwm_num |
PWM channel to use when backlight type is set to PWM |
5.1.6 liot_lcdDev_t
LCD driver configuration structure. For detailed usage, refer to the LCD Driver Addition Guide.
typedef struct{
liot_hal_lcdDev_func_t func;
liot_hal_lcdDev_info_t info;
}liot_hal_lcdDev_t;
Parameters
Type |
Parameter |
Description |
|---|---|---|
liot_hal_lcdDev_func_t |
func |
LCD driver functions |
liot_hal_lcdDev_info_t |
info |
LCD driver configuration info |
5.2 liot_lcd_clear_screen
Performs a full-screen refresh.
This API ultimately calls the full interface in liot_hal_lcdDev_func_t.
Declaration
liot_lcd_errcode_e liot_lcd_clear_screen(liot_lcd_handle_t handle,
uint16_t color);
Parameters
handle: [in] LCD handle
color: [in] Fill color
Return Value
liot_lcd_errcode_e: Error code, see section 4.1
5.3 liot_lcd_draw_point
Draws a single point on the LCD.
Declaration
liot_lcd_errcode_e liot_lcd_draw_point(liot_lcd_handle_t handle,
uint32_t x,
uint32_t y,
uint16_t color);
Parameters
handle: [in] LCD handle
x: [in] X-axis coordinate
y: [in] Y-axis coordinate
color: [in] Color
Return Value
liot_lcd_errcode_e: Error code, see section 4.1
5.4 liot_lcd_draw_line
Draws a line on the LCD.
Declaration
liot_lcd_errcode_e liot_lcd_draw_line(liot_lcd_handle_t handle,
uint16_t sx,
uint16_t sy,
uint16_t ex,
uint16_t ey,
uint16_t color);
Parameters
handle: [in] LCD handle
sx: [in] Start X coordinate
sy: [in] Start Y coordinate
ex: [in] End X coordinate
ey: [in] End Y coordinate
color: [in] Color
Return Value
liot_lcd_errcode_e: Error code, see section 4.1
5.5 liot_lcd_draw_rectangle
Draws a rectangle on the LCD.
Declaration
liot_lcd_errcode_e liot_lcd_draw_rectangle(liot_lcd_handle_t handle,
uint16_t sx,
uint16_t sy,
uint16_t ex,
uint16_t ey,
uint16_t color);
Parameters
handle: [in] LCD handle
sx: [in] Start X coordinate
sy: [in] Start Y coordinate
ex: [in] End X coordinate
ey: [in] End Y coordinate
color: [in] Color
Return Value
liot_lcd_errcode_e: Error code, see section 4.1
5.6 liot_lcd_draw_circle
Draws a circle on the LCD.
Declaration
liot_lcd_errcode_e liot_lcd_draw_circle(liot_lcd_handle_t handle,
uint16_t sx,
uint16_t sy,
uint16_t r,
uint16_t color);
Parameters
handle: [in] LCD handle
sx: [in] Circle center X coordinate
sy: [in] Circle center Y coordinate
r: [in] Radius
color: [in] Color
Return Value
liot_lcd_errcode_e: Error code, see section 4.1
5.7 liot_lcd_write
Displays an image on the LCD.
This API ultimately calls the fill interface in liot_hal_lcdDev_func_t.
Declaration
liot_lcd_errcode_e liot_lcd_write(liot_lcd_handle_t handle,
uint16_t sx,
uint16_t sy,
uint16_t ex,
uint16_t ey,
uint8_t *buf);
Parameters
handle: [in] LCD handle
sx: [in] Start X coordinate
sy: [in] Start Y coordinate
ex: [in] End X coordinate
ey: [in] End Y coordinate
buf: [in] Pointer to image data buffer
Return Value
liot_lcd_errcode_e: Error code, see section 4.1
5.8 liot_lcd_set_brightness
Sets the LCD backlight brightness.
When backlight type is LIOT_LCD_BACKLIGHT_PWM: brightness level can be set from 0 to 100.
When backlight type is LIOT_LCD_BACKLIGHT_GPIO: brightness can only be 0 (off) or 1 (on); any value greater than 1 is treated as 1.
When backlight type is LIOT_LCD_NO_BACKLIGHT: this setting has no effect.
Declaration
liot_lcd_errcode_e liot_lcd_set_brightness(liot_lcd_handle_t handle,
uint8_t level);
Parameters
handle: [in] LCD handle
level: [in] Brightness level, 0–100
Return Value
liot_lcd_errcode_e: Error code, see section 4.1
5.9 liot_lcd_display_on
Turns on the LCD display.
This API ultimately calls the display_on interface in liot_hal_lcdDev_func_t.
Declaration
liot_lcd_errcode_e liot_lcd_display_on(liot_lcd_handle_t handle);
Parameters
handle: [in] LCD handle
Return Value
liot_lcd_errcode_e: Error code, see section 4.1
5.10 liot_lcd_display_off
Turns off the LCD display.
This API ultimately calls the display_on interface in liot_hal_lcdDev_func_t.
Declaration
liot_lcd_errcode_e liot_lcd_display_off(liot_lcd_handle_t handle);
Parameters
handle: [in] LCD handle
Return Value
liot_lcd_errcode_e: Error code, see section 4.1
5.11 liot_lcd_sleep_in
Puts the LCD into sleep mode.
This API ultimately calls the sleep_in interface in liot_hal_lcdDev_func_t.
Declaration
liot_lcd_errcode_e liot_lcd_sleep_in(liot_lcd_handle_t handle);
Parameters
handle: [in] LCD handle
Return Value
liot_lcd_errcode_e: Error code, see section 4.1
5.12 liot_lcd_sleep_out
Wakes the LCD from sleep mode.
This API ultimately calls the sleep_in interface in liot_hal_lcdDev_func_t.
Declaration
liot_lcd_errcode_e liot_lcd_sleep_out(liot_lcd_handle_t handle);
Parameters
handle: [in] LCD handle
Return Value
liot_lcd_errcode_e: Error code, see section 4.1
5.13 liot_lcd_refresh
Refreshes the LCD display. This is an optional API, applicable to screens that require manual refresh, such as an SSD1306 with a frame buffer.
This API ultimately calls the refresh interface in liot_hal_lcdDev_func_t.
Declaration
liot_lcd_errcode_e liot_lcd_refresh(liot_lcd_handle_t handle);
Parameters
handle: [in] LCD handle
Return Value
liot_lcd_errcode_e: Error code, see section 4.1
6 LCD Driver Addition Guide
To better support LCD development, the LTE-EC71X series OpenCPU SDK includes pre-built drivers for several common LCD panels. To support custom LCD screens, the SDK also allows adding custom LCD drivers.
6.1 Pre-built LCD Drivers
The pre-built LCD drivers are located at LSDK/components/driver/lcd/src/. The currently pre-built drivers are listed below. All values are development/test data — refer to actual hardware for production use:
LCD Driver IC |
Interface Support |
Color Format |
Resolution |
|---|---|---|---|
ST7789 |
LSPI/SPI |
RGB565 |
320*240 |
GC9A01 |
LSPI/SPI |
RGB565 |
240*240 |
ST7735 |
LSPI/SPI |
RGB565 |
160*128 |
SSD1306 |
SPI |
Monochrome |
128*64 |
GC9D01 |
LSPI/SPI |
RGB565 |
160*160 |
ST7567 |
LSPI/SPI |
Monochrome |
128*64 |
6.2 Adding a Custom LCD Driver
6.2.1 Create the LCD Driver Source File
Create a new LCD driver source file. It can be placed in the pre-built driver path or in the user project folder. Either way, it must be declared in the corresponding folder’s Makefile so the source file is compiled correctly.
Using the pre-built driver path LSDK/components/driver/lcd/src/ as an example: after placing Custom_lcd.c in that directory, add the corresponding object file Custom_lcd.o to the Makefile at LSDK/components/driver/lcd/Makefile:
C++LCDDRV_DIR := $(DRIVER_DIR)/lcd
LCDDRV_SRC := $(LCDDRV_DIR)/src
CFLAGS_INC += -I $(TOP)/$(LCDDRV_DIR)/inc
LCDDRV_COBJS += $(LCDDRV_SRC)/liot_lcdDev_GC9A01.o
LCDDRV_COBJS += $(LCDDRV_SRC)/liot_lcdDev_GC9D01.o
LCDDRV_COBJS += $(LCDDRV_SRC)/liot_lcdDev_SSD1306.o
LCDDRV_COBJS += $(LCDDRV_SRC)/liot_lcdDev_ST7567.o
LCDDRV_COBJS += $(LCDDRV_SRC)/liot_lcdDev_ST7735.o
LCDDRV_COBJS += $(LCDDRV_SRC)/liot_lcdDev_ST7789.o
LCDDRV_COBJS += $(LCDDRV_SRC)/Custom_lcd.o
LCDDRV_COBJS := $(addprefix $(BUILDDIR)/, $(LCDDRV_COBJS))
ifeq ($(TOOLCHAIN),GCC)
libusr-y += lib_lcddrv.a
$(BUILDDIR)/lib/libusr/lib_lcddrv.a: $(LCDDRV_COBJS)
@mkdir -p $(dir $@)
$(ECHO) AR $@
$(Q)$(AR) -cr $@ $^
endif
6.2.2 Define the LCD Driver Structure
LCD driver configuration is managed through the liot_hal_lcdDev_t structure, which holds all interfaces and parameters.
6.2.2.1 liot_hal_lcdDev_t
LCD driver configuration structure
This structure has two parts: LCD driver functions and LCD driver info. The driver functions define the functions used to control the LCD; the driver info records the resolution, display orientation, and other properties.
typedef struct{
liot_hal_lcdDev_func_t func;
liot_hal_lcdDev_info_t info;
}liot_hal_lcdDev_t;
Parameters
Type |
Parameter |
Description |
|---|---|---|
liot_hal_lcdDev_func_t |
func |
LCD driver functions |
liot_hal_lcdDev_info_t |
info |
LCD driver configuration info |
6.2.2.2 liot_hal_lcdDev_info_t
LCD driver info structure
The resolution, orientation, and color format displayed at runtime are all controlled by the values in this structure.
typedef struct{
uint16_t id;
uint32_t interface;
uint32_t width;
uint32_t height;
liot_hal_lcdDev_dir_e direction;
liot_hal_lcdDev_color_depth_e color_depth;
}liot_hal_lcdDev_info_t;
Parameters
Type |
Parameter |
Description |
|---|---|---|
uint16_t |
id |
LCD driver ID |
uint32_t |
interface |
Supported interfaces, recorded via bitwise OR |
uint32_t |
width |
Horizontal pixel count in the default (unrotated) state |
uint32_t |
height |
Vertical pixel count in the default (unrotated) state |
liot_hal_lcdDev_dir_e |
direction |
Counter-clockwise rotation direction for the display |
liot_hal_lcdDev_color_depth_e |
color_depth |
LCD color depth |
6.2.2.3 liot_hal_lcdDev_func_t
LCD driver function structure
To ensure a custom LCD works correctly, the user must define the required functions in this structure. Because different I2C LCD screens transmit commands and data differently, custom interfaces are provided for user-defined implementations.
typedef struct{
int (*init)(liot_hal_lcd_handle_t handle);
int (*addrSet)(liot_hal_lcd_handle_t handle, uint16_t sx, uint16_t sy,
uint16_t ex, uint16_t ey);
int (*fill)(liot_hal_lcd_handle_t handle, uint16_t sx, uint16_t sy,
uint16_t ex, uint16_t ey, void* buf);
int (*full)(liot_hal_lcd_handle_t handle, uint16_t sx, uint16_t sy,
uint16_t ex, uint16_t ey, uint16_t color);
int (*strWrite)(liot_hal_lcd_handle_t handle, uint16_t sx, uint16_t sy,
uint16_t ex, uint16_t ey, char* str);
int (*display_on)(liot_hal_lcd_handle_t handle, bool on);
int (*sleep_in)(liot_hal_lcd_handle_t handle, bool in);
int (*refresh)(liot_hal_lcd_handle_t handle);
// The following are user-defined interfaces for I2C LCD screens.
// If non-NULL, they replace the default internal implementations.
int (*custom_i2c_cmd_send)(liot_hal_lcd_handle_t handle, uint8_t *cmd,
uint32_t len);
int (*custom_i2c_data_send)(liot_hal_lcd_handle_t handle,
uint8_t *data, uint32_t len);
}liot_hal_lcdDev_func_t;
Parameters
Type |
Required |
Description |
|---|---|---|
int (*init) |
Required |
LCD driver initialization; configures LCD registers |
int (*addrSet) |
Required |
Set display address window |
int (*fill) |
Required |
Display an image in a specified region |
int (*full) |
Required |
Fill a specified region with a single color |
int (*strWrite) |
Optional |
Display text in a specified region; for LCDs with built-in font ROM |
int (*display_on) |
Optional |
Turn display on/off |
int (*sleep_in) |
Optional |
Enter/exit sleep mode |
int (*refresh) |
Optional |
Refresh display; for screens with a frame buffer, e.g. SSD1306 |
int (*custom_i2c_cmd_send) |
Optional |
User-defined I2C command send interface |
int (*custom_i2c_data_send) |
Optional |
User-defined I2C data send interface |
6.2.2.4 Filling in the LCD Driver Structure
Using the pre-built ST7789 driver as an example, fill in the driver info and functions according to the actual LCD screen being used:
liot_hal_lcdDev_t liot_st7789_dev = {
.func = {
.init = liot_st7789_init,
.addrSet = liot_st7789_addrset,
.fill = liot_st7789_fill,
.full = liot_st7789_full,
.strWrite = NULL,
.display_on = liot_st7789_display_on,
.sleep_in = liot_st7789_sleep_in,
.refresh = NULL,
},
.info = {
.id = 0x7789,
.interface = LIOT_LCD_INTERFACE_LSPI |
LIOT_LCD_INTERFACE_SPI |
LIOT_LCD_INTERFACE_8080,
.width = 240,
.height = 320,
.direction = LIOT_LCD_DIR_90_ANGLE,
.color_depth = LIOT_LCD_COLOR_RGB565,
},
};
6.2.3 Writing the LCD Driver Functions
Define the functions required by the LCD driver inside liot_hal_lcdDev_func_t. These functions are called when the user invokes liot_lcd_init, liot_lcd_write, and other APIs.
Using the pre-built ST7789 driver as an example, the init function is shown below. liot_hal_lcd_write_cmd and liot_hal_lcd_transmit_cmd are HAL LCD APIs defined in LSDK/components/driver/lcd/inc/liot_lcdDev.h.
static int liot_st7789_init(liot_hal_lcd_handle_t handle)
{
liot_hal_lcd_write_cmd(handle, 0x11, 0x00);
switch(liot_st7789_dev.info.direction)
{
case LIOT_LCD_DIR_0_ANGLE:
liot_hal_lcd_write_cmd(handle, 0x36, 0x00); break;
case LIOT_LCD_DIR_90_ANGLE:
liot_hal_lcd_write_cmd(handle, 0x36, 0x70); break;
case LIOT_LCD_DIR_180_ANGLE:
liot_hal_lcd_write_cmd(handle, 0x36, 0xA0); break;
case LIOT_LCD_DIR_270_ANGLE:
liot_hal_lcd_write_cmd(handle, 0x36, 0xC0); break;
default: liot_hal_lcd_write_cmd(handle, 0x36, 0x00); break;
}
if(((liot_hal_lcd_config_t*)handle)->interface.type == LIOT_LCD_INTERFACE_LSPI &&
((liot_hal_lcd_config_t*)handle)->interface.lspi.lcd_2_data_lane == true)
{
liot_hal_lcd_write_cmd(handle, 0xE7, 0x10);
}
liot_hal_lcd_write_cmd(handle, 0x21, 0x00);
liot_hal_lcd_write_cmd(handle, 0x3A, 0x05);
uint8_t set_rate_cmd[] = {0x0c,0x0c,0x00,0x33,0x33};
liot_hal_lcd_transmit_cmd(handle, 0xB2, set_rate_cmd, sizeof(set_rate_cmd));
liot_hal_lcd_write_cmd(handle, 0xB7, 0x35);
liot_hal_lcd_write_cmd(handle, 0xBB, 0x20);
liot_hal_lcd_write_cmd(handle, 0xC0, 0x2C);
liot_hal_lcd_write_cmd(handle, 0xC2, 0x01);
liot_hal_lcd_write_cmd(handle, 0xC3, 0x0B);
liot_hal_lcd_write_cmd(handle, 0xC4, 0x20);
liot_hal_lcd_write_cmd(handle, 0xC6, 0x0F);
uint8_t reg_PWCTRL1[] = {0xa4,0xa1};
liot_hal_lcd_transmit_cmd(handle, 0xD0, reg_PWCTRL1, sizeof(reg_PWCTRL1));
uint8_t reg_PVGAMCTRL[] = {0xd0,0x03,0x09,0x0e,0x11,0x3d,0x47,0x55,
0x53,0x1a,0x16,0x14,0x1f,0x22}; // Positive voltage gamma
liot_hal_lcd_transmit_cmd(handle, 0xE0, reg_PVGAMCTRL, sizeof(reg_PVGAMCTRL));
uint8_t reg_NVGAMCTRL[] = {0xd0,0x02,0x08,0x0d,0x12,0x2c,0x43,0x55,
0x53,0x1e,0x1b,0x19,0x20,0x22}; // Negative voltage gamma
liot_hal_lcd_transmit_cmd(handle, 0xE1, reg_NVGAMCTRL, sizeof(reg_NVGAMCTRL));
liot_hal_lcd_write_cmd(handle, 0x29, 0x00);
return 0;
}
6.2.4 Declare and Reference the LCD Driver Structure
After the driver structure is created and filled in, declare it in the user project:
LIOT_ADD_DISPLAY(liot_st7789_dev);
When calling liot_lcd_init, set the lcdDev member of liot_lcd_config_t to the declared driver structure:
liot_lcd_config_t cfg = {
.interface = {
.type = LIOT_LCD_INTERFACE_LSPI,
.lspi.num = LIOT_LSPI_PORT2,
.lspi.lcd_3_line_spi = false,
.lspi.lcd_2_data_lane = false,
.lspi.speed = LIOT_LSPI_51MHZ,
.lspi.cb = lcd_event_callback,
.blk.type = LIOT_LCD_BACKLIGHT_PWM,
.blk.pin = 102,
.blk.pwm_num = LIOT_PWM_3,
.rst_pin = 49,
},
.lcdDev = liot_st7789_dev,
};
liot_lcd_handle_t lcd = liot_lcd_init(&cfg);
The custom LCD driver is now complete and ready to use with the LCD APIs.
7 Demo
7.1 Demo Source Code
/**
* @File Name: liot_lcd_demo.c
* @brief LCD display demo, demonstrates the complete usage flow of the liot_lcd framework
* @Author : Chenhz
* @Email : ciot_iot_support@lierda.com
* @Version : 1.0
* @Creat Date : 2023-11-29
*
* @copyright Copyright (c) 2023 Lierda Science & Technology Group Co., Ltd.
*
* Usage flow:
* 1. Declare the LCD driver with the LIOT_ADD_DISPLAY macro
* 2. Construct liot_lcd_config_t (interface type / pins / backlight / reset, etc.)
* 3. Initialize the LCD with liot_lcd_init
* 4. Call drawing APIs (clear_screen / draw_line / draw_circle / write, etc.)
* 5. Call liot_lcd_refresh to push the frame buffer to the screen
*/
#include <stdio.h>
#include <string.h>
#include "cmsis_os2.h"
#include "stdlib.h"
#include "lierda_app_main.h"
#include "liot_os.h"
#include "liot_gpio.h"
#include "slpman.h"
#include "fastmath.h"
#include "liot_lcd.h"
/* Lierda Logo image data, format: RGB565, size: 100x60 pixels
* Generated by an image-to-array tool; each 2 bytes represent 1 pixel (R5-G6-B5)
* Total size = 100 * 60 * 2 = 12000 bytes
*/
// 100*60
// const uint8_t Lierda_logo[12000] = { ... }; // Add image data here
/* ================= LCD Driver Declaration =================
* LIOT_ADD_DISPLAY macro: registers an LCD driver instance with the framework,
* which automatically links the corresponding driver code.
* Multiple drivers can be declared simultaneously; select which one to use
* at runtime via the lcdDev pointer.
* Each driver corresponds to one LCD controller IC, e.g.:
* liot_st7789_dev - ST7789 (common 1.3" / 1.54" color screen)
* liot_gc9a01_dev - GC9A01 (common 1.28" round screen)
* liot_st7735_dev - ST7735 (common 0.96" / 1.8" color screen)
* liot_ssd1306_12864_dev - SSD1306 (0.96" monochrome OLED, 128x64)
* liot_gc9d01_dev - GC9D01 (small color screen)
* liot_st7567_dev - ST7567 (monochrome LCD)
* liot_gc9307_dev - GC9307 (common 2.4" color screen)
*/
LIOT_ADD_DISPLAY(liot_st7789_dev);
LIOT_ADD_DISPLAY(liot_gc9a01_dev);
LIOT_ADD_DISPLAY(liot_st7735_dev);
LIOT_ADD_DISPLAY(liot_ssd1306_12864_dev);
LIOT_ADD_DISPLAY(liot_gc9d01_dev);
LIOT_ADD_DISPLAY(liot_st7567_dev);
LIOT_ADD_DISPLAY(liot_gc9307_dev);
/* ================= Interface and Hardware Configuration =================
* LIOT_LCD_DEMO_TEST_LSPI: use LSPI interface (EC718-specific LCD interface, supports DMA2D acceleration)
* LIOT_LCD_DEMO_TEST_SPI: use standard SPI interface (more portable, but slower)
* Only one can be selected at a time; switch with 1/0.
* Note: EC716 series does not support LSPI — must use SPI.
*/
#define LIOT_LCD_DEMO_TEST_LSPI 1 // Use LSPI interface; EC716 series does not support LSPI
#define LIOT_LCD_DEMO_TEST_SPI 0 // Use SPI interface
/* LCD count: 1 = single screen, 2 = dual screen (dual screen currently supports LSPI only)
* For dual screen, prepare a second cfg1 configuration and enable the
* #if LIOT_LCD_DEMO_NUM >= 2 branches in the code.
*/
#define LIOT_LCD_DEMO_NUM 1 // Number of LCDs to drive; 1 or 2 (dual screen supports LSPI only)
/* LSPI transfer-complete callback (triggered when DMA async transfer finishes)
* Currently empty; add post-transfer processing here if needed.
* Note: this function executes in interrupt context — do not perform time-consuming operations.
*/
static void lcd_event_callback(void)
{
}
void liot_lcd_demo_thread(void *argv)
{
uint16_t i = 0, j = 0, k = 0;
uint32_t lcdticks = 0;
uint32_t width = 0, height = 0;
/* ====== Step 1: Select LCD driver ======
* Point to one of the driver instances declared with LIOT_ADD_DISPLAY above.
* Different ICs have different init sequences, resolutions, and orientations —
* the driver handles these internally.
* To switch screens, change only this line.
*/
liot_hal_lcdDev_t *lcdDev = &liot_gc9307_dev;
/* ====== Step 2: Build LCD configuration ======
* interface: communication interface configuration
* .type - interface type: LIOT_LCD_INTERFACE_LSPI (high speed) or LIOT_LCD_INTERFACE_SPI (general)
* .lspi.num - LSPI port: EC718 supports LSPI_PORT0/PORT1/PORT2; must match hardware pins
* .lspi.lcd_3_line_spi - 3-wire SPI mode (no DC pin, uses 9-bit commands); false = 4-wire (with DC pin)
* .lspi.lcd_2_data_lane - dual data lane (requires hardware support); false = single lane
* .lspi.speed - LSPI clock frequency: LIOT_LSPI_25MHZ etc.; higher = faster refresh, but LCD IC must support it
* .lspi.sync - true = synchronous (blocks until complete); false = async (DMA, use with callback)
* .lspi.cb - async transfer-complete callback; valid when sync = false
* .lspi.cs - chip select: LIOT_LSPI_CS0/CS1; use CS1 for the second screen in dual-screen mode
* blk: backlight configuration
* .type - LIOT_LCD_BACKLIGHT_PWM (PWM dimming) or LIOT_LCD_BACKLIGHT_GPIO (on/off)
* .pin - backlight control pin number
* .pwm_num - PWM channel number (valid when type = PWM)
* rst: reset configuration
* .pin - RST pin number
* .delay - delay after reset (ms), to wait for the LCD IC to stabilize
*/
liot_lcd_config_t cfg = {
.interface = {
.type = LIOT_LCD_INTERFACE_LSPI,
.lspi.num = LIOT_LSPI_PORT2,
.lspi.lcd_3_line_spi = false,
.lspi.lcd_2_data_lane = false,
.lspi.speed = LIOT_LSPI_25MHZ,
.lspi.sync = true,
.lspi.cb = lcd_event_callback,
.lspi.cs = LIOT_LSPI_CS0,
.blk.type = LIOT_LCD_BACKLIGHT_PWM,
.blk.pin = 100,
.blk.pwm_num = LIOT_PWM_0,
.rst.pin = 49,
.rst.delay = 100,
},
.lcdDev = lcdDev,
};
/* ====== Step 3: Power-on delay + power configuration ======
* osDelay(5000): wait for system to stabilize and all peripherals to initialize
* liot_aon_power_on(): enable AON (Always-On) domain power; some modules power the LCD from AON
* liot_gpio_set_voltage: set GPIO voltage to 3.3 V; must match LCD IO level
* slpManSetPmuSleepMode: disable deep sleep to prevent the MCU from sleeping during LCD refresh
*/
osDelay(5000);
liot_aon_power_on();
liot_gpio_set_voltage(LIOT_VOL_3_30V);
slpManSetPmuSleepMode(true, SLP_ACTIVE_STATE, false);
/* ====== Step 4: Initialize LCD ======
* liot_lcd_init: initializes the interface, reset, backlight, and sends IC init commands based on cfg
* Returns an lcd handle; all subsequent drawing APIs operate through this handle.
*/
liot_lcd_handle_t lcd = liot_lcd_init(&cfg);
#if LIOT_LCD_DEMO_NUM >= 2
liot_lcd_handle_t lcd1 = liot_lcd_init(&cfg1);
#endif
/* ====== Step 5: Get effective screen resolution ======
* Adjust width/height based on screen rotation (0°/90°/180°/270°).
* 0°/180°: use original width/height; 90°/270°: swap width and height.
* All subsequent drawing coordinates are calculated from this width/height.
*/
if(lcdDev->info.direction == LIOT_LCD_DIR_0_ANGLE || lcdDev->info.direction == LIOT_LCD_DIR_180_ANGLE)
{
width = lcdDev->info.width;
height = lcdDev->info.height;
}
else
{
width = lcdDev->info.height;
height = lcdDev->info.width;
}
/* ====== Step 6: Backlight brightness fade test ======
* liot_lcd_set_brightness: set backlight brightness (0–100); only effective when backlight type is PWM
* Steps: 100 → 80 → 60 → 40 → 20, 1 second per step, observe brightness fade
*/
liot_lcd_set_brightness(lcd, 100);
liot_rtos_task_sleep_ms(1000);
liot_lcd_set_brightness(lcd, 80);
liot_rtos_task_sleep_ms(1000);
liot_lcd_set_brightness(lcd, 60);
liot_rtos_task_sleep_ms(1000);
liot_lcd_set_brightness(lcd, 40);
liot_rtos_task_sleep_ms(1000);
liot_lcd_set_brightness(lcd, 20);
liot_rtos_task_sleep_ms(1000);
#if LIOT_LCD_DEMO_NUM >= 2
liot_lcd_set_brightness(lcd1, 100);
liot_rtos_task_sleep_ms(1000);
liot_lcd_set_brightness(lcd1, 80);
liot_rtos_task_sleep_ms(1000);
liot_lcd_set_brightness(lcd1, 60);
liot_rtos_task_sleep_ms(1000);
liot_lcd_set_brightness(lcd1, 40);
liot_rtos_task_sleep_ms(1000);
liot_lcd_set_brightness(lcd1, 20);
liot_rtos_task_sleep_ms(1000);
#endif
/* ====== Step 7: Full-screen color fill test ======
* liot_lcd_clear_screen: fills the frame buffer with the specified color (does not push to screen immediately)
* liot_lcd_refresh: sends the frame buffer to the LCD GRAM via LSPI/SPI; screen updates after this call
* Sequence: Red → Green → Blue → White; verifies all three RGB channels
* Color macros defined in liot_lcd.h: RED=0xF800, GREEN=0x07E0, BLUE=0x001F, WHITE=0xFFFF
*/
liot_lcd_clear_screen(lcd, RED);
liot_lcd_refresh(lcd);
liot_rtos_task_sleep_ms(1000);
liot_lcd_clear_screen(lcd, GREEN);
liot_lcd_refresh(lcd);
liot_rtos_task_sleep_ms(1000);
liot_lcd_clear_screen(lcd, BLUE);
liot_lcd_refresh(lcd);
liot_rtos_task_sleep_ms(1000);
liot_lcd_clear_screen(lcd, WHITE);
liot_lcd_refresh(lcd);
liot_rtos_task_sleep_ms(1000);
#if LIOT_LCD_DEMO_NUM >= 2
liot_lcd_clear_screen(lcd1, GREEN);
liot_lcd_refresh(lcd1);
liot_rtos_task_sleep_ms(1000);
liot_lcd_clear_screen(lcd1, BLUE);
liot_lcd_refresh(lcd1);
liot_rtos_task_sleep_ms(1000);
liot_lcd_clear_screen(lcd1, RED);
liot_lcd_refresh(lcd1);
liot_rtos_task_sleep_ms(1000);
liot_lcd_clear_screen(lcd1, WHITE);
liot_lcd_refresh(lcd1);
liot_rtos_task_sleep_ms(1000);
#endif
/* ====== Step 8: Line drawing test ======
* liot_lcd_draw_line: draws a line in the frame buffer (start to end, with specified color)
* Draws two lines: horizontal center line + vertical center line, dividing the screen into four quadrants
*/
liot_lcd_draw_line(lcd, 0, height / 2, width, height / 2, BLUE);
liot_lcd_draw_line(lcd, width / 2, 0, width / 2, height, BLUE);
liot_lcd_refresh(lcd);
#if LIOT_LCD_DEMO_NUM >= 2
liot_lcd_draw_line(lcd1, 0, height / 2, width, height / 2, BLUE);
liot_lcd_draw_line(lcd1, width / 2, 0, width / 2, height, BLUE);
liot_lcd_refresh(lcd1);
#endif
/* ====== Step 9: Display image ======
* liot_lcd_write: writes RGB565 image data into the frame buffer at the specified region
* Parameters: (handle, start_x, start_y, end_x, end_y, image_data_pointer)
* Coordinate range: 0 to (width/height - 1); for a 100x60 image, x: 0–99, y: 0–59
* SSD1306 is a monochrome screen — RGB565 images are not applicable, so it is skipped
*/
if(lcdDev != &liot_ssd1306_12864_dev)
liot_lcd_write(lcd, 0, 0, 100-1, 60-1, (uint8_t *)Lierda_logo);
#if LIOT_LCD_DEMO_NUM >= 2
if(lcdDev != &liot_ssd1306_12864_dev)
liot_lcd_write(lcd1, 0, 0, 100-1, 60-1, (uint8_t *)Lierda_logo);
#endif
/* ====== Step 10: Sine wave drawing ======
* Calculates sine y values point by point and connects them with lines to form an animated sine wave
* Formula: y = (height/2) * sin(1/(width/4) * π * i) + height/2
* sin() argument is in radians; 3.14 approximates π; width/4 controls the number of wave cycles
* 10 ms delay per point creates a left-to-right drawing animation
* k stores the previous y value for line connection
*/
for(i = 0; i < width; i++)
{
j = ( (float)((height) / 2) * (float)sin((float)( (float)1 / (float)(width / 4) ) * 3.14 * i)) + ((height) / 2);
if(i != 0) liot_lcd_draw_point(lcd, i, j, BLUE);
#if LIOT_LCD_DEMO_NUM >= 2
if(i != 0) liot_lcd_draw_point(lcd1, i, j, RED);
#endif
liot_lcd_draw_line(lcd, i, k, i, j, BLUE);
#if LIOT_LCD_DEMO_NUM >= 2
liot_lcd_draw_line(lcd1, i, k, i, j, RED);
#endif
k = j;
liot_lcd_refresh(lcd);
#if LIOT_LCD_DEMO_NUM >= 2
liot_lcd_refresh(lcd1);
#endif
osDelay(10);
}
/* ====== Step 11: Draw circles and rectangles ======
* liot_lcd_draw_circle: draws a hollow circle (center_x, center_y, radius, color)
* liot_lcd_draw_rectangle: draws a hollow rectangle (top_left_x, top_left_y, bottom_right_x, bottom_right_y, color)
* Centered on the screen, draws two concentric circles (r=30 and r=60) and two rectangles (30x30 and 60x60)
*/
liot_lcd_draw_circle(lcd, width / 2, height / 2, 30, GREEN);
liot_lcd_draw_circle(lcd, width / 2, height / 2, 60, GREEN);
liot_lcd_draw_rectangle(lcd, (width / 2) - 30, (height / 2) - 30, (width / 2) + 30, (height / 2) + 30, BLACK);
liot_lcd_draw_rectangle(lcd, (width / 2) - 60, (height / 2) - 60, (width / 2) + 60, (height / 2) + 60, BLACK);
liot_lcd_refresh(lcd);
#if LIOT_LCD_DEMO_NUM >= 2
liot_lcd_draw_circle(lcd1, width / 2, height / 2, 30, BLUE);
liot_lcd_draw_circle(lcd1, width / 2, height / 2, 60, BLUE);
liot_lcd_draw_rectangle(lcd1, (width / 2) - 30, (height / 2) - 30, (width / 2) + 30, (height / 2) + 30, BLACK);
liot_lcd_draw_rectangle(lcd1, (width / 2) - 60, (height / 2) - 60, (width / 2) + 60, (height / 2) + 60, BLACK);
liot_lcd_refresh(lcd1);
#endif
liot_rtos_task_sleep_ms(3000);
/* ====== Step 12: Frame rate test ======
* Cycles Red → Green → Blue full-screen fills and measures time per frame and FPS
* liot_rtos_get_running_time: returns system uptime in ms
* FPS = 1000 / frame_time_ms
* Note: screen will flash during this test
* Factors affecting frame rate: LSPI clock / dual data lane / screen resolution / sync vs async
*/
while (1)
{
for(i = 0; i < 3; i++)
{
lcdticks = liot_rtos_get_running_time();
if(i == 0)
{
liot_lcd_clear_screen(lcd, RED);
liot_lcd_refresh(lcd);
#if LIOT_LCD_DEMO_NUM >= 2
liot_lcd_clear_screen(lcd1, GREEN);
liot_lcd_refresh(lcd1);
#endif
}
else if(i == 1)
{
liot_lcd_clear_screen(lcd, GREEN);
liot_lcd_refresh(lcd);
#if LIOT_LCD_DEMO_NUM >= 2
liot_lcd_clear_screen(lcd1, BLUE);
liot_lcd_refresh(lcd1);
#endif
}
else if(i == 2)
{
liot_lcd_clear_screen(lcd, BLUE);
liot_lcd_refresh(lcd);
#if LIOT_LCD_DEMO_NUM >= 2
liot_lcd_clear_screen(lcd1, RED);
liot_lcd_refresh(lcd1);
#endif
}
lcdticks = liot_rtos_get_running_time() - lcdticks;
liot_trace("LCD loop %dms, %dFPS", lcdticks, lcdticks == 0 ? 0 : 1000/lcdticks);
}
}
}
7.2 Demo Behavior
After the demo runs normally, the following sequence is displayed:
7.2.1 Brightness Test
Backlight brightness decreases step by step from 100% to 20%
1 second per step: 100% → 80% → 60% → 40% → 20%
7.2.2 Crosshair Display
Clear screen to white
Draw a blue crosshair at the center of the screen
Refresh display
7.2.3 Logo and Geometric Shapes
Display Lierda Logo (100×60) in the top-left corner
Draw two concentric green circles at the center (radius 30 and 60)
Draw two concentric black rectangles
Refresh display and hold for 3 seconds
7.2.4 Cycling Color Test
Switch full-screen color every 1 second
Sequence: Red → Green → Blue → White → repeat

