实际应用案例:一种用于嵌入式系统的液晶显示单元设计-男人·海洋 - 新浪BLOG

来源:百度文库 编辑:神马文学网 时间:2024/04/25 16:25:34
[转载][交流]怎样在点阵屏上绘图——基于LCD12864(二)
Chapter Three
— 从“壁画”记事到“甲骨文” —
不得不承认,到目前为止,似乎 如果我们要想做一个电话号码记事本之类的电子助手已经万事俱备了,但真正开始做的时候才发现,我们还没有教会AVR如何去写字。如果说,我们前面已经能在 LCD上画出“壁画”的话,那么要想让别人看懂你记录的到底是什么鬼画符还需要一点点关于“甲骨文”的扫盲。事实上,大家约定俗成的固定大小的图片集或其 子集就是一个被尊称为字库的神圣典籍。在这个圣经里面记录的是一种被称之为“字模码”的东西,对于我们,这种信息可能相当抽象,但是借助LCD,那么字模 码就是一个我们能看懂的字符在显存中存在的模式。
关于这些字模码是如何排列的,自古以来就有数不清的模式。终于有一天,一群中国人伴随着新中国 站了起来,制定了一个叫做国标的标准(GB),根据这个标准,祖国大地的字模码才有了统一的目录,而查询这个目录的方法已经逐渐被人们所淡忘,吹落那源自 电报码的书籍红色封皮上的沧桑,用手轻轻抚摸封皮上的的文字:“区位码”,我们发现,其实他包含了6000个汉字的一级字库其他一些由非常用字组成的多级 字库。
在西方,埃尼阿克的故乡,一群依靠技术侵略世界的疯子根据自己半通不同的习惯制定了一个由128个字符组成的交换标准称之为ASCII码,由于技术大潮的冲击,世界妥协了。
……
这一切的一切都无法改变字库只不过是图片集和的本质。所以,敢于抵抗强权的人们在自己的领土上坚持着自己的信念——我们称之为“小字库技术”。甚至有些人坚持使用图片记事,那么自然的被视作是“无字库技术”。
世界在前进,即便后来世界技术的格局发生了怎样的变化,即便一些曾经约定的不合理的东西也会作为最底层的协议支持者新世界,就像是乌龟驼着的世界。任何触动这些底层的行为都会受到世界的背叛,所以,抛弃情感上的东西,我们来研究一下ACSII的构成原理和实现方法。
对不起大家,我写这些东西的目的就是面向初学者。事实上,如各位所说,我并非高手,所以很多地方漏出了类似“内存 映射”之类的马脚。这里我只想做一点解释,只有我弄懂了的东西,我才能用通俗的方法和大家解释,我一窍不通的东西就只好原样照搬打肿脸充胖子了。呵呵。还 请原谅。事实上,就拿“内存映射”这个问题来说,我使用的大段大段的文字来解释这个概念,因为即便是罗嗦,我也最多只能用“屏幕的一块区域对应内存的一块 区域”这样仍然抽象的话语来解释,反而显得我骗稿费一般,所以不如先提出一个名词,把解释溶化在后面的文字中。
本来,开篇就说得很清楚,我写这 些东西的目的不是等大家来喊牛,姑妄言之,姑妄听之,水平有限,没有刻意去追求什么文本格式上的东西,自然可能不对大家胃口,我以后注意就是了。但是,说 回来,写这些东西的心情和大家写伯克的时候差不多,多半是吐吐心中不吐不快的东西罢了,所以,由着性子,演绎也罢,说明书也罢,文档整理稿也罢,那要看那 一阵子我正在看什么书了,如果哪天我不幸开始看小说,来一个欲知后事请听下回分解也说不定。
我的专业本来就是软件工程,所以写出这些文字,非常自然。
--------------------------------
以上,就是本人借着大家的机会,公然在技术论坛上灌的水。大家五味自知哈。
最近有点忙,第三章可能会推后几天……请原谅
对不起大家,最近刚刚忙完“挑战杯”创业大赛的商业计划书、学校的一套试验系统刚刚设计交付使用、呵呵……对不住大家。我就尽力写一点,可能不会向从前那样一次写很多了,时间可能长一点,但是质量绝对不减。??
3.1 ASCII字符集
ASCII(American Standard Code for Information InterChange)——美国通用信息交换编码。他是现在流行的众多编码的榜样,虽然使用仅仅7位二进制表示(通常用一个字节表示),但是却是众多编 码系统的基础,比方说16位二进制为组成的Unicode编码,证据就是,只要在ASCII码前面加9个零就成完成了转换。当然,仍然有不听话的,比方说 IBM老大的EBCDIC码(大型计算机系统上用的)。
大家都注意到了7位二进制表示的编码显然只能有128个字符的容量,那么,用一个字节 256个字符的容量岂不是造成了浪费?于是,现在PC机上普遍通用的IBM扩展ASCII码从128~255开始扩展了128个字符——注意,这128个 字符并不是通用的,即便在我们能接触的大部分场合他们都有效,但是记住他们的“非常任理事国”的身份是拥有重大意义的。
比方说,我们的显示系统只 需要显示E文字母和数字还有一些标点符号,那么,干什么要这些无用的字符充数呢?要知道,一个字母存储起来需要至少8*7的点阵(7个字节)啊!事实上, 由于几乎所有通用单片机内部都不带有ASCII字库(字模码),所以,我们必须把他们存储起来,并且还不能打破原有的存储模式,不然通过ASCII码作为 索引我们就找不到他们了。为了完成对字模库的简化,我们需要知道他们的构成方式,然后再考虑如何去获取一个已知的标准字模库,并按照我们设计的方法去简化 他……可怜的AVR,存储器又要吃紧了。
从古老的教科书上,很容易获得一张ASCII编码表。因为大家都是搞Embedded System开发的(为了显示大家工作的高深程度,请允许我掉书袋),所以,这里我更多的要讲述一下ASCII编码一些不太被人注意的特性,一些只有从二进制角度才容易看出的特性。
1)ASCII码由7位二进制组成;
[6][5][4][3][2][1][0]
2)[6:5]为用来表示ASCII编码的组分类
控制字符组(显示不需要显示的东西)
数字字符和标点符号组
大写字符和特殊字符
小写字符和特殊字符
3)只要把大写字母的第6位也就是[5]置位就是现了到小写字母的转换,反之亦然;
4)数字的ASCII码[3:0]位的值与它要表示的数值相同;
例如:
“0”? ?? ?? ? 0x30
“1”? ?? ?? ? 0x31
依照上面的编码规则,ASCII码的字模码文件存储的方式为:
char c;
……
fAddress = c * 8 * 16;//超级简单哈,这是计算机系统上标准8*16的ASCII字符集
fAddress = c * 8 * 8; //这是计算机系统使用的8*8小字符集
聪明的大家已经知道如何在存储器中获得字模码了吧?
就是访问存储的“基地址+fAddress”就可以了。
为了便于后面大家实现汉字显示时候的代码移植(回避全角和半角问题),我们使用8*16的大字符集,至于你想使用小字符集,那么就看你的爱好了。
3.2 How to get them?
这里我们来顺手说说字模码获得的问题。
不可否认,现在网上很多兄弟写的字 模制作软件水平之高,已经到了令人叹为观止的地步,只可惜当时我学习字库问题的时候,尚且没有解决温饱,更不用说上网了。而且,这些字模软件无不在客观上 支持了字模的“无政府主义”,小字库和无字库技术在一些不恰当的场合也被大量滥用,严重影响了接着写你代码同志的心情……鬼知道原来跳槽的家伙如何定义那 该死的字库的,所以,提倡在何时的场合使用标准的字模库还是非常有必要的。
首先说说一种简单的获取字模的方法。
不知道还有多少人记得UCDOS,中国汉字操作系统的“希望”。金山WPS,CCED……TX.com,这些东西让人难忘啊。我们的字模库很容易从这样具有我国独立知识产权的系统中获得。所有的东西都放在
UCDOS\
目录下面。我们选取这次需要的文件“ASC16”。顺手说下,汉字库也可以从里面获得“HZK16”。其他的字模库在
UCDOS\FNT
目录下面。
还有一种BT的方法可以获得8*8的字库。大家记得BIOS吧……呵呵,利用TC写一个中断程序,直接读出来……哈哈。后面有机会我会附上代码,如果我能记得地址的话。
------------------------------------------------------
(本章待续)接下来,我将来聊聊对这些字库减肥。
Gorgon Meducer 来点实际的吧。
我们这些学生都等不急了。在这里读帖子的电子爱好者都不是闲着没事逛逛的。
我来加点东西这是钻石生成图:有大液晶的朋友可以看看。
//****************************************************************************
//测试椭圆的图形驱动函数
//***************************************************************************
//显示渐渐形成的钻石
void test_elli(void)
{
Uchar i,j;
for(j=0;j<0x5;j++)
{
ClrScr();
for(i=0;i<100;i=i+2)
{
ellispeMidpoint(220,120,100,i);
//ellispeMidpoint(220,120,i,100);
delay_nms(500);
}
}
}//******************************************************************************
//合并四分椭圆点
//入口参数:中心点xc,yc和长短轴 rx,ry??
//*****************************************************************************
void ellipsePlotPoints(int xc,int yc,int x,int y)
{
setPixel(xc+x,yc+y);
setPixel(xc-x,yc+y);
setPixel(xc+x,yc-y);
setPixel(xc-x,yc-y);
}
//******************************************************************************
//中点椭圆算法
//入口参数:中心点xc,yc和长短轴 rx,ry??
//*****************************************************************************
void ellispeMidpoint(int xc,int yc,int rx,int ry)
{
int rx2=rx*rx;
int ry2=ry*ry;
int tworx2=2*rx2;
int twory2=2*ry2;
int p;
int x=0;
int y=ry;
int px=0;
int py=tworx2*y;
void ellipsePlotPoints(int,int,int,int);
//plot the first set of points
ellipsePlotPoints(xc,yc,x,y);
//region1
p=round(ry2-(rx2*ry)+(0.25*rx2));
while(px{
x++;
px+=twory2;
if(p<0)
p+=ry2+px;
else
{
y--;
py-=tworx2;
p+=ry2+px-py;
}
ellipsePlotPoints(xc,yc,x,y);
}
//region2
p=round(ry2*(x+0.5)*(x+0.5)+rx2*(y-1)*(y-1)-rx2*ry2);
while(y>0)
{
y--;
py-=tworx2;
if(p>0)
p+=rx2-py;
else
{
x++;
px+=twory2;
p+=rx2-py+px;
}
ellipsePlotPoints(xc,yc,x,y);
}
}
__________________________
总是在东风无力的时候...
16M晶振跑Gorgon Meducer 先生的窗口(windows)生成算法都显太慢。填充->擦除->画框在液晶上过程的足迹太明显。
因为没有实际的显示缓冲区,没有办法的事情啊,如果你使用SRAM大的AVR芯片,那么应该可以避免画图过程明显的弊端的。
我用Mega8L填充整个换面,没有使用优化算法,直接用点画完只需要500ms左右,内部8M,所以你说16M还是慢,我不是很理解。呵呵,没有别的意思,全当交流。
还有,没有任何人是学生。你只当我在写blog好么?我没有义务给大家做任何的免费资料即便是垃圾——我只是在写Blog一样的东西。
如果我写得东西能起到抛砖引玉的作用,那是最好的。
chenbin0011能给大家谈谈你的想法么?不然真的没有交流可言了。
我用的320*240点阵液晶。mega128
那个窗口函数很通用,但重复擦写占去了很多时间。??
这样会好点吧:
Box(X-4,Y-4,X+Width-4,Y,2,1);
Box(X-4,Y-4,X,Y+Height-4,2,1);
Box(X,Y,Width+X,Height+Y,1,1);
另外:做图时只用了一个底层借口SetPix();这和RAM大小有什么联系呢。Gorgon Meducer有什么好的处理方法呢?
SetPix函数只有反显时需要知道像素的当前状态,而擦初与显示都不需理会。
基于这种考虑来实现图形界面:针对擦除区域为8的整数倍的区域(字符一般为8的整数倍,这种情况也很多见)重新编写函数,速度又可提升x倍(取决于总线宽度,和读取速度)。
擦除的时候也许要知道当前状态阿,不然会影响到同一个字节内的其他点的。
您说:

这样会好点吧:
Box(X-4,Y-4,X+Width-4,Y,2,1);
Box(X-4,Y-4,X,Y+Height-4,2,1);
Box(X,Y,Width+X,Height+Y,1,1);

我考虑到还要覆盖下面有图片的情况,不只是下面是空白的情况。
对非8的整数倍变换需要取出一个字节,对相应位变换后再写入,如果水平位置大于一个像素可同时变换多位。对垂直方向只要设光标移动方向向下,也可同样处理。不用每次只变换一个Pix.
呵呵,通用和效率很难兼顾呀。
Gorgon Meducer :再请教一下啊,31楼的不画边框怎么理解呢?和什么都不执行有什么区别呢,能给出函数吗?
to chenbin0011
你说:“
对非8的整数倍变换需要取出一个字节,对相应位变换后再写入,如果水平位置大于一个像素可同时变换多位。对垂直方向只要设光标移动方向向下,也可同样处理。不用每次只变换一个Pix.”
这种情况我在前文讨论过了。你注意看一下。

还记得黑白点阵屏幕的显存映射方式么?它简单的使用1个字节表示8个坐标点,同时这1 个字节是沿着屏幕的短边方向映射的,所以当我们想画一条垂直的直线时,对于每一个牵涉到的字节都有可能要重复的操作8次之多,这种操作不是简单的画线,而 是要先读取再计算最后再写这样的复合操作,重复8次只是为了把整个字节变黑显然是一种超级不可容忍的冗余——大家都知道,直接把这个字节读取一次,计算一 次,再写一次就完成了。基于这种思想,我们需要把这种特殊情况单独提取出来,重新优化代码……具体优化代码就留给大家做作业了哈。 ”
关于无边框,是需要的。一下的就是部分伪码。前面也是提供过的……chenbin0011可能太性急,没有注意哈。
if (BoxModel == BoxModel_NoBox)
{
if (FillType == FillType_NoFill)
{
//运行到这里说明我们被程序员耍了……什么不做到这里干什么???
return ;
}
BoxModel = FillType;
}
Line(Xbegin,Ybegin,Xend,Ybegin,BoxModel);
Line(Xbegin,Ybegin,Xbegin,Yend,BoxModel);
Line(Xbegin,Yend,Xend,Yend,BoxModel);
Line(Xend,Ybegin,Xend,Yend,BoxModel);
3.3 字库减肥
查看一下Mega8的Datasheet,上面赫然写着8K Flash,再看看ASC16的大小4K,我不知道有多少人不会打寒颤。更不用说260多K的汉字库了,看来要么外扩存储器,要么只能选择西文显示,并且对ASC16库进行减肥。
其 实,减肥并不是一个可以称之为技术的行为,总原则就是丢弃无用的部分,同时修改索引方式以保证外界使用减肥前方文字魔窟文件的索引方式不至于出错就可以 了。就拿ASC16的减肥工作来说吧,很显然,我们并不需要扩展字符128~255的那个部分,可以减小大小为16*128 = 2K的大小,再加上基本字符基的四个区中,控制字符区显然也是可以舍弃的,所以,还可以减少16*32 = 512B的大小,也就是说,剩下的文件只有1.5K大小了,哈哈,总算可以接受了。当然如果你选择了8*8的字模,就更小了。
以上完成的只是第一步,借助类似UltraEdit这样的编辑工具都可以做到,然后我们再把字模库写成数组的形式,比方说:
const char ASC_Lib[][16] = {
……
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
……
}
包含入头文件,就可以了。
下面我们需要一个访问函数,用来安全的有效的访问到我们需要的字模库:
char *getASCLIB(char ASC)
{
if ((ASC <32 ) || (ASC > 127))
{
return ASC_Lib[CharStringNULL]; //需要一个空字符串作为安全返回
}
return ASC_Lib[ASC];
}
配合12864头文件中公版中都有的那个显示图片的函数,就可以显示字符了。例如:
void DispBITMap(const char *String,char StringLength,char X,char Y,char DispModel);
我们还可以再对这个函数包装一下,实现16 * 4的文本模式。使用CLocate(x,y)来定位字符,用Print("")来显示字符串,用PrintN()来显示数字……哈哈。这一部分就留给大家自己来做了,不过是几个函数加宏定义罢了,相信自己简单的。
至于如何实现任意位置显示字符,还有一些字符特效的实现,在下一节里面具体说明。
这是我写的函数
/********************************************************
* 函数说明:读取显示数据指令              *
* 输出:  显示数据                  *
********************************************************/
char getLCD12864Data(void)
{
char TempData = 0;
char a = 0;
LCD12864_SetModel_Data;
LCD12864_SetModel_Read;
LCD12864_SetEnable;
LCD12864_SetDisable;
LCD12864_SetEnable;
asm("nop");
TempData = ReadDataPORT;
return TempData;
}