news 2026/10/21 13:02:13

嵌入式Linux启动画面psplash源码拆解与集成实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
嵌入式Linux启动画面psplash源码拆解与集成实战

简介: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.cframebuffer 设备操作,open/ioctl/mmap 全在这
psplash-image.h由图片转换生成的 C 数组头文件
psplash-poky-img.h默认示例图片数据
Makefile.am/configure.acautotools 构建脚本
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 验证方法

改完之后怎么确认没问题?我的习惯是三步:

  1. 在 PC 上用qemu模拟 framebuffer 跑一遍,确认逻辑正确;
  2. 板子上先不接屏幕,用cat /dev/fb0 > /tmp/fb.raw抓一帧,在 PC 上用工具转成 PNG 看效果;
  3. 接上屏幕,从冷启动到应用界面全程录像,逐帧检查过渡是否自然。
# 抓取 framebuffer 当前内容(假设 480x272,16位色) cat /dev/fb0 > /tmp/fb.raw # 在 PC 上转换查看(需要知道分辨率和像素格式) # 用 Python PIL 或 ffmpeg 都可以

从那以后我每次改 psplash 的绘制逻辑,都强制走一遍「qemu 验证 → 抓帧比对 → 实机录像」,再也没出现过上线后花屏的尴尬。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 13:02:03

Dify开源AI平台实战:从零搭建设备知识库问答系统

1. 我在项目里为什么最终选了Dify 最近我们在帮一个售后团队搭设备知识库问答系统&#xff0c;Dify开源AI开发平台成了整个项目里最顺手的底座。说句实话&#xff0c;早几年这种项目要么拿 LangChain 硬怼一圈&#xff0c;要么直接裸调大模型API&#xff0c;反正都得自己写前端…

作者头像 李华
网站建设 2026/10/21 13:01:02

FastAPI 容器化部署实战:从 Dockerfile 到 docker-compose 的完整指南

我最早用 FastAPI 做后端时&#xff0c;最头疼的就是部署。代码写好了、测试全绿&#xff0c;一上生产服务器就出各种幺蛾子&#xff0c;有一次甚至因为运维顺手跑了一下 pip 升级&#xff0c;业务接口直接崩了。后来我把 Docker 部署 FastAPI 这套流程完整走通&#xff0c;才发…

作者头像 李华
网站建设 2026/10/21 12:56:53

AI漫剧出海实战:从分镜脚本到全球变现的全链路工作流

1. 这不是“AI漫画”的简单叠加&#xff0c;而是一场内容生产范式的迁移 “AI 漫剧出海实战&#xff1a;从内容生成到全球变现”——这个标题里藏着三个被多数人忽略的关键词&#xff1a; 漫剧 、 出海 、 变现 。它不是教你怎么用Stable Diffusion画一张好看的图&#x…

作者头像 李华
网站建设 2026/10/21 12:56:45

解析hyp.scratch.yaml:目标检测超参数配置与调参实战

干过目标检测训练的朋友&#xff0c;肯定对hyp.scratch.yaml不陌生。它在某开源检测框架的cfg/hyps目录下&#xff0c;名字拆开就是 hyperparameters&#xff08;超参数&#xff09; scratch&#xff08;从零开始&#xff09; yaml&#xff08;配置文件格式&#xff09;。说白了…

作者头像 李华
网站建设 2026/10/21 12:56:13

Redis字符串类型深度剖析:从SDS存储结构到核心命令实战

1. Redis 字符串类型&#xff1a;先搞懂它到底在存什么 Redis 的 String&#xff08;字符串&#xff09;类型是整个 Redis 体系里最基础、也最容易被低估的数据类型。很多人一听“字符串”就下意识觉得“不就是 key-value 存文本嘛”&#xff0c;实际上它承担了缓存、计数器、分…

作者头像 李华
网站建设 2026/10/21 12:56:04

PyTorch模型高并发API部署:Nginx+Gunicorn+Flask+Docker实战

1. 项目概述&#xff1a;为什么一个能扛住每秒上千请求的模型API&#xff0c;往往死在部署这一步&#xff1f; “深度学习模型训练好了&#xff0c;怎么让别人用上&#xff1f;”——这是我在某高校实验室带学生做毕业设计时&#xff0c;被问得最多的一句话。不是模型不准&…

作者头像 李华