简介:psplash源码是一套面向ARM架构嵌入式Linux开发者的开源启动画面工具,用于在系统启动或更新阶段接管屏幕,显示带进度条与动画效果的图形界面,常见于树莓派、开发板等设备。资源包共79个文件,约2.67MB,以C源码与头文件为核心,包含psplash.c、psplash-fb.c等实现文件,以及Makefile、configure等构建脚本,另有po本地化文件、png图片素材与sample示例,覆盖framebuffer后端、颜色配置、图像头生成等模块。已有667人学习下载。通过研读源码,读者可理解psplash与Linux内核协作获取加载进度、更新界面的机制,掌握修改config.h定制颜色字体与进度条样式、交叉编译并集成到目标系统的方法,同时获得调试优化与拓展动画效果的思路,适合希望深入嵌入式启动流程与自定义用户界面的开发者参考。
1. psplash 源码拆包:嵌入式启动画面到底怎么跑起来的
做过嵌入式 Linux 的同行大概都遇到过这个场景:板子上电后屏幕黑着,等根文件系统挂载、init 拉起一堆服务,好几秒过去才看到 Qt 或 LVGL 的界面。中间这段黑屏时间,客户看着像死机。psplash 就是填这段空白的那个东西——一个极轻量的启动画面程序,直接往 framebuffer 上画图,不依赖 X11、不依赖 Wayland,甚至连图形库都不需要,几百 KB 的源码包,编译出来一个可执行文件加一张图片就完事。
这份 psplash 源码包适合谁?做 BSP 移植的、调启动时间的、给产品做开机 logo 的,以及想搞清楚 framebuffer 直写这套机制的人。它不复杂,但坑不少:图片格式、颜色深度、旋转方向、和 init 脚本的配合,每一项都能让你多烧几块板子。下面按「源码结构 → 编译配置 → 集成到启动流程 → 排错 → 进阶」的顺序拆一遍,能抄的地方直接给命令和代码。
2. 源码结构与 framebuffer 直写原理:先看懂再动手
2.1 目录里到底有什么
拿到源码包,先别急着 make。解压后大致是这么几类文件:
| 文件/目录 | 作用 |
|---|---|
psplash.c | 主程序,初始化、绘制、进度条逻辑都在这 |
psplash.h | 结构体与宏定义,颜色、坐标、命令协议 |
psplash-fb.c | framebuffer 设备操作,open/ioctl/mmap 全在这 |
psplash-image.h | 由图片转换生成的 C 数组头文件 |
psplash-poky-img.h | 默认示例图片数据 |
Makefile.am/configure.ac | autotools 构建脚本 |
psplash-write | 往 FIFO 写命令的小工具,用于更新进度 |
核心就三个:psplash.c管流程,psplash-fb.c管硬件,psplash-image.h管画面。很多人编译报错,八成是图片头文件没生成或者格式不对。
2.2 framebuffer 直写是怎么工作的
psplash 不走任何图形栈,直接操作/dev/fb0。流程是标准的 Linux framebuffer 三步:
/* psplash-fb.c 中的关键逻辑,简化后 */ int fd = open("/dev/fb0", O_RDWR); /* 1. 打开 framebuffer 设备 */ ioctl(fd, FBIOGET_VSCREENINFO, &vinfo); /* 2. 取可变屏幕信息 */ ioctl(fd, FBIOGET_FSCREENINFO, &finfo); /* 取固定屏幕信息 */ /* 计算显存大小 = 行字节数 * 高度 */ size_t fb_size = finfo.line_length * vinfo.yres; void *fb = mmap(NULL, fb_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); /* 3. 映射显存 */映射完成后,fb指针就指向了屏幕显存。往这块内存写像素,屏幕立刻变化。psplash 把图片数据按行拷贝进去,再在底部画一条进度条。
这里有个关键参数:vinfo.bits_per_pixel。它决定每个像素占几个字节——16 位是 2 字节(RGB565),24 位是 3 字节,32 位是 4 字节(ARGB8888)。图片头文件里的数据必须和这个值匹配,否则画出来是花屏或者颜色错乱。我一般先在板子上跑一句:
cat /sys/class/graphics/fb0/bits_per_pixel拿到实际值再去生成图片头文件,能省掉一轮返工。
2.3 图片是怎么变成 C 数组的
psplash 不读 PNG/JPG,它要的是原始像素数组。源码包里通常带一个转换脚本(make-image-header.sh或类似名字),逻辑是:用工具把图片转成指定格式的原始数据,再包成 C 数组。
# 常见做法:先把 PNG 转成 RGB565 原始数据,再生成头文件 # 假设目标屏幕是 16 位色、480x272 pngtopnm logo.png | ppmquant 256 | pnmtoplainpnm > logo.ppm # 用脚本转成 C 头文件(脚本名以实际包内为准) ./make-image-header.sh logo.png POKY生成的psplash-logo-img.h里是一个uint8_t psplash_logo_img[]数组,外加宽高宏定义。注意:图片尺寸最好和屏幕分辨率一致,或者至少宽度对齐。宽度不对齐会导致每行拷贝错位,画面斜着撕裂——这个现象我第一次遇到时以为是屏幕坏了,查了半天才发现是图片宽度不是 4 的倍数。
提示:转换前先确认屏幕的像素格式和字节序。RGB565 有大端小端之分,板子和 PC 不一致时颜色会红蓝互换。
3. 编译与配置:autotools 交叉编译的完整走法
3.1 交叉编译环境准备
psplash 用 autotools 构建,交叉编译需要指定 host 和编译器。假设工具链前缀是arm-linux-gnueabihf-,标准流程:
# 1. 生成 configure 脚本(如果源码包没带) ./autogen.sh # 2. 配置,指定交叉编译器和安装路径 ./configure \ --host=arm-linux-gnueabihf \ --prefix=/usr \ CC=arm-linux-gnueabihf-gcc \ --disable-static \ --enable-shared # 3. 编译 make -j4 # 4. 安装到临时目录,方便打包进根文件系统 make DESTDIR=/path/to/rootfs install参数说明:--host告诉 configure 这是交叉编译,会去找arm-linux-gnueabihf-gcc;--prefix=/usr决定装到目标系统的/usr/bin;DESTDIR是暂存目录,不污染宿主机。如果 configure 报找不到编译器,检查工具链是否在 PATH 里,或者用绝对路径写CC。
3.2 关键编译选项与裁剪
psplash 本身没几个开关,但有几个地方值得注意:
--enable-shared/--disable-static:嵌入式一般用动态链接省空间,但如果根文件系统很小、不想带一堆 .so,可以反过来编静态。- 图片头文件路径:默认用包内自带的,要换自己的 logo,得改
Makefile.am里的psplash-image.h依赖,或者直接替换生成的头文件。 - 进度条颜色:在
psplash.h里有一组宏定义,比如PSPLASH_BAR_COLOR、PSPLASH_BG_COLOR,改完重新编译即可。
/* psplash.h 中可调的颜色定义,格式随像素深度变化 */ #define PSPLASH_BAR_COLOR 0xffffffff /* 进度条颜色,32位ARGB */ #define PSPLASH_BG_COLOR 0x00000000 /* 背景色 */ #define PSPLASH_BAR_WIDTH 8 /* 进度条高度,像素 */改颜色时注意:这些值的编码方式跟bits_per_pixel绑定。16 位色下写 32 位的值,颜色会完全不对。稳妥做法是先用默认值跑通,再逐项改。
3.3 生成自己的启动 logo
替换 logo 是最高频的需求。完整步骤:
# 1. 准备一张和屏幕同分辨率的 PNG,去掉透明通道(psplash 不处理 alpha 混合) # 2. 转成 PPM 中间格式 convert logo.png -alpha off -depth 8 logo.ppm # 需要 ImageMagick # 3. 用包内脚本生成头文件,第二个参数是数组名前缀 ./make-image-header.sh logo.ppm LOGO # 4. 替换原头文件,重新编译 cp psplash-logo-img.h psplash-image.h make clean && make逻辑说明:-alpha off是关键,psplash 直接拷贝像素,不做 alpha 混合,带透明通道的 PNG 转出来会有黑边或杂色。-depth 8保证每通道 8 位,避免高位深数据导致数组长度对不上。生成的头文件里数组长度必须等于宽 × 高 × 每像素字节数,不等就是转换参数错了。
注意:如果屏幕是竖屏但图片是横屏,别在图片里旋转,用 psplash 的旋转参数或 framebuffer 的
rotate属性处理,否则进度条坐标会错位。
4. 集成到启动流程:和 init 脚本配合的三种方式
4.1 最简集成:init 里直接拉起
psplash 编译好扔进/usr/bin,在 init 脚本早期启动它:
#!/bin/sh # 在挂载完 /proc /sys、framebuffer 驱动就绪之后 /usr/bin/psplash & # 后续启动服务,期间可以更新进度 /usr/bin/psplash-write "PROGRESS 30" /etc/init.d/start_network /usr/bin/psplash-write "PROGRESS 60" /etc/init.d/start_app /usr/bin/psplash-write "QUIT" # 退出 psplash,交还屏幕psplash-write通过 FIFO(默认/tmp/psplash_fifo)给主进程发命令。PROGRESS后跟 0-100 的整数,QUIT让 psplash 退出并清屏。顺序很重要:必须在 framebuffer 驱动加载之后启动,否则 open/dev/fb0会失败。
4.2 用 systemd 管理(如果根文件系统用 systemd)
# /etc/systemd/system/psplash.service [Unit] Description=Boot Splash DefaultDependencies=no After=sysinit.target Before=multi-user.target [Service] Type=simple ExecStart=/usr/bin/psplash ExecStop=/usr/bin/psplash-write QUIT RemainAfterExit=yes [Install] WantedBy=multi-user.target关键点是DefaultDependencies=no和After=sysinit.target,保证它在服务启动前就跑起来。ExecStop负责退出时清屏,不然 psplash 的画面会一直留在屏幕上,盖住后面的应用界面。
4.3 进度条同步的坑
很多人集成完发现进度条不动,或者动一下就卡住。原因通常是:psplash-write写 FIFO 时主进程没在读,或者 FIFO 路径不一致。psplash 默认 FIFO 路径在编译时确定,可以用--with-fifo之类的选项改(具体看 configure 支持),也可以直接改源码里的宏。
# 确认 FIFO 是否存在、权限对不对 ls -l /tmp/psplash_fifo # 手动发一条命令测试 echo "PROGRESS 50" > /tmp/psplash_fifo如果手动发命令有效但脚本里无效,多半是脚本执行时 FIFO 还没创建——psplash 启动到创建 FIFO 有几毫秒延迟,脚本里加个sleep 0.2或者循环等待 FIFO 出现再继续。
5. 避坑与排查:五个真实翻车记录
5.1 花屏、颜色错乱
现象:psplash 启动后屏幕是彩色雪花,或者 logo 颜色完全不对(红蓝互换)。原因:图片头文件的像素格式和 framebuffer 实际bits_per_pixel不匹配,或者字节序反了。解决:先读/sys/class/graphics/fb0/bits_per_pixel确认深度,再用对应参数重新生成头文件。RGB565 大小端问题,在转换脚本里加字节交换,或者改psplash-fb.c里的写入逻辑。
5.2 画面撕裂、斜着错位
现象:图片能显示,但每行往右偏移,整体斜着。原因:图片宽度和 framebuffer 的line_length不匹配。framebuffer 每行有对齐填充,图片宽度不是对齐值的整数倍时,拷贝就错位。解决:把 logo 宽度改成 4 或 8 的倍数,或者改psplash-fb.c里的拷贝逻辑,按finfo.line_length逐行处理而不是整块 memcpy。
5.3 启动后黑屏,psplash 没反应
现象:执行 psplash 后没有任何输出,屏幕全黑。原因:/dev/fb0不存在或权限不对,或者 framebuffer 驱动还没加载。解决:ls -l /dev/fb0确认设备节点存在;检查内核配置里 framebuffer 驱动是否编入;确认启动顺序在驱动加载之后。用strace /usr/bin/psplash能看到卡在哪个系统调用。
5.4 进度条不动
现象:psplash 画面正常,但psplash-write "PROGRESS 50"没效果。原因:FIFO 路径不匹配,或者主进程没在监听。解决:确认 psplash 启动时打印的 FIFO 路径(可以加调试输出),和psplash-write用的路径一致。检查 FIFO 文件权限,非 root 用户可能写不进去。
5.5 退出后屏幕残留
现象:psplash-write "QUIT"后画面还在,应用界面被盖住。原因:psplash 退出时没有清屏,或者应用没有重新初始化 framebuffer。解决:确认 QUIT 命令被正确处理;在应用启动前手动清一次 framebuffer,或者让 psplash 退出时把屏幕填成背景色。有些方案是 psplash 退出后由应用接管 framebuffer,这时要保证应用重新 mmap。
6. 进阶:把 psplash 改成你自己的启动信息面板
跑通基本流程后,psplash 还能干更多事。它的源码足够小,改起来没有心理负担。我一般会做两件事:一是把进度条换成文字状态提示,二是加一个简单的动画效果。
6.1 加文字渲染
psplash 本身不带字体渲染,但可以内嵌一个点阵字库。思路是在psplash-image.h旁边再加一个font.h,存 ASCII 字符的点阵数据,然后在psplash.c的绘制函数里加一个draw_text():
/* 简化示例:在指定位置画一个字符 */ static void draw_char(uint32_t *fb, int x, int y, char c, uint32_t color) { const uint8_t *glyph = font8x16[(int)c]; /* 每个字符 16 字节点阵 */ for (int row = 0; row < 16; row++) { for (int col = 0; col < 8; col++) { if (glyph[row] & (0x80 >> col)) { fb[(y + row) * fb_width + (x + col)] = color; } } } }参数说明:fb是 mmap 后的显存指针,x/y是字符左上角坐标,color按当前像素深度编码。字库可以用现成的 8x16 点阵,也可以用工具从 TTF 生成。注意坐标不要超出屏幕范围,否则会写越界。
6.2 进度条动画与平滑过渡
默认进度条是跳变的,PROGRESS 30直接跳到 30% 位置。想要平滑动画,可以在主循环里加插值:
/* 在 psplash 主循环中,逐步逼近目标进度 */ static int current_progress = 0; static int target_progress = 0; while (current_progress != target_progress) { if (current_progress < target_progress) current_progress++; else current_progress--; draw_progress_bar(current_progress); usleep(10000); /* 10ms 一步,100 步约 1 秒 */ }这样进度条会以每步 10ms 的速度平滑移动,视觉上舒服很多。代价是主循环被占用,期间不处理 FIFO 命令——所以插值步长别太小,或者用非阻塞方式读 FIFO。
6.3 验证方法
改完之后怎么确认没问题?我的习惯是三步:
- 在 PC 上用
qemu模拟 framebuffer 跑一遍,确认逻辑正确; - 板子上先不接屏幕,用
cat /dev/fb0 > /tmp/fb.raw抓一帧,在 PC 上用工具转成 PNG 看效果; - 接上屏幕,从冷启动到应用界面全程录像,逐帧检查过渡是否自然。
# 抓取 framebuffer 当前内容(假设 480x272,16位色) cat /dev/fb0 > /tmp/fb.raw # 在 PC 上转换查看(需要知道分辨率和像素格式) # 用 Python PIL 或 ffmpeg 都可以从那以后我每次改 psplash 的绘制逻辑,都强制走一遍「qemu 验证 → 抓帧比对 → 实机录像」,再也没出现过上线后花屏的尴尬。希望帮到你。
本文还有配套的精品资源,点击获取