Skip to main content

Working with MicroPython

This chapter contains the following sections. Please read as needed:

MicroPython Getting Started Tutorial

New to Pico MicroPython development and want to get started quickly? We have prepared a general introductory tutorial for you. These tutorial is designed to help developers quickly become familiar with Thonny IDE and start developing. It covers environment setup, project creation, component usage, and peripheral programming, helping you take the first step in MicroPython programming.

Setting Up Development Environment

Please refer to the Install and Configure Thonny IDE Tutorial to download and install the Thonny IDE.

Demo

The MicroPython demos are located in the examples\MicroPython directory of the demo package.

DemoBasic Program DescriptionDependency Library
01_GUILCD GUI display program-
02_SDMount TF card-
03_IMUGet six-axis IMU data-
04_LVGLLCD LVGL display programLVGL V9.3

01_GUI

Demo Description

  • Use SPI to communicate with the LCD and implement touch tracking on the LCD via GUI.

Hardware Connection

  • Connect the board to the computer using a USB cable

Code Analysis

  • lcd = LCD_1inch54(): Create an LCD object.
  • touch = Touch_CST816T(): Initialize the touch object.
  • touch_gesture(): Gesture test.
  • touch_hand_writing(): Draw points and display them on the LCD.

Operation Result

  • Upload all .py files from the 01_GUI folder to the development board using Thonny, and run the RP2350-Touch-LCD-1.54.py program.

    MicroPython-Example-1

02_SD

Demo Description

  • Uses SPI to communicate with the TF card and mounts the TF card to the development board. After successful mounting, you can view and modify the contents of the TF card via Thonny.

Hardware Connection

  • Insert a TF card
  • Connect the board to the computer using a USB cable

Code Analysis

  • sdcard.SDCard(spi, cs, baudrate): Creates a TF card object and binds the initialized SPI interface and CS pin to the TF card driver.
  • uos.mount(sd, '/sd'): Mounts the TF card file system to the /sd directory. After successful mounting, users can perform file read/write operations on the TF card via the /sd path, such as creating, reading, or deleting files.

Operation Result

  • Upload all py files from the 02_SD folder to the development board via Thonny and reset the board. After resetting, the development board will automatically mount the TF card to the sd directory according to the boot.py program.

    MicroPython-Example-2

03_IMU

Demo Description

  • Uses I2C to communicate with the onboard six-axis sensor and reads the sensor data.

Hardware Connection

  • Connect the board to the computer using a USB cable

Code Analysis

  • IMU = QMI8658(): Creates an IMU object.
  • IMU.Read_XYZ(): Reads the six-axis sensor data.

Operation Result

  • Run the py files in the 03_IMU folder using Thonny.

    MicroPython-Example-4

04_LVGL

Demo Description

  • Uses SPI to communicate with the LCD and implements functions like displaying text and images via LVGL. The version used is 9.3. The LVGL V9 documentation does not provide Python examples. For detailed development, refer to the LVGL documentation.

Hardware Connection

  • Connect the board to the computer using a USB cable

Compile and Run

  • Operation steps

    • Press and hold the BOOT button on the board, connect the board to your computer via USB, then release the button. The computer will recognize the board as a removable drive. Copy the XXX-LVGL.uf2 file from the firmware\MicroPython directory of the example package to the board.

    • Open Thonny, upload the examples and lib directories from the example package to the board.

      MicroPython-Example-5

    • Open the file XXX_LVGL_test.py in the examples directory and run the program by following the steps shown below.

      MicroPython-Example-6

  • Building firmware

    • Build environment: Ubuntu 22.04

    • GitHub: lv_micropython

    • Compile firmware: refer to the Raspberry Pi Pico port section

    • Firmware path: lv_micropython\ports\rp2\build-XXX\firmware.uf2

Code Analysis

Source Code Structure

  • For the LVGL library source code, refer to lv_micropython

  • LVGL library settings are in the lv_micropython\lib\lv_bindings\lv_conf.h file, where you can set display refresh frequency, system memory usage, etc.

  • Application code for LVGL is located in examples\LVGL_example.py and lib\LVGL.py in the project folder.

    MicroPython-Example-7

LVGL Initialization

Before using the LVGL graphics library, you need to import the LVGL library.

  • Import LVGL library

    Code location: lib\LVGL.py

    Function: imports the LVGL library and uses the alias lv for convenience.

    import lvgl as lv
  • LVGL initialization

    Code location: lib\LVGL.py

    Description: LVGL core initialization is located in the initialization function of the LVGL class and is automatically called when an LVGL object is created.

    if not lv.is_initialized(): lv.init()
  • Create LVGL object

    Code location: examples\XXX_LVGL_test.py

    Function: creates an LVGL object and passes LCD and TSC objects as parameters

    # Init LVGL
    LVGL(LCD=LCD,TSC=TSC)

LVGL Run

The LVGL library periodically calls the heartbeat interface function lv.tick_inc to inform LVGL of elapsed time, so that LVGL can update its internal time state and handle time‑related tasks such as animations and timers. In addition, the lv.task_handler function must be called so that LVGL can promptly process events and tasks, ensuring UI responsiveness and refresh.

  • LVGL run interface

    Code location: lib\LVGL.py

    Implementation: creates an event_loop object. In the initialization function of this class, a timer is created. This timer automatically calls the heartbeat interface function and event handler at set intervals. The call interval can be adjusted via the freq parameter, e.g., lv_utils.event_loop(freq=200), default is 40 ms.

    # Create event loop if not yet present
    if not lv_utils.event_loop.is_running(): self.event_loop=lv_utils.event_loop()
  • LVGL heartbeat interface

    • Code location: lib\lv_utils.py

    Implementation: ensure that the priority of lv.task_handler is lower than that of lv.tick_inc. Therefore, in this example, lv.tick_inc is called in the timer callback function.

    # Call timer callback function every 5ms
    self.timer.init(mode=Timer.PERIODIC, period=self.delay, callback=self.timer_cb) # in this example, self.delay = 5

    def timer_cb(self, t):
    lv.tick_inc(self.delay)
    if self.scheduled < self.max_scheduled:
    try:
    micropython.schedule(self.task_handler_ref, 0)
    self.scheduled += 1 # The number of tasks being processed has increased
    except:
    pass
  • LVGL task handler

    • Code location: lib\lv_utils.py

    Implementation: to handle LVGL tasks, lv.task_handler must be called regularly. In this example, it is called after lv.tick_inc in the timer callback function.

    def task_handler(self, _):
    try:
    if lv._nesting.value == 0:
    lv.task_handler()
    if self.refresh_cb: self.refresh_cb()
    self.scheduled -= 1 # The number of tasks being processed has decreased
    except Exception as e:
    if self.exception_sink:
    self.exception_sink(e)

LVGL Display

To enable LVGL display, a display driver must be initialized and its various properties configured, such as color format, draw buffer, rendering mode, and display callback function. At each LV_DEF_REFR_PERIOD (configured in lv_conf.h), LVGL checks if anything requiring a redraw has occurred on the UI. 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 refresh rate setting

    Code location: lv_micropython\lib\lv_bindings\lv_conf.h

    Setting method: modify LV_DEF_REFR_PERIOD in lv_conf.h to change the screen refresh time.

    #define LV_DEF_REFR_PERIOD 10 // Unit: ms, here is 10ms
  • LVGL display related variable definitions

    Code location: lib\LVGL.py

    Function: buf0 and buf1 are sized to 50% of the screen display area to implement LVGL double buffering, which helps reduce tearing during full‑screen refreshes while effectively improving the screen refresh rate.

    # Init LVGL display
    buf1 = lv.draw_buf_create(self.LCD.width, self.LCD.height // 2 , color_format, 0)
    buf2 = lv.draw_buf_create(self.LCD.width, self.LCD.height // 2 , color_format, 0)
  • LVGL display device registration

    Code location: lib\LVGL.py

    Function: according to design requirements, complete the core structure variables of the LVGL library, initialize the display driver disp_drv, and set the draw buffers. The draw buffers are simple arrays used by LVGL to render screen content. Once rendering is ready, the content of the draw buffer is sent to the display using the disp_drv_flush_cb function set in the display driver.

    self.disp_drv = lv.display_create(self.LCD.width, self.LCD.height) # Create a display driver object and set the width and height
    self.disp_drv.set_color_format(color_format) # Set color format to RGB565
    self.disp_drv.set_draw_buffers(buf1, buf2) # Set the drawing buffer
    self.disp_drv.set_render_mode(lv.DISPLAY_RENDER_MODE.PARTIAL) # Set the rendering mode to partial refresh mode
    self.disp_drv.set_flush_cb(self.disp_drv_flush_cb) # Set display callback function
  • LVGL display callback function

    Code location: lib\LVGL.py

    Function: mainly completes the drawing of the image in the refresh area.

    def disp_drv_flush_cb(self,disp_drv,area,color_p)
    Parameters:
    disp_drv : Displays driver structure pointers, which contain information about the display and function pointers. This parameter is often used to notify you that the refresh is complete
    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.
    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

    Code location: lib\LVGL.py

    Implementation: to minimize processor utilization, DMA is used for color data transfer. color_p is set as the read address, and the SPI bus output data register is the write address. The code is long, only a partial example is shown. For the complete code, download the example.

    def disp_drv_flush_cb(self,disp_drv,area,color_p):
    self.rp2_wait_dma() # Wait for DMA to be idle

    ......
    self.rgb565_swap_func(data_view, size) # Convert color format

    self.LCD.setWindows(area.x1, area.y1, area.x2+1, area.y2+1) # Set LVGL interface display position

    ...... # DMA configuration

    self.rp2_dma.enable() # Enable DMA

    self.rp2_wait_dma() # Wait for DMA to be idle

    self.disp_drv.flush_ready() # Notify LVGL that data transfer is complete

LVGL Input

In LVGL, users can register input devices such as touchpads, mice, keyboards, or encoders, etc. Users can control the user interface through these input devices to achieve better interaction.

  • Frequency of calling the input device callback in LVGL

    Code location: lv_micropython\lib\lv_bindings\lv_conf.h

    Setting method: uses the same macro as the screen refresh time. The input device callback is called every 10ms to update events triggered by the input device. Can be set by modifying the LV_DEF_REFR_PERIOD macro.

    #define LV_DEF_REFR_PERIOD 10 // unit: ms, here 10ms
  • LVGL input device registration

    Code location: examples\LVGL_example.py

    Setting method: register the touch screen device indev_drv and initialize it.

    # Init touch screen as input device
    self.indev_drv = lv.indev_create() # Create an object
    self.indev_drv.set_type(lv.INDEV_TYPE.POINTER) # Register a touchscreen device
    self.indev_drv.set_read_cb(self.indev_drv_read_cb) # Set callback function
  • LVGL input device callback function

    Code location: lib\LVGL.py

    Function: mainly used to update input events.

    def indev_drv_read_cb(indev_drv, data)
    Parameters:
    indev_drv : Pointer to the input device driver structure in LVGL. In this case, the structure is a touch screen input device driver
    data : Pointer to the input device driver structure in LVGL. In this case, the structure is used to store the status and data of the input device, including the current touch state (pressed or released) and the coordinates of the touch points
  • Touch screen input device callback implementation

    Code location: lib\CST816T.py, lib\LVGL.py

    Implementation: mainly updates the touch state and touch point coordinates via touch interrupt.

    def Int_Callback(self,pin):
    if self.Mode == 0 :
    self.Gestures = self._read_byte(0x01)

    elif self.Mode == 1 :
    self.Flag = 1
    self.get_point()

    elif self.Mode == 2 :
    self.Flag = 1
    self.get_point()
    self.Gestures = self._read_byte(0x01)

    def indev_drv_read_cb( self, indev_drv, data):
    self.rp2_wait_dma()

    data.point.x = self.TSC.X_point
    data.point.y = self.TSC.Y_point

    data.state = 1 if self.TSC.Flag == 1 else 0
    self.TSC.Flag = 0

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.

  • Create LVGL screen object

    Code location: examples\LVGL_example.py

    Function: creates an LVGL screen object and passes LCD, TSC, and IMU objects as parameters.

    # Init WIDGETS
    WIDGETS(LCD=LCD,TSC=TSC,IMU=IMU)
  • Create LVGL tile

    Code location: examples\LVGL_example.py

    Function: A Tile view is a container object whose elements (called tiles) can be arranged in a grid. Users can navigate between tiles by swiping. Use the tile view object to call add_tile(tileview, row_id, col_id, dir) to create a new tile at row row_id and column col_id. dir can be set to parameters such as lv.DIR.LEFT/RIGHT/TOP/BOTTOM/HOR/VER/ALL to allow moving to adjacent tiles in the given direction by swiping.

    # Create a tile at (0,0) that supports swiping down to (0,1)
    self.tv = lv.tileview(self.scr)
    self.tile1 = self.tv.add_tile(0, 0, lv.DIR.BOTTOM)
  • Create LVGL widget

    Code location: examples\LVGL_example.py

    Function: create a widget. Different widgets require different function interfaces. You can choose a parent object when creating.

    # Create a table widget, where tile2 is its parent object. Can be replaced with list, title, or any object that can have children.
    self.table_imu_data = lv.table(self.tile2)
  • Alignment and positioning of LVGL widgets

    Code location: examples\LVGL_example.py

    Function: allows a widget to be offset‑positioned relative to a reference point. The reference point for alignment offset is the center of the widget.

    Alignment standard: LVGL supports both internal and external alignment. 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 widget 15 pixels to the right from the center point
    self.table_imu_data.align(lv.ALIGN.CENTER, 15 ,0)

    MicroPython-Example-8

  • Changing font size in LVGL widgets

    Code location: lv_micropython\lib\lv_bindings\lv_conf.h, examples\LVGL_example.py

    Function: in practice, a screen may need multiple font sizes. You can enable multiple font sizes in lv_conf.h and set the default font size. To set the font size, you need to style the widget so that it renders according to the set style. Use add_style to define how each part of the widget is rendered under different states.

    // lv_micropython\lib\lv_bindings\lv_conf.h
    #define LV_FONT_MONTSERRAT_16 1 // Enable font 16
    #define LV_FONT_MONTSERRAT_18 1 // Enable font 18
    #define LV_FONT_DEFAULT &lv_font_montserrat_18 // Set the default font size as 18
    # examples\LVGL_example.py
    table_imu_data= lv.style_t() # create style object
    table_imu_data.init() # initialize
    table_imu_data.set_text_font(lv.font_montserrat_16) # set font size to 16

    self.table_imu_data.add_style(style_imu_table, 0) # set the style
  • LVGL widget event handling

    Code location: examples\LVGL_example.py

    Function: in LVGL, you can add event callback functions to widgets so that when events such as clicking, scrolling, or redrawing occur, the event triggers and the callback function is executed. Call obj.add_event_cb(event_cb, filter, None) to add the event handler event_cb for the event filter to the widget obj. When the widget obj triggers the filter event, the system automatically calls event_cb. The last parameter is a pointer to any custom data available in the event.

    # Add event handler LV_EVENT_VALUE_CHANGED to widget sw, using function sw_event_cb
    self.sw.add_event_cb(self.sw_event_cb, lv.EVENT.VALUE_CHANGED, None)

Operation Result

  • Run the LCD_1in54_LVGL_test.py file in the 04_LVGL\examples directory using Thonny.

    Wiring Diagram