Project Icon

lv_binding_micropython

MicroPython LVGL图形库绑定实现

lv_binding_micropython项目实现了MicroPython与LVGL图形库的绑定。该项目通过自动生成模块,使MicroPython能够访问LVGL的主要功能。它支持内存管理、并发处理、结构类、全局变量和回调函数等特性,并提供显示和输入驱动程序的多种实现方式。项目还包含事件循环管理,简化了LVGL在MicroPython环境中的使用流程。

构建 lv_micropython unix 端口

LVGL 的绑定


此仓库是 lv_micropython 的子模块。 请 fork lv_micropython 以快速开始使用 LVGL MicroPython 绑定。


另请参阅 Micropython + LittlevGL 博客文章。(LittlevGL 是 LVGL 的前身。) 有关高级功能,请参阅 纯 MicroPython 显示驱动程序 博客文章。 如有问题和讨论,请使用论坛:https://forum.lvgl.io/c/micropython

MicroPython

LVGL 的 MicroPython 绑定提供了一个自动生成的 MicroPython 模块,其中包含允许用户访问大部分 LVGL 库的类和函数。 该模块由脚本 gen_mpy.py 自动生成。 此脚本读取、预处理并解析 LVGL 头文件,然后生成一个 C 文件 lv_mpy.c,该文件定义了从 MicroPython 访问 LVGL 的 MicroPython 模块(API)。 Micopython 的构建脚本(Makefile 或 CMake)应自动运行 gen_mpy.py 以生成和编译 lv_mpy.c

  • 如果您想查看生成的 lv_mpy.c 的示例,请查看 lv_mpy_example.c。注意,它唯一导出(非静态)的符号是 mp_module_lvgl,应在 MicroPython 中将其注册为一个模块。
  • lv_binding_micropython 通常作为 lv_micropython 的 git 子模块使用,后者构建 MicroPython + LVGL + lvgl-bindings,但也可用于 MicroPython 的其他分支。

值得注意的是,Micropython 绑定模块(lv_mpy.c)依赖于 LVGL 配置。 LVGL 通过 lv_conf.h 进行配置,可以启用或禁用不同的对象和功能。LVGL 绑定仅为启用的对象和功能生成。更改 lv_conf.h 需要重新运行 gen_mpy.py,因此最好在构建脚本中自动运行它,就像 lv_micropython 所做的那样。

内存管理

当 LVGL 作为 MicroPython 库构建时,它被配置为使用 MicroPython 内存分配函数分配内存,并利用 MicroPython 的垃圾回收("gc")。 这意味着为 LVGL 使用分配的结构不需要显式释放,gc 会处理这个问题。 为了正确工作,LVGL 被配置为使用 gc 和 MicroPython 的内存分配函数,并将所有 LVGL "根"全局变量注册到 MicroPython 的 gc 中。

从用户的角度来看,可以创建结构,当不再引用它们时,gc 会自动收集它们。 然而,LVGL 屏幕对象(没有父对象的 lv.obj)会自动分配给默认显示,因此即使不再显式引用,也不会被 gc 收集。 当您想释放一个屏幕及其所有后代以便 gc 可以收集它们的内存时,请确保在不再需要它时调用 screen.delete()

请确保保留对显示驱动程序和输入驱动程序的引用,以防止它们被收集。

并发性

LVGL 的这个 MicroPython 绑定实现假设 MicroPython 和 LVGL 在单个线程同一线程上运行(或者在没有多线程的情况下运行)。 没有使用同步手段(锁、互斥锁)。 然而,对 LVGL 的异步调用仍然会定期进行,用于屏幕刷新和其他 LVGL 任务,如动画。

这是通过使用内部 MicroPython 调度器(必须启用)来实现的,通过调用 mp_sched_schedule。 当屏幕需要刷新时,会调用 mp_sched_schedule。LVGL 期望函数 lv_task_handler 被定期调用(参见 lvgl/README.md#porting)。这通常在显示设备驱动程序中处理。 这里是一个示例,通过 mp_sched_schedule 调用 lv_task_handler 来刷新 LVGL。mp_lv_task_handler 被安排在与 MicroPython 运行的相同线程上运行,它同时调用 lv_task_handler 处理 LVGL 任务,以及 monitor_sdl_refr_core 刷新显示和处理鼠标事件。

在使用 REPL(交互式控制台)时,在等待用户输入时也可能发生异步事件。在这个示例中,我们在等待按键时只是定期调用 mp_handle_pendingmp_handle_pending 负责调度通过 mp_sched_schedule 注册的异步事件。

结构类和全局变量

LVGL绑定脚本解析LVGL头文件并提供API来访问LVGL的(如btn)和结构体(如color_t)。所有结构体和类都可以在lvgl micropython模块下使用。

lvgl类包含:

  • 函数(如set_x
  • 与该类相关的枚举(如btnSTATE

lvgl结构体只包含可读写的属性。例如:

c = lvgl.color_t()
c.ch.red = 0xff

结构体也可以通过字典初始化。例如,上面的示例可以这样写:

c = lvgl.color_t({'ch': {'red' : 0xff}})

所有lvgl全局变量(函数、枚举、类型)都可以在lvgl模块下使用。例如,lvgl.SYMBOL是符号字符串的"枚举",lvgl.anim_create将创建动画等。

回调函数

在C中,回调是一个函数指针。 在MicroPython中,我们还需要为每个回调注册一个MicroPython可调用对象。 因此,在MicroPython绑定中,我们需要为每个回调同时注册一个函数指针和一个MicroPython对象。

因此,我们定义了一个回调约定,期望lvgl头文件以特定方式定义。按照约定声明的回调将允许绑定在注册回调时在函数指针旁边注册一个MicroPython对象,并在调用回调时访问该对象。 当注册或调用回调时,MicroPython可调用对象会自动保存在提供的user_data变量中。

回调约定假设以下情况:

  • 有一个结构体包含一个名为void * user_data的字段。
  • 该结构体的指针作为回调注册函数的第一个参数提供。
  • 该结构体的指针作为回调本身的第一个参数提供。

另一种选择是回调函数指针只是结构体的一个字段,在这种情况下,我们期望同一个结构体也包含user_data字段。

还有一种选择是:

  • 一个名为void * user_data的参数作为最后一个参数提供给注册函数。
  • 回调本身接收void *作为最后一个参数

在这种情况下,用户应该提供None或一个字典作为注册函数的user_data参数。 回调将在最后一个参数中接收一个可以转换为字典的Blob。 (参见下面的async_call示例)

只要遵循上述约定,lvgl MicroPython绑定脚本就会在设置和使用回调时自动设置和使用user_data

从用户的角度来看,任何Python可调用对象(如Python常规函数、类函数、lambda等)都可以用作lvgl回调。例如:

lvgl.anim_set_custom_exec_cb(anim, lambda anim, val, obj=obj: obj.set_y(val))

在这个例子中,为动画anim注册了一个执行回调,它将动画obj的y坐标。 lvgl API函数也可以直接用作回调,所以上面的例子也可以这样写:

lv.anim_set_exec_cb(anim, obj, obj.set_y)

不遵循回调约定的lvgl回调不能与micropython可调用对象一起使用。关于调整lvgl回调以符合约定的讨论:https://github.com/lvgl/lvgl/issues/1036

用户不能直接使用 user_data字段,因为它在内部用于保存指向MicroPython对象的指针。

显示和输入驱动

LVGL可以配置使用不同的显示和不同的输入设备。更多信息可在LVGL文档中找到。 注册驱动本质上是调用注册函数(例如disp_drv_register)并传递一个函数指针作为参数(实际上是一个包含函数指针的结构体)。函数指针用于访问实际的显示/输入设备。

在使用MicroPython实现LVGL显示或输入驱动时,有3种选择:

  • 实现纯Python驱动。这是实现驱动最简单的方式,但性能可能较差。
  • 实现纯C驱动。
  • 实现混合驱动,其中关键部分(如flush函数)用C实现,非关键部分(如初始化显示)用Python实现。

纯/混合驱动的一个例子是ili9XXX.py

驱动注册最终应该在MicroPython脚本中执行,对于纯/混合驱动可以在驱动代码本身中执行,对于C驱动可以在用户代码中执行(例如,在SDL驱动的情况下)。在Python而不是C中注册驱动很重要,这样可以让用户在不构建项目和更改C文件的情况下轻松选择和替换驱动。

在创建显示或输入LVGL驱动时,确保让用户可以在运行时配置所有参数,如SPI引脚、频率等。 最终,用户希望只构建一次固件,并在不重新构建C项目的情况下使用同一驱动的不同配置。 这与标准LVGL C驱动不同,通常使用宏来配置参数,并要求用户在任何配置更改时重新构建。

示例:


# 初始化ILI9341显示

from ili9XXX import ili9341
self.disp = ili9341(dc=32, cs=33, power=-1, backlight=-1)

# 注册xpt2046触摸驱动

from xpt2046 import xpt2046
self.touch = xpt2046()

示例:

# 初始化

import lvgl as lv
lv.init()

from lv_utils import event_loop

WIDTH = 480
HEIGHT = 320

event_loop = event_loop()
disp_drv = lv.sdl_window_create(WIDTH, HEIGHT)
mouse = lv.sdl_mouse_create()
keyboard = lv.sdl_keyboard_create()
keyboard.set_group(self.group)

在这个例子中,我们使用LVGL内置的LVGL驱动。

目前Micropyton支持的驱动有

  • LVGL内置驱动程序使用Unix/Linux SDL(显示、鼠标、键盘)和帧缓冲(/dev/fb0)
  • ESP32的ILI9341驱动
  • ESP32的XPT2046驱动
  • ESP32的FT6X36(电容触摸IC)驱动
  • ESP32的原始电阻触摸驱动(ADC直接连接到屏幕,无触摸IC)

驱动代码位于/driver目录下。

也可以通过提供回调函数(disp_drv.flush_cbindev_drv.read_cb等)用纯MicroPython实现驱动。 目前支持的ILI9341、FT6X36和XPT2046都是纯MicroPython驱动。

驱动程序在哪里?

LVGL C驱动和MicroPython驱动(C或Python)是相互独立的。 主要原因是配置方式不同:

  • C驱动通常使用C宏进行配置(使用哪些引脚、频率等) 任何配置更改都需要重新构建固件,这是可以理解的,因为应用程序的任何更改都需要重新构建固件。
  • 在MicroPython中,驱动程序要么随MicroPython固件一起构建(如果是C驱动),要么根本不构建(如果是纯Python驱动)。在运行时用户初始化驱动并进行配置。如果用户切换SPI引脚或其他配置,无需重新构建固件,只需更改Python脚本并在运行时重新初始化驱动即可。

因此MicroPython驱动的位置是https://github.com/lvgl/lv_binding_micropython/tree/master/driver,与https://github.com/lvgl/lv_drivers无关。

事件循环

LVGL需要一个事件循环来重绘屏幕、处理用户输入等。 默认事件循环在lv_utils.py中实现,使用MicroPython定时器来调度LVGL调用。 如果需要,它还支持在uasyncio中运行事件循环。 如果事件循环尚未运行,某些驱动程序会自动启动它。要为这些驱动程序配置事件循环,只需在注册驱动程序之前初始化事件循环。 LVGL原生驱动(如SDL驱动)不会启动事件循环。你必须显式启动事件循环,否则屏幕不会刷新。

事件循环可以这样启动:

from lv_utils import event_loop
event_loop = event_loop()

你可以通过提供参数来配置它,详见lv_utils.py。

向项目添加MicroPython绑定

"MicroPython + lvgl + 绑定"的示例项目是lv_mpy。 以下是向现有MicroPython项目添加lvgl的步骤。(这些示例来自lv_mpy):

  • lib下添加lv_bindings作为子模块。
  • lib中添加lv_conf.h
  • 编辑Makefile以自动运行gen_mpy.py并构建其产物。示例
  • 将lvgl模块和显示/输入驱动程序注册为MicroPython的内置模块。示例
  • 将lvgl根添加到gc根。示例
  • 通过设置几个LV_MEM_CUSTOM_*LV_GC_*宏配置lvgl使用垃圾回收示例 lv_conf.h已移至lv_binding_micropython git模块。
  • 确保在partitions.csv中正确配置分区,并为LVGL模块留出足够空间。
  • 我忘记了什么吗?请告诉我。

gen_mpy.py语法

用法: gen_mpy.py [-h] [-I <包含路径>] [-D <宏名称>]
                  [-E <预处理文件>] [-M <模块名称字符串>]
                  [-MP <前缀字符串>] [-MD <元数据文件名>]
                  输入 [输入 ...]

位置参数:
  输入

可选参数:
  -h, --help            显示此帮助消息并退出
  -I <包含路径>, --include <包含路径>
                        预处理器包含路径
  -D <宏名称>, --define <宏名称>
                        定义预处理器宏
  -E <预处理文件>, --external-preprocessing <预处理文件>
                        防止预处理。假设输入文件已预处理
  -M <模块名称字符串>, --module_name <模块名称字符串>
                        模块名称
  -MP <前缀字符串>, --module_prefix <前缀字符串>
                        模块前缀,每个函数名都以此开头
  -MD <元数据文件名>, --metadata <元数据文件名>
                        可选文件,用于发出元数据(内省)

示例:

python gen_mpy.py -MD lv_mpy_example.json -M lvgl -MP lv -I../../berkeley-db-1.xx/PORT/include -I../../lv_binding_micropython -I. -I../.. -Ibuild -I../../mp-readline -I ../../lv_binding_micropython/pycparser/utils/fake_libc_include ../../lv_binding_micropython/lvgl/lvgl.h

绑定其他C库

lvgl绑定脚本可用于将其他C库绑定到MicroPython。 我曾将它用于lodepng和ESP-IDF的部分内容。 更多详情请阅读这篇博客文章

MicroPython绑定使用方法

一个简单的例子:advanced_demo.py。 更多示例可以在/examples文件夹下找到。

导入和初始化LVGL

import lvgl as lv
lv.init()

注册显示和输入驱动

from lv_utils import event_loop

WIDTH = 480
HEIGHT = 320

event_loop = event_loop()
disp_drv = lv.sdl_window_create(WIDTH, HEIGHT)
mouse = lv.sdl_mouse_create()
keyboard = lv.sdl_keyboard_create()
keyboard.set_group(self.group)

在这个例子中,LVGL原生SDL显示和输入驱动在MicroPython的unix端口上注册。

这里是另一个针对ESP32 ILI9341 + XPT2046驱动的示例:

import lvgl as lv

# 导入ILI9341驱动并初始化

from ili9XXX import ili9341
disp = ili9341()

# 导入XPT2046驱动并初始化

from xpt2046 import xpt2046
touch = xpt2046()

默认情况下,ILI9341和XPT2046都在同一个SPI总线上初始化,使用以下参数:

  • ILI9341: miso=5, mosi=18, clk=19, cs=13, dc=12, rst=4, power=14, backlight=15, spihost=esp.HSPI_HOST, mhz=40, factor=4, hybrid=True
  • XPT2046: cs=25, spihost=esp.HSPI_HOST, mhz=5, max_cmds=16, cal_x0 = 3783, cal_y0 = 3948, cal_x1 = 242, cal_y1 = 423, transpose = True, samples = 3

你可以在ili9341/xpt2046构造函数中更改这些参数。 如果需要,你也可以通过提供miso/mosi/clk参数将它们初始化在不同的SPI总线上。将它们设置为-1以使用现有(已初始化)的spihost总线。

这里是另一个例子,这次导入并初始化M5Stack Core2设备的显示和触摸驱动。该设备使用I2C总线上的FT6336芯片读取电容触摸屏,并使用ili9342显示控制器,与ili9341相比有一些信号是反转的:

from ili9XXX import ili9341
disp = ili9341(mosi=23, miso=38, clk=18, dc=15, cs=5, invert=True, rot=0x10)

from ft6x36 import ft6x36
touch = ft6x36(sda=21, scl=22, width=320, height=280)

驱动init参数

通过为驱动的init方法提供widthheightstart_xstart_ycolormodeinvertrot参数,可以支持多种不同的显示模块。

显示尺寸

widthheight参数应设置为显示器在使用方向上的宽度和高度。显示器可能有一个比可见显示区域更大的内部帧缓冲区。start_xstart_y参数用于指示相对于内部帧缓冲区开始的位置,可见像素从哪里开始。

颜色处理

colormodeinvert参数控制显示器如何处理颜色。

显示方向

rot参数用于设置显示器的MADCTL寄存器。MADCTL寄存器控制像素写入帧缓冲区的顺序。这设置了显示器的方向或旋转。

有关MADCTL寄存器以及如何确定显示器的colormoderot参数的更多信息,请参阅examples/madctl目录中的README.md文件。

st7789驱动类

默认情况下,st7789驱动使用以下与TTGO T-Display兼容的参数初始化:

    st7789(
        miso=-1, mosi=19, clk=18, cs=5, dc=16, rst=23, power=-1, backlight=4,
        backlight_on=1, power_on=0, spihost=esp.HSPI_HOST, mhz=40, factor=4, hybrid=True,
        width=320, height=240, start_x=0, start_y=0, colormode=COLOR_MODE_BGR, rot=PORTRAIT,
        invert=True, double_buffer=True, half_duplex=True, asynchronous=False, initialize=True)

参数描述
miso显示器SPI数据输入引脚,如果不使用则设为-1,因为许多st7789显示器没有这个引脚
mosi显示器SPI数据输出引脚(必需)
clkSPI时钟引脚(必需)
cs显示器片选引脚
dc显示器数据/命令选择引脚(必需)
rst显示器复位引脚
power显示器电源开启引脚,如果不使用则设为-1
power_on电源开启引脚值
backlight显示器背光控制引脚
backlight_on背光开启引脚值
spihostESP SPI端口
mhzSPI波特率(MHz)
factor帧缓冲区缩小因子
hybrid布尔值,True使用C刷新例程,False使用纯Python驱动
width显示器宽度
height显示器高度
colormode显示器颜色模式
rot显示方向,可选PORTRAIT、LANDSCAPE、INVERSE_PORTRAIT、INVERSE_LANDSCAPE或与颜色模式进行OR运算的原始MADCTL值
invert显示器颜色反转设置
double_buffer布尔值,True使用双缓冲,False使用单缓冲(节省内存)
half_duplex布尔值,True使用半双工SPI通信
asynchronous布尔值,True使用异步例程
initialize布尔值,True初始化显示器

TTGO T-Display st7789配置示例

import lvgl as lv
from ili9XXX import st7789

disp = st7789(width=135, height=240, rot=st7789.LANDSCAPE)

TTGO TWatch-2020 st7789配置示例

import lvgl as lv
from ili9XXX import st7789

import axp202c

# 初始化电源管理器,设置背光
axp = axp202c.PMU()
axp.enablePower(axp202c.AXP202_LDO2)
axp.setLDO2Voltage(2800)

# 初始化显示器
disp = st7789(
    mosi=19, clk=18, cs=5, dc=27, rst=-1, backlight=12, power=-1,
    width=240, height=240, rot=st7789.INVERSE_PORTRAIT, factor=4)

st7735驱动类

默认情况下,st7735驱动使用以下参数初始化。参数描述与st7789相同。

    st7735(
        miso=-1, mosi=19, clk=18, cs=13, dc=12, rst=4, power=-1, backlight=15, backlight_on=1, power_on=0,
        spihost=esp.HSPI_HOST, mhz=40, factor=4, hybrid=True, width=128, height=160, start_x=0, start_y=0,
        colormode=COLOR_MODE_RGB, rot=PORTRAIT, invert=False, double_buffer=True, half_duplex=True,
        asynchronous=False, initialize=True):

ST7735 128x128配置示例

from ili9XXX import st7735, MADCTL_MX, MADCTL_MY

disp = st7735(
    mhz=3, mosi=18, clk=19, cs=13, dc=12, rst=4, power=-1, backlight=15, backlight_on=1,
    width=128, height=128, start_x=2, start_y=1, rot=PORTRAIT)

ST7735 128x160配置示例

from ili9XXX import st7735, COLOR_MODE_RGB, MADCTL_MX, MADCTL_MY

disp = st7735(
    mhz=3, mosi=18, clk=19, cs=13, dc=12, rst=4, backlight=15, backlight_on=1,
    width=128, height=160, rot=PORTRAIT)

创建带有按钮和标签的屏幕

scr = lv.obj()
btn = lv.button(scr)
btn.align(lv.scr_act(), lv.ALIGN.CENTER, 0, 0)
label = lv.label(btn)
label.set_text("按钮")

# 加载屏幕

lv.scr_load(scr)

创建结构体实例

symbolstyle = lv.style_t(lv.style_plain)

symbolstyle将是一个lv_style_t实例,初始化为与lv_style_plain相同的值

设置结构体中的字段

symbolstyle.text.color = lv.color_hex(0xffffff)

symbolstyle.text.color将被初始化为lv_color_hex返回的颜色结构体

使用字典设置嵌套结构体

symbolstyle.text.color = {"red":0xff, "green":0xff, "blue":0xff}

创建对象实例

self.tabview = lv.tabview(lv.scr_act())

对象构造函数的第一个参数是父对象,第二个是从哪个元素复制此元素。 这两个参数都是可选的。

调用对象方法

self.symbol.align(self, lv.ALIGN.CENTER,0,0)

在此示例中,lv.ALIGN是一个枚举,lv.ALIGN.CENTER是一个枚举成员(整数值)。

使用回调

for btn, name in [(self.btn1, '播放'), (self.btn2, '暂停')]:
    btn.set_event_cb(lambda obj=None, event=-1, name=name: self.label.set_text('%s %s' % (name, get_member_name(lv.EVENT, event))))

使用带有user_data参数的回调:

def cb(user_data):
    print(user_data.cast()['value'])

lv.async_call(cb, {'value':42})

列出可用的函数/成员/常量等

print('\n'.join(dir(lvgl)))
print('\n'.join(dir(lvgl.btn)))
...
项目侧边栏1项目侧边栏2
推荐项目
Project Cover

豆包MarsCode

豆包 MarsCode 是一款革命性的编程助手,通过AI技术提供代码补全、单测生成、代码解释和智能问答等功能,支持100+编程语言,与主流编辑器无缝集成,显著提升开发效率和代码质量。

Project Cover

AI写歌

Suno AI是一个革命性的AI音乐创作平台,能在短短30秒内帮助用户创作出一首完整的歌曲。无论是寻找创作灵感还是需要快速制作音乐,Suno AI都是音乐爱好者和专业人士的理想选择。

Project Cover

有言AI

有言平台提供一站式AIGC视频创作解决方案,通过智能技术简化视频制作流程。无论是企业宣传还是个人分享,有言都能帮助用户快速、轻松地制作出专业级别的视频内容。

Project Cover

Kimi

Kimi AI助手提供多语言对话支持,能够阅读和理解用户上传的文件内容,解析网页信息,并结合搜索结果为用户提供详尽的答案。无论是日常咨询还是专业问题,Kimi都能以友好、专业的方式提供帮助。

Project Cover

阿里绘蛙

绘蛙是阿里巴巴集团推出的革命性AI电商营销平台。利用尖端人工智能技术,为商家提供一键生成商品图和营销文案的服务,显著提升内容创作效率和营销效果。适用于淘宝、天猫等电商平台,让商品第一时间被种草。

Project Cover

吐司

探索Tensor.Art平台的独特AI模型,免费访问各种图像生成与AI训练工具,从Stable Diffusion等基础模型开始,轻松实现创新图像生成。体验前沿的AI技术,推动个人和企业的创新发展。

Project Cover

SubCat字幕猫

SubCat字幕猫APP是一款创新的视频播放器,它将改变您观看视频的方式!SubCat结合了先进的人工智能技术,为您提供即时视频字幕翻译,无论是本地视频还是网络流媒体,让您轻松享受各种语言的内容。

Project Cover

美间AI

美间AI创意设计平台,利用前沿AI技术,为设计师和营销人员提供一站式设计解决方案。从智能海报到3D效果图,再到文案生成,美间让创意设计更简单、更高效。

Project Cover

AIWritePaper论文写作

AIWritePaper论文写作是一站式AI论文写作辅助工具,简化了选题、文献检索至论文撰写的整个过程。通过简单设定,平台可快速生成高质量论文大纲和全文,配合图表、参考文献等一应俱全,同时提供开题报告和答辩PPT等增值服务,保障数据安全,有效提升写作效率和论文质量。

投诉举报邮箱: service@vectorlightyear.com
@2024 懂AI·鲁ICP备2024100362号-6·鲁公网安备37021002001498号