文章目录

  • 1. LCD背景
  • 2. LCD驱动
    • 2.1 Device
    • 2.2 Driver
      • 2.2.1 fbmem_init()
      • 2.2.2 register_framebuffer()
      • 2.2.3 /dev/fb0 文件操作
      • 2.2.4 '/sys/class/graphics/fb0/blank' fb notifier
    • 2.3 Boot Logo
  • 3. 背光驱动
    • 3.1 eCAP0模块
      • 3.1.1 Device
      • 3.1.2 Driver
    • 3.2 PWM蜂鸣器
  • 4. FrameBuffer调试
    • 4.1 背光
    • 4.2 fb
    • 4.3 截屏

1. LCD背景

帧缓冲(framebuffer)是Linux为显示设备提供的一个接口,把显存抽象后的一种设备,他允许上层应用程序在图形模式下直接对显示缓冲区进行读写操作。这种操作是抽象的,统一的。用户不必关心物理显存的位置、换页机制等等具体细节。这些都是由Framebuffer设备驱动来完成的。

帧缓冲驱动的应用广泛,在linux的桌面系统中,Xwindow服务器就是利用帧缓冲进行窗口的绘制。

Linux FrameBuffer 本质上只是提供了对图形设备的硬件抽象,在开发者看来,FrameBuffer 是一块显示缓存,往显示缓存中写入特定格式的数据就意味着向屏幕输出内容。所以说FrameBuffer就是一块白板。例如对于初始化为16 位色的FrameBuffer 来说, FrameBuffer中的两个字节代表屏幕上一个点,从上到下,从左至右,屏幕位置与内存地址是顺序的线性关系。

帧缓存可以在系统存储器(内存)的任意位置,视频控制器通过访问帧缓存来刷新屏幕。 帧缓存也叫刷新缓存 Frame buffer 或 refresh buffer, 这里的帧(frame)是指整个屏幕范围。

帧缓存有个地址,是在内存里。我们通过不停的向frame buffer中写入数据, 显示控制器就自动的从frame buffer中取数据并显示出来。全部的图形都共享内存中同一个帧缓存。

CPU指定显示控制器工作,则显示控制器根据CPU的控制到指定的地方去取数据 和 指令, 目前的数据一般是从显存里取, 如果显存里存不下,则从内存里取, 内存也放不下,则从硬盘里取,当然也不是内存放不下,而是为了节省内存的话,可以放在硬盘里,然后通过 指令控制显示控制器去取。帧缓存 Frame Buffer,里面存储的东西是一帧一帧的, 显卡会不停的刷新Frame Buffer, 这每一帧如果不捕获的话, 则会被丢弃,也就是说是实时的。这每一帧不管是保存在内存还是显存里, 都是一个显性的信息,这每一帧假设是800x600的分辨率, 则保存的是800x600个像素点,和颜色值。

显示器可以显示无限种颜色,目前普通电脑的显卡可以显示32位真彩、24位真彩、16位增强色、256色。除256色外,大家可以根据自己的需要在显卡的允许范围之内随意选择。很多用户有一种错误概念,认为256色是最高级的选项,而实际上正好相反。256色是最低级的选项,它已不能满足彩色图像的显示需要。16位不是16种颜色,而是2的16次平方(256×256)种颜色,但256色就是256(2的8次平方)种颜色。所以16位色要比256色丰富得多。

帧缓冲设备对应的设备文件为/dev/fb*,如果系统有多个显示卡,Linux下还可支持多个帧缓冲设备,最多可达32 个,分别为/dev/fb0到/dev/fb31,而/dev/fb则为当前缺省的帧缓冲设备,通常指向/dev/fb0。当然在嵌入式系统中支持一个显示设备就够了。帧缓冲设备为标准字符设备,主设备号为29,次设备号则从0到31。分别对应/dev/fb0-/dev/fb31。通过/dev/fb,应用程序的操作主要有这几种:

  • 1. 读/写(read/write)/dev/fb:相当于读/写屏幕缓冲区。例如用 cp /dev/fb0 tmp命令可将当前屏幕的内容拷贝到一个文件中,而命令cp tmp > /dev/fb0 则将图形文件tmp显示在屏幕上。

  • 2.映射(map)操作:由于Linux工作在保护模式,每个应用程序都有自己的虚拟地址空间,在应用程序中是不能直接访问物理缓冲区地址的。为此, Linux在文件操作 file_operations结构中提供了mmap函数,可将文件的内容映射到用户空间。对于帧缓冲设备,则可通过映射操作,可将屏幕缓冲区的物理地址映射到用户空间的一段虚拟地址中,之后用户就可以通过读写这段虚拟地址访问屏幕缓冲区,在屏幕上绘图了。实际上,使用帧缓冲设备的应用程序都是通过映射操作来显示图形的。由于映射操作都是由内核来完成,下面我们将看到,帧缓冲驱动留给开发人员的工作并不多。

  • 3. I/O控制:对于帧缓冲设备,对设备文件的ioctl操作可读取/设置显示设备及屏幕的参数,如分辨率,显示颜色数,屏幕大小等等。ioctl的操作是由底层的驱动程序来完成的。

2. LCD驱动

2.1 Device

kernel\arch\arm\mach-omap2\board-am335xevm.c:

am335x_evm_setup() -> setup_am335x() -> am_board_dev_cfg:

/* am335x board */
static struct evm_dev_cfg am_board_dev_cfg[] = {{lcdc_init,     DEV_ON_BASEBOARD, PROFILE_ALL},};↓static const struct display_panel disp_panel = {WVGA,32,32,COLOR_ACTIVE,
};static struct lcd_ctrl_config lcd_cfg = {&disp_panel,.ac_bias        = 255,.ac_bias_intrpt      = 0,.dma_burst_sz      = 16,.bpp          = 32,.fdd          = 0x80,.tft_alt_mode       = 0,.stn_565_mode      = 0,.mono_8bit_mode        = 0,.invert_line_clock = 1,.invert_frm_clock  = 0,.sync_edge     = 0,.sync_ctrl     = 1,.raster_order      = 0,
};struct da8xx_lcdc_platform_data  G070VW01_pdata = {.manu_name              = "AUO",.controller_data        = &lcd_cfg,.type                   = "G070VW01",
};static void lcdc_init(int evm_id, int profile)
{struct da8xx_lcdc_platform_data *lcdc_pdata;setup_pin_mux(lcdc_pin_mux);if (conf_disp_pll(300000000)) {pr_info("Failed configure display PLL, not attempting to""register LCDC\n");return;}/* (1) 根据不同开发板的不同屏幕,选择不同的配置参数 */switch (evm_id) {case GEN_PURP_EVM:case GEN_PURP_DDR3_EVM:lcdc_pdata = &TFC_S9700RTWV35TR_01B_pdata;break;case EVM_SK:lcdc_pdata = &NHD_480272MF_ATXI_pdata;break;/* (2) 的配置 */case AM_BOARD:lcdc_pdata = &G070VW01_pdata;break;default:pr_err("LCDC not supported on this evm (%d)\n",evm_id);return;}lcdc_pdata->get_context_loss_count = omap_pm_get_dev_context_loss_count;/* (2) 创建LCD Controller对应的Device */if (am33xx_register_lcdc(lcdc_pdata))pr_info("Failed to register LCDC device\n");return;
}↓int __init am33xx_register_lcdc(struct da8xx_lcdc_platform_data *pdata)
{int id = 0;struct platform_device *pdev;struct omap_hwmod *oh;char *oh_name = "lcdc";char *dev_name = "da8xx_lcdc";     // (1) 指定device的name,用这个和Driver来进行适配oh = omap_hwmod_lookup(oh_name);if (!oh) {pr_err("Could not look up LCD%d hwmod\n", id);return -ENODEV;}pdev = omap_device_build(dev_name, id, oh, pdata,sizeof(struct da8xx_lcdc_platform_data), NULL, 0, 0);if (IS_ERR(pdev)) {WARN(1, "Can't build omap_device for %s:%s.\n",dev_name, oh->name);return PTR_ERR(pdev);}return 0;
}

lcdc 相关hwmod的配置,定义了寄存器和中断配置。

kernel\arch\arm\mach-omap2\omap_hwmod_33xx_data.c:

/* lcdc */
static struct omap_hwmod_class_sysconfig lcdc_sysc = {.rev_offs    = 0x0,.sysc_offs   = 0x54,.sysc_flags = (SYSC_HAS_SIDLEMODE | SYSC_HAS_MIDLEMODE),.idlemodes = (SIDLE_FORCE | SIDLE_NO | SIDLE_SMART),.sysc_fields  = &omap_hwmod_sysc_type2,
};static struct omap_hwmod_class am33xx_lcdc_hwmod_class = {.name      = "lcdc",.sysc       = &lcdc_sysc,
};static struct omap_hwmod_irq_info am33xx_lcdc_irqs[] = {{ .irq = 36 },{ .irq = -1 }
};struct omap_hwmod_addr_space am33xx_lcdc_addr_space[] = {{.pa_start  = 0x4830E000,.pa_end       = 0x4830E000 + SZ_8K - 1,.flags       = ADDR_MAP_ON_INIT | ADDR_TYPE_RT,},{ }
};struct omap_hwmod_ocp_if am33xx_l3_main__lcdc = {.master     = &am33xx_l3_main_hwmod,.slave     = &am33xx_lcdc_hwmod,.addr     = am33xx_lcdc_addr_space,.user     = OCP_USER_MPU,
};static struct omap_hwmod_ocp_if *am33xx_lcdc_slaves[] = {&am33xx_l3_main__lcdc,
};static struct omap_hwmod am33xx_lcdc_hwmod = {.name      = "lcdc",.class      = &am33xx_lcdc_hwmod_class,.clkdm_name = "lcdc_clkdm",.mpu_irqs = am33xx_lcdc_irqs,.main_clk   = "lcdc_fck",.prcm       = {.omap4  = {.clkctrl_offs   = AM33XX_CM_PER_LCDC_CLKCTRL_OFFSET,.modulemode    = MODULEMODE_SWCTRL,},},.slaves        = am33xx_lcdc_slaves,.slaves_cnt   = ARRAY_SIZE(am33xx_lcdc_slaves),.flags        = (HWMOD_SWSUP_SIDLE | HWMOD_SWSUP_MSTANDBY),
};

2.2 Driver

kernel\drivers\video\da8xx-fb.c:

#define DRIVER_NAME "da8xx_lcdc"static struct platform_driver da8xx_fb_driver = {.probe = fb_probe,.remove = __devexit_p(fb_remove),.suspend = fb_suspend,.resume = fb_resume,.driver = {.name = DRIVER_NAME,.owner = THIS_MODULE,},
};static int __init da8xx_fb_init(void)
{return platform_driver_register(&da8xx_fb_driver);
}↓static int __devinit fb_probe(struct platform_device *device)
{struct da8xx_lcdc_platform_data *fb_pdata =device->dev.platform_data;struct lcd_ctrl_config *lcd_cfg;struct da8xx_panel *lcdc_info;struct fb_info *da8xx_fb_info;struct clk *fb_clk = NULL;struct da8xx_fb_par *par;resource_size_t len;int ret, i;unsigned long ulcm;if (fb_pdata == NULL) {dev_err(&device->dev, "Can not get platform data\n");return -ENOENT;}/* (1) 获取到lcdc的寄存器信息,并映射成虚拟地址来使用lcdc的寄存器信息是在hwmod中定义的,并且在对应platform device创建时注册成了resource*/lcdc_regs = platform_get_resource(device, IORESOURCE_MEM, 0);if (!lcdc_regs) {dev_err(&device->dev,"Can not get memory resource for LCD controller\n");return -ENOENT;}len = resource_size(lcdc_regs);lcdc_regs = request_mem_region(lcdc_regs->start, len, lcdc_regs->name);if (!lcdc_regs)return -EBUSY;/* (1.1) 寄存器物理地址映射成虚拟地址 */da8xx_fb_reg_base = (resource_size_t)ioremap(lcdc_regs->start, len);if (!da8xx_fb_reg_base) {ret = -EBUSY;goto err_request_mem;}fb_clk = clk_get(&device->dev, NULL);if (IS_ERR(fb_clk)) {dev_err(&device->dev, "Can not get device clock\n");ret = -ENODEV;goto err_ioremap;}pm_runtime_irq_safe(&device->dev);pm_runtime_enable(&device->dev);pm_runtime_get_sync(&device->dev);/* Determine LCD IP Version *//* (2) 从寄存器中读出lcdc的IP版本 */switch (lcdc_read(LCD_PID_REG)) {case 0x4C100102:lcd_revision = LCD_VERSION_1;break;case 0x4F200800:case 0x4F201000:lcd_revision = LCD_VERSION_2;break;default:dev_warn(&device->dev, "Unknown PID Reg value 0x%x, ""defaulting to LCD revision 1\n",lcdc_read(LCD_PID_REG));lcd_revision = LCD_VERSION_1;break;}/* (3) 根据LCD名称,查找到lcdc对应配置我们的LCD配置为"G070VW01",对应lcdc配置为:[5] = {.name = "G070VW01",.width = 800,.height = 480,.hfp = 24,.hbp = 96,.hsw = 72,.vfp = 3,.vbp = 7,.vsw = 10,.pxl_clk = 33300000,.invert_pxl_clk = 0,},*/for (i = 0, lcdc_info = known_lcd_panels;i < ARRAY_SIZE(known_lcd_panels);i++, lcdc_info++) {if (strcmp(fb_pdata->type, lcdc_info->name) == 0)break;}if (i == ARRAY_SIZE(known_lcd_panels)) {dev_err(&device->dev, "GLCD: No valid panel found\n");ret = -ENODEV;goto err_pm_runtime_disable;} elsedev_info(&device->dev, "GLCD: Found %s panel\n",fb_pdata->type);lcd_cfg = (struct lcd_ctrl_config *)fb_pdata->controller_data;/* (4) 分配fb的控制数据结构 */da8xx_fb_info = framebuffer_alloc(sizeof(struct da8xx_fb_par),&device->dev);if (!da8xx_fb_info) {dev_dbg(&device->dev, "Memory allocation failed for fb_info\n");ret = -ENOMEM;goto err_pm_runtime_disable;}par = da8xx_fb_info->par;par->dev = &device->dev;par->lcdc_clk = fb_clk;
#ifdef CONFIG_CPU_FREQpar->lcd_fck_rate = clk_get_rate(fb_clk);
#endifpar->pxl_clk = lcdc_info->pxl_clk;if (fb_pdata->panel_power_ctrl) {par->panel_power_ctrl = fb_pdata->panel_power_ctrl;par->panel_power_ctrl(1);}lcd_reset(par);/* allocate frame buffer *//* (5) 分配frame buffer内存空间:非常重要的步骤,计算参数如下:lcdc_info->width = 800      // 宽度像素点lcdc_info->height = 480     // 高度像素点lcd_cfg->bpp = 32           // 每个像素点颜色需要的bitLCD_NUM_BUFFERS = 2         // buffer数量所以我们framebuffer的最终大小为:800x480x(32/8)x2 = 3072000 bytes.*/par->vram_size = lcdc_info->width * lcdc_info->height * lcd_cfg->bpp;ulcm = lcm((lcdc_info->width * lcd_cfg->bpp)/8, PAGE_SIZE);par->vram_size = roundup(par->vram_size/8, ulcm);par->vram_size = par->vram_size * LCD_NUM_BUFFERS;par->vram_virt = dma_alloc_coherent(NULL,par->vram_size,(resource_size_t *) &par->vram_phys,GFP_KERNEL | GFP_DMA);if (!par->vram_virt) {dev_err(&device->dev,"GLCD: kmalloc for frame buffer failed\n");ret = -EINVAL;goto err_release_fb;}/* (5.1) framebuffer虚拟地址 */da8xx_fb_info->screen_base = (char __iomem *) par->vram_virt;/* (5.2) framebuffer物理地址 */da8xx_fb_fix.smem_start    = par->vram_phys;da8xx_fb_fix.smem_len      = par->vram_size;da8xx_fb_fix.line_length   = (lcdc_info->width * lcd_cfg->bpp) / 8;par->dma_start = par->vram_phys;par->dma_end   = par->dma_start + lcdc_info->height *da8xx_fb_fix.line_length - 1;/* allocate palette buffer *//* (6) 分配调色板需要的内存 */par->v_palette_base = dma_alloc_coherent(NULL,PALETTE_SIZE,(resource_size_t *)&par->p_palette_base,GFP_KERNEL | GFP_DMA);if (!par->v_palette_base) {dev_err(&device->dev,"GLCD: kmalloc for palette buffer failed\n");ret = -EINVAL;goto err_release_fb_mem;}memset(par->v_palette_base, 0, PALETTE_SIZE);par->irq = platform_get_irq(device, 0);if (par->irq < 0) {ret = -ENOENT;goto err_release_pl_mem;}/* Initialize par *//* (7) 初始化framebuffer需要的数据结构,并且注册 */da8xx_fb_info->var.bits_per_pixel = lcd_cfg->bpp;da8xx_fb_var.xres = lcdc_info->width;da8xx_fb_var.xres_virtual = lcdc_info->width;da8xx_fb_var.yres         = lcdc_info->height;da8xx_fb_var.yres_virtual = lcdc_info->height * LCD_NUM_BUFFERS;da8xx_fb_var.grayscale =lcd_cfg->p_disp_panel->panel_shade == MONOCHROME ? 1 : 0;da8xx_fb_var.bits_per_pixel = lcd_cfg->bpp;da8xx_fb_var.hsync_len = lcdc_info->hsw;da8xx_fb_var.vsync_len = lcdc_info->vsw;da8xx_fb_var.pixclock = da8xxfb_pixel_clk_period(par);da8xx_fb_var.right_margin = lcdc_info->hfp;da8xx_fb_var.left_margin  = lcdc_info->hbp;da8xx_fb_var.lower_margin = lcdc_info->vfp;da8xx_fb_var.upper_margin = lcdc_info->vbp;/* Initialize fbinfo */da8xx_fb_info->flags = FBINFO_FLAG_DEFAULT;da8xx_fb_info->fix = da8xx_fb_fix;da8xx_fb_info->var = da8xx_fb_var;da8xx_fb_info->fbops = &da8xx_fb_ops;da8xx_fb_info->pseudo_palette = par->pseudo_palette;da8xx_fb_info->fix.visual = (da8xx_fb_info->var.bits_per_pixel <= 8) ?FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_TRUECOLOR;ret = fb_alloc_cmap(&da8xx_fb_info->cmap, PALETTE_SIZE, 0);if (ret)goto err_release_pl_mem;da8xx_fb_info->cmap.len = par->palette_sz;par->lcdc_info = lcdc_info;par->lcd_cfg = lcd_cfg;/* initialize var_screeninfo */da8xx_fb_var.activate = FB_ACTIVATE_FORCE;fb_set_var(da8xx_fb_info, &da8xx_fb_var);dev_set_drvdata(&device->dev, da8xx_fb_info);/* initialize the vsync wait queue */init_waitqueue_head(&par->vsync_wait);par->vsync_timeout = HZ / 5;par->which_dma_channel_done = -1;spin_lock_init(&par->lock_for_chan_update);/* Register the Frame Buffer  *//* (7.1) 注册framebuffer */if (register_framebuffer(da8xx_fb_info) < 0) {dev_err(&device->dev,"GLCD: Frame Buffer Registration Failed!\n");ret = -EINVAL;goto err_dealloc_cmap;}/* (8) 注册lcdc的调节cpu频率的接口 */
#ifdef CONFIG_CPU_FREQret = lcd_da8xx_cpufreq_register(par);if (ret) {dev_err(&device->dev, "failed to register cpufreq\n");goto err_cpu_freq;}
#endifif (lcd_revision == LCD_VERSION_1)lcdc_irq_handler = lcdc_irq_handler_rev01;elselcdc_irq_handler = lcdc_irq_handler_rev02;/* (9) 注册中断处理函数 */ret = request_irq(par->irq, lcdc_irq_handler, 0,DRIVER_NAME, par);if (ret)goto irq_freq;return 0;...
}

2.2.1 fbmem_init()

在分析register_framebuffer()之前,我们先看一下fb系统的初始化。特别简单:

kernel\drivers\video\fbmem.c:

static int __init
fbmem_init(void)
{/* (1) 创建proc文件系统'/proc/fb' */proc_create("fb", 0, NULL, &fb_proc_fops);/* (2) 注册fb系统的主字符设备 */if (register_chrdev(FB_MAJOR,"fb",&fb_fops))printk("unable to get major %d for fb devs\n", FB_MAJOR);/* (3) 注册sysfs文件系统'/sys/class/graphic' */fb_class = class_create(THIS_MODULE, "graphics");if (IS_ERR(fb_class)) {printk(KERN_WARNING "Unable to create fb class; errno = %ld\n", PTR_ERR(fb_class));fb_class = NULL;}return 0;
}↓

核心的操作在这个主字符设备上面,因为用户程序都是通过/dev/fb*这样的从设备文件接口来操作framebuffer的。

static const struct file_operations fb_fops = {.owner =    THIS_MODULE,.read =        fb_read,.write =   fb_write,.unlocked_ioctl = fb_ioctl,
#ifdef CONFIG_COMPAT.compat_ioctl = fb_compat_ioctl,
#endif.mmap =      fb_mmap,.open =        fb_open,.release = fb_release,
#ifdef HAVE_ARCH_FB_UNMAPPED_AREA.get_unmapped_area = get_fb_unmapped_area,
#endif
#ifdef CONFIG_FB_DEFERRED_IO.fsync =   fb_deferred_io_fsync,
#endif.llseek =    default_llseek,
};

这个字符设备定义了一套通用的对framebuffer操作的函数,如果framebuffer有自定义函数优先使用自定义的函数:

static int
fb_open(struct inode *inode, struct file *file)
__acquires(&info->lock)
__releases(&info->lock)
{/* (1) 获取从设备号 */int fbidx = iminor(inode);struct fb_info *info;int res = 0;/* (2) 根据从设备号获取到fb的控制数据结构 */info = get_fb_info(fbidx);if (!info) {request_module("fb%d", fbidx);info = get_fb_info(fbidx);if (!info)return -ENODEV;}if (IS_ERR(info))return PTR_ERR(info);mutex_lock(&info->lock);if (!try_module_get(info->fbops->owner)) {res = -ENODEV;goto out;}file->private_data = info;/* (3) 如果有,调用fb自定义的open()函数 */if (info->fbops->fb_open) {res = info->fbops->fb_open(info,1);if (res)module_put(info->fbops->owner);}
#ifdef CONFIG_FB_DEFERRED_IOif (info->fbdefio)fb_deferred_io_open(info, inode, file);
#endif
out:mutex_unlock(&info->lock);if (res)put_fb_info(info);return res;
}static ssize_t
fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{unsigned long p = *ppos;/* (1) 获取到fb的控制数据结构 */struct fb_info *info = file_fb_info(file);u8 *buffer, *dst;u8 __iomem *src;int c, cnt = 0, err = 0;unsigned long total_size;if (!info || ! info->screen_base)return -ENODEV;if (info->state != FBINFO_STATE_RUNNING)return -EPERM;/* (2) 如果fb有自定义的read()函数,调用自定义的read()函数 */if (info->fbops->fb_read)return info->fbops->fb_read(info, buf, count, ppos);/* (3) 否则使用通用的read()函数da8xx-fb驱动没有自定义read()函数,是使用系统默认的read()函数。*/total_size = info->screen_size;if (total_size == 0)total_size = info->fix.smem_len;if (p >= total_size)return 0;if (count >= total_size)count = total_size;if (count + p > total_size)count = total_size - p;buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,GFP_KERNEL);if (!buffer)return -ENOMEM;src = (u8 __iomem *) (info->screen_base + p);if (info->fbops->fb_sync)info->fbops->fb_sync(info);while (count) {c  = (count > PAGE_SIZE) ? PAGE_SIZE : count;dst = buffer;fb_memcpy_fromfb(dst, src, c);dst += c;src += c;if (copy_to_user(buf, buffer, c)) {err = -EFAULT;break;}*ppos += c;buf += c;cnt += c;count -= c;}kfree(buffer);return (err) ? err : cnt;
}

2.2.2 register_framebuffer()

那我们继续来分析register_framebuffer()做的事情:

register_framebuffer()↓static int do_register_framebuffer(struct fb_info *fb_info)
{int i;struct fb_event event;struct fb_videomode mode;if (fb_check_foreignness(fb_info))return -ENOSYS;do_remove_conflicting_framebuffers(fb_info->apertures, fb_info->fix.id,fb_is_primary_device(fb_info));if (num_registered_fb == FB_MAX)return -ENXIO;num_registered_fb++;/* (7.1.1) 分配一个从设备号 */for (i = 0 ; i < FB_MAX; i++)if (!registered_fb[i])break;fb_info->node = i;atomic_set(&fb_info->count, 1);mutex_init(&fb_info->lock);mutex_init(&fb_info->mm_lock);/* (7.1.2) 创建'/sys/class/graphic/fb*' */fb_info->dev = device_create(fb_class, fb_info->device,MKDEV(FB_MAJOR, i), NULL, "fb%d", i);if (IS_ERR(fb_info->dev)) {/* Not fatal */printk(KERN_WARNING "Unable to create device for framebuffer %d; errno = %ld\n", i, PTR_ERR(fb_info->dev));fb_info->dev = NULL;} elsefb_init_device(fb_info);if (fb_info->pixmap.addr == NULL) {fb_info->pixmap.addr = kmalloc(FBPIXMAPSIZE, GFP_KERNEL);if (fb_info->pixmap.addr) {fb_info->pixmap.size = FBPIXMAPSIZE;fb_info->pixmap.buf_align = 1;fb_info->pixmap.scan_align = 1;fb_info->pixmap.access_align = 32;fb_info->pixmap.flags = FB_PIXMAP_DEFAULT;}}  fb_info->pixmap.offset = 0;if (!fb_info->pixmap.blit_x)fb_info->pixmap.blit_x = ~(u32)0;if (!fb_info->pixmap.blit_y)fb_info->pixmap.blit_y = ~(u32)0;if (!fb_info->modelist.prev || !fb_info->modelist.next)INIT_LIST_HEAD(&fb_info->modelist);fb_var_to_videomode(&mode, &fb_info->var);fb_add_videomode(&mode, &fb_info->modelist);/* (7.1.3) 根据从设备号注册fb控制结构 */registered_fb[i] = fb_info;event.info = fb_info;if (!lock_fb_info(fb_info))return -ENODEV;fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);unlock_fb_info(fb_info);return 0;
}

创建了/sys/class/graphic/fb*以后,udev会自动创建/dev/fb*设备节点。

2.2.3 /dev/fb0 文件操作

用户态程序通过/dev/fb*来操作framebuffer。

open() → fb_open()read() → fb_read()write() → fb_write()

2.2.4 ‘/sys/class/graphics/fb0/blank’ fb notifier

在register_framebuffer()时会创建一个文件节点/sys/class/graphics/fb0/blank,操作这个节点会发出FB_BLANK相关的消息给需要接收的背光设备,来操作背光。

register_framebuffer() → do_register_framebuffer() → fb_init_device() → device_attrs[]↓static struct device_attribute device_attrs[] = {__ATTR(blank, S_IRUGO|S_IWUSR, show_blank, store_blank),};↓store_blank() → fb_blank() ↓int
fb_blank(struct fb_info *info, int blank)
{   int ret = -EINVAL;if (blank > FB_BLANK_POWERDOWN)blank = FB_BLANK_POWERDOWN;/* (1) 调用fb驱动自定义函数来转换blank值 */if (info->fbops->fb_blank)ret = info->fbops->fb_blank(blank, info);if (!ret) {struct fb_event event;event.info = info;event.data = &blank;/* (2) 发出一个blank相关的notifier操作 */fb_notifier_call_chain(FB_EVENT_BLANK, &event);}return ret;
}

info->fbops->fb_blank()调用到了da8xx-fb驱动的cfb_blank()函数:

static int cfb_blank(int blank, struct fb_info *info)
{struct da8xx_fb_par *par = info->par;int ret = 0;if (par->blank == blank)return 0;par->blank = blank;switch (blank) {case FB_BLANK_UNBLANK:if (par->panel_power_ctrl)par->panel_power_ctrl(1);lcd_enable_raster();break;case FB_BLANK_NORMAL:case FB_BLANK_VSYNC_SUSPEND:case FB_BLANK_HSYNC_SUSPEND:case FB_BLANK_POWERDOWN:if (par->panel_power_ctrl)par->panel_power_ctrl(0);lcd_disable_raster(WAIT_FOR_FRAME_DONE);break;default:ret = -EINVAL;}return ret;
}

2.3 Boot Logo

在frambuffer初始化完以后,可以显示初始logo:

fbcon_init() -> fbcon_prepare_logo() -> fb_prepare_logo() -> fb_find_logo():

const struct linux_logo * __init_refok fb_find_logo(int depth)
{#ifdef CONFIG_LOGO_LINUX_CLUT224/* Generic Linux logo */logo = &logo_linux_clut224;
#endif}

最终找到了logo_linux_clut224,对应kernel\drivers\video\logo\logo_linux_clut224.ppm

3. 背光驱动

可以使用两种方式调节背光:

Backlight is through eCAP0_in_PWM0_out pin, controls brightness via eCAP0 module. LCD EVM also has alternative backlight control via TLC59108 power control chip. This is via do not implement(DNI) R36 resistor on non-alpha boards, only populated in-case of non-availability of eCAP0_in_PWM0_out pin.背光通过eCAP0_in_PWM0_out引脚,通过eCAP0模块控制亮度。 LCD EVM还可以通过TLC59108电源控制芯片来控制背光。 这是通过仅在eCAP0_in_PWM0_out引脚不可用的情况下在非Alpha板上通过(DNI)R36电阻来实现的。

3.1 eCAP0模块

3.1.1 Device

kernel\arch\arm\mach-omap2\board-am335xevm.c:

static struct platform_pwm_backlight_data am335x_backlight_data2 = {.pwm_id         = "ecap.2",.ch             = -1,.lth_brightness = 21,.max_brightness = AM335X_BACKLIGHT_MAX_BRIGHTNESS,.dft_brightness = AM335X_BACKLIGHT_DEFAULT_BRIGHTNESS,.pwm_period_ns  = AM335X_PWM_PERIOD_NANO_SECONDS,
};/* HX START */
static struct platform_device am335x_backlight = {.name           = "pwm-backlight",.id             = -1,.dev      = {.platform_data = &am335x_backlight_data2,},
};static struct pwmss_platform_data  pwm_pdata[3] = {{.version = PWM_VERSION_1,},{.version = PWM_VERSION_1,},{.version = PWM_VERSION_1,},
};static int __init backlight_init(void)
{int status = 0;if (backlight_enable) {int ecap_index = 0;switch (am335x_evm_get_id()) {case GEN_PURP_EVM:case GEN_PURP_DDR3_EVM:ecap_index = 0;break;case EVM_SK:/* 配置 */case AM_BOARD:/** Invert polarity of PWM wave from ECAP to handle* backlight intensity to pwm brightness*/ecap_index = 2;//hxtest1pwm_pdata[ecap_index].chan_attrib[0].inverse_pol = true;am335x_backlight.dev.platform_data =&am335x_backlight_data2;//hxtest1 2018/12/19break;default:pr_err("%s: Error on attempting to enable backlight,"" not supported\n", __func__);return -EINVAL;}am33xx_register_ecap(ecap_index, &pwm_pdata[ecap_index]);platform_device_register(&am335x_backlight);}return status;
}

3.1.2 Driver

kernel\drivers\video\backlight\pwm_bl.c:

static struct platform_driver pwm_backlight_driver = {.driver       = {.name   = "pwm-backlight",.owner = THIS_MODULE,},.probe     = pwm_backlight_probe,.remove      = pwm_backlight_remove,.suspend    = pwm_backlight_suspend,.resume        = pwm_backlight_resume,
};static int __init pwm_backlight_init(void)
{return platform_driver_register(&pwm_backlight_driver);
}↓pwm_backlight_probe()↓struct backlight_device *backlight_device_register(const char *name,struct device *parent, void *devdata, const struct backlight_ops *ops,const struct backlight_properties *props)
{struct backlight_device *new_bd;int rc;pr_debug("backlight_device_register: name=%s\n", name);new_bd = kzalloc(sizeof(struct backlight_device), GFP_KERNEL);if (!new_bd)return ERR_PTR(-ENOMEM);mutex_init(&new_bd->update_lock);mutex_init(&new_bd->ops_lock);new_bd->dev.class = backlight_class;new_bd->dev.parent = parent;new_bd->dev.release = bl_device_release;dev_set_name(&new_bd->dev, name);dev_set_drvdata(&new_bd->dev, devdata);/* Set default properties */if (props) {memcpy(&new_bd->props, props,sizeof(struct backlight_properties));if (props->type <= 0 || props->type >= BACKLIGHT_TYPE_MAX) {WARN(1, "%s: invalid backlight type", name);new_bd->props.type = BACKLIGHT_RAW;}} else {new_bd->props.type = BACKLIGHT_RAW;}/* (1) 注册'/sys/class/backlight/pwm-backlight'节点 */rc = device_register(&new_bd->dev);if (rc) {kfree(new_bd);return ERR_PTR(rc);}/* (2) 注册一个fb的回调钩子函数来响应FB_EVENT_BLANK/FB_EVENT_CONBLANK事件 */rc = backlight_register_fb(new_bd);if (rc) {device_unregister(&new_bd->dev);return ERR_PTR(rc);}new_bd->ops = ops;#ifdef CONFIG_PMAC_BACKLIGHTmutex_lock(&pmac_backlight_mutex);if (!pmac_backlight)pmac_backlight = new_bd;mutex_unlock(&pmac_backlight_mutex);
#endifreturn new_bd;
}

注册完成后,我们可以通过以下接口来控制背光:

root@am335x-evm:~# cat /sys/class/backlight/pwm-backlight/brightness
80
root@am335x-evm:~# echo 80 >  /sys/class/backlight/pwm-backlight/brightness
root@am335x-evm:~# echo 10 >  /sys/class/backlight/pwm-backlight/brightness
root@am335x-evm:~# echo 0 >  /sys/class/backlight/pwm-backlight/brightness
root@am335x-evm:~# cat /sys/class/backlight/pwm-backlight/brightness
0
root@am335x-evm:~#
root@am335x-evm:~#
root@am335x-evm:~# echo 80 >  /sys/class/backlight/pwm-backlight/brightness
root@am335x-evm:~# cat /sys/class/backlight/pwm-backlight/brightness
80
root@am335x-evm:~#
root@am335x-evm:~# echo 40 >  /sys/class/backlight/pwm-backlight/brightness
root@am335x-evm:~# echo 30 >  /sys/class/backlight/pwm-backlight/brightness
root@am335x-evm:~# echo 10 >  /sys/class/backlight/pwm-backlight/brightness
root@am335x-evm:~# echo 1 >  /sys/class/backlight/pwm-backlight/brightness

3.2 PWM蜂鸣器

另外一个PWM接口被注册成了蜂鸣器,可以通过以下接口控制:

root@am335x-evm:/sys/class/backlight/pwm-buzzer# cat brightness
0
root@am335x-evm:/sys/class/backlight/pwm-buzzer# echo 10 > brightness
root@am335x-evm:/sys/class/backlight/pwm-buzzer# echo 0 > brightness
root@am335x-evm:/sys/class/backlight/pwm-buzzer#
root@am335x-evm:/sys/class/backlight/pwm-buzzer# echo 20 > brightness
root@am335x-evm:/sys/class/backlight/pwm-buzzer# echo 0 > brightness
root@am335x-evm:/sys/class/backlight/pwm-buzzer#
root@am335x-evm:/sys/class/backlight/pwm-buzzer# echo 100 > brightness
root@am335x-evm:/sys/class/backlight/pwm-buzzer# cat brightness
100
root@am335x-evm:/sys/class/backlight/pwm-buzzer# echo 10 > brightness
root@am335x-evm:/sys/class/backlight/pwm-buzzer# echo 0 > brightness

4. FrameBuffer调试

4.1 背光

打开、关闭背光:

to unblank 打开背光:
$echo "0" > /sys/class/graphics/fb0/blank
to blank 关闭背光:
$echo "4" > /sys/class/graphics/fb0/blank

调节背光亮度:

root@am335x-evm:~# cat /sys/class/backlight/pwm-backlight/brightness
80
root@am335x-evm:~# echo 80 >  /sys/class/backlight/pwm-backlight/brightness
root@am335x-evm:~# echo 10 >  /sys/class/backlight/pwm-backlight/brightness
root@am335x-evm:~# echo 0 >  /sys/class/backlight/pwm-backlight/brightness
root@am335x-evm:~# cat /sys/class/backlight/pwm-backlight/brightness
0
root@am335x-evm:~#
root@am335x-evm:~#
root@am335x-evm:~# echo 80 >  /sys/class/backlight/pwm-backlight/brightness
root@am335x-evm:~# cat /sys/class/backlight/pwm-backlight/brightness
80
root@am335x-evm:~#
root@am335x-evm:~# echo 40 >  /sys/class/backlight/pwm-backlight/brightness
root@am335x-evm:~# echo 30 >  /sys/class/backlight/pwm-backlight/brightness
root@am335x-evm:~# echo 10 >  /sys/class/backlight/pwm-backlight/brightness
root@am335x-evm:~# echo 1 >  /sys/class/backlight/pwm-backlight/brightness

4.2 fb

读取fb配置信息:

root@am335x-evm:~# fbset -imode "800x480-77"# D: 38.401 MHz, H: 38.711 kHz, V: 77.421 Hzgeometry 800 480 800 960 32timings 26041 96 24 7 3 72 10rgba 8/16,8/8,8/0,8/24
endmodeFrame buffer device information:Name        : DA8xx FB DrvAddress     : 0x87400000Size        : 3072000Type        : PACKED PIXELSVisual      : TRUECOLORXPanStep    : 0YPanStep    : 1YWrapStep   : 0LineLength  : 3200Accelerator : No
root@am335x-evm:~#

读出fb原始内容:

root@am335x-evm:~# dd if=/dev/fb0 of=fb.cap
6000+0 records in
6000+0 records out
root@am335x-evm:~# ls -l fb.cap
-rw-r--r--    1 root     root       3072000 Jan  1 00:09 fb.cap

计算出每个像素点占用8字节:

800x480 = 384000
3072000/384000 = 8

fb测试程序:

#include <linux/fb.h>
#include <stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include <sys/mman.h>int main()
{  int fbfd = 0;  struct fb_var_screeninfo vinfo;  struct fb_fix_screeninfo finfo;  long int screensize = 0;  char * fbp = NULL;int i = 0;/*打开设备文件*/  fbfd = open("/dev/fb0", O_RDWR);  /*取得屏幕相关参数*/  ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo);  ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo);  /*计算屏幕缓冲区大小*/  screensize = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8;  printf("vinfo.xres = %d \n", vinfo.xres);printf("vinfo.yres = %d \n", vinfo.yres);printf("vinfo.bits_per_pixel = %d \n", vinfo.bits_per_pixel);printf("screensize = %d bytes\n", screensize);/*映射屏幕缓冲区到用户地址空间*/  fbp=(char*)mmap(0,screensize,PROT_READ|PROT_WRITE,MAP_SHARED, fbfd, 0);  /*下面可通过fbp指针读写缓冲区*/ for(i=0; i<screensize;i+=4){// Little endian 小端*fbp = 0xFF;        // Blue*(fbp+1) = 0xFF;    // Green*(fbp+2) = 0xFF;    // Red*(fbp+3) = 0xFF;    // Alpha 透明度?fbp+=4;}close(fbfd);
}
root@am335x-evm:~# /home/root/mmcblk0p1/fb_test
vinfo.xres = 800
vinfo.yres = 480
vinfo.bits_per_pixel = 32

fb_var_screeninfo、fb_fix_screeninfo两个结构体的定义:

//不可变信息,应用程序没法设置它,偶尔读出来查阅!
struct fb_fix_screeninfo { char id[16]; /* identification string eg "TT Bui ltin" *///framebuffer在显存中的物理地址:unsigned long smem_start; /* Start of frame buffer mem *//* (physical address) */__u32 smem_len; /* Length of frame buffer mem */__u32 type; /* see FB_TYPE_* */__u32 type_aux; /* Interleave for interleaved Plane s */__u32 visual; /* see FB_VISUAL_* */__u16 xpanstep; /* zero if no hardware panning */__u16 ypanstep; /* zero if no hardware panning */__u16 ywrapstep; /* zero if no hardware ywrap */__u32 line_length; /* length of a line in bytes */unsigned long mmio_start; /* Start of Memory Mapped I/O *//* (physical address) */__u32 mmio_len; /* Length of Memory Mapped I/O */__u32 accel; /* Indicate to driver which *//* specific chip/card we have */__u16 capabilities; /* see FB_CAP_* */__u16 reserved[2]; /* Reserved for future compatibilit y */
};//可变信息,应用程序可以设置它,较多的可关注。
struct fb_var_screeninfo { __u32 xres;//可视分辨率 /* visible resolution */__u32 yres;__u32 xres_virtual;//虚拟分辨率 /* virtual resolution */__u32 yres_virtual;__u32 xoffset;//参考点坐标 /* offset from virtual to visible */__u32 yoffset; /* resolution */__u32 bits_per_pixel;//bpp /* guess what */__u32 grayscale;//灰度级图 /* 0 = color, 1 = grayscale, *//* >1 = FOURCC */struct fb_bitfield red;//描述红色/* bitfield in fb mem if true color, */struct fb_bitfield green;//描述绿色/* else only length is significant */struct fb_bitfield blue;//描述蓝色struct fb_bitfield transp;//描述透明度/* transparency */__u32 nonstd; /* != 0 Non standard pixel format */__u32 activate; /* see FB_ACTIVATE_* */__u32 height;//物理尺寸大小 /* height of picture in mm */__u32 width; /* width of picture in mm */__u32 accel_flags; /* (OBSOLETE) see fb_info.flags *//* Timing: All values in pixclocks, except pixclock (of course) */__u32 pixclock;//像素时钟 /* pixel clock in ps (pico seconds) */__u32 left_margin;//初始化时序要用 /* time from sync to picture */__u32 right_margin; /* time from picture to sync */__u32 upper_margin; /* time from sync to picture */__u32 lower_margin;__u32 hsync_len; /* length of horizontal sync */__u32 vsync_len; /* length of vertical sync */__u32 sync; /* see FB_SYNC_* */__u32 vmode; /* see FB_VMODE_* */__u32 rotate; /* angle we rotate counter clockwise */__u32 colorspace; /* colorspace for FOURCC-based modes */__u32 reserved[4]; /* Reserved for future compatibility */
};

4.3 截屏

ffmpeg -vcodec rawvideo -f rawvideo -pix_fmt argb  -s 800X480 -i fb.cap.robot -f image2 -vcodec png fb.cap.robot.png
ffmpeg -vcodec rawvideo -f rawvideo -pix_fmt argb  -s 800X480 -i fb.cap.red -f image2 -vcodec png fb.cap.red.png
ffmpeg -vcodec rawvideo -f rawvideo -pix_fmt argb  -s 800X480 -i fb.cap.green -f image2 -vcodec png fb.cap.green.png
ffmpeg -vcodec rawvideo -f rawvideo -pix_fmt argb  -s 800X480 -i fb.cap.blue -f image2 -vcodec png fb.cap.blue.png

交叉编译zlib

tar xf zlib-1.2.11.tar.gz  && cd zlib-1.2.11
CC=arm-linux-gnueabihf-gcc ./configure --prefix=/home/myc/vx/qt_project/libpng_install
make && make install

交叉编译libpng

tar xf libpng-1.6.37.tar.gz && cd libpng-1.6.37
export CPPFLAGS="-I /home/myc/vx/qt_project/libpng_install/include"
export LDFLAGS="-L/home/myc/vx/qt_project/libpng_install/lib"
./configure CC=arm-linux-gnueabihf-gcc --prefix=/home/myc/vx/qt_project/libpng_install --host=arm-linux
make && make install

交叉编译截图软件:

cd Framebuffer_shot-master
arm-linux-gnueabihf-gcc famebuffer_shot_png.c -o famebuffer_shot_png -lpng -lz -I /home/myc/vx/qt_project/libpng_install/include -L/home/myc/vx/qt_project/libpng_install/lib

打包lib.tar:

cd /home/myc/vx/qt_project/libpng_install
tar -cvf lib.tar lib/

目标板:

cp  /home/root/mmcblk0p1/lib.tar /home/root/
cd /home/root/
tar -xvf lib.tar
export LD_LIBRARY_PATH=/home/root/libcd /home/root/mmcblk0p1/
./famebuffer_shot_png// 生成screen.png截屏文件

AM335x LCD驱动解析相关推荐

  1. Am335x lcd驱动分析

    2019独角兽企业重金招聘Python工程师标准>>> 一 文件列表 本文使用的为sdk6.0 kernel版本为3.2 并未使用dts am335x的lcd驱动相关文件有: (ke ...

  2. lcd驱动解析(一)

    硬件执行流程1 硬件执行流程2 这两幅图的差别在于mix的位置,mix的功能包括alpha blending,color-key,图层处理等.流程1,这些工作是有软件完成的,流程2是由硬件完成的. 这 ...

  3. lcd驱动解析(二)

    init部分主要完成的任务是:屏的初始化,显示的初始化,最后打开lcd,背光,等待图片数据输入,然后输出.(产生fbinfo结构体给fbmem.c使用) 用户操作的流程包括:ioctl控制图片的显示属 ...

  4. S3C2440上LCD驱动(FrameBuffer)实例开发讲解

    一.开发环境 主  机:VMWare--Fedora 9  开发板:Mini2440--64MB Nand, Kernel:2.6.30.4  编译器:arm-linux-gcc-4.3.2 二.背景 ...

  5. TI OMAP平台BSP学习笔记之 - LCD 驱动(3)

    通过前面两个系列的学习,我们已经了解DSS系统,LCD基本原理,DSS设备树的配置等基本知识.本文简单学习和梳理LCD设备驱动的代码,方便项目中快速bring up和debug. 此系列文章基于TI的 ...

  6. 我所理解的高通平台Lcd驱动框架

    帧缓冲(framebuffer)是 Linux 系统为显示设备提供的一个接口,它将显示缓冲区抽象,屏蔽图像硬件的底层差异,允许上层应用程序在图形模式下直接对显示缓冲区进行读写操作.用户不必关系物理显示 ...

  7. LCD驱动源码分析(s3cfb.c)

    1.驱动源码分析大致思路 (1)分析LCD驱动首先需要分析内核的帧缓冲子系统,因为LCD驱动就是按照帧缓冲子系统提供的注册接口来注册的: (2)内核帧缓冲子系统参考博客:<Linux 帧缓冲子系 ...

  8. S3C2410驱动分析之LCD驱动

    作者:刘昊昱 博客:http://blog.csdn.net/liuhaoyutz 内核版本:2.6.36 源码路径:drivers/video/s3c2410fb.c 本文分析S3C2410的LCD ...

  9. LCD驱动分析【转】

    转自:http://blog.csdn.net/hanmengaidudu/article/details/21559153 1.S3C2440上LCD驱动 (FrameBuffer)实例开发讲解 其 ...

最新文章

  1. 盘点程序员写过的惊天 Bug
  2. 公子龙:我读研期间通过实习和比赛收入五十万
  3. SAP UI5是如何从浏览器读取语言设置并按照优先级排序的
  4. bootstrap-table toolbar图标换文字_iPhone 也能随意换字体啦~
  5. 潮流配色+定制音效 OPPO Enco X蓝调版真无线降噪耳机解析
  6. Linux中的configure、pkg-config、pkg_config_path
  7. Danfo.js专题 - Danfo.js与Dnotebook简介与入门
  8. 来,通过 Excel 来认识神器——POI
  9. 树莓派4B Ubuntu20.04 ROS2 DSO realsense d455
  10. mariadb 卸载 Kali_Adobe官方卸载工具软件安装教程
  11. 软件测试计划和测试方案的区别
  12. 四年前,我设计了一款纹胸小样儿……如图……
  13. 关闭windows Defender的自动扫描
  14. 营救公主的100种方法
  15. 课堂派作业第一题(附思路)已改完!
  16. Kotlin object的三种用法
  17. 迭代器 iter()
  18. c语言模拟石头 剪子布游戏,用C++如何做出石头剪刀布的游戏。。。初学者不给力啊...
  19. 深度学习中,范数有什么意义
  20. 手机的IMEI、MEID、ICCID、UDID、IMSI

热门文章

  1. arduino 机器视觉编程_关于机器视觉笔迹识别和Arduino控制机器人的设计
  2. 微软账户登录出现错误
  3. L2W3作业 TensorFlow教程
  4. BakAndImgCD 24.0发布:轻松备份和克隆磁盘驱动器
  5. 使用Fiddle出现同时监听夜神模拟器和本地电脑的问题
  6. 随机微分方程学习笔记02 Doob鞅不等式
  7. 计算机毕业设计java+ssm水果销售商城系统(源码+系统+mysql数据库+Lw文档)
  8. 如何使用dbx分析core文件_[转]使用DBX分析AIX 下的 CoreDump
  9. matlab基础知识 (六) 调用函数
  10. JavaScript:递归输出 输入的数(输入12345,输出12345)