首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> C++ 入門

ARM9 LCD 驅動開發全攻略:硬體架構、時序配置與 FrameBuffer 子系統實現

     編輯:C++ 入門

硬體基礎:S3C2440 LCD 控制器架構

LCD控制器框圖

  • REGBANK:17 個控制寄存器 + 256×16 調色板記憶體,配置所有參數。
  • LCDCDMA:專用 DMA 通道,從記憶體搬運影像資料。
  • TIMEGEN & LPC3600:產生 LCD 所需控制時序(VSYNC, HSYNC, VCLK, VDEN 等)。
  • VIDPRCS:資料格式轉換(調色板查表、RGB 排序),輸出至 VD[23:0]。

時序參數理解

垂直時序 水平時序

  • 垂直:VBPD (VSYNC 後延遲) → LINEVAL (有效行數) → VFPD (幀結束延遲) → VSPW (VSYNC 脈寬)。
  • 水平:HBPD (HSYNC 後延遲) → HOZVAL (有效列數) → HFPD (行結束延遲) → HSPW (HSYNC 脈寬)。
  • 所有參數單位為 VCLK/HSYNC 週期,需依 LCD 面板規格書設定。

Linux FrameBuffer 子系統架構

FrameBuffer架構

  • fbmem.c:核心層,提供 open/read/write/ioctl/mmap 等設備操作介面。
  • xxxfb.c:驅動層,實作硬體相關操作(fb_ops 結構體),由廠商/開發者編寫。

驅動開發四部曲

1. 分配 fb_info 結構體


struct fb_info *s3c_lcd = framebuffer_alloc(0, NULL);

2. 設置參數


// 固定參數
strcpy(s3c_lcd->fix.id, "mylcd");
s3c_lcd->fix.smem_len = 320*240*32/8;  // 顯存大小
s3c_lcd->fix.type = FB_TYPE_PACKED_PIXELS;
s3c_lcd->fix.visual = FB_VISUAL_TRUECOLOR;
s3c_lcd->fix.line_length = 240*4;  // 一行位元組數

// 可變參數
s3c_lcd->var.xres = 240; s3c_lcd->var.yres = 320;
s3c_lcd->var.xres_virtual = 240; s3c_lcd->var.yres_virtual = 320;
s3c_lcd->var.bits_per_pixel = 32;  // 實際 24bpp,但 2440 分配 32bit
s3c_lcd->var.red.offset = 16; s3c_lcd->var.red.length = 8;
s3c_lcd->var.green.offset = 8; s3c_lcd->var.green.length = 8;
s3c_lcd->var.blue.offset = 0; s3c_lcd->var.blue.length = 8;
s3c_lcd->var.activate = FB_ACTIVATE_NOW;

// 操作函數
s3c_lcd->fbops = &s3c_lcdfb_ops;
// 調色板
s3c_lcd->pseudo_palette = pseudo_palette;

3. 硬體相關操作

3.1 GPIO 配置

GPIO配置 GPBCON GPCCON


gpbcon = ioremap(0x56000010, 8); gpbdat = gpbcon+1;
gpccon = ioremap(0x56000020, 4);
gpdcon = ioremap(0x56000030, 4);
gpgcon = ioremap(0x56000060, 4);

*gpccon = 0xaaaaaaaa;  // VD[7:0], LCDVF[2:0], VM, VFRAME, VLINE, VCLK, LEND
*gpdcon = 0xaaaaaaaa;  // VD[23:8]
*gpgcon |= (3<<8);     // GPG4 = LCD_PWREN

0xaaaaaaaa = 二進位 1010...,將對應腳位設為 Function 2 (LCD 專用)。

3.2 LCD 控制器寄存器配置

建立寄存器結構體映射:


struct lcd_regs {
    unsigned long lcdcon1, lcdcon2, lcdcon3, lcdcon4, lcdcon5;
    unsigned long lcdsaddr1, lcdsaddr2, lcdsaddr3;
    unsigned long redlut, greenlut, bluelut;
    unsigned long reserved[9];
    unsigned long dithmode, tpal;
    unsigned long lcdintpnd, lcdsrcpnd, lcdintmsk, lpcsel;
};
static volatile struct lcd_regs* lcd_regs = ioremap(0x4D000000, sizeof(struct lcd_regs));
LCDCON1:時鐘與模式

// VCLK = HCLK / [(CLKVAL+1)×2] = 100MHz / [(7+1)×2] = 6.25MHz ≈ 6.39MHz (面板需求)
// MMODE=0, PNRMODE=11(TFT), BPPMODE=1101(24bpp), ENVID=0(稍後啟用)
lcd_regs->lcdcon1 = (7<<8) | (3<<5) | (0x0D<<1);
LCDCON2:垂直時序

// VBPD=2, LINEVAL=319(320行), VFPD=2, VSPW=0(脈寬1)
lcd_regs->lcdcon2 = (2<<24) | (319<<14) | (2<<6) | (0<<0);
LCDCON3:水平時序

// HBPD=20, HOZVAL=239(240列), HFPD=10
lcd_regs->lcdcon3 = (20<<19) | (239<<8) | (10<<0);
LCDCON4:HSYNC 脈寬

// HSPW=10
lcd_regs->lcdcon4 = 10;
LCDCON5:極性與格式

// HSYNC/VDEN 低電平有效,PWREN=0,LSB valid(高位字節不用)
lcd_regs->lcdcon5 = (0<<10) | (1<<9) | (1<<8) | (0<<12) | (0<<1) | (0<<0);

3.3 分配顯存並設定 DMA 位址


// 分配 DMA 可寫合併記憶體
s3c_lcd->screen_base = dma_alloc_writecombine(NULL, s3c_lcd->fix.smem_len, 
                                                &s3c_lcd->fix.smem_start, GFP_KERNEL);

// 設定 LCD DMA 起始/結束/行長度位址(單位:半字=2bytes)
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 = (240*32/16);  // 一行長度(半字)

3.4 啟動 LCD


lcd_regs->lcdcon1 |= (1<<0);  // ENVID=1 啟用控制器
lcd_regs->lcdcon5 |= (1<<3);  // PWREN=1 啟用面板電源

4. 註冊 FrameBuffer


register_framebuffer(s3c_lcd);

完整驅動代碼


#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

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, lcdcon2, lcdcon3, lcdcon4, lcdcon5;
    unsigned long lcdsaddr1, lcdsaddr2, lcdsaddr3;
    unsigned long redlut, greenlut, bluelut;
    unsigned long reserved[9];
    unsigned long dithmode, tpal;
    unsigned long lcdintpnd, lcdsrcpnd, lcdintmsk, 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, *gpbdat, *gpccon, *gpdcon, *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) {
    if (regno > 16) return 1;
    unsigned int val = 0;
    val |= chan_to_field(red, &info->var.red);
    val |= chan_to_field(green, &info->var.green);
    val |= chan_to_field(blue, &info->var.blue);
    pseudo_palette[regno] = val;
    return 0;
}

static int __init lcd_init(void) {
    s3c_lcd = framebuffer_alloc(0, NULL);
    // ... 設置 fix, var, fbops, pseudo_palette ...
    
    // GPIO 映射與配置
    gpbcon = ioremap(0x56000010, 8); gpbdat = gpbcon+1;
    gpccon = ioremap(0x56000020, 4);
    gpdcon = ioremap(0x56000030, 4);
    gpgcon = ioremap(0x56000060, 4);
    *gpccon = 0xaaaaaaaa; *gpdcon = 0xaaaaaaaa; *gpgcon |= (3<<8);
    
    // LCD 控制器映射與配置
    lcd_regs = ioremap(0x4D000000, sizeof(struct lcd_regs));
    lcd_regs->lcdcon1 = (7<<8) | (3<<5) | (0x0D<<1);
    lcd_regs->lcdcon2 = (2<<24) | (319<<14) | (2<<6) | 0;
    lcd_regs->lcdcon3 = (20<<19) | (239<<8) | 10;
    lcd_regs->lcdcon4 = 10;
    lcd_regs->lcdcon5 = (1<<9) | (1<<8);
    
    // 分配顯存
    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 = (240*32/16);
    
    // 啟動
    lcd_regs->lcdcon1 |= 1; lcd_regs->lcdcon5 |= (1<<3);
    
    register_framebuffer(s3c_lcd);
    return 0;
}

static void __exit lcd_exit(void) {
    unregister_framebuffer(s3c_lcd);
    lcd_regs->lcdcon1 &= ~1; lcd_regs->lcdcon5 &= ~(1<<3);
    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");

關鍵技巧總結

  1. 時序參數對照面板規格書:VBPD/VFPD/VSPW/HBPD/HFPD/HSPW 必須精確匹配。
  2. 位寬處理:24bpp 時 S3C2440 實際分配 32bit/像素,LCDCON5 的 BPP24BL、BSWP、HWSWP 決定高位字節位置。
  3. DMA 位址計算:LCDSADDR1/2/3 以半字(2bytes)為單位,物理位址右移 1 位。
  4. GPIO 多功能腳位:查資料手冊確認每組腳位的 Function 選擇值(通常 0xAAAAAAAA 為 LCD 專用)。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved