历史上的今天
今天是:2024年10月19日(星期六)
2021年10月19日 | S3c2440的LCD显示项目
2021-10-19 来源:eefocus
一、原理
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)
上一篇:ARM中断基础知识
史海拾趣
|
摘要:介绍了将电源模块并联,并构成冗余结构进行供电的好处,讲述了几种传统的并联均流电路,讨论了各种方式下的工作过程及优缺点,并对均流技术的发展做了展望。 关键词:电源模块;并联;冗余;均流 1 概述   ...… 查看全部问答> |
|
电子设计大赛是否有必要参加?就如同在问其到底是鸡肋还是鸡喙,值得商榷。 近日听说2009全国电子专业人才设计与技能大赛已经开始报名,作为一个工作两年的电路设计工程师,我有我自己的看法。 首先要谈谈上学与就业的关系,上学的意义 ...… 查看全部问答> |
|
[0xe3a01001] mov r1,#1 [0xee031f10] mcr p15,0x0,r1,c3,c0,0 [0xee02af10] mcr p15,0x0,r10,c2,c0,0 [0xee080f17] & ...… 查看全部问答> |
|
安装eMbedded Visual C++后出现的问题,请大家帮助 我在安装好eMbedded Visual C++后,编译一个程序成功,然后启动了Emulator,但是在之后,Connecting to the device 失败,编译好的程序不能上载,这是为什么呀。 … 查看全部问答> |
|
本人现在想学习嵌入式开发,看过不少资料有ARM,DSP,现在就想由一个开始学,不知道学习那一个更加实际?? 还有我需要根据我的学习方向来定我的论文内容,如果是 ARM不知道具体的那方面可以去提升到论文的高度去写?不是到嵌入式数据库这个方向怎 ...… 查看全部问答> |
|
>>>looking for someone interested in the kernel/ARM core Hi Guys, i am looking for someone interested in the kernel/ARM core. will be paid well. drop a line at linuxkernelembedded@yahoo.co.uk to make an appointment with me if you feel you might be the one i am looking for at the mom ...… 查看全部问答> |




