效果显示
原理说明
无论使用哪种芯片, 要实现uboot到系统应用启动过程不间断logo显示, 整个启动过程大概如下:
- uboot启动
- 初始化显示屏驱动(
这里确定好framebuffer地址
) - 从mmc(sd卡)读取bmp图片进行显示
- 打开背光
- 初始化显示屏驱动(
- 内核启动
- 加载显示屏驱动,
取消初始化lcd控制器(时钟等配置)
, 使用uboot配置好的寄存器;使用与uboot相同的framebuffer地址
, 确保显示相同内容
- 加载显示屏驱动,
- 系统启动
- 启动自己的qt界面, 刷新屏幕显示
- 内核显示屏驱动, 不能重新初始化lcd控制器, 否则会出现屏幕闪烁/黑屏灯情况, uboot已经配置好lcd相关的寄存器并且正常显示了, 使用uboot配置好的就行
- 内核framebuffer的地址要指定与uboot相同的地址, 因为uboot已经将图片数据加载到内存进行显示了, 内核使用相同的地址便能显示相同的图片
由于framebuffer占用的ddr地址是在uboot就决定好了, 所以不能让kernel把framebuffer占用的ddr当成普通的mem处理. 可以用reserved-memory机制预留一段kernel无法使用的内存地址, 也可以将framebuffer设计在ddr的最高位置, 然后通过uboot启动参数传内存大小"mem=..."时相应改小.
- 这里说明的只是一种实现方案, 如果芯片支持多图层显示, 可以有不同的实现方法, 这里不细讲
在lichee zero上的实现说明
这次实现基于以下环境实现
开发板 | 荔枝派lichee zero |
---|---|
uboot | U-Boot 2017.01-rc2 |
kernel | Linux 4.10.15 |
文件系统 | 由buildroot制作 |
Qt | Qt5.9.1 |
内核修改
显示屏驱动
内核的显示屏驱动使用的是 Simple Framebuffer
源码: linux\arch\arm\boot\dts\sun8i-v3s.dtsi
内核doc说明如下
这是一种简单的显示屏驱动方式, lcd控制器相关的配置由firmware(类似于另外一个驱动)或者bootloader进行setup, 对该驱动来说, 只需要维护lcd对于的framebuffer就可以了, 不需要进行lcd控制相关的寄存器配置
对于lichee zero来说, lcd控制器是由uboot进行配置的, 内核显示屏驱动无需改动
fbcon
fbcon实现在内核启动过程中的基于framebuffer的终端显示, 也就是实现显示屏打印出启动的信息, 这个会影响启动logo的显示
lichee zero的内核配置中是强制使能了fbcon的, 无法通过配置关闭fbcon, 只能修改makefile.
不过也可以通过启动参数实现不显示fbcon, 在uboot修改中会讲到.
综上, 内核源码实际上无需改动.
uboot修改
启用splash
uboot提供了splash实现logo的读取与显示, 不过并没有使用到
这里进行修改实现从mmc(sd卡)读取图片显示
- 添加splash宏
添加以下宏定义, 使能uboot的splash功能
源码: u-boot\include\configs\sun8i.h
- 修改uboot默认环境变量, 添加splash参数
修改默认的uboot环境变量, 添加splash相关的环境变量
"splashfile=logo.bmp\0" \
"splashimage=0x41000000\0" \
"splashsource=mmc_fs\0" \
源码: u-boot\include\configs\sunxi-common.h
相关环境变量的含义如下:
splashfile | 指定logo图片的名称 |
---|---|
splashimage | 指定将图片加载到内存的地址 |
splashsource | 指定从哪里读取图片, mmc_fs说明是mmc, 即sd卡, 默认是第一个分区 |
添加以上环境变量后, uboot启动后将在mmc(sd卡)的第一个分区(也就是boot分区)读取命名为logo.bmp的图片, 加载到内存地址并进行bmp图片的解析并显示.
所以更换logo只需要替换sd卡第一个分区的logo.bmp即可.
uboot支持显示24bpp的bmp图片, 大小保存于屏幕一致即可, 像我使用的是800x480的24bpp的bmp图片,
关闭uboot默认显示的企鹅logo
uboot默认显示了一个企鹅logo, 需要将其关闭
使能了splash的CONFIG_SPLASH_SCREEN这个宏后, 显示完logo后会直接返回, 不会再调用logo_plot显示企鹅logo了
所以这块其实不用改动
源码: u-boot\drivers\video\cfb_console.c
背光
uboot的lcd控制器初始化时, 会打开背光, 这样的效果其实不好, 会出现一小段白屏
改成显示完图片后再打开背光
- 添加控制背光的接口
源码: u-boot\drivers\video\sunxi_display.c
源码: u-boot\include\video_fb.h
- 去掉初始化时打开背光, 在显示完图片后打开背光
源码: u-boot\drivers\video\sunxi_display.c
源码: u-boot\drivers\video\cfb_console.c
修改启动参数
内核启动时, 会将启动信息打印到显示屏
这里通过修改uboot的启动参数, 关闭内核启动时的lcd打印
这里改动了两点:
- 去掉 console=tty0, 这样内核启动过程, 显示屏就不会当成终端打印启动信息了
- 添加 fbcon=map:1, 取消定向fb0, 同时关闭10分钟息屏
第二点改动可以参考内核关于fbcon的说明
源码: linux\Documentation\fb\fbcon.txt
fbcon=map:<0123>可以指定tty与fb的对应关系, logo使用的是fb0, 这里配置成1, fbcon就不会在fb0上作用, 实现关闭显示屏终端
关键代码解析
这里说明uboot如何将显示屏framebuffer的地址告诉kernel?
在内核设备树中, 有个framebuffer的节点, 它的status是"disable".
源码: linux\arch\arm\boot\dts\sun8i-v3s.dtsi
实际上, 在uboot加载内核设备树, 然后启动内核时, 会修改内核的设备树的framebuffer节点, 添加上显示屏framebuffer的地址并将其使能
因此, 内核加载显示屏驱动时能获取到正确的framebuffer地址
在uboot启动内核时, 会调用sunxi_simplefb_setup()函数将simple-framebuffer的设备节点添加到设备树中
关键代码如下:
源码: u-boot\drivers\video\sunxi_display.c
int sunxi_simplefb_setup(void *blob)
{
...
// 这里填充内核设备树的 memory node, 声明内存的大小
// 值的注意的是, ddr的尾部用来存放framebuffer
// 因此内核可用的 内存大小 = ddr总大小 - framebuffer所需的大小
// 即 gd->bd->bi_dram[0].size - sunxi_display.fb_size
/*
* Do not report the framebuffer as free RAM to the OS, note we cannot
* use fdt_add_mem_rsv() here, because then it is still seen as RAM,
* and e.g. Linux refuses to iomap RAM on ARM, see:
* linux/arch/arm/mm/ioremap.c around line 301.
*/
start = gd->bd->bi_dram[0].start;
size = gd->bd->bi_dram[0].size - sunxi_display.fb_size;
ret = fdt_fixup_memory_banks(blob, &start, &size, 1);
if (ret) {
eprintf("Cannot setup simplefb: Error reserving memory\n");
return ret;
}
// 这里填充并使能内核设备树的 simple-framebuffer 节点
// 第三个参数即为 framebuffer 的地址
// 因此内核通过该节点获取 framebuffer 地址
ret = fdt_setup_simplefb_node(blob, offset, sunxi_display.fb_addr,
graphic_device->winSizeX, graphic_device->winSizeY,
graphic_device->plnSizeX, "x8r8g8b8");
...
}