• LCD驱动程序之代码编写


    学习目的:

    • 实现LCD驱动程序编写

    前面通过对linux内核中LCD的驱动框架进行了分析,弄清楚了内核中LCD的驱动框架,通过分析知道内核中已经在fbmem.c文件中注册了LCD这一类设备的字符设备驱动,向上实现了上层通用的访问接口,向下给驱动设计者预留了fb_info注册接口。现在基于我们的开发板平台(JZ2240),编写适配于自己硬件的驱动程序

    从前面分析可以知道,编写LCD驱动需要驱动的编写者去完成以下内容(可参考内核中s3c2410fb.c):

    1) 分配一个fb_info结构体

    2) 根据使用LCD硬件特征,设置fb_info结构体的一些参数

    3) 配置硬件相关操作

    4) 注册fb_info结构体

    下面我们就开始按照这些步骤,完成LCD驱动程序的编写

    1、fb_info结构体介绍

    fb_info结构体是LCD驱动程序中最核心的数据结构,应用程序通过fbmem.c文件中注册的设备访问入口,找到打开设备文件的fb_info结构体,最终访问到硬件,fb_info结构体声明如下:

    struct fb_info {
        int node;
        int flags;
        struct fb_var_screeninfo var;    /* Current var */
        struct fb_fix_screeninfo fix;    /* Current fix */
        struct fb_monspecs monspecs;    /* Current Monitor specs */
        struct work_struct queue;    /* Framebuffer event queue */
        struct fb_pixmap pixmap;    /* Image hardware mapper */
        struct fb_pixmap sprite;    /* Cursor hardware mapper */
        struct fb_cmap cmap;        /* Current cmap */
        struct list_head modelist;      /* mode list */
        struct fb_videomode *mode;    /* current mode */
    
    #ifdef CONFIG_FB_BACKLIGHT
        /* assigned backlight device */
        /* set before framebuffer registration, 
           remove after unregister */
        struct backlight_device *bl_dev;
    
        /* Backlight level curve */
        struct mutex bl_curve_mutex;    
        u8 bl_curve[FB_BACKLIGHT_LEVELS];
    #endif
    #ifdef CONFIG_FB_DEFERRED_IO
        struct delayed_work deferred_work;
        struct fb_deferred_io *fbdefio;
    #endif
    
        struct fb_ops *fbops;
        struct device *device;        /* This is the parent */
        struct device *dev;        /* This is this fb device */
        int class_flag;                    /* private sysfs flags */
    #ifdef CONFIG_FB_TILEBLITTING
        struct fb_tile_ops *tileops;    /* Tile Blitting */
    #endif
        char __iomem *screen_base;    /* Virtual address */
        unsigned long screen_size;    /* Amount of ioremapped VRAM or 0 */ 
        void *pseudo_palette;        /* Fake palette of 16 colors */ 
    #define FBINFO_STATE_RUNNING    0
    #define FBINFO_STATE_SUSPENDED    1
        u32 state;            /* Hardware state i.e suspend */
        void *fbcon_par;                /* fbcon use-only private area */
        /* From here on everything is device dependent */
        void *par;    
    };

    可以看到fb_info的结构体比较复杂,它的肯定是为了抽象出所有LCD硬件而设计的,但我们不需要去了解每一个成员变量的功能和使用方法。在这里面有三个比较重要的结构体,分别是fb_var_screeninfo、fb_fix_screeninfo、fb_ops,这三个结构体也是在每个LCD驱动程序中都必需设置的

    1)fb_var_screeninfo结构体记录了用户可以修改的显示参数,比如屏幕的分辨率、屏幕位域

    2)fb_fix_screeninfo结构体记录了用户不可修改的参数,比如屏幕的显存的位置、长度

    3)fb_ops结构体记录了操作底层硬件函数的地址,如果fb_ops中定义了read函数,应用程序的read函数最终调用到fb_ops中read函数指针指向的函数,通过file->fops->read中找到打开设备fb_info,调用fb_info->fbops->read

    2、驱动使用函数介绍

    struct fb_info *framebuffer_alloc(size_t size, struct device *dev);------------------------------>①
    void framebuffer_release(struct fb_info *info);
    /*================================================================================================*/
    int register_framebuffer(struct fb_info *fb_info);----------------------------------------------->② int unregister_framebuffer(struct fb_info *fb_info);
    /*================================================================================================*/

    void *dma_alloc_writecombine(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp);---->③
    size:分配内存的大小
    handler:申请到内存的物理地址

    gfp:分配出来内存的参数,常用标志如下:
    GFP_ATOMIC 用来从中断处理和进程上下文之外的其他代码中分配内存. 从不睡眠.
    GFP_KERNEL 内核内存的正常分配. 可能睡眠.
    GFP_USER 用来为用户空间页来分配内存; 它可能睡眠.
    返回申请到内存的虚拟地址

    void dma_free_writecombine(struct device *dev, size_t size, void *cpu_addr, dma_addr_t handle);
    cpu_addr:虚拟地址,
    handle:物理地址

    动态分配一个fb_info结构体,size代表额外分配的内存供驱动使用,可以用fb_info中的fbcon_par指针指向这块内存,如果不使用设置为0,dev指针,指向fb的device,可以设置为NULL

    ② 向内核注册和注销fb_info结构体

    ③ 分配和注销连续地址的内存,一般LCD控制器使用的显存地址是连续的,分配显存时需要使用这些函数

    3、驱动程序编写

    3.1 入口函数

    入口函数里面完成了LCD驱动中的大部分工作,分配了fb_info结构体、分配了连续地址内存作为LCD控制器显存,设置了fb_info结构体的一些信息,配置硬件,注册fb_info结构体

    int lcd_drv_init(void)
    {
        lcd_fb_info = framebuffer_alloc(0, NULL);
    
        /* set fb_info variable part */
        lcd_fb_info->var.xres           = 480;   //x方向分辨率      
        lcd_fb_info->var.yres           = 272;   //y方向分别率
        lcd_fb_info->var.xres_virtual   = 480;   //x方向虚拟分辨率
        lcd_fb_info->var.yres_virtual   = 272;   //y方向虚拟分辨率
        lcd_fb_info->var.bits_per_pixel = 16;    //每像素位数
    
        /* color format RGB565 */
        lcd_fb_info->var.red.length     = 5;     //lcd颜色格式
        lcd_fb_info->var.red.offset      = 11;
        lcd_fb_info->var.green.length     = 6;
        lcd_fb_info->var.green.offset     = 5;
        lcd_fb_info->var.blue.length     = 5;
        lcd_fb_info->var.blue.offset     = 0;
         
        lcd_fb_info->var.activate       = FB_ACTIVATE_NOW;
            
        /* set fb_info fixed part */
        strcpy(lcd_fb_info->fix.id, "tft-lcd");    
        lcd_fb_info->fix.smem_len       = 480*272*16/8;           //显存大小
        lcd_fb_info->fix.type             = FB_TYPE_PACKED_PIXELS;
        lcd_fb_info->fix.visual            = FB_VISUAL_TRUECOLOR;
        lcd_fb_info->fix.line_length    = 480*16/8;               //显示一行需要字节数
    
        /* set fb_info fbops pointer*/
        lcd_fb_info->fbops                = &lcd_fb_ops;
    
        lcd_fb_info->pseudo_palette = pseudo_palette;             //调色板颜色
    
        lcd_fb_info->screen_size        = 480*272*16/8;
        
        /* set lcd connect gpio pin register */
        gpbcon = ioremap(0x56000010, 8);
        gpbdat = gpbcon+1;
        gpccon = ioremap(0x56000020, 4);
        gpdcon = ioremap(0x56000030, 4);
        gpgcon = ioremap(0x56000060, 4);
    
        *gpccon  = 0xaaaaaaaa;   /* GPIO管脚用于VD[7:0],LCDVF[2:0],VM,VFRAME,VLINE,VCLK,LEND */
        *gpdcon  = 0xaaaaaaaa;   /* GPIO管脚用于VD[23:8] */
        
        *gpbcon &= ~(3);  /* GPB0设置为输出引脚 */
        *gpbcon |= 1;
        *gpbdat &= ~1;     /* 输出低电平 */
    
        *gpgcon |= (3<<8); /* GPG4用作LCD_PWREN */
    
        /* set s3c2440 lcd control register */
        lcd_regs = ioremap(0x4D000000, sizeof(struct lcd_regs));
    
        /* bit[17:8]: VCLK = HCLK / [(CLKVAL+1) x 2], LCD手册P14
         *            10MHz(100ns) = 100MHz / [(CLKVAL+1) x 2]
         *            CLKVAL = 4
         * bit[6:5]: 0b11, TFT LCD
         * bit[4:1]: 0b1100, 16 bpp for TFT
         * bit[0]  : 0 = Disable the video output and the LCD control signal.
         */
        lcd_regs->lcdcon1  = (4<<8) | (3<<5) | (0x0c<<1);
    
        /* 垂直方向的时间参数
         * bit[31:24]: VBPD, VSYNC之后再过多长时间才能发出第1行数据
         *             
         * bit[23:14]: 多少行, 
         * bit[13:6] : VFPD, 发出最后一行数据之后,再过多长时间才发出VSYNC
         *             
         * bit[5:0]  : VSPW, VSYNC信号的脉冲宽度
         */
        lcd_regs->lcdcon2  = (1<<24) | (271<<14) | (1<<6) | (9<<0);
    
    
        /* 水平方向的时间参数
         * bit[25:19]: HBPD, VSYNC之后再过多长时间才能发出第1行数据
         *             HBPD=16
         * bit[18:8]: 多少列,
         * bit[7:0] : HFPD, 发出最后一行里最后一个象素数据之后,再过多长时间才发出HSYNC
         *             
         */
        lcd_regs->lcdcon3 = (1<<19) | (479<<8) | (1<<0);
    
        /* 水平方向的同步信号
         * bit[7:0]    : HSPW, HSYNC信号的脉冲宽度
         */    
        lcd_regs->lcdcon4 = 40;
    
        /* 信号的极性 
         * bit[11]: 1=565 format
         * bit[10]: 0 = The video data is fetched at VCLK falling edge
         * bit[9] : 1 = HSYNC信号要反转,即低电平有效 
         * bit[8] : 1 = VSYNC信号要反转,即低电平有效 
         * bit[6] : 0 = VDEN不用反转
         * bit[3] : 0 = PWREN输出0
         * bit[1] : 0 = BSWP
         * bit[0] : 1 = HWSWP 2440手册P413
         */
        lcd_regs->lcdcon5 = (1<<11) | (0<<10) | (1<<9) | (1<<8) | (1<<0);
        
        /* 3.3 分配显存(framebuffer), 并把地址告诉LCD控制器 */
        lcd_fb_info->screen_base = dma_alloc_writecombine(NULL, lcd_fb_info->fix.smem_len, &lcd_fb_info->fix.smem_start, GFP_KERNEL);
        
        lcd_regs->lcdsaddr1  = (lcd_fb_info->fix.smem_start >> 1) & ~(3<<30);
        lcd_regs->lcdsaddr2  = ((lcd_fb_info->fix.smem_start + lcd_fb_info->fix.smem_len) >> 1) & 0x1fffff;
        lcd_regs->lcdsaddr3  = (480*16/16);  /* 一行的长度(单位: 2字节) */    
         
        /* 启动LCD */
        lcd_regs->lcdcon1 |= (1<<0); /* 使能LCD本身 */
        lcd_regs->lcdcon5 |= (1<<3);
        *gpbdat |= 1;     /* 输出高电平, 使能背光 */
        
        register_framebuffer(lcd_fb_info);
    
        return 0;
    }

    配置硬件部分分以下几个步骤

    1)寄存器物理地址到虚拟地址映射

    2)配置相关GPIO引脚

    3)根据LCD芯片手册时序图,设置LCD控制器各寄存器值

    4)使能LCD控制器、开启背光

    入口函数在设置好fb_info结构体,完成硬件配置工作后,调用register_framebuffer向内核注册fb_info信息

    3.2 fb_ops结构体实现

    static struct fb_ops lcd_fb_ops = {
        .owner        = THIS_MODULE,
        .fb_setcolreg    = s3c_lcdfb_setcolreg,------------------>①
        .fb_fillrect    = cfb_fillrect,-------------------------->②
        .fb_copyarea    = cfb_copyarea,-------------------------->③
        .fb_imageblit    = cfb_imageblit,------------------------>④
    };

    fb_ops中实现了4个函数指针,cfb_fillrect、cfb_copuarea、cfb_imageblit函数由内核实现的函数,对/dev/fbn设备文件进行访问时,会调用到这些函数

    ① 调用s3c_lcdfb_setcolreg函数,来设置调色板fb_info->pseudo_palette

    ② 填充矩形

    ③ 拷贝显存数据

    ④ 图片显示

    3.3 出口函数

    void lcd_drv_exit(void)
    {
        unregister_framebuffer(lcd_fb_info);
        lcd_regs->lcdcon1 &= ~(1<<0); /* 关闭LCD本身 */
        *gpbdat &= ~1;     /* 关闭背光 */
        dma_free_writecombine(NULL, lcd_fb_info->fix.smem_len, lcd_fb_info->screen_base, lcd_fb_info->fix.smem_start);
        framebuffer_release(lcd_fb_info);
    
        iounmap(gpbcon);
        iounmap(gpccon);
        iounmap(gpdcon);
        iounmap(gpgcon);
        iounmap(lcd_regs);    
    }

    驱动卸载时,注销注册的fb_info结构体,关闭LCD,释放申请的显存,释放动态分配的fb_info结构体,取消寄存器物理地址到虚拟地址映射

    4、驱动程序测试

    内核中如果已经将内核自带的lcd驱动编译在内,需要重新make menuconfig将lcd自带的驱动编译成模块

    Device Drivers  --->
        Graphics support  --->  
            <*> Support for frame buffer devices  ---> 
                 <M>   S3C2410 LCD framebuffer support  

    执行make uImage生成uImage文件

    执行make modules,因为内核自带的LCD驱动没有编进内核,LCD驱动相关其他代码肯定也没编译进内核,而我们编写驱动的fb_ops里的成员使用了内核中关于LCD驱动的fb_fillrect(), fb_copyarea(),fb_imageblit()函数,我们需要将其编译成.ko文件在加载lcd驱动之前加载

    使用insmod加载编译好的驱动程序

    insmod cfbcopyarea.ko
    insmod cfbfillrect.ko
    insmod cfbimgblt.ko
    insmod lcd_drv.ko

    使用echo hello > /dev/tty1命令进行测试

    如果驱动正常,内核中Console display driver support / <*> Framebuffer Console support 被编译在内,lcd上将显示hello字符串

    完整驱动代码

    #include <linux/module.h>
    #include <linux/kernel.h>
    #include <linux/errno.h>
    #include <linux/string.h>
    #include <linux/mm.h>
    #include <linux/slab.h>
    #include <linux/delay.h>
    #include <linux/fb.h>
    #include <linux/init.h>
    #include <linux/dma-mapping.h>
    #include <linux/interrupt.h>
    #include <linux/workqueue.h>
    #include <linux/wait.h>
    #include <linux/platform_device.h>
    #include <linux/clk.h>
    
    #include <asm/io.h>
    #include <asm/uaccess.h>
    #include <asm/div64.h>
    
    #include <asm/mach/map.h>
    //nclude <asm/arch/regs-lcd.h>
    //nclude <asm/arch/regs-gpio.h>
    //nclude <asm/arch/fb.h>
    
    static int s3c_lcdfb_setcolreg(unsigned int regno, unsigned int red,
                     unsigned int green, unsigned int blue,
                     unsigned int transp, struct fb_info *info);
    
    
    struct lcd_regs {
        unsigned long    lcdcon1;
        unsigned long    lcdcon2;
        unsigned long    lcdcon3;
        unsigned long    lcdcon4;
        unsigned long    lcdcon5;
        unsigned long    lcdsaddr1;
        unsigned long    lcdsaddr2;
        unsigned long    lcdsaddr3;
        unsigned long    redlut;
        unsigned long    greenlut;
        unsigned long    bluelut;
        unsigned long    reserved[9];
        unsigned long    dithmode;
        unsigned long    tpal;
        unsigned long    lcdintpnd;
        unsigned long    lcdsrcpnd;
        unsigned long    lcdintmsk;
        unsigned long    lpcsel;
    };
    
    static struct fb_info *lcd_fb_info;
    
    static struct fb_ops lcd_fb_ops = {
        .owner        = THIS_MODULE,
        .fb_setcolreg    = s3c_lcdfb_setcolreg,
        .fb_fillrect    = cfb_fillrect,
        .fb_copyarea    = cfb_copyarea,
        .fb_imageblit    = cfb_imageblit,
    };
    
    static volatile unsigned long *gpbcon;
    static volatile unsigned long *gpbdat;
    static volatile unsigned long *gpccon;
    static volatile unsigned long *gpdcon;
    static volatile unsigned long *gpgcon;
    static volatile struct lcd_regs* lcd_regs;
    static u32 pseudo_palette[16];
    
    
     static inline unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf)
    {
        chan &= 0xffff;
        chan >>= 16 - bf->length;
        return chan << bf->offset;
    }
    
    
    static int s3c_lcdfb_setcolreg(unsigned int regno, unsigned int red,
                     unsigned int green, unsigned int blue,
                     unsigned int transp, struct fb_info *info)
    {
        unsigned int val;
        
        if (regno > 16)
            return 1;
    
        /* 用red,green,blue三原色构造出val */
        val  = chan_to_field(red,    &info->var.red);
        val |= chan_to_field(green, &info->var.green);
        val |= chan_to_field(blue,    &info->var.blue);
        
        //((u32 *)(info->pseudo_palette))[regno] = val;
        pseudo_palette[regno] = val;
        return 0;
    }
    
    
    
    int lcd_drv_init(void)
    {
        lcd_fb_info = framebuffer_alloc(0, NULL);
    
        /* set fb_info variable part */
        lcd_fb_info->var.xres           = 480;
        lcd_fb_info->var.yres           = 272;
        lcd_fb_info->var.xres_virtual   = 480;
        lcd_fb_info->var.yres_virtual   = 272;
        lcd_fb_info->var.bits_per_pixel = 16;
    
        /* color format RGB565 */
        lcd_fb_info->var.red.length     = 5;
          lcd_fb_info->var.red.offset      = 11;
          lcd_fb_info->var.green.length     = 6;
          lcd_fb_info->var.green.offset     = 5;
          lcd_fb_info->var.blue.length     = 5;
          lcd_fb_info->var.blue.offset     = 0;
         
        lcd_fb_info->var.activate       = FB_ACTIVATE_NOW;
            
        /* set fb_info fixed part */
        strcpy(lcd_fb_info->fix.id, "tft-lcd");    
        lcd_fb_info->fix.smem_len       = 480*272*16/8;
        lcd_fb_info->fix.type             = FB_TYPE_PACKED_PIXELS;
        lcd_fb_info->fix.visual            = FB_VISUAL_TRUECOLOR;
        lcd_fb_info->fix.line_length    = 480*16/8;
    
        /* set fb_info fbops pointer*/
        lcd_fb_info->fbops                = &lcd_fb_ops;
    
        lcd_fb_info->pseudo_palette = pseudo_palette;
    
        lcd_fb_info->screen_size        = 480*272*16/8;
        
        /* set lcd connect gpio pin register */
        gpbcon = ioremap(0x56000010, 8);
        gpbdat = gpbcon+1;
        gpccon = ioremap(0x56000020, 4);
        gpdcon = ioremap(0x56000030, 4);
        gpgcon = ioremap(0x56000060, 4);
    
        *gpccon  = 0xaaaaaaaa;   /* GPIO管脚用于VD[7:0],LCDVF[2:0],VM,VFRAME,VLINE,VCLK,LEND */
        *gpdcon  = 0xaaaaaaaa;   /* GPIO管脚用于VD[23:8] */
        
        *gpbcon &= ~(3);  /* GPB0设置为输出引脚 */
        *gpbcon |= 1;
        *gpbdat &= ~1;     /* 输出低电平 */
    
        *gpgcon |= (3<<8); /* GPG4用作LCD_PWREN */
    
        /* set s3c2440 lcd control register */
        lcd_regs = ioremap(0x4D000000, sizeof(struct lcd_regs));
    
        /* bit[17:8]: VCLK = HCLK / [(CLKVAL+1) x 2], LCD手册P14
         *            10MHz(100ns) = 100MHz / [(CLKVAL+1) x 2]
         *            CLKVAL = 4
         * bit[6:5]: 0b11, TFT LCD
         * bit[4:1]: 0b1100, 16 bpp for TFT
         * bit[0]  : 0 = Disable the video output and the LCD control signal.
         */
        lcd_regs->lcdcon1  = (4<<8) | (3<<5) | (0x0c<<1);
    
        /* 垂直方向的时间参数
         * bit[31:24]: VBPD, VSYNC之后再过多长时间才能发出第1行数据
         *             LCD手册 T0-T2-T1=4
         *             VBPD=3
         * bit[23:14]: 多少行, 320, 所以LINEVAL=320-1=319
         * bit[13:6] : VFPD, 发出最后一行数据之后,再过多长时间才发出VSYNC
         *             LCD手册T2-T5=322-320=2, 所以VFPD=2-1=1
         * bit[5:0]  : VSPW, VSYNC信号的脉冲宽度, LCD手册T1=1, 所以VSPW=1-1=0
         */
        lcd_regs->lcdcon2  = (1<<24) | (271<<14) | (1<<6) | (9<<0);
    
    
        /* 水平方向的时间参数
         * bit[25:19]: HBPD, VSYNC之后再过多长时间才能发出第1行数据
         *             LCD手册 T6-T7-T8=17
         *             HBPD=16
         * bit[18:8]: 多少列, 240, 所以HOZVAL=240-1=239
         * bit[7:0] : HFPD, 发出最后一行里最后一个象素数据之后,再过多长时间才发出HSYNC
         *             LCD手册T8-T11=251-240=11, 所以HFPD=11-1=10
         */
        lcd_regs->lcdcon3 = (1<<19) | (479<<8) | (1<<0);
    
        /* 水平方向的同步信号
         * bit[7:0]    : HSPW, HSYNC信号的脉冲宽度, LCD手册T7=5, 所以HSPW=5-1=4
         */    
        lcd_regs->lcdcon4 = 40;
    
        /* 信号的极性 
         * bit[11]: 1=565 format
         * bit[10]: 0 = The video data is fetched at VCLK falling edge
         * bit[9] : 1 = HSYNC信号要反转,即低电平有效 
         * bit[8] : 1 = VSYNC信号要反转,即低电平有效 
         * bit[6] : 0 = VDEN不用反转
         * bit[3] : 0 = PWREN输出0
         * bit[1] : 0 = BSWP
         * bit[0] : 1 = HWSWP 2440手册P413
         */
        lcd_regs->lcdcon5 = (1<<11) | (0<<10) | (1<<9) | (1<<8) | (1<<0);
        
        /* 3.3 分配显存(framebuffer), 并把地址告诉LCD控制器 */
        lcd_fb_info->screen_base = dma_alloc_writecombine(NULL, lcd_fb_info->fix.smem_len, &lcd_fb_info->fix.smem_start, GFP_KERNEL);
        
        lcd_regs->lcdsaddr1  = (lcd_fb_info->fix.smem_start >> 1) & ~(3<<30);
        lcd_regs->lcdsaddr2  = ((lcd_fb_info->fix.smem_start + lcd_fb_info->fix.smem_len) >> 1) & 0x1fffff;
        lcd_regs->lcdsaddr3  = (480*16/16);  /* 一行的长度(单位: 2字节) */    
         
        /* 启动LCD */
        lcd_regs->lcdcon1 |= (1<<0); /* 使能LCD本身 */
        lcd_regs->lcdcon5 |= (1<<3);
        *gpbdat |= 1;     /* 输出高电平, 使能背光 */
        
        register_framebuffer(lcd_fb_info);
    
        return 0;
    }
    
    void lcd_drv_exit(void)
    {
        unregister_framebuffer(lcd_fb_info);
        lcd_regs->lcdcon1 &= ~(1<<0); /* 关闭LCD本身 */
        *gpbdat &= ~1;     /* 关闭背光 */
        dma_free_writecombine(NULL, lcd_fb_info->fix.smem_len, lcd_fb_info->screen_base, lcd_fb_info->fix.smem_start);
        framebuffer_release(lcd_fb_info);
    
        iounmap(gpbcon);
        iounmap(gpccon);
        iounmap(gpdcon);
        iounmap(gpgcon);
        iounmap(lcd_regs);    
    }
    
    module_init(lcd_drv_init);
    module_exit(lcd_drv_exit);
    
    MODULE_LICENSE("GPL");
    lcd_drv.c
  • 相关阅读:
    ios swift 支持cocoaPods
    iOS 国际化
    ios storyboard全解析 (二)
    ios storyboard全解析 (一)
    UML类图的几个关系自我总结,(入门级)
    crypt 病毒
    js思维导向图
    关于索引的使用
    SQL Server 索引结构及其使用
    关于js数组的那些事
  • 原文地址:https://www.cnblogs.com/053179hu/p/13849580.html
Copyright © 2020-2023  润新知