一、 簡介:
要使一塊LCD正常的顯示文字或圖像,不僅需要LCD驅動器,而且還需要相應的LCD控制器。在通常情況下,生産廠商把LCD驅動器會以COF/COG的形式與LCD玻璃基闆制作在一起,而LCD控制器則是由外部的電路來實作,現在很多的MCU内部都內建了LCD控制器,如S3C2410/2440等。通過LCD控制器就可以産生LCD驅動器所需要的控制信号來控制STN/TFT屏了。
二、需要知道的幾點:
1. S3C2440A内部的 LCD 控制器的框圖如下
根據資料手冊來描述一下:
a:LCD控制器由REGBANK、LCDCDMA、TIMEGEN、VIDPRCS寄存器組成;
b:REGBANK由17個可程式設計的寄存器組和一塊256*16的調色闆記憶體組成,它們用來配置LCD控制器的;
c:LCDCDMA是一個專用的DMA,它能自動地把在偵記憶體中的視訊資料傳送到LCD驅動器,通過使用這個DMA通道,視訊資料在不需要CPU的幹預的情況下顯示在LCD屏上;
d:VIDPRCS接收來自LCDCDMA的資料,将資料轉換為合适的資料格式,比如說4/8位單掃,4位雙掃顯示模式,然後通過資料端口VD[23:0]傳送視訊資料到LCD驅動器;
e:TIMEGEN由可程式設計的邏輯組成,他生成LCD驅動器需要的控制信号,比如VSYNC、HSYNC、VCLK和LEND等等,而這些控制信号又與REGBANK寄存器組中的LCDCON1/2/3/4/5的配置密切相關,通過不同的配置,TIMEGEN就能産生這些信号的不同形态,進而支援不同的LCD驅動器(即不同的STN/TFT屏)。
2. 顯示器顯示的原理:
LCD側提供的外部接口信号:
VSYNC/VFRAME/STV:垂直同步信号(TFT)/幀同步信号(STN)/SEC TFT信号;
HSYNC/VLINE/CPV:水準同步信号(TFT)/行同步脈沖信号(STN)/SEC TFT信号;
VCLK/LCD_HCLK:象素時鐘信号(TFT/STN)/SEC TFT信号;
VD[23:0]:LCD像素資料輸出端口(TFT/STN/SEC TFT);
VDEN/VM/TP:資料使能信号(TFT)/LCD驅動交流偏置信号(STN)/SEC TFT 信号;
LEND/STH:行結束信号(TFT)/SEC TFT信号;
LCD_LPCOE:SEC TFT OE信号;
LCD_LPCREV:SEC TFT REV信号;
LCD_LPCREVB:SEC TFT REVB信号。
所有顯示器顯示圖像的原理都是從上到下,從左到右的。即,一副圖像可以看做是一個矩形,由很多排列整齊的點一行一行組成,這些點稱之為像素。那麼這幅圖在LCD上的顯示原理就是:
A:顯示指針從矩形左上角的第一行第一個點開始,一個點一個點的在LCD上顯示,在上面的時序圖上用時間線表示就為VCLK,我們稱之為像素時鐘信号;
B:當顯示指針一直顯示到矩形的右邊就結束這一行,那麼這一行的動作在上面的時序圖中就稱之為1 Line;
C:接下來顯示指針又回到矩形的左邊從第二行開始顯示,注意,顯示指針在從第一行的右邊回到第二行的左邊是需要一定的時間的,我們稱之為行切換;
D:如此類推,顯示指針就這樣一行一行的顯示至矩形的右下角才把一副圖顯示完成。是以,這一行一行的顯示在時間線上看,就是時序圖上的HSYNC;
E:然而,LCD的顯示并不是對一副圖像快速的顯示一下,為了持續和穩定的在LCD上顯示,就需要切換到另一幅圖上(另一幅圖可以和上一副圖一樣或者不一樣,目的隻是為了将圖像持續的顯示在LCD上)。那麼這一副一副的圖像就稱之為幀,在時序圖上就表示為1 Frame,是以從時序圖上可以看出1 Line隻是1 Frame中的一行;
F:同樣的,在幀與幀切換之間也是需要一定的時間的,我們稱之為幀切換,那麼LCD整個顯示的過程在時間線上看,就可表示為時序圖上的VSYNC。
上面時序圖上各時鐘延時參數的含義如下:(這些參數的值,LCD産生廠商會提供相應的資料手冊)
VBPD(vertical back porch):表示在一幀圖像開始時,垂直同步信号以後的無效的行數,對應驅動中的upper_margin;
VFBD(vertical front porch):表示在一幀圖像結束後,垂直同步信号以前的無效的行數,對應驅動中的lower_margin;
VSPW(vertical sync pulse width):表示垂直同步脈沖的寬度,用行數計算,對應驅動中的vsync_len;
HBPD(horizontal back porch):表示從水準同步信号開始到一行的有效資料開始之間的VCLK的個數,對應驅動中的left_margin;
HFPD(horizontal front porth):表示一行的有效資料結束到下一個水準同步信号開始之間的VCLK的個數,對應驅動中的right_margin;
HSPW(horizontal sync pulse width):表示水準同步信号的寬度,用VCLK計算,對應驅動中的hsync_len;
以上這些參數的值将分别儲存到REGBANK寄存器組中的LCDCON1/2/3/4/5寄存器中
3. LCD 控制器中的幀緩沖(FrameBuffer)
采用16BPP 顔色模式的 RGB 在位元組中的順序:
4. 調色闆(Palette) / 顔色查找表LUT(Look Up Table)
調色闆隻有圖檔的顔色小于等于256色的時候才有,16位高彩和24位32位真彩是沒有調色闆的.
調色闆的存在的意義隻是在當初486以前為了節省空間的一種采用索引的壓縮算法,現在沒有人這種東西。
調色闆是為了節約空間所用的,相當于一個索引。隻有16位以下的才用調色闆,真彩色不用調色闆。
調色闆實際上是一個有256個表項的RGB顔色表,顔色表的每項是一個24位的RGB顔色值。使用調色闆時,在視訊記憶體中存儲的不是的24位顔色值,而是調色闆的4位或8位的索引。
三、linux 源代碼分析
1. 幀緩沖裝置驅動在linux子系統中的結構如下:
圖中可以看到,fbmem.c是通用的部分,xxxfb.c是具體到某個晶片的部分。自己分析代碼的時候可以檢視s3c2410fb.c
幾個特别的結構體:
struct fb_info 結構體
struct fb_info {
...
struct fb_var_screeninfo var; /* 可變參數 */
struct fb_fix_screeninfo fix; /* 固定參數 */
...
struct fb_ops *fbops; /* 新裝置提供的操作方法 */
...
char __iomem *screen_base; /* 顯存虛拟位址 */
unsigned long screen_size; /* 虛拟位址空間大小 */
void *pseudo_palette; /* 假的16位調色闆 */
...
}
fbmem.c 代碼設計方式同輸入子系統差不多,這裡不再介紹,隻是總結作為特定晶片的設定步驟,即,xxxfb.c的思路:
子產品入口函數中:
1)配置設定一個fb_info結構體
2)設定fb_info
2.1)設定固定的參數fb_info-> fix
2.2) 設定可變的參數fb_info-> var
2.3) 設定操作函數fb_info-> fbops
2.4) 設定fb_info 其它的成員
3)設定硬體相關的操作
3.1)配置LCD引腳
3.2)根據LCD手冊設定LCD控制器
3.3)配置設定顯存(framebuffer),把位址告訴LCD控制器和fb_info
4)開啟LCD,并注冊fb_info: register_framebuffer()
4.1) 直接在init函數中開啟LCD(可優化)
控制LCDCON5允許PWREN信号,
然後控制LCDCON1輸出PWREN信号,
輸出GPB0高電平來開背光,
4.2) 注冊fb_info
在驅動exit出口函數中:
1)解除安裝核心中的fb_info
2) 控制LCDCON1關閉PWREN信号,關背光,iounmap登出位址
3)釋放DMA緩存位址dma_free_writecombine()
4)釋放注冊的fb_info
四、執行個體:
我的開發闆子是jz2440,晶片型号是S3C2440A,螢幕是4.3寸的螢幕
lcd.c
#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>
#include <mach/regs-lcd.h>
/*#include <asm/arch/regs-gpio.h>*/
#include <mach/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_ops s3c_lcdfb_ops = {
.owner = THIS_MODULE,
.fb_setcolreg = s3c_lcdfb_setcolreg,
.fb_fillrect = cfb_fillrect,
.fb_copyarea = cfb_copyarea,
.fb_imageblit = cfb_imageblit,
};
static struct fb_info *s3c_lcd;
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]; // 僞調色闆
/* from pxafb.c */
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;
}
static int lcd_init(void)
{
/* 1. 配置設定一個fb_info */
s3c_lcd = framebuffer_alloc(0, NULL);
/* 2. 設定 */
/* 2.1 設定固定的參數 */
strcpy(s3c_lcd->fix.id, "mylcd");
s3c_lcd->fix.smem_len = 480*272*16/8;
s3c_lcd->fix.type = FB_TYPE_PACKED_PIXELS;
s3c_lcd->fix.visual = FB_VISUAL_TRUECOLOR; /* TFT */
s3c_lcd->fix.line_length = 480*2;
/* 2.2 設定可變的參數 */
s3c_lcd->var.xres = 480;
s3c_lcd->var.yres = 272;
s3c_lcd->var.xres_virtual = 480;
s3c_lcd->var.yres_virtual = 272;
s3c_lcd->var.bits_per_pixel = 16;
/* RGB:565 */
s3c_lcd->var.red.offset = 11;
s3c_lcd->var.red.length = 5;
s3c_lcd->var.green.offset = 5;
s3c_lcd->var.green.length = 6;
s3c_lcd->var.blue.offset = 0;
s3c_lcd->var.blue.length = 5;
s3c_lcd->var.activate = FB_ACTIVATE_NOW;
/* 2.3 設定操作函數 */
s3c_lcd->fbops = &s3c_lcdfb_ops;
/* 2.4 其他的設定 */
s3c_lcd->pseudo_palette = pseudo_palette;
/*s3c_lcd->screen_base = ;*/ /* 顯存的虛拟位址 */
s3c_lcd->screen_size = 480*272*16/8;
/* 3. 硬體相關的操作 */
/* 3.1 配置GPIO用于LCD */
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 */
/* 3.2 根據LCD手冊設定LCD控制器, 比如VCLK的頻率等 */
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);
#if 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);
/* 水準方向的時間參數
* 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);
/* 水準方向的同步信号
* * bit[7:0] : HSPW, HSYNC信号的脈沖寬度, LCD手冊T7=5, 是以HSPW=5-1=4
* */
lcd_regs->lcdcon4 = 40;
#else
lcd_regs->lcdcon2 = S3C2410_LCDCON2_VBPD(5) | \
S3C2410_LCDCON2_LINEVAL(319) | \
S3C2410_LCDCON2_VFPD(3) | \
S3C2410_LCDCON2_VSPW(1);
lcd_regs->lcdcon3 = S3C2410_LCDCON3_HBPD(10) | \
S3C2410_LCDCON3_HOZVAL(239) | \
S3C2410_LCDCON3_HFPD(1);
lcd_regs->lcdcon4 = S3C2410_LCDCON4_MVAL(13) | \
S3C2410_LCDCON4_HSPW(0);
#endif
/* 信号的極性
* 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控制器 */
s3c_lcd->screen_base = dma_alloc_writecombine(NULL, s3c_lcd->fix.smem_len, &s3c_lcd->fix.smem_start, GFP_KERNEL);
lcd_regs->lcdsaddr1 = (s3c_lcd->fix.smem_start >> 1) & ~(3<<30);
lcd_regs->lcdsaddr2 = ((s3c_lcd->fix.smem_start + s3c_lcd->fix.smem_len) >> 1) & 0x1fffff;
lcd_regs->lcdsaddr3 = (480*16/16); /* 一行的長度(機關: 2位元組) */
/*s3c_lcd->fix.smem_start = xxx;*/ /* 顯存的實體位址 */
/* 啟動LCD */
lcd_regs->lcdcon1 |= (1<<0); /* 使能LCD控制器 */
lcd_regs->lcdcon5 |= (1<<3); /* 使能LCD本身 */
*gpbdat |= 1; /* 輸出高電平, 使能背光 */
/* 4. 注冊 */
register_framebuffer(s3c_lcd);
return 0;
}
static void lcd_exit(void)
{
unregister_framebuffer(s3c_lcd);
lcd_regs->lcdcon1 &= ~(1<<0); /* 關閉LCD本身 */
*gpbdat &= ~1; /* 關閉背光 */
dma_free_writecombine(NULL, s3c_lcd->fix.smem_len, s3c_lcd->screen_base, s3c_lcd->fix.smem_start);
iounmap(lcd_regs);
iounmap(gpbcon);
iounmap(gpccon);
iounmap(gpdcon);
iounmap(gpgcon);
framebuffer_release(s3c_lcd);
}
module_init(lcd_init);
module_exit(lcd_exit);
MODULE_LICENSE("GPL");
Makefile
KERN_DIR = /work/system/linux-2.6.22.6
all:
make -C $(KERN_DIR) M=`pwd` modules
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
obj-m += lcd.o
其中
dma_alloc_writecombine 函數傳回實體位址給 s3c_lcd->fix.smem_start,傳回虛拟位址給 s3c_lcd->screen_base
給了一個例子,例子中為什麼要向右移動1位呢?
原因是用16位表示一個像素導緻。
測試方法:
重新配置核心,去掉預設的 LCD
不讓 S3C2410 編譯進核心,配置成子產品形式,通過make modules方式生成子產品。
Device Drivers-> Graphics support:
<M> S3C2410 LCD framebuffer support
将上邊的例子,交叉編譯,會有個 lcd.ko,在 lcd.ko 中用到了上邊的配置成子產品生成的子產品:
位置在 [核心目錄]/drivers/video 下。
ls *.ko
cfbcopyarea.ko cfbfillrect.ko cfbimgblt.ko s3c2410fb.ko
我們需要前三個,連同 lcd.ko 一起發送到闆子上,在闆子上加載上所有這些,最後加載 lcd.ko
此時,我們就能看到螢幕亮起來了,
可以用如下的方式将資料發送到 lcd 上:
echo hello> /dev/tty1 // LCD上便顯示hello字段
cat Makefile>/dev/tty1 // LCD上便顯示Makeflie檔案的内容
或者在 /etc/inittab 中添加
tty1::askfirst:-/bin/sh //将sh程序(指令行)輸出到tty1裡,也就是使LCD輸出資訊
然後重新開機,将子產品全部加載便能看到