
什么是 MicroPython?#
MicroPython 是面向微控制器的 Python。使用 MicroPython,您可以编写 Python 3 代码并在资源有限的裸机架构上运行。
MicroPython 的亮点#
- 紧凑 - 仅需 256k 代码空间和 16k 内存即可运行。不需要操作系统,当然您也可以在操作系统上运行它。
- 兼容 - 力求与标准 Python(称为 CPython)尽可能兼容。
- 多功能 - 支持多种架构(x86、x86-64、ARM、ARM Thumb、Xtensa)。
- 交互式 - 无需编译-烧写-启动循环。使用 REPL(交互式提示符),您可以输入命令并立即执行,运行脚本等。
- 流行 - 支持众多平台。用户群体不断扩大。著名的分支包括 MicroPython、CircuitPython 和 MicroPython_ESP32_psRAM_LoBo。
- 嵌入式导向 - 专门为嵌入式系统提供模块,如用于访问低级硬件(I/O 引脚、ADC、UART、SPI、I2C、RTC、定时器等)的 machine 模块。
为什么选择 MicroPython + LVGL?#
目前 MicroPython 没有一个好的高级 GUI 库。LVGL 是一个用 C 实现、提供 C API 的优秀高级 GUI 库。LVGL 是一个面向对象、基于组件的库,这使得它非常适合映射到像 Python 这样的高级语言。
在 MicroPython 中使用 LVGL 的优势#
- 用 Python 开发 GUI - 使用一种非常流行的高级语言,支持面向对象编程等范式。
- 快速迭代周期 - 使用 C 时,每次迭代包括修改代码 → 构建 → 烧写 → 运行。在 MicroPython 中,只需修改代码 → 运行。您甚至可以使用 REPL(交互式提示符)交互式地运行命令。
MicroPython + LVGL 的使用场景#
- 快速 GUI 原型设计 - 快速尝试不同的设计和布局。
- 缩短开发周期 - 无需编译/烧写开销即可修改和微调 GUI。
- 抽象 GUI 建模 - 定义可重用的复合对象,利用 Python 的语言特性,如继承、闭包、列表推导、生成器、异常处理、任意精度整数等。
- 更广泛的可访问性 - 使 LVGL 能够被更多受众使用。无需了解 C 即可在嵌入式系统上创建精美的 GUI。这与 CircuitPython 的愿景非常契合,CircuitPython 设计时就考虑到教育,让新手或没有经验的用户更容易开始嵌入式开发。
那么它看起来是什么样的?#
它与 C API 非常相似,但 LVGL 组件采用面向对象的方式。
让我们直接看一个示例!
一个简单的示例#
import lvgl as lv
lv.init()
scr = lv.obj()
btn = lv.btn(scr)
btn.align(lv.scr_act(), lv.ALIGN.CENTER, 0, 0)
label = lv.label(btn)
label.set_text("Button")
lv.scr_load(scr)python在这个示例中,我们创建了一个按钮,将其居中对齐,并在上面添加了一个文本标签"Button"。最后,我们加载带有按钮的屏幕以显示它。
一个更高级的示例#
在这个示例中,我假设您已经具备一些 LVGL 的基础知识。如果没有,请快速浏览一下 LVGL 教程。
class SymbolButton(lv.btn):
def __init__(self, parent, symbol, text):
super().__init__(parent)
self.symbol = lv.label(self)
self.symbol.set_text(symbol)
self.symbol.set_style(symbolstyle)
self.label = lv.label(self)
self.label.set_text(text)python在这个示例中,我们创建了一个名为 SymbolButton 的可重用复合组件。它是一个类,因此我们可以从它创建对象实例。它是复合的,因为它由多个原生 LVGL 对象组成:
- 一个按钮 -
SymbolButton继承自lv.btn。lv.btn是一个原生 LVGL 按钮组件。 - 一个符号标签 - 一个具有符号样式(符号字体)的标签,作为
self的子对象(即 SymbolButton 继承的父按钮的子对象)。lv.label是一个原生 LVGL 标签组件,表示另一个组件内的文本。 - 一个文本标签 - 一个带有文本的标签,作为
self的另一个子对象。
SymbolButton 构造函数(__init__ 函数)创建了这两个标签,并设置了它们的内容和样式。
以下是如何使用我们的 SymbolButton 的示例:
self.btn1 = SymbolButton(page, lv.SYMBOL.PLAY, "Play")
self.btn1.set_size(140,100)
self.btn1.align(None, lv.ALIGN.IN_TOP_LEFT, 10, 0)
self.btn2 = SymbolButton(page, lv.SYMBOL.PAUSE, "Pause")
self.btn2.set_size(140,100)
self.btn2.align(self.btn1, lv.ALIGN.OUT_RIGHT_TOP, 10, 0)python这里,我们设置了每个按钮的大小,将 btn1 对齐到页面,并将 btn2 相对于 btn1 对齐。我们调用了复合组件 SymbolButton 的 set_size 和 align 方法——这些方法是从 SymbolButton 的父类 lv.btn 继承的,lv.btn 是一个 LVGL 原生对象。
结果如下所示:

要获取更完整的示例(包括其他对象类型以及动作回调函数和驱动程序注册),请查看这个演示脚本。
以下是在 MicroPython 中使用 LVGL 的更多示例:
创建带有按钮和标签的屏幕#
scr = lv.obj()
btn = lv.btn(scr)
btn.align(lv.scr_act(), lv.ALIGN.CENTER, 0, 0)
label = lv.label(btn)
label.set_text("Button")
# 加载屏幕
lv.scr_load(scr)python创建结构体实例#
symbolstyle = lv.style_t(lv.style_plain)pythonsymbolstyle 将是一个 lv_style_t 的实例,初始化为与 lv_style_plain 相同的值。
设置结构体中的字段#
symbolstyle.text.color = lv.color_hex(0xffffff)pythonsymbolstyle.text.color 将被初始化为 lv_color_hex 返回的颜色结构体。
使用字典设置嵌套结构体#
symbolstyle.text.color = {"red":0xff, "green":0xff, "blue":0xff}python创建对象实例#
self.tabview = lv.tabview(lv.scr_act())python对象构造函数的第一个参数是父对象,第二个参数是要从哪个元素复制此元素。
调用对象方法#
self.symbol.align(self, lv.ALIGN.CENTER, 0, 0)python在这个示例中,lv.ALIGN 是一个枚举,lv.ALIGN.CENTER 是一个枚举成员(一个整数值)。
使用回调函数#
for btn, name in [(self.btn1, 'Play'), (self.btn2, 'Pause')]:
btn.set_action(lv.btn.ACTION.CLICK, lambda action,name=name: self.label.set_text('%s click' % name) or lv.RES.OK)python这里,我们有一个循环,为按钮 btn1 和 btn2 设置动作。btn1 的动作是将 label 文本设置为"Play click",btn2 的动作是将 label 文本设置为"Pause click"。
这是如何工作的?首先您需要理解两个 Python 特性:lambda 和闭包。set_action 函数需要两个参数:一个动作枚举(在本例中为 CLICK)和一个函数。在 Python 中,函数是"一等公民",这意味着它们可以被当作值并传递给另一个函数,就像这个例子中一样。
我们传递的函数是一个 lambda,这是一个匿名函数。它的第一个参数是动作,第二个参数是来自 for 循环的 name 变量。该函数不使用 action 参数,但使用 name 来设置标签的文本。
设置完标签文本后,lambda 函数完成并返回 lv.RES.OK 值。lambda 不能有 return 语句,因为它必须是一个表达式。set_text 的求值结果为 None,因此 set_text(...) or lv.RES.OK 的求值结果为 lv.RES.OK,并被视为 lambda 函数的返回值。
您可能会问自己——为什么我们需要将 name 作为参数传递?为什么不像这样在 lambda 中直接使用它:lambda action: self.label.set_text('%s click' % name)?
嗯,**这样做不会正确工作!**像这样使用 name 会创建一个闭包,这是一个记住封闭作用域中值(在本例中为 name)的函数对象。问题在于,在 Python 中,name 的解析是在执行 name 时完成的。如果我们将 name 放在 lambda 函数中,就太晚了——name 已经被设置为 Pause,因此两个按钮都会设置"Pause click"文本。我们需要在 for 循环迭代执行时设置 name,而不是在执行 lambda 函数时。因此我们将 name 作为参数传递,这是它被解析的时刻。这里有一篇简短的 Stack Overflow 帖子解释了这一点。
目前绑定限制为每个对象只能有一个回调函数。
它是如何工作的?#
一个脚本解析 LVGL 头文件并创建 MicroPython 模块。
要在 MicroPython 中使用 LVGL,您需要 MicroPython Binding for LVGL。此绑定是 LVGL MicroPython 模块的生成器。它本质上是一个 Python 脚本,读取并解析 LVGL C 头文件,然后从中生成 MicroPython 模块。该模块可以在 MicroPython 中用于访问大部分 LVGL API。
LVGL 是一个面向对象、基于组件的库。有一个名为 lv_obj 的基类,所有其他组件都从它继承,在组件之间创建了一个层次结构。对象有它们的方法函数,继承它们父类的方法等。MicroPython Binding for LVGL 利用了这种设计,并在 Python 中对此类层次结构进行建模。您可以通过继承从现有 LVGL 组件创建自己的(纯 Python)复合组件。
有关更多详细信息,请参阅 MicroPython Binding for LVGL 的 README。
如何使用它?#
最快的开始方式:Fork lv_micropython。它包含 MicroPython + LVGL 的可用 Unix(Linux)和 ESP32 移植。
MicroPython Binding for LVGL(lv_binding_micropython)旨在简化在 MicroPython 中使用 LVGL。原则上它可以支持任何 MicroPython 分支。
要将它添加到某个 MicroPython 分支,您需要在 MicroPython lib 下将 lv_binding_micropython 添加为 git 子模块。lv_binding_micropython 本身包含 LVGL 作为 git 子模块。在 MicroPython 代码本身中,只需要进行很少的更改。您需要在 MicroPython Makefile 中添加一些行来创建 LVGL 绑定模块并编译 LVGL,并且还需要通过编辑 mpconfigport.h 将新的 lvgl 模块添加到 MicroPython。
作为示例,我创建了 lv_micropython——一个带有 LVGL 绑定的 MicroPython 分支。您可以按原样使用它,或者将其作为如何将 LVGL 与 MicroPython 集成的示例。lv_micropython 目前可以在 unix 移植和 ESP32 移植上与 LVGL 一起使用。
可用的驱动程序#
LVGL 需要显示屏和输入设备的驱动程序。MicroPython 绑定包含一些示例驱动程序,这些驱动程序在 lv_micropython 上注册和使用:
- SDL unix 驱动程序(显示屏和鼠标)
- ILI9341 驱动程序(用于 ESP32)
- 原始电阻式触摸(用于 ESP32,ADC 直接连接到屏幕,无触摸 IC)
为其他显示屏和输入设备创建新驱动程序很容易。如果您添加了新驱动程序,我们很乐意将其添加到 MicroPython Binding,所以请向我们发送 pull request!
常见问题#
如何知道 MicroPython 中有哪些 LVGL 对象和函数可用?#
几乎所有的都可用!如果缺少某些您需要的功能,请在 MicroPython Binding Issues 部分打开一个 issue,我们会尝试添加它们。
- 运行启用了 LVGL 模块的 MicroPython(例如
lv_micropython) - 打开 REPL(交互式控制台)
import lvgl as lv- 输入
lv.+ TAB 进行自动补全。将显示 LVGL 所有支持的类和函数。 - 另一个选项:
help(lv) - 另一个选项:
print('\n'.join(dir(lv))) - 您也可以递归地执行此操作。例如
lv.btn.+ TAB,或print('\n'.join(dir(lv.btn)))
您还可以查看 LVGL 绑定模块本身。它在 MicroPython 构建期间生成,通常称为 lv_mpy.c。
这是一个庞大的 API!仅 LVGL 绑定模块就有超过 25K 行代码!这还没算 LVGL 代码本身!#
这取决于 LVGL 配置。它可以很小也可以很大。请记住,LVGL 绑定模块是在您构建 MicroPython 时生成的,基于 LVGL 头文件和配置文件 - lv_conf.h。如果您在 lv_conf.h 中启用了所有内容——模块将会很大。您可以通过更改 lv_conf.h 中的定义来禁用功能并删除不需要的组件,模块将变得小得多。
无论如何,请记住该模块位于程序内存中。它本身不消耗 RAM,只消耗 ROM。从 RAM 的角度来看,LVGL 对象的每个实例通常只会消耗额外的几个字节,以表示 LVGL 对象周围的 MicroPython 包装器对象。
我想试试!最快的开始方式是什么?#
最快的开始方式:Fork lv_micropython。它包含 MicroPython + LVGL 的可用 unix(Linux)和 ESP32 移植。
Python 上的 LVGL?这不是有点......慢吗?#
不慢。所有 LVGL 功能(如渲染图形)仍然在 C 中执行。MicroPython 绑定只为 LVGL API 提供包装器,例如创建组件、设置它们的属性、布局、样式等。与其他 LVGL 功能相比,花费在这些操作上的周期非常少。
我可以在 XXXX MicroPython 分支上使用 LVGL 绑定吗?#
很可能可以!您需要将 MicroPython Binding for LVGL 作为子模块添加到您的分支中,并对 Makefile 和您的移植中的 mpconfigport.h 进行一些小的更改,但仅此而已。有关更多详细信息,请查看 README。
我可以在 XXXX 显示屏/输入设备硬件上使用 LVGL 绑定吗?#
可以,但您需要驱动程序。LVGL 需要显示屏和输入设备的驱动程序。一旦您的硬件有了 C 驱动程序,将其包装为 MicroPython 中的模块并与 LVGL Binding for MicroPython 一起使用就非常简单了。您可以在 LVGL Binding for MicroPython 的 driver 目录中看到一些此类驱动程序(及其包装器 MicroPython 模块)的示例。
我需要分配一个 LVGL 结构体(如 Style、Color 等)。我该怎么做?如何为其分配/释放内存?#
在大多数情况下,您不需要担心内存分配。这是因为 LVGL 可以利用 MicroPython 的 gc(垃圾回收)。当内存被分配时,MicroPython 会知道何时在不再需要时释放它。
LVGL 结构体在 lvgl 模块下作为 MicroPython 类实现。您可以像创建任何其他对象一样创建它们:
import lvgl as lv
s = lv.style_t()python您还可以创建一个结构体,它是另一个结构体的副本:
import lvgl as lv
s = lv.style_t(lv.style_plain)python您可以像 C 结构体一样访问它们,使用 Python 属性:
s.text.color = lv.color_hex(0xffffff)pythonMicroPython 上的 LVGL 有些东西出错了/无法工作/缺失!#
请在 GitHub 上 MicroPython Binding for LVGL 的 MicroPython Binding Issues 部分报告错误和问题。您也可以在 LVGL 论坛上联系我们以提问或进行任何其他讨论。
