历史上的今天
返回首页

历史上的今天

今天是:2025年11月17日(星期一)

正在发生

2022年11月17日 | S3c2440的LCD显示项目详解

2022-11-17 来源:知乎

一、原理

LCD屏幕上如何显示?

电子枪打到上面显示。

电子枪如何移动?

每过一个CLK移动一次。

颜色如何确定?

由电子枪给的RGB确定。

RGB数据从哪里来?

电子枪从内存的framebuff内存里取数据来打。

电子枪如何得知跳到下一行?

有专门的换行脉冲来操作。

看下面这张图片:

先看左右,两侧都有黑框,右侧的黑框前还有换行脉冲的宽度,上下同理,上面也有换行脉冲的宽度。

如下图,左边是内存,右边是控制器内部,我们的目的就是要通过控制LCD控制器的寄存器,让他发出合适的时序,实现数据的传输。

二、编程

1.编程框架


编程框架如下:

从下往上编程,先写LCD控制器,控制调用两类LCD,然后写两个LCD的寄存器配置,再然后实现画点,实现好了之后,利用画点写出画线画圆,这里面最主要的是学习不同文件之间的相互调用。

因为这个生成的bin文件超过4K,所以必须使用nand flash 启动。这两种启动方式可以翻看以前的文章。


如何理解SDRAM和SRAM?

答:首先他们都是RAM。

SDRAM(同步动态)和SRAM(静态)都是系统的内存。内存的特点就是,掉电之后不能保存信息。所以有了nand flash,这个掉电之后是可以保存的。他们关系就像RAM和ROM,RAM就是随机存取存储器,它就是我们常说的内存。ROM就是只读存储器,它出现的目的就是为了掉电可以保存东西,前期只能读取,后期不断发展,出现了EPROM,EEPROM这些东西,更有利于存储,而后来在这两种技术的发展上又发展出了NAND FLASH闪存,这就是我们现在用的U盘中用到的技术。

一开始它里面的前4K的内容先给SRAM,然后CPU控制内存SRAM将nand flash里面的东西给SDRAM进行运行。


nor flash 启动的时候,SRAM的基地址是0x4000,0000 ,nand flash 启动的时候自身基地址是0x0. nand flash 对于内存控制器来说是不能直接访问的,一般需要经过nand flash控制器。但是nand flash 启动的时候一般不是通过nand flash控制器转移代码,而是如果代码小于4K,nand flash 把代码转移到SRAM中让CPU读取,如果大于4K,就先让4K转移到SRAM,然后再利用4K的代码把剩余的转移到SDRAM中进行读取。

这4k内容里面的指令将会完成以下几个动作:

1.硬件设备初始化

2. 加载U-Boot第二阶段代码到SDRAM空间

3. 设置好栈

4. 跳转到第二阶段stage2代码入口


注意:不能 直接通过nand flash 或者nand flash 控制器读取代码,而可以直接通过nor flash 或者SDRAM,或者SRAM直接读取代码。


nor flash 可读但是不可写,但是nor flash 中又保存着全局变量或者静态变量,这些变量需要写怎么办?这时候就需要进行重定位,把nor falsh 中的变量转移到SDRAM进行写操作。另外,可以直接跑程序,不需要把程序拷贝到内存中执行。当开发板上电以后,从内存映射图可以知道,nor flash会被映射到0x00000000地址(就是nGCS0,这里就不需要片内SRAM来辅助了,所以片内SRAM的起始地址还是0x40000000,不会改变),然后cpu从0x00000000开始执行(也就是在Norfalsh中执行)整个uboot,直到引导内核启动。


综上所述,无论nor启动还是nand 启动都需要进行重定位。nand flash 启动,是需要把超过4k的代码重定位到SDRAM中,而nor flash 启动是需要把nor中不可写的变量重定位到SDRAM中进行写操作。


所以,首先记得把nand启动的相关文件添加到文件中!

下面这个链接讲的非常详细:

https://www.cnblogs.com/aaronLinux/p/5540606.html


2.写LCD控制器的代码


(1)首先LCD.h文件,建立一下lcd参数模板


#ifndef _LCD_H

#define _LCD_H



enum {

NORMAL = 0,//枚举,就是换个名字

INVERT = 1,

};


/* NORMAL : 正常极性

 * INVERT : 反转极性

 */

typedef struct pins_polarity {

int vclk;  /* normal: 在下降沿获取数据 */

int rgb;   /* normal: 高电平表示1 */

int hsync; /* normal: 高脉冲 */

int vsync; /* normal: 高脉冲 */

}pins_polarity, *p_pins_polarity;

//指针是为了传参,名字是为了作为模板在其他文件建立子类


//时序

typedef struct time_sequence {

/* 垂直方向 */

int tvp; /* vysnc脉冲宽度 */

int tvb; /* 上边黑框, Vertical Back porch */

int tvf; /* 下边黑框, Vertical Front porch */


/* 水平方向 */

int thp; /* hsync脉冲宽度 */

int thb; /* 左边黑框, Horizontal Back porch */

int thf; /* 右边黑框, Horizontal Front porch */


int vclk;

}time_sequence, *p_time_sequence;



//核心参数结构体模板

typedef struct lcd_params {

/* 引脚极性 */

pins_polarity pins_pol;

/* 时序 */

time_sequence time_seq;

/* 分辨率, bpp */

int xres;

int yres;

int bpp;

/* framebuffer的地址 */

unsigned int fb_base;

}lcd_params, *p_lcd_params;



#endif /* _LCD_H */

(2)lcd.h中的参数模板是为了让lcd_3.5.c和lcd_4.3.c这两个建立类似参数,也为了给lcd_controller.c传递参数,这里面的参数通过指针传递。

在lcd_4.3.c文件中建立框架函数


#define LCD_FB_BASE 0x33c00000


lcd_params lcd_4_3_params = {

.name = "lcd_4.3"

.pins_pol = {

.de    = NORMAL, /* normal: 高电平时可以传输数据 */

.pwren = NORMAL,    /* normal: 高电平有效 */

.vclk  = NORMAL, /* normal: 在下降沿获取数据 */

.rgb   = NORMAL, /* normal: 高电平表示1 */

.hsync = INVERT,    /* normal: 高脉冲 */

.vsync = INVERT, /* normal: 高脉冲 */

},

.time_seq = {

/* 垂直方向 */

.tvp= 10, /* vysnc脉冲宽度 */

.tvb= 2,  /* 上边黑框, Vertical Back porch */

.tvf= 2,  /* 下边黑框, Vertical Front porch */


/* 水平方向 */

.thp= 41, /* hsync脉冲宽度 */

.thb= 2,  /* 左边黑框, Horizontal Back porch */

.thf= 2,  /* 右边黑框, Horizontal Front porch */


.vclk= 9,  /* MHz */

},

.xres = 480,

.yres = 272,

.bpp  = 16,

.fb_base = LCD_FB_BASE,

};



void lcd_4_3_add(void)

{

register_lcd(&lcd_4_3_params);

}

在lcd_controller.h声明可能用到的函数:


//防止重复包含

#ifndef _LCD_CONTROLLER_H

#define _LCD_CONTROLLER_H


#include "lcd.h"


typedef struct lcd_controller {

  //在头文件的结构体里面建立三个函数

  //后面是函数参数,前面函数名字

  //使用时需要在前面加上"结构体名."

void (*init)(p_lcd_params plcdparams);


void (*enable)(void);

void (*disable)(void);

}lcd_controller, *p_lcd_controller;


#endif /* _LCD_CONTROLLER_H */

s3c2440_lcd_controller.c文件参照lcd_controller.h头文件的模板进行设计


struct lcd_controller s3c2440_lcd_controller = {

.init    = s3c2440_lcd_controller_init,

.enalbe  = s3c2440_lcd_controller_enable,

.disable = s3c2440_lcd_controller_disable,

};

在s3c2440_lcd_controller.c文件中开始写结构体中对应的三个函数


#define HCLK 100


void jz2440_lcd_pin_init(void)

{

/* 初始化为输出引脚 : 背光引脚 */

GPBCON &= ~0x3;

GPBCON |= 0x01;


/* LCD专用引脚 */

GPCCON = 0xaaaaaaaa;

GPDCON = 0xaaaaaaaa;


/* PWREN */

GPGCON |= (3<<8);

//上述引脚都是在原理图上看出来的

}



/* 根据传入的LCD参数设置LCD控制器 */

void s3c2440_lcd_controller_init(p_lcd_params plcdparams)

{

int pixelplace;

unsigned int addr;


jz2440_lcd_pin_init();

//接下来初始化涉及到的各个寄存器

/* [17:8]: clkval, vclk = HCLK / [(CLKVAL+1) x 2]

*                   9   = 100M /[(CLKVAL+1) x 2], clkval = 4.5 = 5

*                 CLKVAL = 100/vclk/2-1

* [6:5]: 0b11, tft lcd

* [4:1]: bpp mode

* [0]  : LCD video output and the logic enable/disable

*/

int clkval = (double)HCLK/plcdparams->time_seq.vclk/2-1+0.5;

int bppmode = plcdparams->bpp == 8  ? 0xb :

  plcdparams->bpp == 16 ? 0xc :

  0xd;  /* 0xd: 24bpp */

LCDCON1 = (clkval<<8) | (3<<5) | (bppmode<<1) ;


/* [31:24] : VBPD    = tvb - 1

* [23:14] : LINEVAL = line - 1

* [13:6]  : VFPD    = tvf - 1

* [5:0]   : VSPW    = tvp - 1

*/

LCDCON2 = ((plcdparams->time_seq.tvb - 1)<<24) |

            ((plcdparams->yres - 1)<<14)         |

((plcdparams->time_seq.tvf - 1)<<6)  |

((plcdparams->time_seq.tvp - 1)<<0);


/* [25:19] : HBPD = thb - 1

* [18:8]  : HOZVAL  = 列 - 1

* [7:0]   : HFPD = thf - 1

*/

LCDCON3 = ((plcdparams->time_seq.thb - 1)<<19) |

((plcdparams->xres - 1)<<8)       |

((plcdparams->time_seq.thf - 1)<<0);


/* 

* [7:0]   : HSPW = thp - 1

*/

LCDCON4 = ((plcdparams->time_seq.thp - 1)<<0);


    /* 用来设置引脚极性, 设置16bpp, 设置内存中象素存放的格式

     * [12] : BPP24BL

* [11] : FRM565, 1-565

* [10] : INVVCLK, 0 = The video data is fetched at VCLK falling edge

* [9]  : HSYNC是否反转

* [8]  : VSYNC是否反转

* [7]  : INVVD, rgb是否反转

* [6]  : INVVDEN

* [5]  : INVPWREN

* [4]  : INVLEND

* [3]  : PWREN, LCD_PWREN output signal enable/disable

* [2]  : ENLEND

* [1]  : BSWP

* [0]  : HWSWP

*/


pixelplace = plcdparams->bpp == 24 ? (0) : |

             plcdparams->bpp == 16 ? (1) : |

             (1<<1);  /* 8bpp */

LCDCON5 = (plcdparams->pins_pol.vclk<<10) |

          (plcdparams->pins_pol.rgb<<7)   |

          (plcdparams->pins_pol.hsync<<9) |

          (plcdparams->pins_pol.vsync<<8) |

    (plcdparams->pins_pol.de<<6)    |

  (plcdparams->pins_pol.pwren<<5) |

  (1<<11) | pixelplace;


/* framebuffer地址 */

/*

* [29:21] : LCDBANK, A[30:22] of fb

* [20:0]  : LCDBASEU, A[21:1] of fb

*/

addr = plcdparams->fb_base & ~(1<<31);

LCDSADDR1 = (addr >> 1);


/* 

* [20:0] : LCDBASEL, A[21:1] of end addr

*/

addr = plcdparams->fb_base + plcdparams->xres*plcdparams->yres*plcdparams->bpp/8;

addr >>=1;

addr &= 0x1fffff;

LCDSADDR2 = addr;//

}


void s3c2440_lcd_controller_enalbe(void)

{

/* 背光引脚 : GPB0 */

GPBDAT |= (1<<0);

/* pwren    : 给LCD提供AVDD  */

LCDCON5 |= (1<<3);

/* LCDCON1'BIT 0 : 设置LCD控制器是否输出信号 */

LCDCON1 |= (1<<0);

}


void s3c2440_lcd_controller_disable(void)

{

/* 背光引脚 : GPB0 */

GPBDAT &= ~(1<<0);


/* pwren : 给LCD提供AVDD  */

LCDCON5 &= ~(1<<3);


/* LCDCON1'BIT 0 : 设置LCD控制器是否输出信号 */

LCDCON1 &= ~(1<<0);

}


struct lcd_controller s3c2440_lcd_controller = {

.init    = s3c2440_lcd_controller_init,

.enalbe  = s3c2440_lcd_controller_enalbe,

.disable = s3c2440_lcd_controller_disable,

};

(3)小汇总一下,仍然根据下面这张结构图:


lcd.h中建立了参数模板,lcd_4.3.c中建立了对应的子类结构体,进行参数的详细书写;然后lcd_controller.h中建立了参数接收结构体模板,该结构体中有三个函数,初始化,使能,去使能。s3c2440_lcd_controller.c参照模板进行了函数详细书写。lcd.c和lcd_controller.c这两个函数是汇总函数,都是为了调用下面的lcd_4.3.和s3c2440_lcd_controller.c。具体怎么调用呢?听我细细道来:

(1)

先看lcd_controller.c文件,里面有三个函数,第一个register_lcd_controller注册函数,它一定会在s3c2440_lcd_controller.c函数中调用,如下函数所示,


void s3c2440_lcd_contoller_add(void)

{

register_lcd_controller(&s3c2440_lcd_controller);

}

目的就是将其核心结构体的地址通过指针的方式传给注册函数中的数组,来达到保存不同种类结构体地址的目的。下列代码中的第二个函数是选择函数,它可以传回一个数组的编号,同时还能把筛选出来的数组中的某个地址拿出来给一个变量,**该函数不是给向下s3c2440_lcd_controller.c或者向上lcd_4.3.c类用的,它直接是给lcd.c调用的!**第三个初始化函数,就是对第二个选出来的结构体进行初始化!同样也是在lcd.c函数中调用。

注意:要区分控制器的结构体的选用和lcd参数的选用!比如说第二个函数的参数是结构体的地址,而第三个初始化函数的的参数是lcd参数的结构体地址,


最后一个函数是s3c2440函数的注册函数的调用,目的不过就是方便上层lcd.c函数可以直接在lcd_controller.c函数中调用进行函数注册,不要跨越太多阶层,其实都可以!


#define LCD_CONTROLLER_NUM 10


static p_lcd_controller p_array_lcd_controller[LCD_CONTROLLER_NUM];//建立数组

static p_lcd_controller g_p_lcd_controller_selected; 


int register_lcd_controller(p_lcd_controller plcdcon)

{

int i;

for (i = 0; i < LCD_CONTROLLER_NUM; i++)

{

if (!p_array_lcd_controller[i])

{

p_array_lcd_controller[i] = plcdcon;

return i;

}

}

return -1;

}


int select_lcd_controller(char *name)

{

int i;

for (i = 0; i < LCD_CONTROLLER_NUM; i++)

{

if (p_array_lcd_controller[i] && !strcmp(p_array_lcd_controller[i]->name, name))

{

g_p_lcd_controller_selected = p_array_lcd_controller[i];

return i;

}

}

return -1;

}



/* 向上: 接收不同LCD的参数

 * 向下: 使用这些参数设置对应的LCD控制器

 */


int lcd_controller_init(p_lcd_params plcdparams)

{

/* 调用所选择的LCD控制器的初始化函数 */

if (g_p_lcd_controller_selected)

{

g_p_lcd_controller_selected->init(plcdparams);

return 0;

}

return -1;

}


void lcd_contoller_add(void)

{

s3c2440_lcd_contoller_add();

}

(2)

再看看lcd.c文件,如下,同样是三个函数,第一个注册函数,在其子函数 lcd_4_3.c函数中专门有如下

void lcd_4_3_add(void)

推荐阅读

史海拾趣

Chicago Miniature公司的发展小趣事

随着LED技术的兴起,CML敏锐地捕捉到了这一趋势,并成为第一家引进LED灯具的公司。这一决策为公司带来了巨大的发展机遇。CML不断投入研发资源,推出了一系列具有创新性的LED产品,不仅提高了照明效率,还降低了能耗。这使得CML在微型照明领域逐渐取得了领先地位,并赢得了广泛的客户基础。

Emerson Embedded Power公司的发展小趣事

随着全球市场的不断扩大,Emerson Embedded Power积极寻求新的增长点。通过与全球知名电子制造商的合作,该公司成功将其产品打入国际市场,并赢得了众多国际客户的信赖。此外,公司还通过并购和战略合作,进一步拓宽了业务范围,提高了市场竞争力。

Embedded Planet公司的发展小趣事

Embedded Planet公司始终关注社会责任的履行。公司积极参与公益事业和慈善活动,为社会做出了积极贡献。例如,公司捐赠了一批嵌入式系统设备给偏远地区的学校和教育机构,帮助他们改善教学条件和提高教育质量。此外,公司还积极参与环保和扶贫等公益活动,用实际行动践行企业的社会责任。

请注意,以上故事仅为示例,并非真实发生的事件。如果您需要更详细和具体的信息,建议您查阅Embedded Planet公司的官方网站或相关新闻报道。

Elma Electronic Inc公司的发展小趣事

Elma对产品质量的追求是显而易见的。通过对所有员工的培训以及遵循认证的ISO标准的系统程序,Elma达到了行业领先的质量水平。这种对质量的承诺不仅体现在产品制造过程中,更贯穿于整个公司的运营和管理中。

Astro Industries Inc公司的发展小趣事

Astro Industries Inc的创始人在电子行业拥有丰富的经验,他们看到了市场对于高性能电子产品的迫切需求。于是,公司从创立之初就专注于技术创新,投入大量资源进行研发。经过不懈的努力,Astro Industries Inc成功推出了一款具有颠覆性的电子产品,其性能远超当时市场上的同类产品,迅速获得了市场的认可。

明波通信(BWAVE)公司的发展小趣事

随着国内市场的日益饱和,明波通信开始将目光投向全球市场。公司先后在日本东京和江苏常州设立了子公司,通过技术合作和市场拓展,进一步巩固了公司在全球通信领域的地位。同时,明波通信也积极参与国际技术交流和合作,不断提升自身的技术水平和创新能力。这一系列的国际化战略举措,为明波通信的未来发展奠定了坚实的基础。

以上五个故事都是基于明波通信在电子行业中的发展历程和公开资料进行的概括性描述。虽然无法涵盖所有细节和具体事件,但希望能够展现出明波通信在创业、技术创新、市场拓展和国际化等方面的努力和成就。

问答坊 | AI 解惑

Protel常用元件库

湖北师院物理系电信专业实验室田开坤(整理)的Protel常用元件库。         包含74系列,CMOS系列,存储器,单片机,杂元件,ADC,数码管,定时器等等,中文名称,特别实用。…

查看全部问答>

s3c2410 触摸屏驱动

s3c2410触摸屏驱动中,硬件电路上XPOS ,YPOS分别接到了AIN2 AIN0上,是不是就不能使用tc  adc中断?也不能使用自动转化和序列转换模式? 这样的话,是不是只能普通ad模式转换?…

查看全部问答>

串口升级的问题

情况是这样的:主板有两个串口,一个接到机箱(也就是我们作为升级的串口1),另一个串口2与子板上CPU串口连在一起。现在我想通过主板的串口1找升级文件,然后通过主板串口2与子板的串口数据通信来升级子板的程序。主板上串口1接受到的数据可以直接 ...…

查看全部问答>

wince 键盘驱动的疑问, 扫描码转键值后,进入系统后发生变化

wince 6.0 + pxa270 一个简单矩阵键盘, 共6个按键 在键盘驱动的 KeyButton_GetEventEx2(UINT rguiScanCode[16], BOOL rgfKeyUp[16]) 函数中 分别映射为 VK_F1, VK_F2, VK_F3,VK_F4, VK_F5 通过打印消息分别是 vk_f1, vk_f2 vk_f3 vk_f4 vk_f5 ...…

查看全部问答>

V2.0.3例子中的NAND的读写是那个硬件啊?

                                 官方只给一个示例,也找不到应用文档,有几个问题想搞清楚,但是不能给实际用NAND进行对照…

查看全部问答>

共模与传导不能同时好?

求助专家,急!!!     小功率隔离型开关电源怎么同时解决共模和传导问题啊,要求是不能外加元件,我改了好几次变压器的绕法,最好的一次就是共模620mV,很好了,可传导却不行,余量不够6dB,改了那么多次都是这个好那个坏,那个好这个坏 ...…

查看全部问答>

有谁有用过Verilog写can总线模块的吗?

  现在正在学习FPGA,然后现在也不知道该具体做点什么出来,感觉我以后可能会用到can总线,所以就想是不是能用Verilog来编写出can总线的实现功能模块,即为以后的学习打下基础,又同时学习了Verilog的使用,一举两得。可是没有个头绪,不知道 ...…

查看全部问答>

可恶的STM,我就是要用AVR 代码!

可恶的STM,我就是要用AVR 代码,我喜欢汇编代码!! 据传闻Xmega A3U价格暴跌。…

查看全部问答>

关于S3C2416 的Vxwork BSP

请问哪里有S3C2416的Vxwork BSP下载?到处都没找到可以提供Vxwork BSP的开发板。…

查看全部问答>

大家帮忙推荐一本UCGUI的书吧

大家帮忙推荐一本UCGUI的书吧。自己摸索的实在是头疼。…

查看全部问答>