Template: LCD2 for Pico use

From Waveshare Wiki
Jump to: navigation, search

Hardware Connection

When connecting the Pico/Pico2, please be careful not to reverse the corresponding direction. You can judge the direction by observing the end with a USB silkscreen on the module and the USB port end of the Pico/Pico2 (you can also judge the direction by the pin number of the female connector on the module and the pin number of the Pico/Pico2).
You can connect the display according to the table.

Pico connection pin correspondence
LCD Pico/Pico2 Function
VCC VSYS Power input
GND GND Ground
DIN GP11 SPI communication MOSI pin, slave device data input
CLK GP10 SPI communication SCK pin, slave device clock input
CS GP9 SPI chip select pin (active low)
DC GP8 Data/command control pin (high for data, low for command)
RST GP12 External reset pin (active low)
BL GP13 Backlight control
KEY0 GP15 User key KEY0
KEY1 GP17 User key KEY1
KEY2 GP2 User key KEY2
KEY3 GP3 User key KEY3

Pico-LCD-2-connect.jpg

Basic Introduction

Demo Download

Open the Raspberry Pi terminal and execute:
Method 1: Download from our official website, we recommend this method.

sudo apt-get install p7zip-full
cd ~
sudo wget https://files.waveshare.com/wiki/common/Pico_code.7z
7z x Pico_code.7z -o./Pico_code
cd ~/Pico_code
cd c/build/

Demo

C

  • The following tutorial is for operating on a Raspberry Pi, but due to the multi-platform and portable characteristics of cmake, it can also be successfully compiled on a PC, with some differences in operation that you will need to judge on your own.
  • For compilation, please make sure that it is in the c directory:
cd ~/Pico_code/c/

Create and enter the build directory, and add the SDK: Here ../../pico-sdk is the directory of your SDK. There is a build in the demo, which can be accessed directly

cd build
export PICO_SDK_PATH=../../pico-sdk
(Note: Make sure to write the correct path to your own SDK)

Execute cmake, automatically generate the Makefile file:

#Pico
cmake -DPICO_BOARD=pico -DPICO_PLATFORM=rp2040 ..
#Pico2
cmake -DPICO_BOARD=pico2 -DPICO_PLATFORM=rp2350 ..

Open the main.c file in the c folder, you can modify the example program as needed, which can drive the company's Pico series display, and the source code will be updated continuously. Please choose the corresponding LCD or OLED test function, and comment out the irrelevant functions.
800px-Pico-lcd-0.96-img-main.png
Execute make to generate an executable file, the first compilation takes a long time

make -j9

After the compilation is completed, the uf2 file will be generated. Press and hold the button on the Pico/Pico2 board, connect Pico/Pico2 to the USB port of the Raspberry Pi via Micro USB cable, then release the button. After connecting, the Raspberry Pi will automatically recognize a removable disk, and copy the main.uf2 file in the build folder to the recognized removable disk.

#Pico
cp main.uf2 /media/pi/RPI-RP2/
#Pico2
cp main.uf2 /media/pi/RP2350

Python

Working with Windows

  • 1. Press and hold the BOOTSET button on the Pico board, connect the Pico/Pico2 to the USB port of the computer via the Micro USB cable, and release the button after the computer recognizes a removable hard disk.
  • 2. Copy the .uf2 format file from the Python directory to the recognized removable drive
  • 3. Open Thonny IDE (Note: Make sure to use the latest version of Thonny, otherwise there is no support package for Pico, the latest version under Windows is v3.3.3)
  • 4. Click Tools > Settings - > Interpreter and select Pico/Pico2 and the corresponding port as shown in the figure

Pico-lcd-0.96-img-config.png

  • 5. File -> Open -> python/Pico-LCD-2/Pico-LCD-2.py, click Run, as shown in the following figure:

Pico-lcd-0.96-img-run.png
This demo provides a simple program.

Working with Raspberry Pi

  • The process of firmware flashing is the same as on Windows. You can choose to copy the. uf2 format file to Pico/Pico2 on your PC or Raspberry Pi.
  • 2. Open the Thonny IDE on the Raspberry Pi (click on Raspberry Pi logo -> Programming -> Thonny Python IDE), you can view the version information by going to Help->About Thonny

To ensure your version is supported by the Pico package, you can also click on Tools -> Options... -> Interpreter and select MicroPython (Raspberry Pi Pico and ttyACM0 port)
As shown below:
Pico-lcd-0.96-img-config2.png
If your current Thonny version does not have the pico support package, input the following command to update the Thonny IDE

sudo apt upgrade thonny
  • 3. Click File -> Open... -> python/Pico-LCD-2/Pico-LCD-2.py to run the script

GUI API Details

If you have used our SPI screen before, you should be familiar with this example program

Underlying Hardware Interfaces

We have encapsulated the underlying layer, and due to different hardware platforms, the internal implementation is different. If you need to understand the internal implementation, you can check the corresponding directory
You can see a lot of definitions in DEV_Config.c(.h), located in the directory: ...\c\lib\Config

  • Data type:
#define UBYTE   uint8_t
#define UWORD   uint16_t
#define UDOUBLE uint32_t
  • Module initialization and exit processing:
void DEV_Module_Init(void);
void DEV_Module_Exit(void);
Note:
1. Here are some GPIOs before and after using the LCD screen.
  • GPIO read/write:
void 	DEV_Digital_Write(UWORD Pin, UBYTE Value);
UBYTE 	DEV_Digital_Read(UWORD Pin);
  • SPI write data:
void DEV_SPI_WriteByte(UBYTE Value);

Upper Layer Applications

For the screen, what if you need to paint, display Chinese and English characters, display pictures, etc., these are all done by the upper layer applications. Many of you have asked about some graphics processing, and we have provided some basic functions here The GUI can be found in the directory: ..\c\lib\GUI\GUI_Paint.c(.h)
LCD PICO GUI 1.png
The character fonts that GUI depends on are in the following directory: RaspberryPi\c\lib\Fonts
LCD rpi Font.png

  • New Image Property: Create a new image property that includes the name, width, height, angle of rotation, and color of the image cache
void Paint_NewImage(UWORD *image, UWORD Width, UWORD Height, UWORD Rotate, UWORD Color)
Parameters:
 	image: The name of the image cache, which is actually a pointer to the first address of the image cache;
 	Width: The width of the image cache;
 	Height: The height of image cache;
 	Rotate: The angle of image flipping;
 	Color: The initial color of the image;
  • Select Image Cache: The purpose of selecting the image cache is that you can create multiple image properties, and there can be multiple image caches. You can select each image you create
void Paint_SelectImage(UBYTE *image)
Parameters:
 	image: The name of the image cache, which is actually a pointer to the first address of the image cache;
  • Image Rotation: Set the rotation angle of the selected image, preferably after Paint_SelectImage (), you can choose to rotate 0, 90, 180, 270
void Paint_SetRotate(UWORD Rotate)
Parameters:
 	Rotate: Image rotation angles can be chosen as ROTATE_0, ROTATE_90, ROTATE_180, ROTATE_270 corresponding to 0, 90, 180, 270 degrees
[Note] Under different selection angles, the coordinates correspond to different starting pixels. Here taking 1.14 as an example, there are four images in sequence: 0°, 90°, 180°, 270°. For reference only
Pico LCD Rotate.jpg
  • Image Mirror Flip: Set the mirror flip of the selected image, you can choose no mirroring, horizontal mirroring, vertical mirroring, and mirroring around the image center
void Paint_SetMirroring(UBYTE mirror)
Parameter:
 	mirror: The mirroring method of the image can be selected as MIRROR_NONE, MIRROR_HORIZONTAL, MIRROR_VERTICAL, and MIRROR_ORIGIN, which correspond to no mirroring, horizontal mirroring, vertical mirroring, and mirroring around the image center, respectively
  • Set the position and color of the point to be displayed in the cache: This is the core function of the GUI, handling point position and color displayed in the cache;
void Paint_SetPixel(UWORD Xpoint, UWORD Ypoint, UWORD Color)
Parameters:
 	Xpoint: The X position of the point in the image cache
 	Ypoint: The Y position of the point in the image cache
 	Color: The color of the point
  • Image cache fill color: Fill the image cache with a certain color, which is generally used as a screen whitening
void Paint_Clear(UWORD Color)
Parameters:
 	Color: The filled color
  • Fill color of image cache window: Fill a certain part of the image cache window with a certain color, usually used as a window whitening function, commonly used for displaying time, whitening for one second
void Paint_ClearWindows(UWORD Xstart, UWORD Ystart, UWORD Xend, UWORD Yend, UWORD Color)
Parameters:
 	Xstart: The starting point X coordinate of the window
 	Ystart: The starting point Y coordinate of the window
 	Xend: The endpoint X coordinate of the window
 	Yend: The endpoint Y coordinate of the window
 	Color: The filled color
  • Draw a dot: In the image cache, draw a dot at (Xpoint, Ypoint), you can choose the color, the size, and the style of the dot
void Paint_DrawPoint(UWORD Xpoint, UWORD Ypoint, UWORD Color, DOT_PIXEL Dot_Pixel, DOT_STYLE Dot_Style)
Parameters:
 	Xpoint: The X coordinate of the dot
 	Ypoint: The Y coordinate of the dot
 	Color: The filled color
 	Dot_Pixel: The size of the dots, which provides the default 8 sizes of dots
 	 	 typedef enum {
 	 	 	 DOT_PIXEL_1X1  = 1,	// 1 x 1
 	 	 	 DOT_PIXEL_2X2  , 		// 2 X 2
 	 	 	 DOT_PIXEL_3X3  , 	 	// 3 X 3
 	 	 	 DOT_PIXEL_4X4  , 	 	// 4 X 4
 	 	 	 DOT_PIXEL_5X5  , 		// 5 X 5
 	 	 	 DOT_PIXEL_6X6  , 		// 6 X 6
 	 	 	 DOT_PIXEL_7X7  , 		// 7 X 7
 	 	 	 DOT_PIXEL_8X8  , 		// 8 X 8
 	 	} DOT_PIXEL;
 	Dot_Style: The style of the dot, the method to expand the size of the dot is that, whether to expand from the dot as the center or from the dot as the lower left corner to the upper right
 	 	typedef enum {
 	 	   DOT_FILL_AROUND  = 1,		
 	 	   DOT_FILL_RIGHTUP,
 	 	} DOT_STYLE;
  • Draw a line: In the image cache, you can draw a line from (Xstart, Ystart) to (Xend, Yend) and choose the color, the width and the style of the line
void Paint_DrawLine(UWORD Xstart, UWORD Ystart, UWORD Xend, UWORD Yend, UWORD Color, LINE_STYLE Line_Style , LINE_STYLE Line_Style)
Parameters:
 	Xstart: The starting point X coordinate of the line
 	Ystart: The starting point Y coordinate of the line
 	Xend: The endpoint X coordinate of the line
 	Yend: The endpoint Y coordinate of the line
 	Color: The filled color
 	Line_width: The width of the lines, which provides the default 8 widths
 	 	typedef enum {
 	 	 	 DOT_PIXEL_1X1  = 1,	        // 1 x 1
 	 	 	 DOT_PIXEL_2X2  , 		// 2 X 2
 	 	 	 DOT_PIXEL_3X3  ,		// 3 X 3
 	 	 	 DOT_PIXEL_4X4  ,		// 4 X 4
 	 	 	 DOT_PIXEL_5X5  , 		// 5 X 5
 	 	 	 DOT_PIXEL_6X6  , 		// 6 X 6
 	 	 	 DOT_PIXEL_7X7  , 		// 7 X 7
 	 	 	 DOT_PIXEL_8X8  , 		// 8 X 8
 	 	} DOT_PIXEL;
 	 Line_Style: The style of the line, which selects whether the line is connected in a straight line or as a dashed line
 	 	typedef enum {
 	 	 	 LINE_STYLE_SOLID = 0,
 	 	 	 LINE_STYLE_DOTTED,
 	 	} LINE_STYLE;
  • Draw a rectangle: In the image cache, you can draw a rectangle from (Xstart, Ystart) to (Xend, Yend) and choose the color, the width of the line, and whether to fill the inside of the rectangle
void Paint_DrawRectangle(UWORD Xstart, UWORD Ystart, UWORD Xend, UWORD Yend, UWORD Color, DOT_PIXEL Line_width, DRAW_FILL Draw_Fill)
Parameters:
 	Xstart: The starting point X coordinate of the rectangle
 	Ystart: The starting point Y coordinate of the rectangle
 	Xend: The endpoint X coordinate of the rectangle
 	Yend: The endpoint Y coordinate of the rectangle
 	Color: The filled color
 	Line_width: The width of the four sides of the rectangle, providing the default 8 widths
 	 	typedef enum {
 	 	 	 DOT_PIXEL_1X1  = 1,	        // 1 x 1
 	 	 	 DOT_PIXEL_2X2  , 		// 2 X 2
 	 	 	 DOT_PIXEL_3X3  ,		// 3 X 3
 	 	 	 DOT_PIXEL_4X4  ,		// 4 X 4
 	 	 	 DOT_PIXEL_5X5  , 		// 5 X 5
 	 	 	 DOT_PIXEL_6X6  , 		// 6 X 6
 	 	 	 DOT_PIXEL_7X7  , 		// 7 X 7
 	 	 	 DOT_PIXEL_8X8  , 		// 8 X 8
 	 	} DOT_PIXEL;
 	Draw_Fill: Filling, whether to fill the inside of the rectangle
 	 	typedef enum {
 	 	 	 DRAW_FILL_EMPTY = 0,
 	 	 	 DRAW_FILL_FULL,
 	 	} DRAW_FILL;
  • Draw a circle: In the image cache, draw a circle with a radius of Radius with (X_Center Y_Center) as the center of the circle, and you can choose the color, the width of the line, and whether to fill the inside of the circle
void Paint_DrawCircle(UWORD X_Center, UWORD Y_Center, UWORD Radius, UWORD Color, DOT_PIXEL Line_width, DRAW_FILL Draw_Fill)
Parameters:
 	X_Center: The X coordinate of the center of the circle
 	Y_Center: The Y coordinate of the center of the circle
 	Radius: The radius of the circle
 	Color: The filled color
 	Line_width: The width of the arc, which provides the default 8 widths
 	 	typedef enum {
 	 	 	 DOT_PIXEL_1X1  = 1,            // 1 x 1
 	 	 	 DOT_PIXEL_2X2  , 		// 2 X 2
 	 	 	 DOT_PIXEL_3X3  ,		// 3 X 3
 	 	 	 DOT_PIXEL_4X4  ,		// 4 X 4
 	 	 	 DOT_PIXEL_5X5  , 		// 5 X 5
 	 	 	 DOT_PIXEL_6X6  , 		// 6 X 6
 	 	 	 DOT_PIXEL_7X7  , 		// 7 X 7
 	 	 	 DOT_PIXEL_8X8  , 		// 8 X 8
 	 	} DOT_PIXEL;
 	Draw_Fill: Filling, whether to fill the inside of the circle
 	 	typedef enum {
 	 	 	 DRAW_FILL_EMPTY = 0,
 	 	 	 DRAW_FILL_FULL,
 	 	} DRAW_FILL;
  • Write Ascii characters: In the image cache, with (Xstart Ystart) as the left vertex, write an Ascii character, and you can select the Ascii code visual character font library, font foreground color, and font background color
void Paint_DrawChar(UWORD Xstart, UWORD Ystart, const char Ascii_Char, sFONT* Font, UWORD Color_Foreground, UWORD Color_Background)
Parameters:
 	Xstart: The left vertex X coordinate of the character
 	Ystart: The left vertex Y coordinate of the character
 	Ascii_Char: Ascii character
 	Font: The Ascii code visual character font library, provides the following fonts in the Fonts folder:
 	 	font8: 5*8 font
 	 	font12: 7*12 font
 	 	font16: 11*16 font
 	 	font20: 14*20 font
 	 	font24: 17*24 font
 	Color_Foreground: Font color
 	Color_Background: Background color
  • Write English strings: In the image cache, write a string of English characters with (Xstart Ystart) as the left vertex, and you can select the Ascii code visual character font library, font foreground color, and font background color
void Paint_DrawString_EN(UWORD Xstart, UWORD Ystart, const char * pString, sFONT* Font, UWORD Color_Foreground, UWORD Color_Background)
Parameters:
 	Xstart: The left vertex X coordinate of the character
 	Ystart: The left vertex Y coordinate of the character
 	pString: A string is a pointer
 	Font: The Ascii code visual character font library, provides the following fonts in the Fonts folder:
 	 	font8: 5*8 font
 	 	font12: 7*12 font
 	 	font16: 11*16 font
 	 	font20: 14*20 font
 	 	font24: 17*24 font
 	Color_Foreground: Font color
 	Color_Background: Background color
  • Write Chinese strings: In the image cache, write a string of Chinese characters with (Xstart Ystart) as the left vertex, and you can select the GB2312 encoding character font library, font foreground color, and font background color
void Paint_DrawString_CN(UWORD Xstart, UWORD Ystart, const char * pString, cFONT* font, UWORD Color_Foreground, UWORD Color_Background)
Parameters:
 	Xstart: The left vertex X coordinate of the character
 	Ystart: The left vertex Y coordinate of the character
 	pString: A string is a pointer
 	Font: The GB2312 encoding character library, the following fonts are provided in the Fonts folder:
 	 	font12CN: Ascii character font 11*21, Chinese font 16*21
 	 	font24CN: Ascii character font 24*41, Chinese font 32*41
 	Color_Foreground: Font color
 	Color_Background: Background color
  • Write numbers: In the image cache, write a string of numbers with (Xstart Ystart) as the left vertex, and you can select the Ascii code visual character font library, font foreground color, and font background color
void Paint_DrawNum(UWORD Xpoint, UWORD Ypoint, double Nummber, sFONT* Font, UWORD Digit,UWORD Color_Foreground, UWORD Color_Background);
Parameters:
 	Xstart: The left vertex X coordinate of the character
 	Ystart: The left vertex Y coordinate of the character
 	Number: The displayed number, here is stored in a 32-bit long int type, which can be displayed up to 2147483647.
 	Font: The Ascii code visual character font library, provides the following fonts in the Fonts folder:
 	 	font8: 5*8 font
 	 	font12: 7*12 font
 	 	font16: 11*16 font
 	 	font20: 14*20 font
 	 	font24: 17*24 font
        Digit: Display decimal places
 	Color_Foreground: Font color
 	Color_Background: Background color
  • Display time: In the image cache, display time with (Xstart Ystart) as the left vertex, and you can select the Ascii code visual character font library, font foreground color, and font background color
void Paint_DrawTime(UWORD Xstart, UWORD Ystart, PAINT_TIME *pTime, sFONT* Font, UWORD Color_Background, UWORD Color_Foreground)
Parameters:
 	Xstart: The left vertex X coordinate of the character
 	Ystart: The left vertex Y coordinate of the character
 	pTime: The displayed time, where a time structure is defined, and only the hours, minutes, and seconds are passed to the parameters;
 	Font: The Ascii code visual character font library, provides the following fonts in the Fonts folder:
 	 	font8: 5*8 font
 	 	font12: 7*12 font
 	 	font16: 11*16 font
 	 	font20: 14*20 font
 	 	font24: 17*24 font
 	Color_Foreground: Font color
 	Color_Background: Background color


LVGL Demos

Function Introduction

This demo is compatible with multiple LCDs and can be customized based on whether the device is equipped with a joystick and the number of buttons. Use the joystick to move a virtual pointer, and use the buttons to switch interfaces, simulate pointer clicks, and toggle switch states and other functions. An overview of the demo functions for each LCD is shown in the table below:

Pico-LCD LVGL demo functions description
Model Interface1 Interface2 Interface3
Pico-LCD-0.96 An image A virtual pointer, a button, a label to record the number of button clicks None
Pico-LCD-1.14 An image A virtual pointer, a button, a label to record the number of button clicks None
Pico-LCD-1.3 An image A virtual pointer, a button, a label to record the number of button clicks Two switches
Pico-LCD-1.44 An image A button, a label to record the number of button clicks Two switches
Pico-LCD-1.8 An image An image None
Pico-LCD-2 An image A button, a label to record the number of button clicks Two switches

Demo Introduction

This example is used to test the LVGL widdet interaction and style beautification, etc.. For specific development of LVGL, please refer to LVGL development documentation.

Implement Function

  1. In this example, DMA is used to transfer color data to the SPI bus, reducing CPU utilization, and controlling CPU usage to less than 35% and memory usage to less than 20% during simple interactions.
  2. The example system clock is 150MHz. The peripheral clock frequency for SPI is set to match the system clock, and the LVGL library's dual buffer mechanism is utilized. Data transfer occurs in one buffer while the other buffer is rendering, ensuring smooth animation.
  3. This example demonstrates the simple use of LVGL widgets by switching the interface through buttons and switching between button and switch states by pressing a button.

Compile and Run

  • Pico VSCode
  1. Select the project directory and import the project
    600px-Pico-vscode-23.jpg
  2. Select the version and SDK version, click Complie to compile
    Pico-vscode-19.JPG
  • Windows
    • Refer to Windows Environment Setup Tutorial to complete the environment setup
    • Open VS 2022 -> Tool -> Command Line -> Developer Powershell
    • Set the absolute address of pico-sdk as PICO_SDK_PATH, for example, set pico-sdk address as "D:\pico\pico-sdk"
      setx PICO_SDK_PATH "D:\pico\pico-sdk"
    • Download the demo, enter the source code directory, if the build directory already exists, you can go directly into it. If not, you can create this directory:
      mkdir build
      cd build
    • Execute cmake, automatically generate the Makefile file:
      cmake -G "NMake Makefiles" ..
    • Execute nmake to generate the executable file, and input the following content in the terminal:
      nmake
      After compilation, it will generate a .uf2 formatted file.
    • Press the onboard boot key, connect the board to the USB interface of the PC through a Micro USB cable. And then release the key, the PC will identify the pico as a removable driver. Finally, you need to copy the compiled file in .uf2 format to Pico.
  • Ubuntu
    • Refer to Chapter 2. The SDK of Pico Getting Started to complete the environment setup
    • Open a terminal, set the value of the environment variable PICO_SDK_PATH to the absolute path of pico-sdk, for example, if my pico-sdk path is "/home/pico/pico-sdk"
      nano ~/.bashrc
      #Add the following content at the last line
      export PICO_SDK_PATH="/home/pico/pico-sdk"
    • After setting, save and exit. The configuration takes effect
      source ~/.bashrc
    • Download the demo, enter the source code directory, if the build directory already exists, you can go directly into it. If not, you can create this directory:
      mkdir build
      cd build
    • Execute cmake, it will generate Makefile file:
      cmake ..
    • Execute nmake to generate the executable file, and input the following content in the terminal:
      nmake
      After compilation, it will generate a .uf2 formatted file.
    • Press the onboard boot key, connect the board to the USB interface of the PC through a Micro USB cable. And then release the key, the PC will identify the pico as a removable driver. Finally, you need to copy the compiled file in .uf2 format to Pico.

Source Code Analysis

Source Code Structure

  • The source code of the LVGL library is at lib/lvgl of the project file folder, and the version used is "8.1". For secondary development, please refer to the development documentation corresponding to the used version.
  • The related settings for the LVGL library are at examples/inc/lv_conf.h of the project file folder, where you can set display refresh rates, system usage data, and so on.
    600px-Pico-LCD-LVGL-inc.png
  • The application code for the LVGL library is located in examples/src/LVGL_example.c and examples/src/LCD_XinXX_LVGL_test.c of the project folder. Where XinXX represents the size of the LCD, for example, the file corresponding to 0in96 is LCD_0in96_LVGL_test.c.
    600px-Pico-LCD-LVGL-src.png

Data Storage

This example uses a data structure to store and manage data related to LVGL. It contains pointers to different interfaces, pointers to widgets (buttons, labels, switches) in each interface, and variables used to record button clicks and button states.

  • Data Structure
    • Demo location: examples/inc/LVGL_example.h
    • Function introduction: Used for storing and managing the required data.
    typedef struct {
        lv_obj_t *scr[4];     // Used to store different interfaces
        lv_obj_t *cur;        // Pointer
        lv_obj_t *btn;        // Button
        lv_obj_t *label;      // Label
        lv_obj_t *sw_1;       // Switch 1 
        lv_obj_t *sw_2;       // Switch 2
        uint16_t click_num;   // Button clicks
        uint16_t KEY_now;     // Current key status
        uint16_t KEY_old;     // Previous key status
    } lvgl_data_struct;
    

Key Reading

For key reading, this example uses key scanning methods to implement it, and we will provide a detailed introduction below. The program also provides corresponding comments.

  • Key Initialization
    • Demo location: examples/src/LCD_XinXX_LVGL_test.c
    • Function introduction: Initialize all physical buttons of the module.
    LCD_XINXX_KEY_Init();
    
  • Key-related macro definitions
    • Demo location: lib/Config/DEV_Config.h
    • Usage: This demo is compatible with multiple LCDs. If there is a joystick, set the value of LCD_AS_JOY to 1. If there is no joystick, set it to 0. The state of all keys is stored in a hexadecimal variable, and each bit of that variable can be used to represent the state of a key, such as the state of KEY_A in the first bit and the state of KEY_B in the second bit. Each bit has two states of 1 or 0, which means that the key is pressed or released. If the first bit is 1, it means that KEY_A is pressed, and 0 means that KEY_A is released. This method supports detecting multiple keys being pressed simultaneously.
    #define LCD_HAS_JOY  1
    
    #define KEY_A     0x0001
    #define KEY_B     0x0002
    #define KEY_X     0x0004
    #define KEY_Y     0x0008
    #define KEY_UP    0x0010
    #define KEY_DOWN  0x0020
    #define KEY_LEFT  0x0040
    #define KEY_RIGHT 0x0080
    #define KEY_CTRL  0x0100
    #define KEY_0     0x0200
    #define KEY_1     0x0400
    #define KEY_2     0x0800
    #define KEY_3     0x1000
    
  • Key reading
    • Demo location: examples/src/LCD_XinXX_LVGL_test.c
    • Implementation method: Define a hexadecimal variable KEY_Value, initialized to 0. If a key press is detected, the corresponding data position of the key is 1. After reading the status of all keys, return KEY_Value. Due to the difference in the number of buttons carried by each module, the key reading function will also be different, here is only an example of Pico-LCD-0.96, please refer to the full example for other models.
    static uint16_t LCD_0IN96_Read_KEY(void) 
    {
        uint16_t KEY_Value = 0;
    
        if (DEV_Digital_Read(LCD_KEY_A)  == 0)          KEY_Value |= KEY_A;    
        if (DEV_Digital_Read(LCD_KEY_B)  == 0)          KEY_Value |= KEY_B;    
    
        if (DEV_Digital_Read(LCD_KEY_UP) == 0)          KEY_Value |= KEY_UP;
        else if (DEV_Digital_Read(LCD_KEY_DOWN)  == 0)  KEY_Value |= KEY_DOWN;
        else if (DEV_Digital_Read(LCD_KEY_LEFT)  == 0)  KEY_Value |= KEY_LEFT;
        else if (DEV_Digital_Read(LCD_KEY_RIGHT) == 0)  KEY_Value |= KEY_RIGHT;
        else if (DEV_Digital_Read(LCD_KEY_CTRL)  == 0)  KEY_Value |= KEY_CTRL;
    
        return KEY_Value;
    }
    
  • Key-value storage
    • Demo location: examples/src/LCD_XinXX_LVGL_test.c
    • Implementation method: Read the current key value and save it to dat ->KEY_now. After processing the key, save the current key value to dat->KEY_old. In the next key processing, this value will serve as the previous state of the key and be used to determine changes in the key state. If a bit was 0 in the previous cycle and is 1 now, it indicates that the key has just been pressed. If a bit was 1 in the previous cycle and is 0 now, it indicates that the key was pressed and released.
    while(1)
    {
        dat->KEY_now = LCD_XINXX_Read_KEY();  // Read current key value
        handle_key_press(dat);                // Key processing
        dat->KEY_old = dat->KEY_now;          // Save as old key value
    }
    
  • Key processing
    • Demo location: examples/src/LVGL_example.c
    • Implementation method: Use dat->KEY_now to determine whether the current key is pressed through AND operation, and combine dat->KEY_old to determine whether the current key is newly pressed.
    void handle_key_press(lvgl_data_struct *dat) 
    {
        // Determine if the KEY_UP button is pressed
        if(dat->KEY_now & KEY_UP) 
        ....
        // Determine if the KEY_A button is newly pressed
        if((dat->KEY_now & KEY_A) && !(dat->KEY_old & KEY_A)) 
        ....
        // Determine if the KEY_A button is released
        else if(!(dat->KEY_now & KEY_A))
        ....
    }
    

LCD Parameter Configuration

To adapt to multiple LCD displays, this example sets three key parameters, allowing users to configure them externally based on the specific characteristics of the target LCD.

  • LCD parameter definition
    • Demo location: examples/src/LVGL_example.c
    • Implementation function: DISP_HOR_RES and DISP_VER_RES are mainly used to initialize the LVGL display buffer; LCD_SetWindows is a function pointer that points to the LCD display position setting function, mainly used to set the display position of the LVGL interface.
    // LCD 
    LCD_SetWindowsFunc LCD_SetWindows;
    uint16_t DISP_HOR_RES;
    uint16_t DISP_VER_RES;
    
  • LCD parameter configuration
    • Demo location: examples/src/LCD_XinXX_LVGL_test.c
    • Implementation function: Configure parameters according to the LCD model used.
    LCD_SetWindows = LCD_XINXX_SetWindows; // Point to LCD display function
    DISP_HOR_RES = LCD_XINXX_WIDTH;        // LCD horizontal resolution
    DISP_VER_RES = LCD_XINXX_HEIGHT;       // LCD LCD vertical resolution
    

LVGL Initialization

Before using LVGL image library, you need to initialize LVGL.

  • The initialization function for the LVGL library
    • Demo location: examples/src/LVGL_example.c
    • Implementation function: Mainly used to initialize the hardware and structure variables required for LVGL.
    LVGL_Init();
  • LVGL library core initialization
    • Demo location: examples/src/LVGL_example.c
    /*2.Init LVGL core*/
    lv_init();
    

LVGL Run

The LVGL library periodically calls the function lv_tick_inc to notify LVGL of the past time, so that LVGL can update its internal time state and handle time-related tasks such as animations, timers, etc. The lv_task_handler function also needs to be called in the loop of the main function so that LVGL can handle events and tasks in time to ensure that the user interface responds and refreshes.

  • LVGL heartbeat interface
    • Demo location: examples/src/LVGL_example.c
    • Implementation method: It is necessary to ensure that the priority of lv_task_handler is lower than the priority of lv_tick_inc. Therefore, in this example, lv_tick_inc is called within the timer callback function.
    //Timer callback function called every 5ms
    add_repeating_timer_ms(5, repeating_lvgl_timer_callback, NULL, &lvgl_timer);
    
    static bool repeating_lvgl_timer_callback(struct repeating_timer *t) 
    {
        lv_tick_inc(5);
        return true;
    }
    
  • LVGL task processor
    • Demo location: examples/src/LCD_XinXX_LVGL_test.c
    • Implementation method: To handle LVGL tasks, you need to regularly call lv_timer_handler(), in this example, it is called in the loop of the main function.
    int main()
    {
        ...
        while(1)
        {
          lv_task_handler();
          DEV_Delay_ms(5); 
          ...
        }
    }
    

LVGL Display

To implement LVGL display, you must initialize a display driver and set the various properties of the display driver, such as color format, draw buffer, rendering mode, and display callback function. At each LV_DISP_DEF_REFR_PERIOD (set in lv_conf.h), LVGL detects if something has happened on the UI that needs to be redrawn. For example, a button is pressed, a chart is changed, an animation occurs, etc. When redrawing is needed, LVGL calls the display callback function to complete the drawing of the image in the refresh area.

  • LVGL display refreshing rate setting
    • Demo location: examples/inc/lv_conf.h
    • Setting method: In the lv_conf.h file, you can also set the display buffer refresh rate. You can modify this definition to change the screen refresh time.
    #define LV_DISP_DEF_REFR_PERIOD  10 // Unit: ms, here is 10ms
    
  • LVGL display color setting
    • Demo location: examples/inc/lv_conf.h
    • Purpose of setting: Since the pixel color storage method constructed by the lv_color_t structure in its default state does not match the data to be transmitted in this example, direct transmission would result in color discrepancies in the displayed image.
    #define LV_COLOR_16_SWAP 1
    
  • LVGL display related variable definition
    • Demo location: examples/src/LVGL_example.c
    • Implementation function: Define the display driver disp_drv, draw the buffer disp_buf. This example implements a double buffering mechanism, with the drawing buffer composed of buffers buf0 and buf1, both set to half the size of the screen display area, which can reduce jagged edges during large area refreshing while effectively improving screen refresh rate; when using a single buffer, it is best to set it to 10% of the screen display area, which can effectively reduce system usage but may result in noticeable jagged edges during large area refresh of image.
    static lv_disp_drv_t disp_drv;
    static lv_disp_draw_buf_t disp_buf;
    static lv_color_t buf0[DISP_HOR_RES * DISP_VER_RES/2];
    static lv_color_t buf1[DISP_HOR_RES * DISP_VER_RES/2];
    
  • LVGL display device registration
    • Demo location: examples/src/LVGL_example.c
    • Implementation function: According to the design requirements, improve the core structure variables of the LVGL library, initialize the display driver disp_drv, and set the drawing buffer, which is a simple array used by LVGL to render screen content. Once the rendering is ready, the content of the draw buffer will be sent to the display using the disp_drv_flush_cb function set in the display driver.
    lv_disp_draw_buf_init(&disp_buf, buf0, buf1, DISP_HOR_RES * DISP_VER_RES / 2); 
    lv_disp_drv_init(&disp_drv);    
    disp_drv.flush_cb = disp_flush_cb;
    disp_drv.draw_buf = &disp_buf;        
    disp_drv.hor_res = DISP_HOR_RES;
    disp_drv.ver_res = DISP_VER_RES;
    lv_disp_t *disp= lv_disp_drv_register(&disp_drv);   
    
  • LVGL display callback function
    • Demo location: examples/src/LVGL_example.c
    • Implementation function: Mainly complete the drawing of the image in the refresh area.
    static void disp_flush_cb(lv_disp_drv_t * disp, const lv_area_t * area, lv_color_t * color_p)
    Parameters:
        lv_disp_drv_t *disp_drv: Displays driver structure pointers, which contain information about the display and function pointers. This parameter is often used to notify you that a refresh is complete
        const lv_area_t *area  : Region structure pointer, containing the position information of the area to be refreshed. In this demo, you can use it for creating TFT display window.
        lv_color_t *color_p    : Color structure pointer, indicating the color data to be displayed in the refresh area. In this demo, it reads the address as DMA input to transmit data to the SPI bus and completes the image drawing
    
  • LVGL display callback function implementation
    • Demo location: examples/src/LVGL_example.c
    • Implementation method: In this example, to maximize the reduction of processor utilization, DMA is used for color data transmission. color_p is set as the read address, and the SPI bus output data register is set as the write address.
    static void disp_flush_cb(lv_disp_drv_t * disp, const lv_area_t * area, lv_color_t * color_p)
    {
        LCD_SetWindows(area->x1, area->y1, area->x2+1, area->y2+1);
        DEV_Digital_Write(LCD_DC_PIN, 1);
        DEV_Digital_Write(LCD_CS_PIN, 0);
        dma_channel_configure(dma_tx,
                              &c,
                              &spi_get_hw(LCD_SPI_PORT)->dr, 
                              color_p, // read address
                              ((area->x2 + 1 - area-> x1)*(area->y2 + 1 - area -> y1))*2,
                              true);
    }
    
  • LVGL refresh completion notification implementation
    • Demo location: examples/src/LVGL_example.c
    • Implementation function: The LVGL core needs to be notified after each image refresh is completed, so that LVGL can prepare the next refresh image for rendering.
    • Implementation method: In this example, the completion of the DMA transmission is notified in the interrupt service function for LVGL image refresh. If a blocking notification mechanism is used, it is not possible to utilize the double buffering mechanism to increase the refresh rate.
    static void dma_handler(void)
    {
        if (dma_channel_get_irq0_status(dma_tx)) {
            dma_channel_acknowledge_irq0(dma_tx);
            DEV_Digital_Write(LCD_CS_PIN, 1);
            lv_disp_flush_ready(&disp_drv);     
        }
    }
    

LVGL Widget Layout

In LVGL, we can create various user interfaces. The basic components of the interface are objects, also called widgets, such as buttons, labels, images, lists, charts, or text areas. In a interface, we can create multiple widgets simultaneously and set their positions, sizes, parent objects, styles, and event handlers and other basic properties.

  • LVGL control initialization function
    • Demo location: examples/src/LCD_XinXX_LVGL_test.c
    • Implementation function: Implement stylized and layout widgets, with the parameter dat used as an input and output parameter to store the initialized interface and various widgets.
    static void Widgets_Init(lvgl_data_struct *dat);
  • LVGL create interface
    • Demo location: examples/src/LCD_XinXX_LVGL_test.c
    • Implementation function: Create an interface.
    //lv_obj_t *scr[4] is a pointer array, where each element can be used to point to an interface
    dat->scr[0] = lv_obj_create(NULL); 
  • LVGL create widget
    • Demo location: examples/src/LCD_XinXX_LVGL_test.c
    • Implementation function: To create a widget, different widgets need to use different function interfaces, you can choose the parent object to create it.
    //Create a widget in which the dat->scr[1] is the parent object of the key, which can be replaced with a widget such as list, title, etc. that can have child objects
    dat->btn = lv_btn_create(dat->scr[1]); 
  • Alignment positioning of LVGL widget
    • Demo location: examples/src/LCD_XinXX_LVGL_test.c
    • Implementation function: Enable a widget to be offset and positioned based on a reference point. The widget aligns to the center of the offset reference point widget.
    • Alignment standard: The LVGL library supports both internal and external alignments. By default, the upper-left corner is the origin, the leftward as the positive horizontal direction, and the downward as the positive vertical direction.
    //Position the btn widget at the center point to offset it by 50 pixels to the left and 50 pixels down
    lv_obj_align(btn, LV_ALIGN_CENTER, -50 , 50);
    RP2040-Touch-LCD-1.28-LVGL-align.png
  • LVGL widget adjust font size
    • Demo location: examples/inc/lv_conf.h and examples/src/LVGL_example.c
    • Implementation function: In actual use, an interface may need to use multiple font sizes. Multiple font sizes can be enabled in lv_conf.h and the default font size can be set. When setting the font size, it is necessary to stylize the widget so that it can be rendered according to the set style. The lv_obj_add_style function can be used to render various parts of the widget in different states.
    #define LV_FONT_MONTSERRAT_14 1                                // Enable font 14
    #define LV_FONT_MONTSERRAT_16 1                                // Enable font 16
    #define LV_FONT_DEFAULT &lv_font_montserrat_14                 // Set the default font size as 14
    
    static lv_style_t style_label;                   
    lv_style_init(&style_label);                                   // Initialize style
    lv_style_set_text_font(&style_label, &lv_font_montserrat_16);  // Set the font size as 16
    lv_obj_add_style(label,&style_label,0);                        // Set label theme style
    

LVGL Interface Function

In this demo, some functions are customized, mainly for pointer movement and clicking.

  • Custom interface functions related to LVGL widgets
    • Demo location: examples/src/LVGL_example.c
    • Implementation function: Mainly used to move the widget and judge whether the pointer click is valid, here is only a brief description, please see the full example for details.
    static void widgets_up(lv_obj_t *widgets);                                                       // Move widget up
    static void widgets_down(lv_obj_t *widgets, uint16_t DISP_VER_RES);   // Move widget down
    static void widgets_left(lv_obj_t *widgets);                                                      // Move widget left
    static void widgets_right(lv_obj_t *widgets, uint16_t DISP_HOR_RES);    // Move widget right
    static bool click_valid(lv_obj_t *cur, lv_obj_t *widgets);                                 // Determine whether the pointer click is valid