uboot实现TJpgDec解码器JPG LOGO显示


以下基于AKNYKA37E平台

建议阅读文章前可以先了解uboot启动流程uboot启动流程分析

无论是哪种启动介质,基本流程都相似,根据下面的uboot启动流程图,结合代码进行理解
在这里插入图片描述

1、board_init_f——板级前置初始化

根据上述链接uboot启动流程,跟随链接中uboot启动流程图,分析一下board_init_f这个函数,该函数位于arch/arm/lib/board.c

#ifdef CONFIG_LCD
	/*
	* no define macro define CONFIG_FB_ADDR
	*/
#ifdef CONFIG_FB_ADDR
	gd->fb_base = CONFIG_FB_ADDR;
#else
	/* 
	* reserve memory for LCD display (always full pages) 
	*/

	// printf("%s ======================>>%d\n",__func__,__LINE__);
	addr = lcd_setmem(addr);
	gd->fb_base = addr;
#endif /* CONFIG_FB_ADDR */
#endif /* CONFIG_LCD */

board_init_f函数,使用配置fb首地址CONFIG_FB_ADDR或者调用lcd_setmem获取fb大小,这里面有板级相关函数需要实现,不过为了先能启动uboot,没有打开fb选项。addr值就是fb首地址,gd->fb_base保存fb首地址。

但奇怪的事,笔者将下面两句代码屏蔽掉并不影响lcd的使用,依旧可以显示logo,感觉分布分配都不会影响LCD的显示,望有大神路过,请求评论解惑

	gd->relocaddr = lcd_setmem(gd->relocaddr);
	gd->fb_base = gd->relocaddr;

继续进入lcd_setmem函数,这个函数用来分配帧缓存地址, 其定义在common/lcd.c

/************************************************************************/
/* ** ROM capable initialization part - needed to reserve FB memory	*/
/************************************************************************/
/*
 * This is called early in the system initialization to grab memory
 * for the LCD controller.
 * Returns new address for monitor, after reserving LCD buffer memory
 *
 * Note that this is running from ROM, so no write access to global data.
 */
ulong lcd_setmem(ulong addr)
{
	ulong size;
	int line_length;

	/*
	* accord config for lcd fb base addr request reserved space
	*/
	debug("LCD panel info: %d x %d, %d bit/pix\n", panel_info.vl_col,
		panel_info.vl_row, panel_info.vl_bpix == LCD_COLOR24 ? 24 : NBITS(panel_info.vl_bpix));
	size = lcd_get_size(&line_length);

	/* Round up to nearest full page, or MMU section if defined */
	size = ALIGN(size, CONFIG_LCD_ALIGNMENT);
	addr = ALIGN(addr - CONFIG_LCD_ALIGNMENT + 1, CONFIG_LCD_ALIGNMENT);

	/* Allocate pages for the frame buffer. */
	addr -= size;
	debug("Reserving %ldk for LCD Framebuffer at: %08lx\n", size >> 10, addr);
	printf("uboot display lcd logo!\n");
	
	printf("\033[35m uboot time %s \033[0m\n", __TIMESTAMP__);

	return addr;
}

panel_info 变量 ak_fb.c 驱动中定义,定义了具体 LCD 的相关信息,lcd_get_size 函数来获得帧缓存大小。

/*
 * board_init_f(arch/arm/lib/board.c) calls lcd_setmem() which needs
 * panel_info.vl_col, panel_info.vl_row and panel_info.vl_bpix to reserve
 * FB memory at a very early stage, i.e even before exynos_fimd_parse_dt()
 * is called. So, we are forced to statically assign it.
 * cdh:check ok , 10 inch lcd(1280x800), 24bit
 */
vidinfo_t panel_info  = {
	.vl_col = LCD_XRES,
	.vl_row = LCD_YRES,
	.vl_width = LCD_XRES,
	.vl_height = LCD_YRES,
	.vl_bpix = LCD_COLOR24,
};

之所以调用lcd_setmem分配FB内存,参考board_init_f介绍所述,uboot 会将自己重定位到 DRAM 最后面的地址区域,也就是将自己拷贝到 DRAM 最后面的内存区域中。比如 gd 应该存放到哪个位置,malloc 内存池应该存放到哪个位置等等。这些信息都保存在 gd 的成员变量中,因此要对 gd 的这些成员变量做初始化。最终形成一个完整的内存“分配图”,在后面重定位 uboot 的时候就会用到这个内存“分配图”。因此在拷贝之前肯定要给 uboot 各部分分配好内存位置和大小。

这么做的目的

  1. 给 Linux 腾出空间,防止 Linuxkernel 覆盖掉 uboot,将 DRAM 前面的区域完整的空出来。
  2. uboot是在某些只读存储器上运行,比如ROM、nor flash等等。需要将这部分代码拷贝到DDR上才能完整运行uboot。

到这board_init_f初始化中关于LCD的相关操作就结束了,跟进uboot启动流程图,继续定位到board_init_r这个函数中

2、board_init_r——板级后置初始化

经过Uboot重定向后,Uboot运行于新的地址空间,board_init_r主要作为Uboot运行的最后初始化步骤。该函数位于arch/arm/lib/board.c

函数与board_init_f类似,主要用于初始化各类外设信息,包括:initr_dmDM模型初始化,initr_mmcMMC驱动初始化等,且对gd , bd 数据结构赋值初始化,最终,uboot就运行到了run_main_loop,进而执行main_loop这个函数。

跟着board_init_r进入stdio_init函数,该函数对多个多个stdio设备进行初始化,其中包括LCD


	stdio_init();	/* get the devices list going. */

int stdio_init (void)
{
#if defined(CONFIG_NEEDS_MANUAL_RELOC)
	/* already relocated for current ARM implementation */
	ulong relocation_offset = gd->reloc_off;
	int i;

	/* relocate device name pointers */
	for (i = 0; i < (sizeof (stdio_names) / sizeof (char *)); ++i) {
		stdio_names[i] = (char *) (((ulong) stdio_names[i]) +
						relocation_offset);
	}
#endif /* CONFIG_NEEDS_MANUAL_RELOC */

	/* Initialize the list */
	INIT_LIST_HEAD(&(devs.list));

#ifdef CONFIG_SYS_I2C
	i2c_init_all();
#else
#if defined(CONFIG_HARD_I2C)
	i2c_init (CONFIG_SYS_I2C_SPEED, CONFIG_SYS_I2C_SLAVE);
#endif
#endif
#ifdef CONFIG_LCD
	drv_lcd_init ();
#endif
#if defined(CONFIG_VIDEO) || defined(CONFIG_CFB_CONSOLE)
	drv_video_init ();
#endif
#ifdef CONFIG_KEYBOARD
	drv_keyboard_init ();
#endif
#ifdef CONFIG_LOGBUFFER
	drv_logbuff_init ();
#endif
	drv_system_init ();
	serial_stdio_init ();
#ifdef CONFIG_USB_TTY
	drv_usbtty_init ();
#endif
#ifdef CONFIG_NETCONSOLE
	drv_nc_init ();
#endif
#ifdef CONFIG_JTAG_CONSOLE
	drv_jtag_console_init ();
#endif
#ifdef CONFIG_CBMEM_CONSOLE
	cbmemc_init();
#endif
	return (0);
}

继续来到drv_lcd_init函数,这里是本章的重点内容

3、drv_lcd_init——LCD设备初始化

#define JPG_LOGO_DISPLAY
int drv_lcd_init(void)
{
	struct stdio_dev lcddev;
	
	gd->fb_base = getenv_ulong("lcdloadaddr", 16, 0x83d02000);
	if(gd->fb_base == 0){
		printf("eror:lcd init gd->fb_base == 0\n");
		return -1;
	}
	//debug("gd->fb_base:0x%x\n", gd->fb_base);
	#ifndef JPG_LOGO_DISPLAY
	if(read_logo_data(gd->fb_base) == -1)
	{
		printf("eror:read_logo_data fail\n");
		return -1;
	}

	//debug("%s,line:%d, logo data:0x%x\n", __func__, __LINE__, REG32(0x83d02000+0x9000));
	#else
    int logo_size = 100 * 1024;
    unsigned char *logo_data = malloc(logo_size);
    if (read_logo_data((void*)logo_data) == -1)
    {
        printf("eror:read_logo_data fail\n");
        return -1;
    }
    uboot_logo_sjpg_decode(logo_data,logo_size,gd->fb_base);
    free(logo_data);
	#endif
	lcd_base = (void *)gd->fb_base;
	
	lcd_init(lcd_base);		/* LCD initialization */
	//debug("cdh:%s, @@line:%d\n", __func__, __LINE__);

	/* Device initialization */
	memset(&lcddev, 0, sizeof(lcddev));

	strcpy(lcddev.name, "lcd");
	lcddev.ext   = 0;			/* No extensions */
	lcddev.flags = DEV_FLAGS_OUTPUT;	/* Output only */
	lcddev.putc  = lcd_putc;		/* 'putc' function */
	lcddev.puts  = lcd_puts;		/* 'puts' function */

	///rc = stdio_register(&lcddev);
	return 0;
}

此处笔者有疑问,第6行gd->fb_base根据getenv_ulong函数中环境变量中取地址作LCD加载地址,那么前面lcd_setmem分配的内存又有什么意义,uboot重定位后fb的首地址与getenv_ulong不会造成冲突吗?根据getenv_ulong获取的LCD加载地址,地址里的内存又是从何而来,什么时候分配的?

第12行中,JPG_LOGO_DISPLAY用于区分是否使用JPG图片代替RGB888显示LOGO,若未定义JPG_LOGO_DISPLAY,则根据函数read_logo_data从flash读取出RGB888数据加载到帧缓冲区中。
若烧入flash中的数据为JPG,则申请一块内存存放JPG数据,再将数据从flash读出,经过uboot_logo_sjpg_decode函数进行解码成RGB888,丢入帧缓冲区中,JPGRGB888格式LOGO显示的区别就在于此,多了一步解码步骤,后续操作与RGB888一致。

进入uboot_logo_sjpg_decode函数,该函数主要调用TJpgDec—轻量级JPEG解码器接口实现,需要用户自己实现,可单独分出一个文件夹存放关于解码JPG的所有源文件,此函数笔者存放在名为tjpg/sjpeg_decode.c自定义文件中,与TJpgDec源码放在一起,uboot可编译链接库后调用uboot_logo_sjpg_decode函数进行解码

int uboot_logo_sjpg_decode(unsigned char *src, int len, unsigned char *lcd_base)
{
    io_source_t io_source;
    io_source.raw_sjpg_data = src;
    io_source.raw_sjpg_data_size = len;
    io_source.raw_sjpg_data_next_read_pos = 0;
    io_source.img_cache_buff = lcd_base;
    uint8_t *wokb_temp = malloc(TJPGD_WORKBUFF_SIZE);

    JDEC jd;
    JRESULT rc = jd_prepare(&jd, input_func, wokb_temp, (size_t)TJPGD_WORKBUFF_SIZE, &io_source);

    if (rc == JDR_OK)
    {
    	/* 准备好解码。图像信息有效 */
        printf("jpg:w=%d,h=%d \n", jd.width, jd.height);
        rc = jd_decomp(&jd, img_data_cb, 0);
        if (rc == JDR_OK)
        {
        	/* 解码成功。你在这里已经解码图像到帧缓冲区 */
            printf("logo jpg decode succeed\n");
        } 
        else
        {
        	printf("logo jpg decode failed \n");
        }
    }
    else
    {
        printf("logo jpg prepare failed \n");
    }
	/* 释放工作区域 */
    free(wokb_temp);
    
    return -1;
}

继续下面步骤之前建议先访问此链接TJpgDec—轻量级JPEG解码器了解关于解码器的更多信息

在TJpgDec解码JPG前需要优先配置好解码库源码中tjpgd.h文件中几个宏定义

#define JD_SZBUF        512 /* Size of stream input buffer */

#define JD_FORMAT       0   /* Output pixel format 0:RGB888 (3 BYTE/pix), 1:RGB565 (1 WORD/pix) */

#define JD_USE_SCALE    1   /* Use descaling feature for output */

#define JD_TBLCLIP      1   /* Use table for saturation (might be a bit faster but increases 1K bytes of code size) */

#define JD_SCALE        0   /* 0:1/1  1:1/2  2:1/4  3:1/8 */

配置好宏定义后,结合上述解码器链接,定义io_source_t结构体,将此结构体作为device参数,具体作用一会就知道了

typedef struct
{
    uint8_t *raw_sjpg_data;               // Used when type==SJPEG_IO_SOURCE_C_ARRAY.
    uint32_t raw_sjpg_data_size;          // Num bytes pointed to by raw_sjpg_data.
    uint32_t raw_sjpg_data_next_read_pos; // Used for all types.
    uint8_t *img_cache_buff;
} io_source_t;

.raw_sjpg_data:存储需要解码器解码的数据
.raw_sjpg_data_size:待解码数据大小
.raw_sjpg_data_next_read_pos:已读取的JPG数据长度
.img_cache_buff:lcd帧缓冲地址

分配一份TJPGD_WORKBUFF_SIZE大小的内存(这里是4096)由wokb_temp指针接收用来作为工作缓冲区丢入jd_prepare函数

input_func函数作为jd_prepare函数的第二个函数指针,用于读取输入JPG流的数据的输入函数,实现如下

/* 
*@jd:指定解码会话的解码对象
*@buff:指定读缓冲器去保存读取数据。传入NULL将数据从输入流移除
*@ndata:指定从输入流读取或移除的字节数
 */
static size_t input_func(JDEC *jd, uint8_t *buff, size_t ndata)
{
    /* jd_prepare函数丢入的io_source_t指针存储在JDEC *jd解码对象的device成员变量中 */
    io_source_t *io = jd->device;

    if (!io)
        return 0;

    /* JPG信息数据剩余字节大小 */
    const uint32_t bytes_left = io->raw_sjpg_data_size - io->raw_sjpg_data_next_read_pos;

    /* 解析JPG信息数据的字节大小 */
    const uint32_t to_read = ndata <= bytes_left ? (uint32_t)ndata : bytes_left;

    if (to_read == 0)
        return 0;

    if (buff)
    {
        /* JPG信息数据丢入工作缓存区 */
        memcpy(buff, io->raw_sjpg_data + io->raw_sjpg_data_next_read_pos, to_read);
    }

    /* 刷新已读取的JPG数据长度 */
    io->raw_sjpg_data_next_read_pos += to_read;
    
    /*返回读取数据长度*/
    return to_read;
}

jd_prepare返回JDR_OK代表函数执行成功,且编码对象是有效的,可进行接下来的解码操作
jd_prepare函数是JPEG解码会话的第一阶段。它分析JPEG图像和创建解码参数表。函数成功后,会话准备好在jd_decomp函数解码JPEG图像。应用程序可以参考JPEG解码对象中存储的尺寸大小。这个信息将用于在后续的解码阶段配置输出设备device和参数

jd_decomp解码JPEG图像并输出RGB数据,参数三作为解码后缩小图像的比例,禁用此功能设为0,参数二img_data_cb用户定义的输出函数。jd_decomp调用这个函数去输出解码JPEG图像的RGB形式,写解码像素到输出设备

/* 
*@jd:指定解码会话的解码对象
*@data:像素数据流。是按照tjpgdcnf.h文件中JD_FORMAT选项指定的格式组织
*@rect:指定在图像中的矩形区域去输出RGB位图
 */
static int img_data_cb(JDEC *jd, void *data, JRECT *rect)
{
    io_source_t *io = jd->device;
    uint8_t *cache = io->img_cache_buff;
    uint8_t *buf = data;
    const int INPUT_PIXEL_SIZE = 3;//RGB
    /* 行区域像素 */
    const int row_width = rect->right - rect->left + 1; // Row width in pixels.
    /* 行区域数据大小 */
    const int row_size = row_width * INPUT_PIXEL_SIZE;  // Row size (bytes).

    int y = 0;
    for (y = rect->top; y <= rect->bottom; y++)
    {
        int row_offset = y * jd->width * INPUT_PIXEL_SIZE + rect->left * INPUT_PIXEL_SIZE;
        /* 根据偏移地址拷贝至帧缓存区 */
        memcpy(cache + row_offset, buf, row_size);
        buf += row_size;
    }
}

在这个函数中,位图发送到帧缓冲。第一个像素是位图矩形的左上角位置,最后一个像素是右下角位置。 jd_decomp函数执行结束后,返回JDR_OK 代表解码完成,帧缓冲区现在已经填充好RGB数据,释放掉分配的工作缓冲区,解码器TJpgDec解码这部分就结束了
到此只需要将TJpgDec库移植进uboot目录下,链接编译后即可在flash中读取JPG数据进行解码显示在LCD上,LCD初始化部分稍后再说明,先说说移植部分,用户可进入此链接下载TJpgDec解码器源码,下载完后解压到tjpg文件夹下(解压目录可自定义,笔者以自己项目为例),下面是tjpg文件的目录结构目录结构
在这里插入图片描述
sjpeg_decode.c定义了上面提到的几个函数接口uboot_logo_sjpg_decodeimg_data_cbinput_func,及结构体io_source_t,解码器源文件除tjpgd.h需要配置相关宏其余无需改动,笔者将tjpg文件夹放置在/uboot/common/目录下,修改目录下common目录下Makefile添加链接后到os/目录下运行build_uboot.sh编译后烧录即可

COBJS-$(CONFIG_LCD) += tjpg/sjpeg_decode.o
COBJS-$(CONFIG_LCD) += tjpg/tjpgd.o

编译成功后会生成相关的xxx.oxxx.su
在这里插入图片描述

需要注意一点,JPG的文件属性需要与设备树中配置LCD LOGO属性一致,否则LOGO显示会不正常

&lcdc {
    pinctrl-names = "default";
    pinctrl-0 = <&lcd_reset_pins &lcd_power_pins>;
    reset-pins = <&gpio 34 GPIO_ACTIVE_HIGH>;
    pwr-gpio = <&gpio 36 GPIO_ACTIVE_HIGH>;
    backlight-gpio = <&gpio 82 GPIO_ACTIVE_HIGH>;//82

    lcd-logo-width = <1024>;
    lcd-logo-height = <600>; 
    //[fmt1:fmt0] 00 16bits input(RGB565 or BGR565)
    //[fmt1:fmt0] 01 24bits input(RGB888 or BGR888)
    //[fmt1:fmt0] 10 or 11 32bits input(ARGB888 ABGR888 RGBA888 BGRA888)
    lcd-logo-fmt0 = <1>;
    lcd-logo-fmt1 = <0>;
    lcd-logo-rgb-seq = <0>; //0 for BGR, 1 for RGB
    // lcd-rgb-bg-color=<0xFFFFFF>;

    lcd-fb-type = <1>; //0:single buffer; 1:double buffer
    status = "okay";
};

若图片显示效果类似下图,像素有略带偏移,多半是JD_FORMAT配置不正确,如RGB888配置成RGB565,将JD_FORMAT配置成正确即可
像素偏移
若图片显示效果类似下图,颜色出现偏差,则有可能设备树中lcd-logo-rgb-seq属性配置错误
在这里插入图片描述

若图片显示还是不正常,建议将图片进行一次重新编码,这里笔者用的是工具是 ACDSee Photo Studio Professional 2022重新进行另存为并在Options选项中关闭Optimize Huffman codes的勾选保存,后将jpg后缀改成rgb即可,注意文件大小不要超过drv_lcd_init函数中给logo分配的缓存大小

在这里插入图片描述
在这里插入图片描述

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值