LCD驱动开发手册

LCD相关概念

接口介绍

MCU/MPU接口

MCU接口标准名称是I80,将显存内置在LCD模块内部,因为主要针对单片机的领域在使用,因此得名。主要又可以分为8080模式和6800模式,这两者之间主要是时序的区别。 数据位传输8/9/16/18bits, 信号线: CS:片选信号 WR:向TFT-LCD写数据 RD:向TFT-LCD读数据 D[0-17]:数据线 DC:命令/数据标志

RGB接口

显存由系统内存充当,因此能制作较大尺寸,并且显示速度较快 信号线: R[0:7]:红色数据线 G[0:7]:绿色数据线 B[0:7]:蓝色数据线 DE:数据使能线 VS:垂直同步信号线 HS:水平同步信号线 DCLK:像素时钟线

spi接口

3/4线串行接口 信号线: MOSI:主机输出从机输入数据线,用于写lcd ic寄存器 MOSO:主机输入从机输出数据线,用于读lcd ic寄存器 CLK:串行时钟,用于控制速率,由主机提供 CS:从机选择线

262K/65K color

262K全彩指一个像素有18bit来描述颜色,2^18=262K,65K指一个像素有16bit来描述颜色,2^16=65K

SPI接口LCD开发

添加设备树

&spi1{
    lcd0: ili9341@0 {
        compatible = "ilitek,ili9341";
        reg = <0>;
        status = "okay";
        spi-max-frequency = <85000000>;
        regwidth = <8>;
        width = <240>;
        height = <320>;
        rotate = <180>;
        buswidth = <8>;
        bpp = <16>;
        dc-gpios = <&ck_gpa0 8="" gpio_active_high="">;
        reset-gpios = <&gpa1 2="" gpio_active_high="">;
        debug = <0x0>;
      };  
};
fbtft_probe_dt函数将对设备树进行解析 * spi配置设备树描述 ** spi-max-frequency spi的最高频率,影响屏的帧率 * fbtft可选的设备树描述 ** width/height 描述屏的分辨率 ** regwidth lcd控制寄存器位宽,一般为8 ** rotate 显示旋转角度,一般为0/90/180/270 ** buswidth 总线宽度,spi一般为8 ** bpp 每个像素点的位数, *需要注意真正决定屏一帧大小的是struct fbtft_display中的bpp成员* ** backlight 设置为1或者描述了led-gpios将注册backlight_ops ** bgr 设置rgb转换为bgr ** txbuflen 影响txbuf分配大小,txbuf用于write_vmem函数 ** startbyte 设置后,会在发送中添加该字节 ** gamma gamma曲线的字符串表示 ** debug 打印信息控制,设为0xFFFFFFFF全打印 ** x-gpios 可以设置reset/dc/rd/wr/cs/latch/db/led/aux,如果使用gpio模拟spi则需要设置rd/wr/cs gpio的设置还可以参考"gpio wiki":http://192.168.110.254/redmine/projects/robot_os/wiki/LEO_GPIO ### 添加驱动 相关代码在kernel/drivers/staging/fbtft 开发lcd驱动主要就是填充struct fbtft_display结构体
static struct fbtft_display display = { 
    .regwidth = 8,
    .width = WIDTH,
    .height = HEIGHT,
    .txbuflen = TXBUFLEN,
    .gamma_num = 2,
    .gamma_len = 15, 
    .gamma = DEFAULT_GAMMA,
    .fbtftops = { 
        .init_display = init_display,  //初始化液晶屏
        .set_addr_win = set_addr_win, //设置显示区域
        .set_var = set_var,            //设置旋转/格式转换
        .set_gamma = set_gamma,        //设置gamma曲线
    },  
};
FBTFT_REGISTER_DRIVER(DRVNAME, "ilitek,ili9341", &display);
只需要实现lcd特殊的几个函数,后续的显存读写相关操作都在fbtft-core.c已经实现好了 init_display函数可以自己实现,更方便的是可以定义一个init_sequence数组由fbtft框架进行初始化
static int default_init_sequence[] = {
    -1, 0x11,
    -2, 0x20,
    -1, 0xC0, 0x0A, 0x0A,
    -1, 0xC1, 0x41, 0x07,
    ....................
    -1, 0x3A, 0x55,
    -1, 0x29,
    -2, 0x10,
    -3,
};

static struct fbtft_display display = {
    .init_sequence = default_init_sequence,
    .fbtftops = {
      .set_addr_win = set_addr_win,
    .set_var = set_var,
    },
};
-1/-2/-3是标识作用,-1标识需要写入后续值,-2标识需要延时后续值,-3标识结束.fbtft_init_display函数进行解析 ### backlight 目前框架只支持背光开关,不支持背光亮度调节 需要在设备树中添加led-gpios/backlight 加载驱动后背光会被关闭,开启背光需要操作echo 1 > /sys/class/backlight/fb_ili9486/bl_power ### fbtft分析 #### 注册 FBTFT_REGISTER_DRIVER宏创建了struct platform_driver和struct spi_driver结构体,如果匹配上设备树中描述的平台设备或者spi设备将调用
static int fbtft_driver_probe_spi(struct spi_device *spi)                  \
{                                                                          \
    return fbtft_probe_common(_display, spi, NULL);                    \
} 

static int fbtft_driver_probe_pdev(struct platform_device *pdev)           \
{                                                                          \
    return fbtft_probe_common(_display, NULL, pdev);                   \
} 
int fbtft_probe_common(struct fbtft_display *display,
            struct spi_device *sdev, struct platform_device *pdev)
{
    ........
    //创建了分配了结构体struct fb_info并设置了相关参数,这是需要向framebuffer框架注册的重要结构体
    info = fbtft_framebuffer_alloc(display, dev, pdata);
    ........
    //大量的fbtft_ops函数会被设置
    par->fbtftops.write_register = fbtft_write_reg8_bus8;
    par->fbtftops.write_vmem = fbtft_write_vmem16_bus8;
    //如果是spi总线会被设为fbtft_write_spi函数
    par->fbtftops.write = fbtft_write_gpio8_wr;
    ........
    ret = fbtft_register_framebuffer(info);
}

int fbtft_register_framebuffer(struct fb_info *fb_info)
{
    //初始化lcd
    ret = par->fbtftops.request_gpios(par);
    ret = par->fbtftops.init_display(par);
    par->fbtftops.update_display(par, 0, par->info->var.yres - 1);
    .........
    //向framebuffer框架注册,此时会生成/dev/fbX设备节点
    ret = register_framebuffer(fb_info);
}
注册的过程中最主要的就是设置了struct fb_info和struct fbtft_par两个结构体 #### 操作 在注册过程中对lcd进行操作的函数都进行了设置
    fbops->fb_read      =      fb_sys_read;
    fbops->fb_write     =      fbtft_fb_write;
    fbops->fb_fillrect  =      fbtft_fb_fillrect;
    fbops->fb_copyarea  =      fbtft_fb_copyarea;
    fbops->fb_imageblit =      fbtft_fb_imageblit;
    fbops->fb_setcolreg =      fbtft_fb_setcolreg;
    fbops->fb_blank     =      fbtft_fb_blank;
    fbdefio->deferred_io =     fbtft_deferred_io;
    fb_deferred_io_init(info);
........
    par->fbtftops.write = fbtft_write_spi;
    par->fbtftops.read = fbtft_read_spi;
    par->fbtftops.write_vmem = fbtft_write_vmem16_bus8;
    par->fbtftops.write_register = fbtft_write_reg8_bus8;
.........

以写操作为例,应用层调用write对/dev/fbX设备节点进行写

//首先将调用到framebuffer框架注册的file_operations
static ssize_t
fb_write(struct file file, const char __user buf, size_t count, loff_t *ppos)
{
    //将调用注册的write函数
    if (info->fbops->fb_write)
        return info->fbops->fb_write(info, buf, count, ppos);
}

static ssize_t fbtft_fb_write(struct fb_info info, const char __user buf, size_t count, loff_t *ppos) {
//只是拷贝应用层数据到显存的操作 res = fb_sys_write(info, buf, count, ppos);

par->fbtftops.mkdirty(info, -1, 0);

}

static void fbtft_mkdirty(struct fb_info *info, int y, int height) {
......... //将调用到fbdefio->deferred_io schedule_delayed_work(&info->deferred_work, fbdefio->delay); }

fbtft_deferred_io -> fbtft_update_display -> fbtft_write_vmem16_bus8 -> fbtft_write_spi

int fbtft_write_spi(struct fbtft_par par, void buf, size_t len) {
struct spi_transfer t = { .tx_buf = buf, .len = len, }; struct spi_message m;

fbtft_par_dbg_hex(DEBUG_WRITE, par, par->info->device, u8, buf, len,
    "%s(len=%d): ", __func__, len);

if (!par->spi) {
    dev_err(par->info->device,
        "%s: par->spi is unexpectedly NULL\n", __func__);
    return -1;
}           

spi_message_init(&m);
if (par->txbuf.dma && buf == par->txbuf.buf) {
    t.tx_dma = par->txbuf.dma;
    m.is_dma_mapped = 1; 
}
spi_message_add_tail(&t, &m);
return spi_sync(par->spi, &m);

} </pre> 通过分析流程可以知道,整个write过程不需要lcd驱动关心,完全由fbtft框架进行控制,read过程也类似 因此lcd驱动需要做的仅仅是初始化lcd并正确的描述lcd的各项参数,其余的工作都是由框架自行完成

注意点

出现每次显示像素点数量不一致原因
  • 可能是spi速率过高

results matching ""

    No results matching ""