NUC970系列资料之LED屏驱动方案

 

一、 简介

NUC970本身没有LED屏的驱动接口,但有客户想用nuc970来驱动led屏。本文介绍一种用LCD控制器来驱动led屏的方案,并实现了一个demo程序。

二、 LED屏的基本知识

2.1 led屏介绍

LED大屏幕的几个发展阶段:

第一代:单色LED显示屏

第二代:双基色多灰度显示屏

第三代:全彩色多灰度显示屏

第四代:真彩色多灰度显示屏

Led屏的分类:

从使用环境分:室内LED显示屏、室外LED显示屏

从显示颜色分:单基色LED显示屏、双基色LED显示屏、全彩色LED显示屏

从显示功能分:图文LED显示屏(异步屏)、视频LED显示屏

异步屏:

一般由显示单元板(模组)、条屏卡、开关电源、HUB板(可选)组成。通过串口线与计算机连接,进行显示文件的更改,之后可以脱开计算机工作。

clip_image001

同步屏

同步屏系统比较复杂,系统可大可小,一般由计算机、DVI显卡、数据发送卡、同步数据接收卡、HUB板、网线、LED显示屏等组成。系统始终需要联机计算机工作,将计算机的图像文字显示在LED大屏幕上。

clip_image002

2.2 LED屏的一些基本概念

1、像素:是LED显示屏的最小成像单元。俗称“点”或“像素点”。

clip_image003

上图所示由2红2绿组成1个显示像素点

2、显示模块:由若干个显示像素组成的,结构上独立的组成LED显示屏的最小单元。

室内屏用的是8×8的显示模块,即每个显示模块有64个像素

clip_image004clip_image005

室外屏使用的是单个的灯珠,通常由1-3个相同或不同颜色的灯珠组成模块的一个像素点。

clip_image006

clip_image007

如上面右图的室外屏模组就是由2个红色灯珠组成1个显示像素点

3、显示模组:由电路及安装结构确定的并具有显示功能的组成LED显示屏的独立单元。简单说就是为便于组装和显示,出厂的半成品通常是以显示模组形式提供的,将多个显示模块加显示驱动做在一起。室内屏俗称“单元板”;室外屏俗称“模组”,再将若干个模组加上机箱、风扇、电源等构在一起成为“箱体”,多用于大型的全彩屏。

l 室内屏单元板通常有64×32(64列32行、由32个模块组成)、64×16(64列16行、由16个模块组成)等。下图是一个64×16的单元板:

clip_image008clip_image009

室内屏单元板正面 室内屏单元板背面

l 室外屏模组通常有64×32、32×32、32×16、16×16、16×8多种

clip_image010

上图为16×8(2红)的室外屏模组。加了防水结构用于全户外,我们可以看到塑料壳体,最右侧是它的整个结构刨图:显示板上插的是灯珠、背板上是显示驱动电路,这是分体结构的,也有的是将显示板和显示驱动电路做在一块电路板上的整体结构的。

大型室外全彩屏所用箱体通常由若干个模组+机箱+风扇+电源组成

4、LED显示屏屏体: 将单元板/模组/箱体按一定方式拼接在一起,加上控制卡/控制系统、电源和框架等就构成为LED显示屏。

clip_image011

clip_image012

室内屏:显示单元板+控制卡+电源+铝型框架

clip_image013

clip_image014

室外屏:显示模组+控制卡+电源+铝型框架

clip_image015

全彩屏:显示箱体+控制系统+计算机+通讯网络+架体等组成

5点距:就是2个像素点之间的距离。主要是取决于观看者的距离。通常点距的概念用于室外屏,有P6、P7.62、P8、P10、P12、P16、P20等,以毫米mm为单位。室外屏观看距离一般在30米内,采用不大于P16(16mm)的模组。点距越密,显示出来的字笔画越细腻,单位面积像素点越多,显示屏成本越贵。

6扫描方式:通常对于室外屏模组还有一个扫描方式的问题。

扫描方式决定了模组之间连接的形式,扫描方式有1/16、1/8、1/4、1/2、静态这几种。因为LED显示屏是逐行刷新显示的,扫描方式也就决定了显示刷新的方式,如1/16就是每次刷新1行,16行为一个扫描周期,需ABCD四个信号控制;1/8就是每次刷新1行,8行为一个扫描周期,需ABC三个信号控制;其它依次类推。如果采用相同的LED灯,1/16扫的亮度要比1/8低,静态(1/1)的亮度是最高的。户内的屏一般采用1/16扫,户外和半户外的一般采用1/16或者1/8。对于放置在屏经常受到猛烈阳光照射的环境,就最好用1/4扫描。

单元板原理图

clip_image017

汉字的显示:

举例:16×16的点阵汉字,每个汉字32个字节。

比如我们要显示“恭喜发财”这4个字,首先:

送出“恭喜发财”的各头2个字节

每个字节都是8位,这样一共送出了8×8=64位(列),送出这些位信号是通过DI信号端送出的(串行送出),在每送出1位时CLK信号端都要高低变换一次,称为串行移位,使得64位(列)的每一位都被移送到了74HC595的输入端口上。送出锁存信号STB,即STB信号高低变换一次,这样74HC595的输入端口上64位(列)数据就被送到74HC595的输出上,一行显示就出来了。锁存信号也使得下一行数据串行移位送出不会影响到上一行的显示。由单片机再通过74LS138变换ABCD的组合,选出下一个显示行。

重复的过程,但送出的数据相应的向后移动,即“恭喜发财”的3-4字节、5-6字节。。。。。。。

行选择也是从第1行到第16行,16行显示一遍称为一个显示刷新周期,无论LED显示屏的大小如何,一个显示刷新周期必须在20毫秒以内完成,否则会出现闪动,单片机速度很快,32行200列以内的显示通常是没有问题的。但当LED显示屏更大时就要选择速度更快的单片机或DSP来完成了。

2.3 单元板接口及控制信号

1.LED单元板控制信号:

CLK时钟信号

提供给移位寄存器的移位脉冲,每一个脉冲将引起数据移入或移出一位。数据口上的数据必须与时钟信号协调才能正常传送数据,数据信号的频率必须是时钟信号的频率的1/2倍。在任何情况下,当时钟信号有异常时,会使整板显示杂乱无章。

STB锁存信号

将移位寄存器内的数据送到锁存器,并将其数据内容通过驱动电路点亮LED显示出来。但由于驱动电路受EN使能信号控制,其点亮的前提必须是使能为开启状态。锁存信号也须要与时钟信号协调才能显示出完整的图象。在任何情况下,当锁存信号有异常时,会使整板显示杂乱无章。

EN使能信号

整屏亮度控制信号,也用于显示屏消隐。只要调整它的占空比就可以控制亮度的变化。当使能信号出现异常时,整屏将会出现不亮、暗亮或拖尾等现象。

RI数据信号

提供显示图象所需要的数据。必须与时钟信号协调才能将数据传送到任何一个显示点。一般在显示屏中红绿蓝的数据信号分离开来,若某数据信号短路到正极或负极时,则对应的该颜色将会出现全亮或不亮,当数据信号被悬空时对应的颜色显示情况不定。

ABCD行信号

只有在动态扫描显示时才存在,ABCD其实是二进制数,A是最低位,如果用二进制表示ABCD信号控制最大范围是16行(1111),1/4扫描中只要AB信号就可以了,因为AB信号的表示范围是4行(11)。当行控制信号出现异常时,将会出现显示错位、高亮或图像重叠等现象。

2.单元板/模组上显示接口:

显示接口是用用于连接控制卡和单元板/模组之间连接,将控制信号传递。

由于存在不同的扫描方式,也就有不同的接口,使用得最多的是08接口,12接口和04接口。不同的接口主要是信号线的排列顺序不一样,原理是一样的。

室内屏多用08接口,室外屏所采用接口非常杂乱,使用12接口的较多,但12接口也不是户外屏的唯一接口。

接口类型

08接口

04接口

12接口

排列顺序

clip_image018

clip_image019

clip_image020

图片

clip_image021

clip_image022

clip_image023

 

常见于 1/16 1/8扫

常见于1/4扫

常见于1/4扫

常用的接口16PIN 08接口:

clip_image024_   clip_image025

2

A

B

C

D

G1

G2

STB

CLK

16

1

N

N

N

EN

R1

R2

N

N

15

解释:

ABCD 为行选信号

STB(LT)为锁存信号

CLK(CK)为时钟信号

R1,R2,G1,G2为显示数据(组成16行时用到R1、G1,组成32行时,下

面16行用到R2、G2),若是单色只需要R信号,双色时才用到G信号

EN为显示使能,

N为地(GND)

三、用nuc970的lcd控制器驱动led屏的原理

3.1 硬件连接

一般LED的驱动都是用GPIO来模拟,这种方案下对GPIO的翻转速度要求比较高,显示的像素越多、对gpio的翻转速度要求就越高,一般led屏驱动厂商都要求能达到12MHZ的翻转速度。

由于nuc972的gpio翻转速度测试下来只有6MHZ,不能满足客户的要求,故想到了用LCD控制器来驱动LED。LCD控制器的clk最高可达50MHZ,传送数据不成问题。

本文介绍的方案是用Nuc970的lcd控制器提供mpu接口来驱动LED。用80接口来传输数据,WR信号来模拟clk信号。D0-D16来模拟data信号。锁存信号STD、显示使能信号OE、行选择信号ABCD用gpio来模拟。

硬件管脚设定如下:

STD: PD4

OE: PD5

clk: PG7(HSYNC)

A:PD0

B: PD1

C: PD2

R1:DATA0

G1:data1

B1:Data2

R2:data8

G2:data9

B2:data10

DATA0-15:PA0-15

3.2 LCD一帧数据来驱动LED的一行

Lcd控制器只有帧中断(扫描完一帧产生一个中断),而led输出一行信号后,要切换行。所以可以用LCD一帧来表示led的一行。

如要驱动每行4096个像素的LED屏,就需要设定lcd的一帧数据为4096的像素,如可设定LCD屏分辨率为512×8.

四、led驱动程序的实现举例

驱动程序举例是以驱动4096×16的屏为例的。

4.1设定lcd屏的参数

修改dev.c文件

/* LEDRGB 1024×64 LED , 24bits*/

[0] = {

.type = LCM_DCCS_VA_SRC_RGB565,

.bpp = 16,

.width = 512,

.height = 8,

.xres = 512,

.yres = 8,

.pixclock = 20000000,

.left_margin = 20,

.right_margin = 10,

.hsync_len = 10,

.upper_margin = 6,

.lower_margin = 4,

.vsync_len = 2,

.dccs = 0x0000049a,

.fbctrl = 0x01000100,

#ifdef CONFIG_FB_LCD_16BIT_PIN

.devctl = 0x050000c0,

#elif defined(CONFIG_FB_LCD_24BIT_PIN)

.devctl = 0xf50000e0,

#endif

.scale = 0x04000400,

},

4.2修改nuc970fb.c

1.修改获取的内存的大小

八行一组,需要4096x8x2字节内存。

#if defined(CONFIG_LEDRGB_1024x64)

printk(“smem_len=0x%x\n”,smem_len);

#else

smem_len >>= 3;

#endif

2、初始化GPIO及LCD寄存器,使能中断

#ifdef CONFIG_LEDRGB_1024x64

nuc970fb_activate_var(fbinfo);

init_ledrgb(fbi);

colnum=0;

OUT_LED_COL(0);

writel(readl(fbi->io + REG_LCM_INT_CS) | 1<<30, fbi->io + REG_LCM_INT_CS);

writel(readl(fbi->io + REG_LCM_DCCS) | LCM_DCCS_DISP_INT_EN, fbi->io + REG_LCM_DCCS);

writel(readl(fbi->io + REG_LCM_INT_CS) | LCM_INT_CS_DISP_F_EN, fbi->io + REG_LCM_INT_CS);

#endif

3、在中断中切换行及/STD/OE

#ifdef CONFIG_LEDRGB_1024x64

CLR_LCD_OE();

SET_LCD_STD();

CLR_LCD_STD();

OUT_LED_COL(colnum);

colnum++;

colnum=colnum&0x07;

unsigned long vbaddr1;

vbaddr1 = info->fix.smem_start+colnum*(info->fix.smem_len>>3);

/* set frambuffer start phy addr*/

writel(vbaddr1, regs + REG_LCM_VA_BADDR0);

SET_LCD_OE();

#endif

4.3增加GPIO初始化文件

nuc970_ledrgb_128X64.c

//COL_A: PD0

//COL_B: PD1

//COL_C: PD2

//COL_D: PD3

#define COL_A NUC970_PD0

#define COL_B NUC970_PD1

#define COL_C NUC970_PD2

#define COL_D NUC970_PD3

#define LED_STD NUC970_PD4

#define LED_OE NUC970_PD5

#define SET_LCD_STD() gpio_set_value(LED_STD,1) //set std

#define CLR_LCD_STD() gpio_set_value(LED_STD,0) //clear std

#define SET_LCD_OE() gpio_set_value(LED_OE,1) //set oe

#define CLR_LCD_OE() gpio_set_value(LED_OE,0) //clear oe

#define OUT_LED_COL(col) writel((readl(REG_GPIOD_DATAOUT)&0xfffffff0)|((col) &0xf),REG_GPIOD_DATAOUT)

int GPIO_Init(void)

{

int ret;

int i;

printk(“GPIO_Init………………..\n”);

printk(“REG_CLK_PCLKEN0=0x%x\n”,readl(REG_CLK_PCLKEN0));

writel(readl(REG_MFP_GPD_L)&0xff000000,REG_MFP_GPD_L);//gpd0-5

ret = gpio_request(COL_A , “COL_A”);

if (ret) {

printk(“COL_A failed ret=%d\n”,ret);

return ret;

}

gpio_direction_output(COL_A,1);

ret = gpio_request(COL_B , “COL_B”);

if (ret) {

printk(“COL_B failed ret=%d\n”,ret);

return ret;

}

gpio_direction_output(COL_B,1);

ret = gpio_request(COL_C , “COL_C”);

if (ret) {

printk(“COL_C failed ret=%d\n”,ret);

return ret;

}

gpio_direction_output(COL_C,1);

ret = gpio_request(COL_D , “COL_D”);

if (ret) {

printk(“COL_D failed ret=%d\n”,ret);

return ret;

}

gpio_direction_output(COL_D,1);

ret = gpio_request(LED_STD , “LED_STD”);

if (ret) {

printk(“LED_STD failed ret=%d\n”,ret);

return ret;

}

gpio_direction_output(LED_STD,1);

ret = gpio_request(LED_OE , “LED_OE”);

if (ret) {

printk(“LED_OE failed ret=%d\n”,ret);

return ret;

}

gpio_direction_output(LED_OE,1);

CLR_LCD_STD();

SET_LCD_OE();

OUT_LED_COL(0);

return 0;

}

static int init_ledrgb(struct nuc970fb_info *fbi)

{

int err;

printk(“nuc970fb_init_device………………..\n”);

GPIO_Init();

return 0;

}

4.4 led显示程序

1.打开设备及控制显示:

fd = open(“/dev/fb0”, O_RDWR);

if (fd == -1) {

printf(“Cannot open fb0!\n”);

return -1;

}

uVideoSize = LCD_WIDTH * LCD_HEIGHT;

pVideoBuffer = mmap(NULL, uVideoSize, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);

if (pVideoBuffer == MAP_FAILED) {

printf(“mmap() failed\n”);

exit(0);

}

for(i = 0; i < LCD_WIDTH * LCD_HEIGHT/2; i++) {

*(unsigned short *)(pVideoBuffer + i) = 0xF1f1;

}

sleep(2);

memset(pVideoBuffer, 0, uVideoSize);

sleep(2);

2. 汉字显示

unsigned char testdianzhen[]={

/*芯*/

0x08, 0x20, 0x08, 0x24, 0xFF, 0xFE, 0x08, 0x20,

0x08, 0x20, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00,

0x50 ,0x84 ,0x50 ,0x82 ,0x50 ,0x02 ,0x90 ,0x02 ,

0x10 ,0x08 ,0x10 ,0x08 ,0x0F ,0xF8 ,0x00 ,0x00 ,

/*唐*/

0x01 ,0x00 ,0x00 ,0x88 ,0x3F ,0xFC ,0x20 ,0x80 ,

0x2F ,0xF8 ,0x20 ,0x88 ,0x3F ,0xFE ,0x20 ,0x88 ,

0x2F ,0xF8 ,0x20 ,0x80 ,0x2F ,0xF8 ,0x28 ,0x08 ,

0x48 ,0x08 ,0x48 ,0x08 ,0x8F ,0xF8 ,0x08 ,0x08 ,

/*电*/

0x02 ,0x00 ,0x02 ,0x00 ,0x02 ,0x10 ,0x7F ,0xF8 ,

0x42 ,0x10 ,0x42 ,0x10 ,0x7F ,0xF0 ,0x42 ,0x10 ,

0x42 ,0x10 ,0x7F ,0xF0 ,0x42 ,0x10 ,0x02 ,0x00 ,

0x02 ,0x04 ,0x02 ,0x04 ,0x01 ,0xFC ,0x00 ,0x00 ,

/*子*/

0x00 ,0x00 ,0x3F ,0xF0 ,0x00 ,0x10 ,0x00 ,0x20 ,

0x00 ,0x40 ,0x01 ,0x80 ,0x01 ,0x04 ,0xFF ,0xFE ,

0x01 ,0x00 ,0x01 ,0x00 ,0x01 ,0x00 ,0x01 ,0x00 ,

0x01 ,0x00 ,0x01 ,0x00 ,0x05 ,0x00 ,0x02 ,0x00 ,

};

void ByteConvert(unsigned char up,unsigned char down,unsigned short dst[],unsigned char color)

{

unsigned char i;

unsigned short up_t,down_t;

//printf(“up:0x%x,down:0x%x\n”,up,down);

for(i=0;i<8;i++){

dst[i]=0;

up_t=0;

down_t=0;

if(up&0x80)

up_t=color;

up=up<<1;

if(down&0x80)

down_t=color<<8;

down=down<<1;

dst[i]= down_t| up_t;

//printf(” 0x%x,”,dst[i]);

}

// printf(“\ntest\n”);

}

void DisplayChinese(unsigned short x,unsigned short y,unsigned char *dp,unsigned char Num,unsigned char color)

{

unsigned char i,j,k,m;

unsigned short *pcurbuf,*tmpbuf;

unsigned short dst[16];

unsigned short up_l,down_l;

for(k=0;k<Num;k++)

{

for(j=0;j<8;j++)

{

pcurbuf=pVideoBuffer+(x+k*16+(y+j)*LCD_WIDTH);

//printf(” up_l=0x%x,down_l=0x%x\n”,up_l,down_l);

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

{

ByteConvert((*dp),(*(dp+16)),dst,color);

for (m=0;m<8;m++) {

if(pcurbuf>((pVideoBuffer+uVideoSize/2)-1))

break;

*pcurbuf++=dst[m];

}

dp++;

}

}

//printf(” 0x%x\n”,(*dp));

dp=dp+16;

}

}

显示汉字

memset(pVideoBuffer, 0, uVideoSize);

DisplayChinese((LCD_WIDTH-64),0,testdianzhen,4,0×01);

sleep(2);