/dev/fb0 有了,画的色带也对。但色带是我用 dd 灌进去的,不能说明这套显示能撑起真实应用。
最后一关:跑一个图形库。
选 LVGL 和为什么做成配方
LVGL 8.3 有 Linux 的 fbdev 后端,不需要 SDL、不需要 X11,直接写 /dev/fb0。正好对得上。
但交叉编译是个麻烦。我先试了直接调 sysroots-components 里的 gcc,报:
as: unrecognized option "-EL"
那个目录只有 gcc 没有 binutils,-EL 传下去找不到交叉汇编器,退回主机的 as。
所以改走正规路子:写成 Yocto 配方,工具链、sysroot、依赖全交给构建系统。
配方
meta-letsdesign/recipes-graphics/lvgl-demo/lvgl-demo.bb:
SRC_URI = "git://github.com/lvgl/lvgl.git;protocol=https;branch=release/v8.3 \\
file://main.c file://lv_conf.h"
SRCREV = "fbb73d4b3ffe086a7218bd1a8e51f9e7a260c4b9"
do_compile() {
${CC} ${CFLAGS} -I${S}/lvgl -O2 -o lvgl-demo \\
${S}/main.c $(find ${S}/lvgl/src -name '*.c') \\
$(find ${S}/lvgl/demos/widgets -name '*.c') ${LDFLAGS}
}
do_install() {
install -Dm755 lvgl-demo ${D}${bindir}/lvgl-demo
}
【12_01_LVGL接入】
踩到两个小坑:
LIC_FILES_CHKSUM 路径少了 lvgl/ 前缀。因为 SRC_URI 用了 destsuffix=lvgl,文件在子目录里。
从 ipk 解包时找不到 data.tar.gz,Poky 默认用 zstd 压缩,得 tar --zstd -xf data.tar.zst。
main.c:80 行
核心就是一个 flush 回调。
static void fb_flush(lv_disp_drv_t *drv, const lv_area_t *area,
lv_color_t *color_p)
{
int32_t x, y;
uint32_t stride = vinfo.xres_virtual * (vinfo.bits_per_pixel / 8);
for (y = area->y1; y <= area->y2; y++) {
uint8_t *row = fb_mem + (size_t)y * stride;
for (x = area->x1; x <= area->x2; x++) {
lv_color_t c = *color_p++;
row[x * 3 + 0] = c.ch.red;
row[x * 3 + 1] = c.ch.green;
row[x * 3 + 2] = c.ch.blue;
}
}
lv_disp_flush_ready(drv);
}open("/dev/fb0") + FBIOGET_VSCREENINFO + mmap,注册这个回调,然后 lv_demo_widgets(),主循环里调 lv_timer_handler()。
没有别的。
启动:run-lvgl-vnc.sh
启动不是一行命令的事。要先确认 initramfs 存在,再拼一长串 QEMU 参数,还要把 VNC 开起来。这些封装成了一个脚本:
./run-lvgl-vnc.sh
它做三件事:
检查 30-qemu/initramfs-lvgl.cpio.gz 在不在,不在就调 make-initramfs-lvgl.sh 重建
拼好 QEMU 参数
起 QEMU,VNC 开在 5901
核心参数是这样:
qemu-system-arm -M sama7d65 -cpu cortex-a7 -m 512 \ -kernel zImage -dtb sama7d65-sim-lcd.dtb \ -initrd initramfs-lvgl.cpio.gz \ -append "console=ttyS0,115200 rdinit=/init" \ -display none -vnc 127.0.0.1:1 \ -serial mon:stdio
-display none 是因为服务器没显示器。画面靠 VNC:
vncviewer 127.0.0.1:5901
:1 是 display 号,对应端口 5901(5900 + 1)。连上就是一个 480×272 的窗口,实时流,能看到悬浮按钮的呼吸动画,也能用鼠标点标签栏切换页面。
换端口用 ./run-lvgl-vnc.sh 2,对应 5902。跨机器看的话配 SSH 隧道:
ssh -L 5901:127.0.0.1:5901 <服务器> vncviewer 127.0.0.1:5901
开 VNC 踩的两个坑
坑一:编译 QEMU 时没开 VNC。
最初的构建参数里有 --disable-vnc,结果 -display help 只列出 none、curses、dbus,-vnc 直接报未知参数。去掉这个开关重新配置才有。
坑二:could not read keymap file: 'en-us'。
QEMU 去 build/qemu-bundle/usr/local/share/qemu/keymaps/ 找键位文件,而 pc-bios/keymaps/* 是指向构建目录的符号链接,目标不存在。用 cp -L 解引用复制过去才行:
mkdir -p build/qemu-bundle/usr/local/share/qemu/keymaps cp -L pc-bios/keymaps/* build/qemu-bundle/usr/local/share/qemu/keymaps/
跑起来
配方产出的二进制 542 KB,只依赖 libc.so.6。塞进 initramfs,启动,运行:
~ # /usr/bin/lvgl-demo & fb: 480x272 24 bpp lvgl demo started
整段启动过程是这样:
【12_02_运行日志】
从上往下能看出几件事:
mmc0: SDHCI controller on e1208000.mmc ... using ADMA —— SD 控制器起来了
mmcblk0: mmc0:4567 QEMU! 1.00 GiB,p1 p2 —— 1 GB 卡,两个分区
中间那个 ===== 框是 initramfs 的 /init 打的横幅
fb: 480x272 24 bpp —— LVGL 打开了 /dev/fb0,读到的参数和模型的配置一致
lvgl demo started —— 进主循环了
这是从 VNC 客户端截的图:
【12_03_LVGL界面】
窗口标题栏写着 内网-远程连接 (QEMU) - RealVNC Viewer,说明画面是从 QEMU 的 VNC 服务实时传过来的,不是本机渲染。
完整的 widgets demo:标签栏、头像卡片、邮箱电话、两个按钮、右下角悬浮球。
到这一步说明什么
链路是完整的:
HLCDC 驱动 → DRM 框架 → fbdev 接口 → LVGL 图形库 → 真实 GUI 应用
四层全打通。不是"能显示个方块",是能跑有内存分配、有动画、有图片解码的完整图形栈。
这套模型现在的边界
能做的:串口控制台、SD 卡启动到登录、千兆网络、CAN-FD 回环、480×272 显示、跑 LVGL。
不能做的:
PMC 时钟树没建模,用的是 fixed-clock。频率对,但不支持动态调频。
没有 USB、没有音频、没有真正的 TSN。
CAN 只能回环,没接真实总线。
功耗、信号完整性、EMC 这些,只有实物能测。这颗芯片在我手上只做开箱展示,不参与实测。
回头看
从"三个仿真器都不认识它",到能跑起一个带图形界面的 Linux,中间没有捷径——就是把每个外设的寄存器对着驱动啃一遍,然后把坑一个个填上。
最有价值的一条经验是:当现象自相矛盾时,矛盾点本身就是答案的入口。 中断在跑但 CPU 在睡,寄存器访问停了但没崩,这些地方比报错信息更有指向性。
附件
完整的东西都在附件 repro-kit.zip 里(25 KB):
qemu/sama7d65.c 机器模型全部源码,939 行 qemu/meson.build.add 集成改动,一行 qemu/Kconfig.add 集成改动,一段 hw/*.dts 四份仿真设备树 yocto/virt-support.cfg 内核配置片段 yocto/*.bbappend pseudo 修复与内核挂载 yocto/recipes-graphics/ LVGL 配方与 main.c scripts/*.sh 启动与重建脚本 README.md 八节完整步骤 + 踩坑速查表
README 里是从编 BSP 到跑 LVGL 的完整流程,每一步都是可直接执行的命令。照着走一遍,应该能拿到同样的界面。
如果哪一步对不上,先翻 README 最后那张坑位对照表,八条常见报错都列了原因。
我要赚赏金
