Real6410/S3C6410裸机LCD驱动
简述:能够在裸机驱动LCD,并显示图片,打印字符串的裸机程序。
一、编写目的及环境
编写目的
手上有块real6410开发板,最近为其移植了个u-boot-2012.10。然后想把u-boot的控制台终端改到LCD显示。初次尝试不成功便想从简单入手,先写个裸机的驱动,再移 植到u-boot中。
环境
上位机:ubuntu12.04系统、arm-none-eabi-gcc4.6.1交叉编译器、有tftp服务。
开发板:运行开发板自带u-boot-1.1.6,通过tftp命令下载程序、go命令执行程序。
板上LCD为wxcat43-tg3
通迅:kermit显示控制开发板上的u-boot。
特别说明:由于没有仿真器,如果使用SD卡起动调试太过麻烦,所以这里用u-boot下载执行。
二、编译命令
在使用u-boot执行生成的程序期间经常发生”dataabort”。应该都是对齐引起的,后来采用了链接脚本后情况有所好转,担也还是时有发生,原因还在分析中。
Makefile文件
led_test:
arm-none-eabi-gcc -o lcd.o lcd.c -c#编译主程序不链接
arm-none-eabi-gcc -o my_debug.o my_debug.S -c#编译主程序不链接
arm-none-eabi-ld-T u-boot.lds -Bstatic -Ttext 0xc0008000 -g lcd.o my_debug.o -olcd.elf#链接程序,指定链接脚本并将代码段重定位到到地址0xc0008000
arm-none-eabi-objcopy-O binary -S lcd.elf lcd.bin#将生成的elf文件处理成bin文件
sudocp -v lcd.bin /design/tftproot/#将生成的文件复制到tftp服务的目录方便下载
这里的my_debug.S是控制开发板上led灯亮灭的,用于调试,可要可不要。
链接脚本u-boot.lds
这个是从u-boot源码目录复制来的。其实只要.text.rodata .data段就可以了。
OUTPUT_FORMAT("elf32-littlearm","elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
.= 0x00000000;
.= ALIGN(4);#关键是这个,让代码段4字节对齐
.text:
{
*(.text)
}
.= ALIGN(4);
.rodata: { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }
.= ALIGN(4);
.data: { *(.data) }
.= ALIGN(4);
.got: { *(.got) }
__u_boot_cmd_start= .;
.u_boot_cmd: { *(.u_boot_cmd) }
__u_boot_cmd_end= .;
.= ALIGN(4);
.mmudata: { *(.mmudata) }
.= ALIGN(4);
.rel.dyn: {
__rel_dyn_start= .;
*(.rel*)
__rel_dyn_end= .;
}
.dynsym: {
__dynsym_start= .;
*(.dynsym)
}
_end= .; PROVIDE(end = .);
.bss__rel_dyn_start (OVERLAY) : {
__bss_start= .;
*(.bss)
.= ALIGN(4);
__bss_end__= .;
}
/DISCARD/: { *(.dynstr*) }
/DISCARD/: { *(.dynamic*) }
/DISCARD/: { *(.plt*) }
/DISCARD/: { *(.interp*) }
/DISCARD/: { *(.gnu*) }
}
三、源文件
全局变量、结构体定义头文件“lcd.h”
由于部分结构体过大,在这里只给出其来源,具体成员不列出。嗯,官方下的u-boot源码中是没有的,等等看能不能传到网上来。
typedefunsigned char u8;
typedefunsigned int u32;
/*GPIO配置寄存器*/
/*位置:u-boot/arch/arm/include/asm/arcm-s3c64xx/s3c64x0.h*/
typedefstruct {
…...
}s3c64xx_gpio;
/*显示控制器配置寄存器*/
/*位置:u-boot/arch/arm/include/asm/arcm-s3c64xx/s3c64x0.h*/
typedefstruct {
…...
}s3c64xx_fb;
/*重要结构,用于配置LCD参数,包括时序、像素*/
structctfb_res_modes {
intxres; /* visible resolution */
intyres;
/*Timing: All values in pixclocks, except pixclock (of course) */
intpixclock; /* pixel clock in ps (pico seconds) */
intleft_margin; /* time from sync to picture */
intright_margin; /* time from picture to sync */
intupper_margin; /* time from sync to picture */
intlower_margin;
inthsync_len; /* length of horizontal sync */
intvsync_len; /* length of vertical sync */
intsync; /* see FB_SYNC_* */
intvmode; /* see FB_VMODE_* */
};
/*这些是为在屏幕上打印字符准备的,如果不打印字符可不要*/
#defineCONSOLE_BG_COL 0x00 /*字符背景色*/
#defineCONSOLE_FG_COL 0xf0 /*字符前景色*/
#defineVIDEO_FONT_CHARS 256 /* */
#defineVIDEO_FONT_WIDTH 8 /*字体宽度、单位像素*/
#defineVIDEO_FONT_HEIGHT 16 /*字体高度、单位像素*/
#defineVIDEO_FONT_SIZE (VIDEO_FONT_CHARS * VIDEO_FONT_HEIGHT)
/* */
staticconst int video_font_draw_table16[] = {
0x00000000, 0x0000ffff, 0xffff0000, 0xffffffff };
/*ascii字库像素数据表*/
/*位置:u-boot/include/video_font.h这个u-boot官方提供*/
staticunsigned char video_fontdata[VIDEO_FONT_SIZE] = {
…...
};
/*半字交换宏、arm存储数据是以4字节为单位的,RGB565只占2字节存储数据*/
/*那么这2字节在4字节的上半还是下半呢,有时候要交换一下,可以使用这个宏*/
/*因为图片的数据源需要开启半字交换,而字库的数据又不要,所以这个宏要打开*/
#defineVIDEO_FB_16BPP_WORD_SWAP
#ifdefined(VIDEO_FB_16BPP_WORD_SWAP)
#defineSHORTSWAP32(x) ( ((x) >> 16) | ((x) << 16) )
#else
#defineSHORTSWAP32(x) (x)
#endif
/*外部函数声明,却行中的u-boot提供*/
/*通过u-boot的System.map文件查到函数地址、然后通过下面的定义就能调用这些函数*/
void(*printf)(char *, ...)=0xc7e14ca8;
void(*memset)(void * dest, u32 a, u32 size)=0xc7e1cbc0;
void(*memcpy)(void * dest, void * a, u32 size)=0xc7e1cc0c;
/*自定义函数声明*/
voidinit_res_modes();
voidinit_gpio();
staticvoid video_drawchars(int xx, int yy, unsigned char *s, int count);/*打印字符串*/
主要源代码文件“lcd.c”
#include"lcd.h"
#include"Image.h"
/*定义gpio及lcd控制器基地址*/
#define ELFIN_FB_BASE 0x77100000
#define ELFIN_GPIO_BASE 0x7f008000
s3c64xx_fb* const fb = (s3c64xx_fb *)(ELFIN_FB_BASE);
s3c64xx_gpio* const gpio = (s3c64xx_gpio *)(ELFIN_GPIO_BASE);
/*存放LCD显示器资料的结构,习惯用指针所以多定义了个指针出来*/
structctfb_res_modes var_mode;
structctfb_res_modes * res_mode = (struct ctfb_res_modes *)&var_mode;
/*用于存放显示资料的显存、要确保位置可用*/
staticvoid * video_fb_address = 0x51000000;
/*用于控制显示字符的前景背景*/
staticu32 eorx, fgx, bgx; /* color pats */
/*为什么是_start而不是main,因为u-boot.lds中“ENTRY(_start)”指定了入口函数名*/
void_start()
{
/* s3c6410内部一个寄存器定义*/
volatile u32 * MOFPCON = 0x7410800c;
/*这些个原理我也不懂,反正就是控制打印字符的前背景色就是*/
fgx = (((CONSOLE_FG_COL >> 3) << 27) |
((CONSOLE_FG_COL >> 2) << 21) |((CONSOLE_FG_COL >> 3) << 16) |
((CONSOLE_FG_COL >> 3) << 11) |((CONSOLE_FG_COL >> 2) << 5) |
(CONSOLE_FG_COL >> 3));
bgx = (((CONSOLE_BG_COL >> 3) << 27) |
((CONSOLE_BG_COL >> 2) << 21) |((CONSOLE_BG_COL >> 3) << 16) |
((CONSOLE_BG_COL >> 3) << 11) |((CONSOLE_BG_COL >> 2) << 5) |
(CONSOLE_BG_COL >> 3));
eorx = fgx ^ bgx;
/*调用两个自定义函数,定义在后面*/
init_res_modes();
init_gpio();
/*下面很长一部分都是来自于s3c6410手册,关显示控制器初妈化步骤建议*/
/*关闭旁路模式*/
//1. MOFPCON: SEL_BYPASS[3] value @ 0x7410800C must be set as'0'(normal mode) instead of '1'(by-passmode).
*MOFPCON &= ~(1 << 3);
/*设置输出模式为RGB、这个寄存在gpio部分*/
//2. SPCON: LCD_SEL[1:0] value @ 0x7F0081A0 must be set as '00' touse Host I/F Style or as '01' to use RGB I/F Style
gpio->SPCON &= ~(0x3 << 0);
gpio->SPCON |= (0x1 << 0);
/*选择时钟源为HCLK=133MHz、分频值必需大于1、开启分频寄存器*/
/*开启分频后时钟计算VCLK= Video Clock Source / (CLKVAL+1) */
//3. VIDCON0: configure Video output format and displayenable/disable.
fb->VIDCON0 = (1 << 4) | (10 << 6) | (1 <<16);
/*设置控制信号的电平及边沿、需将s3c6410手册与LCD手册对比决定*/
//4. VIDCON1: RGB I/F control signal.
fb->VIDCON1= (0 << 7) | (1 << 6) | (1 << 5) | (0 <<4);
//5.I80IFCONx: i80-system I/F control signal.
//6.ITUIFCON0 : ITU (BT.601) Interface Control
/*时序控制,参照LCD手册填*/
//7. VIDTCONx: configure Video output Timing and determine thesize of display.
fb->VIDTCON0 = (res_mode->upper_margin << 16) | \
(res_mode->lower_margin<< 8) | (res_mode->vsync_len << 0);
fb->VIDTCON1 = (res_mode->left_margin << 16) | \
(res_mode->right_margin<< 8) | (res_mode->hsync_len << 0);
fb->VIDTCON2 = ((res_mode->yres - 1) << 11) |((res_mode->xres - 1) << 0);
/* s3c6410有5个窗口,这里只使用一个,其关系有点类似图层的概念*/
/*开启窗口输出、选定数据源格式16bppRGB565、开启半字交换*/
//8. WINCONx: each window format setting
fb->WINCON0= (1 << 0) | (5 << 2) | (1 <<16); // 16BIT_RGB565
//fb->WINCON0 = (11 << 2) | (1 <<0); // 24BIT_RGB888
/*s3c6410支持虚拟屏,即显存内可放比屏幕大的内容,在截取一屏内容显示*/
/*指定显存中要显示内容的位置偏移,本例未开虚拟屏,显存和显示器像素一样大*/
//9. VIDOSDxA, VIDOSDxB: Window position setting
fb->VIDOSD0A = (0 << 11) | (0 << 0); /*左上坐标系位置指定单位2字节*/
/*右下坐标系位置指定 单位两字节*/
fb->VIDOSD0B = ((res_mode->xres -1) << 11) |((res_mode->yres -1) << 0);
fb->VIDOSD0C = (res_mode->res_mode->yres * 272); /*内容大小单位2字节*/
//10. VIDOSDxC: alpha value setting
/*指定窗口0的显示存起、未地址,大小*/
//11. VIDWxxADDx: source image address setting
memset(video_fb_address, 0, res_mode->xres*res_mode->yres*2& 0xffffff);
fb->VIDW00ADD0B0 = video_fb_address;
fb->VIDW00ADD1B0 = (res_mode->xres*res_mode->yres*2 &0xffffff);
fb->VIDW00ADD2 = (res_mode->xres*2 & 0x1fff);
//12. WxKEYCONx: Color key value register
/*指定窗口的背景色,设置后将只显示这个。猜设置混合后应该可以显示*/
/*如果调试的时候不出图可先开开这个,如能正常显示则说明至少时序没多大问题*/
//13. WINxMAP: window color control
//fb->WIN0MAP = (1 << 24) | (0xff00ff);
//14. WPALCON: Palette controls register
//15. WxPDATAxx: Window Palette Data of the each Index.
/*复制图像到显存,数据来自另一个头文件里的数组”gImage_Imag”文件过大不列出*/
/*我使用的转码工具可能有点问题,右边有几像素图被显示到左边了*/
memcpy(0x51000000, gImage_Imag, 261128);
/*开启显示控制器,执行完这句不出意外的话这时候应该能够正常显示图片了。*/
fb->VIDCON0 |= (1 << 1) | (1 << 0);
/*使用video_drawchars打字字符串*/
/*参数说明:位置x单位像素,位置y,字符串指针,字符串长度*/
video_drawchars(0, 257, "hello u-boot. I'll become a driverengineer !", 45);
/*显LED灯、我调试用,可不要*/
_led_cycle();
}
/*向屏幕显示字符串函数、不带换行等控制字符的功能,直接从u-boot搬过来的,不详解*/
staticvoid video_drawchars(int xx, int yy, unsigned char *s, int count)
{
u8 *cdat, *dest, *dest0;
int rows, offset, c;
offset = yy * res_mode->xres * 2 + xx * res_mode->xres * 2;
dest0= video_fb_address + offset;
while (count--)
{
c = *s;
cdat = video_fontdata + c * VIDEO_FONT_HEIGHT;
for (rows = VIDEO_FONT_HEIGHT, dest = dest0;
rows--;
dest += res_mode->xres * 2) {
u8 bits = *cdat++;
((u32 *) dest)[0] = SHORTSWAP32 ((video_font_draw_table16 [bits >>6] & eorx) ^ bgx);
((u32 *) dest)[1] = SHORTSWAP32 ((video_font_draw_table16 [bits >>4 & 3] & eorx) ^ bgx);
((u32 *) dest)[2] = SHORTSWAP32 ((video_font_draw_table16 [bits >>2 & 3] & eorx) ^ bgx);
((u32 *) dest)[3] = SHORTSWAP32 ((video_font_draw_table16 [bits &3] & eorx) ^ bgx);
}
dest0 += VIDEO_FONT_WIDTH * 2;
s++;
}
}
//初始化LCD显示器参数
voidinit_res_modes()
{
//init ctfb_res_modes
/*表示从水平同步信号开始到一行的有效数据开始之间的VCLK的个数*/
res_mode->left_margin= 2; // HFPD
/*表示一行的有效数据结束到下一个水平同步信号开始之间的VCLK的个数*/
res_mode->right_margin= 2; // HBPD
/*表示水平同步信号的宽度,用VCLK计算*/
res_mode->hsync_len =41; // HSPW
/*表示在一帧图像开始时,垂直同步信号以后的无效的行数*/
res_mode->upper_margin =2; // VFPD
/*表示在一帧图像结束后,垂直同步信号以前的无效的行数*/
res_mode->lower_margin =2; // VBPD
/*表示垂直同步脉冲的宽度,用行数计算*/
res_mode->vsync_len =10; // VSPW
/*LCD像素尺寸、分辨率*/
res_mode->xres =480;
res_mode->yres =272;
/*DOTCLK = fframe × (X + HBP + HFP+HSPW) × (Y + VBP + VFP+VSPW)(单位:MHz)*/
/*pixclock = 10的12次方/DOTCLK (单位:皮秒)fframe表帧率,一般取60*/
res_mode->pixclock =88800;
res_mode->sync =0;
}
/*配置相应的gpio管脚为LCD模式,并关闭上拉/下拉功能*/
voidinit_gpio()
{
gpio->GPICON_IO = 0xaaaaaaaa;
gpio->GPIPUD_IO = 0x00000000;
gpio->GPJCON_IO = 0xaaaaaaaa;
gpio->GPJPUD_IO = 0x00000000;
}
图片转换成二进制数组
先用GIMP裁剪到适合分辨率,此处为480x272。另存为24bpp_RGB888 bmp格式图片。我试过用GIMP另存为.h或.c格式的16bpp_RGB565格式都显示不了。
网上下载一个24bpp转16bpp的工具,转成数据。
四、总结
做LCD驱动的步骤
做LCD驱动,应该是做所有的驱动都应该先看看电路图。
之前只看datasheet,不管是s3c6410还是lcd都说支持24bpp_RGB888格式,我也就采用了。可是,搞了很久始终不出图,一直不明所以。后来无意间在一个论坛上看到楼主说他的屏要不是部分数据引脚被焊在了一起可以使用24bpp格式。我打开电路图来一看,我的果然也是,RGB各信号线都只有5根是正常连接的。
看LCD及LCD控制器的datasheet,特别是时序、控制信号的电平及电平边沿。
按LCD控制器datasheet的步骤初始化寄存器
此处不完全是裸机程序,它的运行需要u-boot的支持。如果要变成完全的裸机程序,还要加上CPU及内存的初始化,设置SP指针等操作后才能跳转到该程序。
所有资源都已上传到CSDN免积分,http://download.csdn.net/detail/jqguardian/4832007
带背景图(56%透明度)、带左上角logo、带控制台输出的u-boot界面图。(上面资源只是个裸机程序,不含u-boot !)