前言
最近用到了240*160的LCD屏,对于图像显示有一个基础研究,用的是单片机的RAM作为GRAM显存的方案。在任意位置显示任意大小的图像、字符。
有纰漏请指出,转载请说明。
学习交流请发邮件 1280253714@qq.com
驱动芯片及其扫描方式
用ST75256作为LCD的驱动芯片时,每8个像素点占用一个字节。
这里需要注意设置的扫描方向,在取模时需要注意取模方向跟扫描方向对应上。
将图像数据直接传输到LCD上
/*
--------------x
|
|
y
*/
/*
*说明 利用图像取模软件,将取模后的图像数据pGram,直接传输到LCD上
*设计思路
pGram数组前6字节为0X21,0X01,0X20,0X00,0X38,0X00,为图像头数据
第一个字节为扫描模式,第二个字节为输出灰度(色彩)
第三四个字节为图像宽度,第五六个字节为图像高度
先调用LcdAddress,进行开窗,即设置将要显示的x、y坐标轴及窗口大小
将pGram数组内的数据,依照扫描方式,调用LcdTransferData,直接传输到LCD
*/
void LcdDisplayNxN(int x, int y, const char pGram[])
{
int i,j;
int charCnt;
u16 nByte = 0;
u16 w = pGram[3] << 8 | pGram[2];
u16 h = pGram[5] << 8 | pGram[4];
y=y>>3;
h=h>>3;
LcdAddress(x,y,w,h);
for(i=0;i<h;i++)
{
for(j=0;j<w;j++)
{
LcdTransferData(pGram[charCnt+6]);
charCnt++;
}
}
}
这种方式存在一个缺陷,虽然是160行的像素,但无法设置其具体到160行的某一行,也就是像初学1602那样,设置第一行/第二行显示的内容。
用单片机的RAM作为图像的显存GRAM
这里用了一种方式,也就是用单片机的一部分RAM,作为图像的显存GRAM,然后可以做固定频率的刷新,也可以GRAM内容更新时才进行屏幕刷新。这时是将一整屏图像写进LCD。
240*160/8=4800byte字节,单片机用u8 LcdGram[240][20]作为LCD的GRAM。
u8 LcdGram[240][20] = {0};
/*
*说明 清除Gram数据
*设计思路 调用memset库函数将整个LcdGram数据清楚
*/
void LcdClearGram(void)
{
memset(&LcdGram,0,sizeof(LcdGram));
}
/*
*说明 更新Gram显存到LCD
*设计思路 设置对应的行地址和列地址,将数据传输到LCD
*/
void LcdRefreshGram(void)
{
u8 i,j;
LcdAddress(0,0,240,20);
for(i=0;i<240;i++)
{
for(j=0;j<20;j++)LcdTransferData(LcdGram[i][j]);
}
}
/*
*说明 将对应的x、y坐标的点数据更新到Gram
*设计思路
x、y不能超过屏幕范围
由于是单色点,8个点用一个byte数据,240*160屏幕用到了240行*20列个byte数据
在寻址y对应到Gram的列位置时,需要将y/8,具体到某个byte的某个bit,需要y%8
*/
void LcdDrawPoint(u8 x,u8 y,u8 t)
{
u8 pos,bx,temp=0;
if(x>240||y>160)return;//超出范围了.
pos=y/8;
bx=y%8;
temp=1<<bx;
if(t)LcdGram[x][pos]|=temp;
else LcdGram[x][pos]&=~temp;
}
/*
--------------x
|
|
y
*/
/*
*说明 利用图像取模软件,将取模后的图像存放在pGram数组里,调用此函数将数组内容放在显存里
*设计思路
pGram数组前6字节为0X21,0X01,0X20,0X00,0X38,0X00,为图像头数据
第一个字节为扫描模式,第二个字节为输出灰度(色彩)
第三四个字节为图像宽度,第五六个字节为图像高度
将pGram数组内的数据,依照扫描方式,调用画点函数,存放在LcdGram显存内
若图像宽为32,高为56,则pGram除头数据外,还有32*56/8=224个字节
如果高度不是8的倍数,则直接退出
这里用垂直扫描模式,当一列数据写Gram完成后,相应地x坐标需要加1,同时y回到起始位置
*/
void LcdDisplayNxN(int x, int y, const u8 pGram[])
{
int charCnt,charNum;
u8 dotCnt,charTmp,hightCnt;
u8 y0 = y;
u16 w = pGram[3] << 8 | pGram[2];
u16 h = pGram[5] << 8 | pGram[4];
if ((h%8)!=0)
return;
h=h>>3;
charNum = w*h;
for(charCnt=0;charCnt<charNum;charCnt++)
{
charTmp = pGram[charCnt+6];
for(dotCnt=0;dotCnt<8;dotCnt++)
{
if(charTmp&0x01)LcdDrawPoint(x,y,1);
else LcdDrawPoint(x,y,0);
charTmp>>=1;
y++;
}
hightCnt++;
if (hightCnt>=h) {
hightCnt = 0;
y = y0;
x++;
}
}
}
/*
*说明 利用字模软件,将取模后的字符数据存放在asciiNxN数组里,调用此函数将数组内容放在显存里
*设计思路
取模时,从空格符开始,依次对下列字符进行取模
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
code为需要显示的ascii字符,字符需要减去第32个字符即空格字符的偏移
取到字符的字模数据后,依照扫描方式,调用画点函数,存放在LcdGram显存内
若字符宽为16,高为24,则一个字符共有16*24/8=48个字节
这里用垂直扫描模式,当一列数据写Gram完成后,相应地x坐标需要加1,同时y回到起始位置
*/
void LcdDispAscii(u8 x, u8 y, u8 code, u8 size)
{
u8 charTmp,charCnt,dotCnt,charNum;
u8 y0=y;
if(size==__SIZE_08_16) {
code = code- ' ';//得到偏移后的值
charNum = 16;
} else if(size==__SIZE_16_24) {
code = code- ' ';//得到偏移后的值
charNum = 48;
} else {
return;
}
for(charCnt=0;charCnt<charNum;charCnt++)
{
if(size==__SIZE_08_16) {
charTmp=ascii8x16[code][charCnt]; //调用1608字体
} else if(size==__SIZE_16_24) {
charTmp=ascii16x24[code][charCnt]; //调用2412字体
} else {
return;
}
for(dotCnt=0;dotCnt<8;dotCnt++)
{
if(charTmp&0x01)LcdDrawPoint(x,y,1);
else LcdDrawPoint(x,y,0);
charTmp>>=1;
y++;
if((y-y0)==size)
{
y=y0;
x++;
break;
}
}
}
}
图像刷新率多少比较合适
可以算一下,如果是8080接口,8位并口,若传输一byte数据大概需要1US,那么传输速度大概是1Mbyte/S,一帧图像有4800byte数据,传输一帧图像需要 4800byte ÷ (1024 × 1024 byte/S)即4600ms,一秒大概传输 1,048,576 byte/S ÷ 4800 byte/帧即218.45帧。
一般来说,当帧率达到或超过每秒12帧时,人眼开始能够感知到连续的运动,从而看起来像是动画。但是,为了获得更加流畅和自然的动画效果,通常建议使用更高的帧率。
在标准的电影和视频制作中,常见的帧率有24fps(每秒24帧)、25fps(主要用于PAL制式的电视广播)和30fps(主要用于NTSC制式的电视广播和某些网络视频)。这些帧率已经足够高,可以产生流畅且自然的动画效果。
考虑到单片机需要执行其他任务,如果显示的是动图,这里可以设置24fps,理论上看起来是比较流畅的,且不会占用太多单片机资源。
注意:
如果使用的是常见的LCD屏幕,一般的刷新率可以达到60Hz(即60帧/秒)。但实际刷新率也可能受到其他因素的限制,例如屏幕驱动器的性能和显示数据传输速度等。
具体刷新优化方案参考以下博文: