uboot实现TJpgDec解码器JPG LOGO显示_uboot 解码 jpeg

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_dm`DM模型初始化,`initr_mmc`MMC驱动初始化等,且对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,丢入帧缓冲区中,`JPG`与`RGB888`格式`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++)
{

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年嵌入式&物联网开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上嵌入式&物联网开发知识点,真正体系化!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新!!

5601634758)]

[外链图片转存中…(img-wGwhg60M-1715601634758)]

[外链图片转存中…(img-koWOgJll-1715601634759)]

[外链图片转存中…(img-0vcbvOhH-1715601634759)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上嵌入式&物联网开发知识点,真正体系化!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新!!

  • 11
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
uboot是一种常用的开源引导加载程序,用于嵌入式系统的引导启动。其中,board_init_r和board_init_f是uboot中的两个重要函数。 board_init_r函数是在uboot启动过程中执行的第一个函数。它负责执行一系列的初始化工作,例如初始化系统时钟、设置内存映射等。此函数被用于配置和初始化各个硬件模块,包括中断控制器、串口控制器、定时器等,以确保系统正常运行。该函数还读取并解析配置文件,加载设备树等操作,为后续的引导加载准备好必要的条件。 board_init_f函数是在board_init_r函数之后调用的。它用于进一步初始化系统,并执行一些与硬件相关的操作。例如,该函数可能会初始化网络接口、USB接口、存储设备等,并设置系统的默认环境变量。此外,board_init_f函数还负责将uboot的控制权交给操作系统的引导加载程序,从而完成uboot的使命。 通过调用board_init_r和board_init_f函数,uboot能够在系统启动时完成各种硬件的初始化和配置工作。这两个函数是uboot启动过程中的重要环节,确保系统能够顺利地加载操作系统并运行。同时,它们也为开发者提供了扩展uboot的接口,可以在这两个函数中添加自定义的初始化代码,以满足系统特定的需求。 总结来说,board_init_r和board_init_f是uboot中两个重要的函数,用于初始化和配置嵌入式系统的硬件,并为操作系统的加载做好准备。它们是uboot启动过程中不可或缺的一部分,保证系统的正常启动和运行。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值