/* AUTHOR: Pinus
* Creat on : 2018-11-1
* KERNEL : linux-4.4.145
* BOARD : JZ2440(arm9 s3c2440)
* REFS : 韦东山视频教程第二期
S3C2440上LCD驱动(FrameBuffer)实例开发讲解
*/
不要说我这取得名字low,我感觉吧,一个东西它能显示出来图像,色彩鲜明,便会给人非常直观的感受,和串口打印信息完全不一样,就是高大上,有档次。
LCD是Liquid Crystal Display的简称,也就是经常所说的液晶显示器LCD能够支持彩色图像的显示和视频的播放,是一种非常重要的输出设备。
这篇文章只讲笼统的概念,为什么呢?
因为LCD不学之前一头雾水,学会之后就很清晰,要想学会重要的是理解LCD显示的原理,和实现控制的原理。只要理解,就可以实现。具体的分析请结合这篇文章S3C2440上LCD驱动(FrameBuffer)实例开发讲解,这篇文章我已经通读过,讲解的很细致,如果仔细看一定收益很多,我就不搬运了。
我会大致介绍,并提供实现内核源码支持LCD,请细读那篇推荐文章,加深理解。
1. LCD工作的硬件需求:
要使一块LCD正常的显示文字或图像,需要LCD驱动器和LCD控制器两个部分。在通常情况下,生产厂商把LCD驱动器会以各种形式与LCD玻璃基板封装在一起,而LCD控制器则是由外部的电路来实现。现在很多的MCU内部都集成了LCD控制器,如手中的s3c2440等。通过LCD控制器就可以产生LCD驱动器所需要的控制信号来控制STN/TFT屏。
2.显示屏显示的大致方式
首先有一个概念,图像是由一个个像素点组成的,LCD在显示的时候也是一个个像素呈现出来的,只是速度很快,在人眼中就是图像了。那么必然要涉及几个问题,一个是图像大小,一个是像素点的大小和颜色组成,一个是像素刷新的时序(一是图像刷新换行时,一是显示完要显示下一帧图像时)等等。而这些在是现实无非是要设置s3c2440对应的寄存器(将LCD控制器的所有寄存器一位一位设置就好了)。
3.frambuffer
帧缓冲区,实际中自然不可能是每动一次就单独把数据向LCD写一次,那样无疑效率会很低,所以常常是开辟一个空间,这个空间里存有一帧的图像,就像显存一样,软件修改这个帧缓冲区,然后一次将所有变化都刷新向LCD。frambuffer就大致如此。
内核以此为中心,应该是模拟了一台虚拟总线,就像前文的input。
帧缓冲设备为标准的字符型设备,在Linux中主设备号29。次设备号定义帧缓冲的个数,最大允许有32个FrameBuffer。
4.怎么写LCD驱动程序?
1. 分配一个fb_info结构体: framebuffer_alloc
2. 设置
3. 注册: register_framebuffer
4. 硬件相关的操作
先去看那篇推荐文章S3C2440上LCD驱动(FrameBuffer)实例开发讲解
在devs.c中
/* LCD Controller */
#ifdef CONFIG_PLAT_S3C24XX
static struct resource s3c_lcd_resource[] = {
[0] = DEFINE_RES_MEM(S3C24XX_PA_LCD, S3C24XX_SZ_LCD),
[1] = DEFINE_RES_IRQ(IRQ_LCD),
};
struct platform_device s3c_device_lcd = {
.name = "s3c2410-lcd",
.id = -1,
.num_resources = ARRAY_SIZE(s3c_lcd_resource),
.resource = s3c_lcd_resource,
.dev = {
.dma_mask = &samsung_device_dma_mask,
.coherent_dma_mask = DMA_BIT_MASK(32),
}
};
void __init s3c24xx_fb_set_platdata(struct s3c2410fb_mach_info *pd)
{
struct s3c2410fb_mach_info *npd;
npd = s3c_set_platdata(pd, sizeof(*npd), &s3c_device_lcd);//s3c_device_lcd->dev.platform_data=pd
if (npd) {
npd->displays = kmemdup(pd->displays,
sizeof(struct s3c2410fb_display) * npd->num_displays, GFP_KERNEL);
if (!npd->displays)
printk(KERN_ERR "no memory for LCD display data\n");
} else {
printk(KERN_ERR "no memory for LCD platform data\n");
}
}
#endif /* CONFIG_PLAT_S3C24XX */
在mach-smdk2440.c中
/* LCD driver info */
这个结构体包含lcd设置的参数,需要根据实际,也就是你自己的LCD数据手册的实际参数设置
static struct s3c2410fb_display smdk2440_lcd_cfg __initdata = {
.lcdcon5 = S3C2410_LCDCON5_FRM565 |
S3C2410_LCDCON5_INVVLINE |
S3C2410_LCDCON5_INVVFRAME |
S3C2410_LCDCON5_PWREN |
S3C2410_LCDCON5_HWSWP,
.type = S3C2410_LCDCON1_TFT,
.width = 480,
.height = 272,
.pixclock = 1000000, //166667, /* HCLK 60 MHz, divisor 10 */
.xres = 480,
.yres = 272,
.bpp = 16,
.left_margin = 4, // Hsync front porch
.right_margin = 4, // Hsync back porch
.hsync_len = 41, // Hsync pulse width
.upper_margin = 2, // Vsync back porch
.lower_margin = 2, // Vsync front porch
.vsync_len = 10, // Vsync pulse width
};
其中.pixclock一项单独说一下
在内核的参考文档中有这样一段话:
The speed at which the electron beam paints the pixels is determined by the
dotclock in the graphics board. For a dotclock of e.g. 28.37516 MHz (millions
of cycles per second), each pixel is 35242 ps (picoseconds) long:
1/(28.37516E6 Hz) = 35.242E-9 s
也就是说VCLK的倒数,再乘10^12即为pixclk。picoseconds单位表示微微秒,即10^12。VCLK的值由具体LCD手册确定。
static struct s3c2410fb_mach_info smdk2440_fb_info __initdata = {
.displays = &smdk2440_lcd_cfg,
.num_displays = 1,
.default_display = 0,
#if 1
/* currently setup by downloader 根据自己的板子指定实际的数据引脚 */
.gpccon = 0xaaaaaaaa, /* PC全部用作LCD功能,VD[7:0], LCD_LPC VM VFRAME VLINE VCLK LEND */
.gpccon_mask = 0xffffffff,
.gpcup = 0xffff, /* 禁止GPC内部上拉 */
.gpcup_mask = 0x0000,
.gpdcon = 0xaaaaaaaa, /* VD[23:8] */
.gpdcon_mask = 0xffffffff,
.gpdup = 0xffff, /* 禁止GPD内部上拉 */
.gpdup_mask = 0x0000,
#endif
//.lpcsel = ((0xCE6) & ~7) | 1<<4,
};
static struct platform_device *smdk2440_devices[] __initdata = {
...
&s3c_device_lcd,
...
};
下面这步,就是把资源放在platform device里,并且打开背光灯
static void __init smdk2440_machine_init(void)
{
s3c24xx_fb_set_platdata(&smdk2440_fb_info); /* 触摸屏 */
s3c_i2c0_set_platdata(NULL);
...
/* jz2440 LCD BackLight */
writel((readl(S3C2410_GPBCON) & ~(3)) | 1, S3C2410_GPBCON); // 初始化背光控制引脚为输出
writel((readl(S3C2410_GPBDAT) | 1), S3C2410_GPBDAT); // 打开背光
}
修改 /etc/inittab
tty1::askfirst:-/bin/sh
用新内核重启开发板
ok
附上一个根据教程自己编写的LCD驱动,有兴趣可以和推荐文章对照着看,增加理解。lcd.c