搜索
bottom↓
回复: 652

[古董贴][交流]怎样在点阵屏上绘图——基于LCD12864

  [复制链接]

出0入296汤圆

发表于 2006-3-1 21:06:00 | 显示全部楼层 |阅读模式
— 前言 —



  前言往往要解释写文章的动机和原因,同时给作者一个正题以外灌水的机会——本文也不例外。



1、为什么我要写这篇文章。

   不可否认,我的确受到了Armok的利诱影响,但是最近发生的一些事情却使我觉得写这篇文章是非常有必要的。在OurAVR上看到很多版本的LCD驱动程序,几乎每一个版本都只是简单的将全部或部分的显示数据Cover到LCD的显存上,完成一个字或者是图片的显示就等着大家喊“牛”了。其实要走的路还很远。对一个工程项目来说,增加n多的成本来提供一个点阵屏作为用户接口,不是一两幅欢迎图片和Now Loading...Please Standy By的提示能糊弄的过去的。用户希望你提供的是友好的图形界面GUI,虽然比不过XP和Apple的华丽,但是由各种基本图形组成的窗口界面还是需要的。

    当我们真的想实现一个图形界面的时候,很快就会发现,我们需要的不仅仅是一个被喊了“牛”的初级驱动,我们需要的是一个图形引擎——一个自定义的图形函数包,没有DirectX的华丽,但是能绘制一个任意的直线或是矩形就够了——结果往往发现无所适从。这个时候,我们遇到的就是一个门槛,真正的嵌入式工程师和一个业余电子爱好者之间的门槛。



2、我如何写这篇文章

   考虑到本人老王卖瓜的习惯,所以请大家一定无比在吃饭前看本人写的技术文章,同时保持耐心等待续集(绝对有续集)。本人现单身,个人问题众多,学习任务重,所以可能有时候写文章象羊拉屎,不对大家胃口,请见谅。

   硬件平台:AVR  Mega8级  

   LCD:     不带字库的12864

   软件平台:ICC

   规范:    符合基本的C编程规范



3、何时开始正文

   广告之后,马上回来……

阿莫论坛20周年了!感谢大家的支持与爱护!!

一只鸟敢站在脆弱的枝条上歇脚,它依仗的不是枝条不会断,而是自己有翅膀,会飞。

出0入296汤圆

 楼主| 发表于 2006-3-1 21:21:03 | 显示全部楼层
Chapter Zero

  — 预备知识 —

其实,本文应该算是计算机图形学的一个具体分支,所以,计算机图形学的基本要求就是本文的基本要求,考虑到各位兄弟的胃口,我就多罗嗦下。



1、位操作

    向LCD12864这种二值屏幕,我们习惯于用1个字节表示连续的8个点,1对应对应位被点亮,0表示不亮,所以对图形的操作最基本的手段就是位操作。

    复习下,常用的位操作,假设Dis表示某一个现存地址的内容

    Dis = Dis~     黑白颠倒

    Dis &= ~(1<<n) 第n处被擦去,

    Dis |= (1<<n)  第n处被画了一个点

    Dis ^= (1<<n)  如果n处是亮的,就变被擦掉;如果n处是空白的,就被点亮……

    …… 差不多就这些



2、作图原理

    点是一切光栅显示设备的基本要素,所有的操作都是以点为基础的,所以学会如何利用点构成线、圆、填充就是必须掌握的——几何不能太差哦。

    还有,结合屏幕的硬件特点,对算法进行优化的一些方法也是需要掌握的。比方说,如何填充之类的……后面会针对LCD12864作详细介绍的。



3、人机交互学

    虽然很多人都没有实实在在学过这门功课,但是多多少少对于界面应该怎样有些许了解。如何利用手中的基本操作函数做出一些特效,如何安排窗体,如何绘制图形界面的一些基本元素如按钮,甚至如何显示汉字,都是人际交互学需要教会你的——总之,如果你没有学过这门课程,你的产品只有你自己用的话——跟着感觉走,没错的。



4、最最重要的物质基础

    你要掌握一种单片机,掌握一种点阵屏幕。

    AVR Inside

    Not Only For LCD12864

出0入296汤圆

 楼主| 发表于 2006-3-1 21:22:38 | 显示全部楼层
Chapter One

— 从点滴开始 —



   前面已经说过,对于栅格显示设备来说,点是一切图形的基础,说白了,不会在液晶屏幕的任意位置随心所欲的画点就不能说你已经掌握了某种液晶屏幕的使用了。事实上,根据笔者的经验,尝试画点往往能暴露出很多问题。就拿LCD12864来说吧,一段时间,笔者总是发现,在片切换的地方有一点点错误……

   

   在说明如何画点之前先说明一个概念:显示缓冲区。

   所谓显示缓冲区,顾名思义,就是显示数据的一个缓冲地带。一般情况下,我们绘图也是直接对这块缓冲区域进行操作的。

   很多朋友就要提问了:我们为什么不直接对屏幕操作呢?

   事实上,LCD本身存在一块显示存储器,也可以被认为是一个显示缓存,直接写在这个存储器的数据并非直接显示出来,而多半需要一个显示指令来影射一下。我们有时候也通过这种类似的技术来实现很大的图片的显示——先画好,再拿给大家看,让人以为你是一下就画好的,当然如果你刻意需要那种图片被“画”出来的效果,则不在讨论之列。问题就在于,LCD是片外资源,对其存储器的访问可以被认为是对片外存储器的访问,其速度显然没有对片内SRAM的操作速度要快。如果我们使用那种常用的串行方式来作图(所谓串行方式作图,就是绘图指令的执行和系统的其他操作时串行的,指令不完成,其它操作就不回被执行),那么对于一些实时性要求高的系统来说就会造成重大隐患——甚至是不符合要求的;如果开辟一段片内存储空间和LCD的存储器一一对应,在相同的时间段内,花费相同的资源来保持着两个存储空间的一致性,那么就可以保证实时系统的稳定和可靠,保证画面显示的正常(因为允许跳帧嘛^_^)。这就是我们为什么需要另外在片内选取一个显示缓冲区的原因。

   以后,我们的操作都是针对显示缓冲区的。显示缓冲区将成为一个概念,无论这个缓冲区位于SRAM内还是LCD内部。比方说,我们需要一个低成本的游戏机——如做一个贪食蛇游戏机送给老师作为课程设计,或者送给小侄子,那么,M8甚至是M48成为首选,问题是,M8绝对无法开辟出一个8 * 128 = 1K大小的数组,所以,这个时候,认定LCD的片内存储器作为显示缓冲区就是不二的选择——好在贪食蛇速度太快了也没有办法玩哈。



   绕了这么多的弯子,究竟如何画点呢?我还想多补充一个原因,关于,为什么需要显示缓冲区,由于点阵屏幕使用的是1个字节来表示8个点的,如果你想使得一个字节中的某一个点被操作而不影响别的点,你起码要知道原来这个位置是什么内容,对于有些使用595级联的LCD设备来说,无法直接从LCD读取现存数据,所以开辟一个缓冲区,从中获得信息加以加工以后再放回去,是这种设备处理显示图形的唯一方法——当然我们没有那么惨,但是从LCD中获取所需点所在字节的内容还是必须的,不然你画一个点就会破坏一条线上所有的数据。



  好的,那么我们实际上已经明确了如何去画一个点。

  首先,根据用户给出的坐标计算出所要画的点所在现存内的地址偏移量;

  然后,我们读出该地址内的数据;

  再次,根据用户所需画点的类型(擦除、画点、反相点)来进行相应的操作获得一个新的数据;

  最后,将该数据写回原来的地址。



  不是很难吧?

---------------------------------------------

LCD12864补充知识

1、关于坐标系。很多人,包括笔者,一开始都看不懂LCD12864的内存影射方式,感觉X、Y似乎不是那么回事。后来才发现,只要把屏幕竖着放一切就好懂了。X还是横轴,Y还是竖轴……但是这显然不符合我们的习惯,我们习惯于长的那个边作为横轴,所以需要一点点坐标之间的转换。

假设输入的是正常习惯的坐标 X,Y DX DY就是LCD上的坐标,那么转换关系是



char DX = (Y >> 3);                                  //计算出属于哪个字节

char BX = Y - (DX << 3);                             //属于该字节的哪个位

char DY = X;



2、读取12864的数据的时候,一定要注意,E信号要在一个下降延之后持续拉高,然后才能正常独处数据;假设直接拉高,的确也能读出数据,但是,等着抓头皮,发帖子“[跪求]大侠帮忙关于12864——请使用明确的大标题……”^_^



---------------------------------------------

废话少说(说的不少了),看源代码:

# define LCD12864_Graphic_Draw            0x01

# define LCD12864_Graphic_Clear           0x00

# define LCD12864_Graphic_Not             0x02



……



void LCD12864Draw(char X,char Y,char Type)

{

    char DX = (Y >> 3);                                  //计算出属于哪个字节

        char BX = Y - (DX << 3);                        //计算出属于字节哪一位

        char TempData = 0;



        LCD12864_ChooseBoth;

       

        setX(DX);

        if (X > 63)

        {

            LCD12864_ChooseCS2;

                X -= 64;

        }

        else

        {

            LCD12864_ChooseCS1;

        }

        setY(X);

       

        TempData = getLCD12864Data();

       

        switch (Type)

        {

            case LCD12864_Graphic_Clear:

                    TempData &= ~(1<<BX);

                    break;

                case LCD12864_Graphic_Not:

                    TempData ^= (1 << BX);

                    break;

                default:

                    TempData |= (1 << BX);

        }

       

    setY(X);

       

        sendDataToLCD(TempData);

}

出0入296汤圆

 楼主| 发表于 2006-3-1 21:23:22 | 显示全部楼层
特别说明一下,关于贪食蛇范例的问题,这篇文章里面只会简单得提及一下。



作为嵌入式系统开发的一个范例,我会另外开一个帖子详细说明开发过程。

这个范例将作为介绍嵌入式系统开发方法的一个很好的例子,用于解释一个系统和一段表示您调通了某一个功能的代码之间有什么区别,同时也将介绍嵌入式开发系统的几种模式(超级循环、调度器),顺便侃一侃时间驱动的系统RTOS (Real Time Operation System实时操作系统)和RTS(Real Time System)实时系统。
头像被屏蔽

出0入0汤圆

发表于 2006-3-2 07:22:06 | 显示全部楼层
谢谢分享。



已经帮你整理好上面的内容的位置了。

出0入0汤圆

发表于 2006-3-2 08:26:14 | 显示全部楼层
如果仅点亮LCD的算牛的话,这个就是牛魔王了,呵呵!

出0入4汤圆

发表于 2006-3-2 08:32:17 | 显示全部楼层
我现在也正在写12864的驱动,用m128,CVAVR。以后要多向Gorgon请教。

出0入296汤圆

 楼主| 发表于 2006-3-2 10:22:43 | 显示全部楼层
Chapter Two

— 一线天 —



[本章导读]



    直线由点构成,更精确的说,直线是由靠近这条线的像素构成。这就引出一个问题,究近那些点算是靠近一条直线;哪些点不算是靠近一条直线,这必须使用一种算法作为依据。实际上,图形学算法和纯几何算法还是有很大差别的,问题就出在一个离散化上面,说白了,你画出的直线很可能是一组波动厉害的锯齿象素群而不是一条看起来有规则变化的直线。

    当然,太过于理论的东西对我们是没有多少实际价值的。下面,我就介绍两种常见的画线思路,一种就是最容易被想到的直线方程计算的方法,另外一种则是被称为布兰森汉姆(Bresenham)的计算机图形学主流算法。

   

    在介绍完这两种算法以后,我们会针对LCD12864的硬件结构为例子,介绍,具体算法的实现和优化。
-----此内容被Gorgon Meducer于2006-03-02,10:28:04编辑过

出0入0汤圆

发表于 2006-3-2 10:34:07 | 显示全部楼层
顶!

出0入0汤圆

发表于 2006-3-2 10:57:01 | 显示全部楼层
1、关于坐标系。很多人,包括笔者,一开始都看不懂LCD12864的内存影射方式,感觉X、Y似乎不是那么回事。后来才发现,只要把屏幕竖着放一切就好懂了。X还是横轴,Y还是竖轴……但是这显然不符合我们的习惯,我们习惯于长的那个边作为横轴,所以需要一点点坐标之间的转换。

假设输入的是正常习惯的坐标 X,Y DX DY就是LCD上的坐标,那么转换关系是



char DX = (Y >> 3);                                  //计算出属于哪个字节

char BX = Y - (DX << 3);                             //属于该字节的哪个位

char DY = X;



首先,先表示一下对Gorgon Meducer 傻孩子大侠的敬仰!!

然后,我想请大侠能不能将关于坐标系的问题图示一下,让小弟这个菜鸟更明白一点呢?

小弟为这个问题犯愁呢!!

还有就是“E信号要在一个下降延之后持续拉高,然后才能正常独处数据;假设直接拉高,的确也能读出数据”,持续拉高与直接拉高的区别!!

出0入296汤圆

 楼主| 发表于 2006-3-2 11:07:45 | 显示全部楼层
首先,我们从最基本的数学算法说起。

   如果我们使用公式y = kx + b来作为绘图的依据,那么就需要分3种情况:水平直线,斜率为0;垂直直线,斜率为五穷达(或者说k不存在);普通直线。

   假设我们已经知道直线的起始坐标点(Xbegin,Ybegin)和终点(Xend,Yend),x,y,是当前的坐标点,如果我们通过增加x反算出y的方法的话,这个公式就可以很容易转换为伪代码。

   LineMode 为直线的类型:水平,垂直,普通

   if Xbegin == Xend then LineMode = 水平

   elseif Ybegin = Yend  then LineMode = 垂直

   else k = (Yend - Ybegin) / (Xend - Xbegin)



   switch LineMode

       case 水平

          for x = Xbegin to Xend

             在x,Ybegin处画点

       case 垂直

          for y = Ybegin to Yend

             在Ebegin,y处画点

       default:

          for x = Xbegin to Xend

          {

             y = kx + b

             在x,y处画点

          }



    非常简单,不是么?注意,大部分情况下,这个算法存在很多乘法和除法,对单片机系统来说,可能不是那么合适哦。画出的点也基本令人满意。

出0入296汤圆

 楼主| 发表于 2006-3-2 11:26:12 | 显示全部楼层
有了上面的代码垫底,我想很多人都可以放心了,因为大不了跑一个高速晶振也能快速的画出直线,否则你还需要耐心的阅读下面的算法。



   首先,我们给出这个算法的伪代码。

   在(x1,y1)到(x2,y2)之间画一条直线

   

   dx 是x到终点横坐标的距离

   dy 是y到终点纵坐标的距离

   ix 是dx的绝对值

   iy 是dy的绝对值

   inc是dx和dy中较大的那个

   plot是是否要画一个点的标志位,boolean变量



   plotx 是当前点所在的横坐标

   ploty 是当前点所在的纵坐标



   plotx = x1

   ploty = y1  

   x = 0

   y = 0



   在 plotx,ploty画一个点——起点

   

   for i = 0 to inc 增量1

       x += ix

       y += iy

       plot = false



       if x > inc then

           plot = true

           x -= inc

               if dx > 0 then plotx ++

               if dx < 0 then plotx --



       if y > inc then

           plot = true

           y -= inc

               if dy > 0 then ploty ++

               if dy < 0 then ploty --



       if plot == true then 在(plotx,ploty)处画点

   这就是计算机图形学中流行的布兰森汉姆(Bresenham)算法,他的意图就是采用离散的整数增量来代替斜率增量计算,学习这个算法,最好的方法不是看多少理论,而是按照上面的伪代码自己完成一条直线的绘制工作,你就能心领神会了——不是小弟我偷懒。



   所有的计算都是简单得整数计算,代码效率自然不用小弟我罗嗦哈。

出0入296汤圆

 楼主| 发表于 2006-3-2 11:42:16 | 显示全部楼层
俗语说,巧妇难为无米之炊,有了点再有了直线算法,距离窗体的绘制不远了,But Stop! “没有最好,但求更好”这是我们做系统开发应该谨记的一条准则。

    有了上面的算法还不够,毕竟,他们只是一些伪代码,针对不同的屏幕——准确地说,是针对不同的显存映射方式,有不同的算法优化方法。究竟有哪些优化方法暂且不论,其实他们都是一个原理,我想先解释一下,为什么我们需要优化算法,或者说,我们需要先弄清楚是什么地方产生了冗余。



    还记得黑白点阵屏幕的显存映射方式么?它简单的使用1个字节表示8个坐标点,同时这1个字节是沿着屏幕的短边方向映射的,所以当我们想画一条垂直的直线时,对于每一个牵涉到的字节都有可能要重复的操作8次之多,这种操作不是简单的画线,而是要先读取再计算最后再写这样的复合操作,重复8次只是为了把整个字节变黑显然是一种超级不可容忍的冗余——大家都知道,直接把这个字节读取一次,计算一次,再写一次就完成了。基于这种思想,我们需要把这种特殊情况单独提取出来,重新优化代码……具体优化代码就留给大家做作业了哈。

   

   同时补充一下,这样做,不是只对画线作了优化,事实上,在后面,矩形的填充里面,这种优化会极大地提高速度,因为填充的本质就是画线(点)……



   ------------------------

   例子程序:(这里就不附送了,为了大家不偷懒哈,最后一起会都给大家的)



   (Chapter One 完)


-----此内容被Gorgon Meducer于2006-03-02,11:44:36编辑过

出0入296汤圆

 楼主| 发表于 2006-3-2 11:47:56 | 显示全部楼层
To :lanxieyu

  所谓“E信号要在一个下降延之后持续拉高,然后才能正常独处数据;假设直接拉高,的确也能读出数据”就是:



E信号在一个下降延之后持续拉高:

  SETBIT_E;

  CLRBIT_E;

  SETBIT_E;



直接拉高:

  SETBIT_E;

出0入0汤圆

发表于 2006-3-3 19:45:22 | 显示全部楼层
多谢Gorgon Meducer 傻孩子指教!!

出5入0汤圆

发表于 2006-3-8 22:11:54 | 显示全部楼层
怎么不见,Gorgon Meducer 傻孩子授课了??

出0入0汤圆

发表于 2006-3-8 23:39:08 | 显示全部楼层
喜欢这种风格的文章,请继续。。。

出10入120汤圆

发表于 2006-3-9 12:01:04 | 显示全部楼层
看了楼主的文章,写的不错,对初学者是非常有益的文章。

出0入296汤圆

 楼主| 发表于 2006-3-9 13:17:42 | 显示全部楼层
Chapter Two

— Windows的世界 —



    [本章导读]



    有一种技术被众人传说为GUI,神秘高深的程度不可言表,就连书店里面的电脑书籍封面上一旦印刷上这几个字就会身价百倍——当然,也不乏像笔者这样买回去以后大呼上当的。我们今天不说桌面系统(Desktop System),笔者只是想戳穿一下GUI的虚伪表面,让书店生意冷淡下——方解心头之恨。

    那么什么是GUI呢?就是图形界面接口(Graphic User Interface),说得通俗就是,一个方便人们和电脑打交道的傻瓜工具。一个简单的道理大家都懂得,文字说不清楚的事情,往往图形和表格能够让人一目了然。所以GUI对于电脑和人类交往起到一个平台的作用。

    说了这么多,我想说明一个问题,就是什么时候我们需要GUI系统。

    1、需要表达的信息和数据相当众多,关系用文字很难表达清楚;

    2、本身就存在大量图形的系统;

    3、没有任何追求花哨目的的系统开发;

    4、用户群体复杂,非固定群体,或者这一群体需要有好清晰的用户界面。

    苏东坡写诗文尚且追求让家里的老婆婆能懂得一二,何况我们花费大量心血的嵌入式系统(Embedded System)呢?大部分时候,一个由花哨的窗体和图标组成的窗口远不及一个句请稍候让人心中要坦然的许多。



    本章的内容,我们将首先介绍窗体的基本实现方法;然后我们将介绍一种非常适合LCD系统的美术窗体;最后我们将针对LCD12864举例说明窗体里面的一些实现的细节。

出0入0汤圆

发表于 2006-3-9 13:59:19 | 显示全部楼层

出0入296汤圆

 楼主| 发表于 2006-3-9 14:11:33 | 显示全部楼层
首先,我们来看看一个立体的窗口是如何组成的,请看图片:



    这就是一个GUI系统中经典窗口风格的原理——被放大了N倍的事实:只不过是一些表现明暗效果的线条和表示窗体轮廓的边框罢了。没有了这些神秘,那么我们如何在LCD12864这样的二值屏幕上面实现这一效果呢?首先,我要说,因人而异,每个人的审美观不同,各有各的设计思路,我只说说我的建议和看法。

    因为LCD12864这样的屏幕时二值屏,只有两种颜色,亮或者灭,那么,就无法通过灰度的颜色来表现窗口的实体,那么一个表现窗口轮廓的方框就是必须的,同时,通过简单的画黑色线条来表现明暗就已经足够了。甚至有些时候,窗口方框可以直接忽略,只要一个表现明暗的黑色线条就可以了。实际上,窗口和按钮是一个同一个东西(从绘图角度来说),当按钮被按下的时候,就改变黑色线条的位置就可以了——Very Easy ,isn`t it?


-----此内容被Gorgon Meducer于2006-03-09,14:13:38编辑过

出0入0汤圆

发表于 2006-3-9 14:33:10 | 显示全部楼层
好贴。。。。

出0入296汤圆

 楼主| 发表于 2006-3-9 16:40:09 | 显示全部楼层
然而,以我个人的脾气来说,什么样的系统配合什么等级的Interface(接口)。一块使用12864点阵屏幕的图形系统往往使用简单的框框和反相显示的黑色条条就已经能够实现功能而且美观大方了,一个有明暗效果的窗体不仅浪费资源而且,会给用户一个画面拥挤的感觉。

   下面我们就简单说一下适用于二值屏幕的一种常用图形界面风格。

出0入296汤圆

 楼主| 发表于 2006-3-9 17:10:32 | 显示全部楼层
不管您相信与否,这种菜单实现仅仅是一个函数多次使用的结果(除了显示汉字的)

我们将使用伪代码的形式写一下这个函数的声明形式:

    void BOX(char Xbegin,char Ybegin,char Xend,char Yend,char BoxModel,char FillType)



    我们一一来说明一下函数的各个参数:

    Xbegin                       方框左上角横坐标

    Ybegin                       方框左上角纵坐标

    Xend                         方框右下角横坐标

    Yend                         方框右下角纵坐标

    BoxModel                     画方框的模式

            其中 画方框有以下的宏定义表示的模式

            # define BoxModel_Draw     0x01    //画边框

            # define BoxModel_NoBox    0x03    //无边框

            # define BoxModel_Clear    0x00    //画白色边框

            # define BoxModel_Not      0x02    //边框反显

    FillType                     矩形填充模式

            其中,矩形填充模式有以下的宏定义

            # define FillType_Fill     0x01    //填充黑色

            # define FillType_Clear    0x00    //填充白色

            # define FillType_Not      0x02    //填充反色

            # define FillType_NoFill   0x03    //不填充



    为了方便说明图片上的效果是如何实现的,我使用伪代码简单举个例子,同时假设,我们拥有一个函数可以显示汉字Print(char x,char y,"String");



    窗体:

    Box(2,2,100,64,BoxModel_NoBox,FillType_Clear);   //清扫出一片空地

    Box(2,2,100,64,BoxModel_Draw,FillType_NoFill);   //窗体轮廓

    Print(4,4,"标题栏");

    Box(3,3,99,22,BoxModel_NoBox,FillType_Not);      //反显效果,注意坐标的细节

    ……具体如法炮制



    说明窗口:

    关于说明窗口,我想,我们可以写一个函数了,就像Windows编程中的控件一样来用。



    void Window(char X,char Y,char Width,char Height)

    {

        Box(X+6,Y+6,X+Width+6,Y+Height+6,BoxModel_NoBox,FillType_Fill);  //黑色阴影

        Box(X,Y,Width+X,Height+Y,BoxModel_NoBox,FillType_Clear);         //清扫空地

        Box(X,Y,Width+X,Height+Y,BoxModel_Draw,FillType_NoFill);         //画框框

    }



    大功告成,是不是很简单啊?^_^

出0入0汤圆

发表于 2006-3-9 17:24:05 | 显示全部楼层
多谢Gorgon Meducer 的无私奉献!

出0入296汤圆

 楼主| 发表于 2006-3-9 17:40:35 | 显示全部楼层
谢谢大家支持,Chapter Two未完待续……

To be continue...

出0入0汤圆

发表于 2006-3-9 17:50:38 | 显示全部楼层




楼主你看是不是这个意思?

出0入0汤圆

发表于 2006-3-9 21:31:32 | 显示全部楼层
嘿嘿~

张科娃厉害哦~

我都还只会画点简单图片,你两个把GUI都搞出来了啊

出0入0汤圆

发表于 2006-3-10 08:43:36 | 显示全部楼层
期待下回解说……

出0入0汤圆

发表于 2006-3-10 10:51:59 | 显示全部楼层
哦,原来是李科哈~~

好久不见了,现在在深圳混得咋样?

出0入296汤圆

 楼主| 发表于 2006-3-12 14:05:11 | 显示全部楼层
前面说了那么多的废话,所有问题似乎都集中到一个函数Box的编写上了。那么在LCD12864和其他同类屏幕上如何实现这个函数呢?我们把这个问题分成两个小的问题来解决:一个是矩形边框的绘制;另外一个问题自然就是填充了。

    依然是那句老话,像素点是构成光栅图形的基本元素,如果不考虑速度的问题,那么直接使用画点的方法来实现边框和填充也是毫无问题的,但嵌入式系统中最终要的恰恰就是效率。

    首先,我们来说说画外框的问题。还记得我们之前的画线函数么?我想很多人应该都试验成功了吧。同样,我们在这就先不给出源代码了,但是我们需要一个便于交流的函数声明,在这里我就自作主张的规定一个画线函数:

    void Line(char Xbegin,char Ybegin,char Xend,char Yend,char LineModel)

    同样对应LineModel我们规定一下宏定义

    # define LineModel_Draw    0x01                       //画线

    # define LineModel_Clear   0x00                       //清除直线

    # define LineModel_Not     0x02                       //反相直线

    很显然这些参数对应着原先的画点函数和优化过的画线函数。那么,Box函数的一个片断就可以写成下面的模式

    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);



    这样我们就完成了边框的绘制,而且显然这个函数由于Line函数本身就是经过优化的,那么这个函数也可以说是拥有初步速度优化的。



    下面,我们来说说简单的矩形填充,关于矩形填充,应该是填充技术中最简单的一种,因为填充对象的形状属性和显存的布局属性具有相似性,所以我们可以略过复杂的边界检测,直接对显存进行填充。废话少说,直接看代码:

    //假定前面我们已经声明了一个临时变量n

    for(n = Xbegin;n<=Xend;n++)

    {

        Line(n,Ybegin+1,n,Yend -1 ,FillType);   //至于为什么画竖线而不是横线,你们可以考虑下下

    }

    现在真相大白,也许你突然明白原先几何书上关于“点动成线,线动成面”的道理了吧。哈哈哈哈……

    顺便说一句,这段代码存在明显可以优化的地方,具体是什么呢?大家留作思考题吧。

--------------------------------------------------------

    [源代码]:为了大家不偷懒,我就先不具体给出了,大家加油哦。

   

    (Chapter Two 完)


-----此内容被Gorgon Meducer于2006-03-12,14:09:01编辑过

出0入296汤圆

 楼主| 发表于 2006-3-20 18:20:04 | 显示全部楼层
Chapter Three

— 从“壁画”记事到“甲骨文” —

    不得不承认,到目前为止,似乎如果我们要想做一个电话号码记事本之类的电子助手已经万事俱备了,但真正开始做的时候才发现,我们还没有教会AVR如何去写字。如果说,我们前面已经能在LCD上画出“壁画”的话,那么要想让别人看懂你记录的到底是什么鬼画符还需要一点点关于“甲骨文”的扫盲。事实上,大家约定俗成的固定大小的图片集或其子集就是一个被尊称为字库的神圣典籍。在这个圣经里面记录的是一种被称之为“字模码”的东西,对于我们,这种信息可能相当抽象,但是借助LCD,那么字模码就是一个我们能看懂的字符在显存中存在的模式。

    关于这些字模码是如何排列的,自古以来就有数不清的模式。终于有一天,一群中国人伴随着新中国站了起来,制定了一个叫做国标的标准(GB),根据这个标准,祖国大地的字模码才有了统一的目录,而查询这个目录的方法已经逐渐被人们所淡忘,吹落那源自电报码的书籍红色封皮上的沧桑,用手轻轻抚摸封皮上的的文字:“区位码”,我们发现,其实他包含了6000个汉字的一级字库其他一些由非常用字组成的多级字库。

    在西方,埃尼阿克的故乡,一群依靠技术侵略世界的疯子根据自己半通不同的习惯制定了一个由128个字符组成的交换标准称之为ASCII码,由于技术大潮的冲击,世界妥协了。

    ……

    这一切的一切都无法改变字库只不过是图片集和的本质。所以,敢于抵抗强权的人们在自己的领土上坚持着自己的信念——我们称之为“小字库技术”。甚至有些人坚持使用图片记事,那么自然的被视作是“无字库技术”。



    世界在前进,即便后来世界技术的格局发生了怎样的变化,即便一些曾经约定的不合理的东西也会作为最底层的协议支持者新世界,就像是乌龟驼着的世界。任何触动这些底层的行为都会受到世界的背叛,所以,抛弃情感上的东西,我们来研究一下ACSII的构成原理和实现方法。

出0入0汤圆

发表于 2006-3-20 22:31:16 | 显示全部楼层
不得不说,个人感觉,这样的讲_法,对于初学者,太深了;对于有一定基础的开发者,又太碎太慢了,尤其到了 40 楼居然开始聊新中国和技术侵略……



sigh……索性改叫“GUI 通俗演义[热烈连载中,吐血跪求各位多多砸票推荐]”然后发到起点VIP去算了

出0入0汤圆

发表于 2006-3-21 08:20:06 | 显示全部楼层
受益菲浅。

出0入0汤圆

发表于 2006-3-21 08:58:12 | 显示全部楼层
不可否认,大虾水平很高,但是不要故弄深沉,别弄些“内存映射”之类的词来吓唬初学者,又不是vc编程。有句老话,能把复杂的东西描述的简单才算高手。

出0入0汤圆

发表于 2006-3-21 09:12:01 | 显示全部楼层
呵呵,怎么和我们做的工作都是一样的,不过佩服老大您把资料全整理出来了.我以前是做Windows程序开发的,特别重视通用性,所以开发思路是完全和你一样的.

我的显示驱动是通用的,只需要实现一个显存到液晶的接口即可以挂在各种液晶屏上.看来你把我的工作全整理出来了.呵呵.

出0入0汤圆

发表于 2006-3-21 10:03:35 | 显示全部楼层
支持、顶。

请断续!

出0入296汤圆

 楼主| 发表于 2006-3-21 12:56:32 | 显示全部楼层
对不起大家,我写这些东西的目的就是面向初学者。事实上,如各位所说,我并非高手,所以很多地方漏出了类似“内存映射”之类的马脚。这里我只想做一点解释,只有我弄懂了的东西,我才能用通俗的方法和大家解释,我一窍不通的东西就只好原样照搬打肿脸充胖子了。呵呵。还请原谅。事实上,就拿“内存映射”这个问题来说,我使用的大段大段的文字来解释这个概念,因为即便是罗嗦,我也最多只能用“屏幕的一块区域对应内存的一块区域”这样仍然抽象的话语来解释,反而显得我骗稿费一般,所以不如先提出一个名词,把解释溶化在后面的文字中。

    本来,开篇就说得很清楚,我写这些东西的目的不是等大家来喊牛,姑妄言之,姑妄听之,水平有限,没有刻意去追求什么文本格式上的东西,自然可能不对大家胃口,我以后注意就是了。但是,说回来,写这些东西的心情和大家写伯克的时候差不多,多半是吐吐心中不吐不快的东西罢了,所以,由着性子,演绎也罢,说明书也罢,文档整理稿也罢,那要看那一阵子我正在看什么书了,如果哪天我不幸开始看小说,来一个欲知后事请听下回分解也说不定。

    我的专业本来就是软件工程,所以写出这些文字,非常自然。



--------------------------------

    以上,就是本人借着大家的机会,公然在技术论坛上灌的水。大家五味自知哈。

    最近有点忙,第三章可能会推后几天……请原谅
-----此内容被Gorgon Meducer于2006-03-21,13:17:02编辑过

出0入0汤圆

发表于 2006-3-21 14:35:15 | 显示全部楼层
期待第三章ing...

出0入0汤圆

发表于 2006-3-21 22:10:31 | 显示全部楼层
个人建议,不妨试试看用mcu实现x-server,通用性一流,主体程序又都开源免费,只是代码量大了点,估计avr的话,就算m2560也很难完整实现一个标准的x协议子集……

出0入0汤圆

发表于 2006-3-22 08:41:55 | 显示全部楼层
呵呵," 内存映射 ",其实大家也了解的,我觉的大虾不要在乎我的话,毕竟你能费心费力整理文字,无丝竹之乱耳,有案牍之劳行,我们大家其实都是支持你继续写下去的.

出0入296汤圆

 楼主| 发表于 2006-3-22 13:09:54 | 显示全部楼层
“不要紧,不要紧

休息,休息一下……”

出0入0汤圆

发表于 2006-3-22 15:08:33 | 显示全部楼层
我们等不几了,快点放些代码出来

出0入0汤圆

发表于 2006-3-22 22:03:13 | 显示全部楼层
不能说牛了,我只有佩服++++++++++++++!!!!!

出0入0汤圆

发表于 2006-3-31 23:31:59 | 显示全部楼层
太好了,受益匪浅,期待

出0入0汤圆

发表于 2006-4-1 11:28:12 | 显示全部楼层
傻孩子 休息好了吗?都快十天了,要讲课了,学生准备听课了.

出0入296汤圆

 楼主| 发表于 2006-4-1 22:15:23 | 显示全部楼层
对不起大家,最近刚刚忙完“挑战杯”创业大赛的商业计划书、学校的一套试验系统刚刚设计交付使用、呵呵……对不住大家。我就尽力写一点,可能不会向从前那样一次写很多了,时间可能长一点,但是质量绝对不减。

出0入296汤圆

 楼主| 发表于 2006-4-1 22:32:04 | 显示全部楼层
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,存储器又要吃紧了。

出0入296汤圆

 楼主| 发表于 2006-4-1 22:46:03 | 显示全部楼层
从古老的教科书上,很容易获得一张ASCII编码表。因为大家都是搞Embedded System开发的(为了显示大家工作的高深程度,请允许我掉书袋),所以,这里我更多的要讲述一下ASCII编码一些不太被人注意的特性,一些只有从二进制角度才容易看出的特性。

    1)ASCII码由7位二进制组成;

    [6][5][4][3][2][1][0]

    2)[6:5]为用来表示ASCII编码的组分类

       0    0        控制字符组(显示不需要显示的东西)

       0    1        数字字符和标点符号组

       1    0        大写字符和特殊字符

       1    1        小写字符和特殊字符

    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的大字符集,至于你想使用小字符集,那么就看你的爱好了。

   


-----此内容被Gorgon Meducer于2006-04-01,22:48:11编辑过

出0入296汤圆

 楼主| 发表于 2006-4-1 23:07:07 | 显示全部楼层
3.2 How to get them?

    这里我们来顺手说说字模码获得的问题。

    不可否认,现在网上很多兄弟写的字模制作软件水平之高,已经到了令人叹为观止的地步,只可惜当时我学习字库问题的时候,尚且没有解决温饱,更不用说上网了。而且,这些字模软件无不在客观上支持了字模的“无Z.F.主义”,小字库和无字库技术在一些不恰当的场合也被大量滥用,严重影响了接着写你代码同志的心情……鬼知道原来跳槽的家伙如何定义那该死的字库的,所以,提倡在何时的场合使用标准的字模库还是非常有必要的。

    首先说说一种简单的获取字模的方法。

    不知道还有多少人记得UCDOS,中国汉字操作系统的“希望”。金山WPS,CCED……TX.com,这些东西让人难忘啊。我们的字模库很容易从这样具有我国独立知识产权的系统中获得。所有的东西都放在

      UCDOS\

      目录下面。我们选取这次需要的文件“ASC16”。顺手说下,汉字库也可以从里面获得“HZK16”。其他的字模库在

      UCDOS\FNT

      目录下面。



    还有一种BT的方法可以获得8*8的字库。大家记得BIOS吧……呵呵,利用TC写一个中断程序,直接读出来……哈哈。后面有机会我会附上代码,如果我能记得地址的话。



------------------------------------------------------

     (本章待续)接下来,我将来聊聊对这些字库减肥。

   
-----此内容被Gorgon Meducer于2006-04-01,23:09:27编辑过

出0入0汤圆

发表于 2006-4-2 09:15:35 | 显示全部楼层
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<py)

   {

    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);

    }

}

出0入0汤圆

发表于 2006-4-2 09:23:49 | 显示全部楼层
16M晶振跑Gorgon Meducer 先生的窗口(windows)生成算法都显太慢。填充->擦除->画框在液晶上过程的足迹太明显。

出0入296汤圆

 楼主| 发表于 2006-4-2 13:44:52 | 显示全部楼层
因为没有实际的显示缓冲区,没有办法的事情啊,如果你使用SRAM大的AVR芯片,那么应该可以避免画图过程明显的弊端的。

    我用Mega8L填充整个换面,没有使用优化算法,直接用点画完只需要500ms左右,内部8M,所以你说16M还是慢,我不是很理解。呵呵,没有别的意思,全当交流。

    还有,没有任何人是学生。你只当我在写blog好么?我没有义务给大家做任何的免费资料即便是垃圾——我只是在写Blog一样的东西。

出0入296汤圆

 楼主| 发表于 2006-4-2 13:48:17 | 显示全部楼层
如果我写得东西能起到抛砖引玉的作用,那是最好的。

    chenbin0011能给大家谈谈你的想法么?不然真的没有交流可言了。

出0入0汤圆

发表于 2006-4-2 14:15:28 | 显示全部楼层
我用的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有什么好的处理方法呢?

出0入0汤圆

发表于 2006-4-2 14:38:06 | 显示全部楼层
SetPix函数只有反显时需要知道像素的当前状态,而擦初与显示都不需理会。

基于这种考虑来实现图形界面:针对擦除区域为8的整数倍的区域(字符一般为8的整数倍,这种情况也很多见)重新编写函数,速度又可提升x倍(取决于总线宽度,和读取速度)。
-----此内容被chenbin0011于2006-04-02,14:58:04编辑过

出0入296汤圆

 楼主| 发表于 2006-4-2 18:02:02 | 显示全部楼层
擦除的时候也许要知道当前状态阿,不然会影响到同一个字节内的其他点的。



您说:



这样会好点吧:

    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);





我考虑到还要覆盖下面有图片的情况,不只是下面是空白的情况。
-----此内容被Gorgon Meducer于2006-04-02,18:02:47编辑过

出0入0汤圆

发表于 2006-4-2 18:41:22 | 显示全部楼层
对非8的整数倍变换需要取出一个字节,对相应位变换后再写入,如果水平位置大于一个像素可同时变换多位。对垂直方向只要设光标移动方向向下,也可同样处理。不用每次只变换一个Pix.

    呵呵,通用和效率很难兼顾呀。

    Gorgon Meducer :再请教一下啊,31楼的不画边框怎么理解呢?和什么都不执行有什么区别呢,能给出函数吗?

出0入0汤圆

发表于 2006-4-2 22:49:38 | 显示全部楼层
建议ARMOK好好整理一下,防在精品文章里,

出0入296汤圆

 楼主| 发表于 2006-4-3 20:35:21 | 显示全部楼层
to chenbin0011

你说:“

对非8的整数倍变换需要取出一个字节,对相应位变换后再写入,如果水平位置大于一个像素可同时变换多位。对垂直方向只要设光标移动方向向下,也可同样处理。不用每次只变换一个Pix.”



这种情况我在前文讨论过了。你注意看一下。



还记得黑白点阵屏幕的显存映射方式么?它简单的使用1个字节表示8个坐标点,同时这1个字节是沿着屏幕的短边方向映射的,所以当我们想画一条垂直的直线时,对于每一个牵涉到的字节都有可能要重复的操作8次之多,这种操作不是简单的画线,而是要先读取再计算最后再写这样的复合操作,重复8次只是为了把整个字节变黑显然是一种超级不可容忍的冗余——大家都知道,直接把这个字节读取一次,计算一次,再写一次就完成了。基于这种思想,我们需要把这种特殊情况单独提取出来,重新优化代码……具体优化代码就留给大家做作业了哈。 ”

出0入296汤圆

 楼主| 发表于 2006-4-3 20:38:55 | 显示全部楼层
关于无边框,是需要的。一下的就是部分伪码。前面也是提供过的……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);

出0入296汤圆

 楼主| 发表于 2006-4-3 23:30:33 | 显示全部楼层
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()来显示数字……哈哈。这一部分就留给大家自己来做了,不过是几个函数加宏定义罢了,相信自己简单的。

    至于如何实现任意位置显示字符,还有一些字符特效的实现,在下一节里面具体说明。

出0入0汤圆

发表于 2006-4-3 23:39:41 | 显示全部楼层
楼主辛苦了!

顶一个!

出0入0汤圆

发表于 2006-4-6 00:08:58 | 显示全部楼层
楼主辛苦了!

顶一个!

出0入0汤圆

发表于 2006-4-11 12:39:54 | 显示全部楼层
沉了,捞一下。

期待3.4

出0入0汤圆

发表于 2006-4-11 13:02:16 | 显示全部楼层
楼主,到时候记得放出全文,好让大家收藏啊

出0入0汤圆

发表于 2006-4-15 11:46:49 | 显示全部楼层
楼主,那里去了,忙什么呢?

要出下文了......

出0入0汤圆

发表于 2006-4-15 20:42:20 | 显示全部楼层
请楼主帮个忙!

我在主函数里写了画点的两句代码

LCD12864Draw(95,3);

LCD12864Draw(95,6);//就是在坐标(95,3)和(95,6)处画两个点

但是现在观察到的现象是:只有一个(95,6)的点,个人认为应该是(95,6)这个点把上个点(95,3)给覆盖了

以下是画点函数!

————————————————————————————————

/*********************************************************************/

//在坐标XY上打个点

// X:0~127

// Y:0~63

void LCD12864Draw(char X,char Y)

{

   char DX = Y / 8;

   char BX = Y % 8;

          

    CSA=1;CSB=1;



   if(X >= 64)

   {       

        X -= 64;

        CSA=0;CSB=1;



        Set_XY(X,DX);

        TempData = Read_0108_Data(1);

       

          TempData |= (1 << BX);

           Set_XY(X,DX);

        Write_0108_Data(TempData);

    }

    else

    {

        CSA=1;CSB=0;



        Set_XY(X,DX);

        TempData = Read_0108_Data(0);



        TempData |= (1 << BX);   

       

            Set_XY(X,DX);

        Write_0108_Data(TempData);

    }

}



以下是读数据的函数:

//================================================================

//  读取KS0108显示数据函数

//================================================================

unsigned char Read_0108_Data(unsigned char cs)

{

        unsigned char Disp_Data;

        if(cs==0)//左屏

        {

                CSA=1;CSB=0;

                RS=1;

                RW=1;

                Lcd_Bus=0xff;

                E=1;

                E=0;

                E=1;

                Disp_Data=Lcd_Bus;

                E=0;

                return Disp_Data;

        }

        else if(cs==1)//右屏

        {

                CSA=0;CSB=1;

                RS=1;

                RW=1;

                Lcd_Bus=0xff;

                E=1;

                E=0;

                E=1;

                Disp_Data=Lcd_Bus;

                E=0;

                return Disp_Data;

        }

}



//个人认为是读数据的函数出现问题

但是仔细看看似乎又没问题!



请大家也帮忙看看了谢谢!

出0入0汤圆

发表于 2006-4-15 23:07:36 | 显示全部楼层
老师,上课吧

出0入296汤圆

 楼主| 发表于 2006-4-16 01:48:13 | 显示全部楼层
这是我写的函数



/********************************************************

*  函数说明:读取显示数据指令                           *

*  输出:    显示数据                                   *

********************************************************/

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;

}



/********************************************************

*  函数说明:12864画点函数                              *

*  输入:    坐标(横向),点类型:画点/清点/反相         *

********************************************************/

void LCD12864Draw(char X,char Y,char Type)

{

    char DX = (Y >> 3);                                  //计算出属于哪个字节

        char BX = Y - (DX << 3);                             //计算出属于字节哪一位

        char TempData = 0;



        LCD12864_ChooseBoth;

       

        setX(DX);

        if (X > 63)

        {

            LCD12864_ChooseCS2;

                X -= 64;

        }

        else

        {

            LCD12864_ChooseCS1;

        }

        setY(X);

       

        TempData = getLCD12864Data();

       

        switch (Type)

        {

            case LCD12864_Graphic_Clear:

                    TempData &= ~(1<<BX);

                    break;

                case LCD12864_Graphic_Not:

                    TempData ^= (1 << BX);

                    break;

                default:

                    TempData |= (1 << BX);

        }

       

    setY(X);

       

        sendDataToLCD(TempData);

}

出0入296汤圆

 楼主| 发表于 2006-4-16 01:49:28 | 显示全部楼层
最近实在不好意思,因为帮阿莫制作一套开发实验系统,所以比较忙,快了,很快就好了。对不住大家哈。

出0入0汤圆

发表于 2006-4-16 12:21:55 | 显示全部楼层
/********************************************************

*  函数说明:读取显示数据指令                           *

*  输出:    显示数据                                   *

********************************************************/

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;

}



你好,傻孩子大虾,请问个问题,这个读数据的函数可以不分左右屏而直接读数的吗?

出0入0汤圆

发表于 2006-4-16 13:08:59 | 显示全部楼层
但是我将我的函数改成了你这样的

还是有问题

就是后面的会把前面的覆盖掉!!!

但是我做了个实验,确定了读出来的数据总是0



楼主,可否简要说明下,读数据的操作要求吗?谢谢!

出0入0汤圆

发表于 2006-4-16 13:15:38 | 显示全部楼层
//================================================================

//  读取KS0108显示数据函数

//================================================================

unsigned char Read_0108_Data(unsigned char cs)

{

        unsigned char Disp_Data;



        if(cs==0)

        {

                CSA=1;CSB=0;

                RS=1;

                RW=1;

                Lcd_Bus=0xff;

                E=1;

                E=0;

                E=1;

                _nop_();

                Disp_Data=Lcd_Bus;

                E=0;

                return Disp_Data;

        }

        else if(cs==1)

        {

                CSA=0;CSB=1;

                RS=1;

                RW=1;

                Lcd_Bus=0xff;

                E=1;

                E=0;

                E=1;

                _nop_();

                Disp_Data=Lcd_Bus;

                E=0;

                return Disp_Data;

        }

}

出0入296汤圆

 楼主| 发表于 2006-4-16 17:04:01 | 显示全部楼层
不同型号的12864读取数据的操作可能略有不同,代码我是测试过得,应该是赢家版本差异的问题。要诀就是要仔细研究和你屏幕型号相同的PDF上面的时序图。

我一般先作片选,再读数据。你可以从画点的代码中开出来。
-----此内容被Gorgon Meducer于2006-04-16,17:05:30编辑过

出0入0汤圆

发表于 2006-4-16 21:46:46 | 显示全部楼层
感谢楼主的帮助

现在的问题是可以读出来了

但是不稳定---------

有时候能读出来

有时候却读不出来?

出0入0汤圆

发表于 2006-4-17 20:29:00 | 显示全部楼层
问题解决了

真是的读数据的问题~!呵呵

//================================================================

//  读取KS0108显示数据函数

//================================================================

unsigned char Read_0108_Data(void)

{

        unsigned char Disp_Data;





                RS=1;

                RW=1;

                Lcd_Bus=0xff;

                E=1;

                E=0;

                E=1;

                Disp_Data=Lcd_Bus;

                E=0;

                return Disp_Data;

}



---------------------------------------------------

以上是原先的代码

以下是修改后正确的代码



//================================================================

//  读取KS0108显示数据函数

//================================================================

unsigned char Read_0108_Data(void)

{

        unsigned char Disp_Data;





                RS=1;

                RW=1;

                Lcd_Bus=0xff;

                E=1;

                E=0;

                E=1;

                _nop_();

                _nop_();

                _nop_();

                _nop_();

                _nop_();

                _nop_();

                Disp_Data=Lcd_Bus;

                E=0;

                return Disp_Data;

}





经过仔细阅读DATASHEET后,

在加上几个空循环,等待数据的建立!OVER!

出0入0汤圆

发表于 2006-4-18 10:33:47 | 显示全部楼层
真正的GUI驱动程序是应该这样做的。我做的不但是这样接口的,连坐标也改了,如X为从左到右,Y从上到下。只是这样一来,速度慢了很多。因为Gorgon Meducer说得不错,要保证下面还有图片时,不会存坏下面的数据。如果用容量大一点的,如M128,就可以做一个缓冲,先在内存中画好,再快速按字节而不是画点输送到液晶上。我用的GUI是从周立功简易GUI中优化过来的。相当完美,因为周立功仅授权购买了实驱板的一份。所以由于版权问题,我无法提供源代码,不过你也可以买他们的实驱板,或书得到。我是浦江的,与周立功毫无关系,决不是做广告。另外我用了周立功的逻辑分析仪,也特好用。这是我做的一个控制系统的欢迎界面,里面有详细的菜单。

以下是工作界面:

出0入296汤圆

 楼主| 发表于 2006-4-18 12:30:05 | 显示全部楼层
看来我需要拆一下周立功的台了。

后面我会详细介绍菜单和各种图形元素的实现方法的。

出0入0汤圆

发表于 2006-4-18 12:34:53 | 显示全部楼层
期待ing!

出0入0汤圆

发表于 2006-4-18 13:14:46 | 显示全部楼层
什么时候出下章?

期待中!!!!!

出0入0汤圆

发表于 2006-4-18 13:50:24 | 显示全部楼层
希望Gorgon Meducer 老师多传授LCD应用基本知识让我等初学者从中受益。谢谢!

出0入0汤圆

发表于 2006-4-18 14:31:56 | 显示全部楼层
thank you very much!

出0入0汤圆

发表于 2006-4-18 15:42:27 | 显示全部楼层
确实是篇好文,顶一下,让更多人受益!

出0入0汤圆

发表于 2006-4-18 15:54:44 | 显示全部楼层
Gorgon Meducer ,各位高手大家好!



麻烦一下介绍如何将汉字的字模结构数组写入flash吧

这是汉字字模信息,请教,如何将此信息写入flash!!!



typedef struct         // 汉字字模显示数据结构

{

        char Index[2];  //两个 字节存放一个汉字

        char Msk[12][2];//存放点阵信息

}typFNT_GB12;

出0入0汤圆

发表于 2006-4-18 20:26:31 | 显示全部楼层
等待啊!很不错!风格好

出0入296汤圆

 楼主| 发表于 2006-4-18 20:36:17 | 显示全部楼层
Gorgon Meducer ,各位高手大家好!



麻烦一下介绍如何将汉字的字模结构数组写入flash吧

这是汉字字模信息,请教,如何将此信息写入flash!!!



typedef struct    // 汉字字模显示数据结构

{

   char Index[2];  //两个 字节存放一个汉字

   char Msk[12][2];//存放点阵信息

}typFNT_GB12;



---------------------------------

从这个结构体来看,要想把你的这个结构体写入flash,只要在

typeFNT_GB12定义的变量定义前加一个const就可以了

比方说

const typeFNT_GB12 DIC[65535];

系统会自动把字库刷进代码段。不过,这位兄弟的存储方法比较占空间哈……

出0入296汤圆

 楼主| 发表于 2006-4-18 20:49:27 | 显示全部楼层
3.4 我行我素

       前面我们说过一个函数:

void DispBITMap(const char *String,char StringLength,char X,char Y,char DispModel);

    借助这个函数,我们很容易把一个定义好的图片(字模)显示出来。但是,这显然太中规中矩了一点。简单的应用,如果我们想做一个图片在屏幕上来回弹的经典屏保效果就很恼火(恼火是四川话麻烦的意思)。显然,在屏幕的任意位置显示图片就成了急需解决的问题。

       一种基本思想就是,我们在系统的某一个地方再开辟一个显示缓冲区,通过一点点计算,把图片影响到的地方都改写好,然后直接把这块缓冲区影射到原先的显示缓冲中,或者干脆直接显示出来。怎么,还是不懂?好吧,这样给你打一个比方:12864的现存结构是以纵向的8个点为一个字节,如果我们想在以8为倍数的行开始(单位是像素)显示一个8*n的字模,那么只需要简单得调用本节开始时后提到的函数把字模送过去就可以了;But,如果我们想在别的位置(显然只有行是问题,对于12864来说,列是很容易设置的),显示这个字符呢?

       你可以自己先思考下下。

出0入296汤圆

 楼主| 发表于 2006-4-18 23:37:17 | 显示全部楼层
对于一个只有1K大小连基本显示缓冲区都开不起的M8来说,你别指望他还能再凭空找出一块地方给你做什么预处理缓冲,问题是,有必要么?实际上,我们每次进行图片变换牵涉到的显存是有限的。比方说,我们要处理一个8*n的字符,那么每次需要参与处理的字节最多只有(8/8)+1个;我们需要处理一个16*n的字符,那么每次需要参与处理的字节最多只有(16/8)+1个。如果需要图片在任意位置显示,那么需要参与计算的字节只有(Y/8)+1个。那么我很容易把缓冲用的显示存储单元数量减少到一个可以接受的范围,比方说12864的8个。

    计算的原理很简单,我们可以很容易写出他的通用函数(以下函数只是伪代码):

    # define DISP_Model_Draw    0x01                       //覆盖

    # define DISP_Model_AND     0x00                       //印花

    # define DISP_Model_Not     0x02                       //反相





    void showPicture(const char *String,char Length,char Height,char X,char Y,DispModel)

    {

        char TempRAM[2] = {0,0};     //声明一个显示缓冲,因为是一个一个的处理,所以最多只需要2个字节,但是对于画线函数,就不是这样了

        char a = 0,b = 0,n = 0;

        char Temp = 0;



        if (Y - (Y >> 3 <<3))

        {

            //普通情况

            ……代码忽略

            return ;

        }



        //需要处理的情况

        for (a = 0;a<Height;a++)

        {

            for (b = 0;b<Length;b++)

            {

                Temp = (Y+a) - ((Y+a) >> 3 <<3);        //求余数

                TempRAM[0] = getRAM(X,(Y+a)>>3);       //这一行明显示伪代码哈

                TempRAM[1] = getRAM(X,((Y+a)>>3)+1);

                switch (DispModel)

                {

                     case DISP_Model_Draw:

                         for (n = 0;n<8-Temp;n++)

                         {

                             if (String[n]<<(7-n)>>7)

                             {

                                 TempRAM[0] |= (1<<(n+Temp));

                             }

                             else

                             {

                                 TempRAM[0] &= ~(1<<(n+Temp));

                             }

                         }

                         for (n = 0;n<Temp;n++)

                         {

                             if (String[n] << (7 - n - Temp)>>7)

                             {

                                 TempRAM[1] |= (1<<n);

                             }

                             else

                             {

                                 TempRAM[1] &= ~(1<<n);

                             }

                         }

                        

                         n++;

                         break;

                     ……以下略

                 }

                 把TempRAM[0]和TempRAM[1]写入到现存中

            }

        }

    }
-----此内容被Gorgon Meducer于2006-04-18,23:39:22编辑过

出0入296汤圆

 楼主| 发表于 2006-4-19 02:53:23 | 显示全部楼层
这一章,我囫囵吞枣的给大家撤了很多原理的东西,章节前后时间跨度之大也是前面诸多章节无法比拟的。本来,按照原来的计划,整篇文章写道这里就应该全部结束了。但是,,实际情况是我们拥有了一种方法却不知道这种方法应该运用的场合。一个GUI创立的目的往往是服务于菜单系统,或者是游戏系统。于是接下来的两张,我就扯一下和本文有千丝万缕关系却是主题之外的东西:1、菜单的结构和实现;2、GUI在游戏中的简单应用。

    以上的文字就算是一个结尾加上一个附录的开头吧。(争取100楼圆满结束全文)



    ADDENDUM ONE:  THE MENU STRUCT AND IT`S USAGE

    附录一:菜单的结构和实现



    为什么菜单被叫做菜单,我想起原因已经无从查考了,这个问题上我再掉故纸堆,罗列一堆各家之言,怕又是要挨骂了。不管你承认与否,菜单的概念已经随着计算机的普及渗透到了我们生活的各个方面。很多东西都可以被称作菜单,很多东西从实质上都拥有菜单的结构。网页中有菜单,网页本身甚至就是一个菜单选项,等等诸如此类。那么菜单拥有怎样的一种结构,我们如何去实现这一结构呢?

    要说清楚这个问题,我想从两个方面来说:第一,菜单是由许多具有一定功能的单位按照一定分类组织在一起的图;第二,如果听不懂第一条,就假象菜单是互联网,一个网页就是一个子菜单,子菜单中很多超链接又连接到其他网页,如此往复,他们之间是通过指针,或者说是通过超链接来沟通的。也就是说,我们已经搞清楚了一个菜单的基本结构单元组成就是一个多链表中的一个结构体单元,他往往被简化成树,或者说在执行退回上一级菜单这个功能的时候,被看作是一个树的结构,但是,它实际是图。菜单之间的任何菜单项之间都有可能产生联系。但是记住,菜单一般是多入一出的结构。

    说的越来越抽象了哈。其实看不懂前面的文字不要紧,我们看下面的代码就没有那么神秘了。

/***********************

*     结构体宏定义     *

***********************/

struct MenuItem       

{

    char MenuCount;

    char *DisplayString;

    void (*Subs)();

    struct MenuItem *ChildrenMenus;

    struct MenuItem *ParentMenus;

}NULL_MENU;



void NULL_FUNCTION(void){}



我们看到,菜单的最小单元MenuItem的组成其实非常简单:

菜单项所在层的菜单项数目(度);

菜单项显示出来的字符串;

菜单需要执行的功能的函数指针(可以为空);

孩子指针(表明选择该菜单项后,跳转到哪个子菜单去);

父指针(表明选择ESC后,跳转到哪个菜单去);



看一个菜单实例:

(源代码)



struct MenuItem MainMenu[3];

struct MenuItem TimeMenu[4];

struct MenuItem VoiceMenu[5];

struct MenuItem RobotMenu[5];

struct MenuItem FlashMenu[5];



/***********************

*     函 数 声 明 区   *

***********************/

void MainMenuInit(void);

void TimeMenuInit(void);

void VoiceMenuInit(void);

void RobotMenuInit(void);

void FlashMenuInit(void);



/**************************************************************

*  函数说明:Flash处理目录初始化函数                          *

**************************************************************/

void FlashMenuInit(void)

{

    FlashMenu[0].MenuCount = 5;

    FlashMenu[0].DisplayString = "  Flash Record  ";

    FlashMenu[0].Subs = FlashRecord;

    FlashMenu[0].ChildrenMenus = &Null;

    FlashMenu[0].ParentMenus = MainMenu;

   

    FlashMenu[1].MenuCount = 5;

    FlashMenu[1].DisplayString = "      Play      ";

    FlashMenu[1].Subs = FlashPlay;

    FlashMenu[1].ChildrenMenus = &Null;

    FlashMenu[1].ParentMenus = MainMenu;

   

    FlashMenu[2].MenuCount = 5;

    FlashMenu[2].DisplayString = "      Pause     ";

    FlashMenu[2].Subs = FlashPause;

    FlashMenu[2].ChildrenMenus = &Null;

    FlashMenu[2].ParentMenus = MainMenu;

   

    FlashMenu[3].MenuCount = 5;

    FlashMenu[3].DisplayString = "  Flash Delete  ";

    FlashMenu[3].Subs = FlashDelete;

    FlashMenu[3].ChildrenMenus = &Null;

    FlashMenu[3].ParentMenus = MainMenu;

   

    FlashMenu[4].MenuCount = 5;

    FlashMenu[4].DisplayString = "      Back      ";

    FlashMenu[4].Subs = NullSubs;

    FlashMenu[4].ChildrenMenus = MainMenu;

    FlashMenu[4].ParentMenus = MainMenu;

}



/**************************************************************

*  函数说明:机器人控制目录初始化函数                         *

**************************************************************/

void RobotMenuInit(void)

{

    RobotMenu[0].MenuCount = 5;

    RobotMenu[0].DisplayString = "   Turn  Left   ";

    RobotMenu[0].Subs = RobotTurnLeft;

    RobotMenu[0].ChildrenMenus = &Null;

    RobotMenu[0].ParentMenus = MainMenu;

   

    RobotMenu[1].MenuCount = 5;

    RobotMenu[1].DisplayString = "   Turn Right   ";

    RobotMenu[1].Subs = RobotTurnRight;

    RobotMenu[1].ChildrenMenus = &Null;

    RobotMenu[1].ParentMenus = MainMenu;

   

    RobotMenu[2].MenuCount = 5;

    RobotMenu[2].DisplayString = "    Go  Ahead   ";

    RobotMenu[2].Subs = RobotGoAhead;

    RobotMenu[2].ChildrenMenus = &Null;

    RobotMenu[2].ParentMenus = MainMenu;

   

    RobotMenu[3].MenuCount = 5;

    RobotMenu[3].DisplayString = "     Go Back    ";

    RobotMenu[3].Subs = RobotGoBack;

    RobotMenu[3].ChildrenMenus = &Null;

    RobotMenu[3].ParentMenus = MainMenu;

   

    RobotMenu[4].MenuCount = 5;

    RobotMenu[4].DisplayString = "      Back      ";

    RobotMenu[4].Subs = NullSubs;

    RobotMenu[4].ChildrenMenus = MainMenu;

    RobotMenu[4].ParentMenus = MainMenu;

   

}



/**************************************************************

*  函数说明:声音处理目录初始化函数                           *

**************************************************************/

void VoiceMenuInit(void)

{

    VoiceMenu[0].MenuCount = 5;

    VoiceMenu[0].DisplayString = "  Voice Record  ";

    VoiceMenu[0].Subs = VoiceRecord;

    VoiceMenu[0].ChildrenMenus = &Null;

    VoiceMenu[0].ParentMenus = MainMenu;

   

    VoiceMenu[1].MenuCount = 5;

    VoiceMenu[1].DisplayString = "      Play      ";

    VoiceMenu[1].Subs = Play;

    VoiceMenu[1].ChildrenMenus = &Null;

    VoiceMenu[1].ParentMenus = MainMenu;

   

    VoiceMenu[2].MenuCount = 5;

    VoiceMenu[2].DisplayString = "      Pause     ";

    VoiceMenu[2].Subs = Pause;

    VoiceMenu[2].ChildrenMenus = &Null;

    VoiceMenu[2].ParentMenus = MainMenu;

   

    VoiceMenu[3].MenuCount = 5;

    VoiceMenu[3].DisplayString = "  Voice Delete  ";

    VoiceMenu[3].Subs = VoiceDelete;

    VoiceMenu[3].ChildrenMenus = &Null;

    VoiceMenu[3].ParentMenus = MainMenu;

   

    VoiceMenu[4].MenuCount = 5;

    VoiceMenu[4].DisplayString = "      Back      ";

    VoiceMenu[4].Subs = NullSubs;

    VoiceMenu[4].ChildrenMenus = MainMenu;

    VoiceMenu[4].ParentMenus = MainMenu;

}



/**************************************************************

*  函数说明:时间设定子目录初始化                             *

**************************************************************/

void TimeMenuInit(void)

{

    TimeMenu[0].MenuCount = 4;

    TimeMenu[0].DisplayString = "    Time Set    ";

    TimeMenu[0].Subs = TimeSet;

    TimeMenu[0].ChildrenMenus = &Null;

    TimeMenu[0].ParentMenus = MainMenu;

   

    TimeMenu[1].MenuCount = 4;

    TimeMenu[1].DisplayString = "    Date Set    ";

    TimeMenu[1].Subs = DateSet;

    TimeMenu[1].ChildrenMenus = &Null;

    TimeMenu[1].ParentMenus = MainMenu;

   

    TimeMenu[2].MenuCount = 4;

    TimeMenu[2].DisplayString = "    AlertSet    ";

    TimeMenu[2].Subs = AlertSet;

    TimeMenu[2].ChildrenMenus = &Null;

    TimeMenu[2].ParentMenus = MainMenu;

   

    TimeMenu[3].MenuCount = 4;

    TimeMenu[3].DisplayString = "      Back      ";

    TimeMenu[3].Subs = NullSubs;

    TimeMenu[3].ChildrenMenus = MainMenu;

    TimeMenu[3].ParentMenus = MainMenu;

   

}



/**************************************************************

*  函数说明:根目录初始化                                     *

**************************************************************/

void MainMenuInit(void)

{

     MainMenu[0].MenuCount = 3;

     MainMenu[0].DisplayString = "    Time Set    ";

     MainMenu[0].Subs = NullSubs;

     MainMenu[0].ChildrenMenus = TimeMenu;

     MainMenu[0].ParentMenus = &Null;



     MainMenu[1].MenuCount = 3;

     MainMenu[1].DisplayString = "  Voice Center  ";

     MainMenu[1].Subs = NullSubs;

     MainMenu[1].ChildrenMenus = VoiceMenu;

     MainMenu[1].ParentMenus = &Null;

/*

     MainMenu[2].MenuCount = 3;

     MainMenu[2].DisplayString = "  Robot Control ";

     MainMenu[2].Subs = NullSubs;

     MainMenu[2].ChildrenMenus = RobotMenu;  

     MainMenu[2].ParentMenus = &Null;

*/     

     MainMenu[2].MenuCount = 3;

     MainMenu[2].DisplayString = "  Flash Option  ";

     MainMenu[2].Subs = NullSubs;

     MainMenu[2].ChildrenMenus = FlashMenu;     

     MainMenu[2].ParentMenus = &Null;



}



   

    struct MenuItem (*MenuPoint) = MainMenu;

    short DisplayStart = 0;

    short UserChoose = 0;

    short DisplayPoint = 0;

    short MaxItems;   





/*****************************

* Struct MenuItem:           *

*    short MenuCount;        *

*    char *DisplayString;    *

*    void (*Subs)();         *

*    MenuItem *ChildrenMenus;*

*    MenuItem *ParentMenus;  *

*****************************/



/***********************

*     函 数 声 明 区   *

***********************/

extern void ClearWatchDog();

void MenuInitialation(void);

void SystemInitialation(void);

void ShowMenu(void);

short GetKeyNum(void);



/**************************************************************

*  函数说明:系统初始化函数                                   *

**************************************************************/

void SystemInitialation(void)

{

        Init_sys();          

        Enable_LCD();                          //初始化字库      函数定义在Splc501sys.asm

        MenuInitialation();               //初始化菜单

        GRAPH                             //图形初始化

}



/**************************************************************

*  函数说明:目录初始化函数                                   *

**************************************************************/

void MenuInitialation(void)

{

    MainMenuInit();

    TimeMenuInit();

    VoiceMenuInit();

    RobotMenuInit();

    FlashMenuInit();

}



/**************************************************************

*  函数说明:目录显示函数                                     *

**************************************************************/

void ShowMenu(void)

{

    short n = 0;



    MaxItems = MenuPoint[0].MenuCount;

    DisplayPoint = DisplayStart;

        if (MaxItems >= 4)

        {

             for (n = 0;n<4;n++)

             {

                         

                 LOCATE(n+1,1);

                 PRINT(MenuPoint[DisplayPoint].DisplayString);

                

                 if ((DisplayPoint) == UserChoose)

                     {

                         BOX(1,n*16+1,126,(n+1)*16-2,1,1);

                     }

                

                     DisplayPoint +=1;

                     if ((DisplayPoint) == (MaxItems))

                     {

                         DisplayPoint = 0;

                     }

             }

         }

         else

         {

             for (n = 0;n<MaxItems;n++)

             {

                         

                 LOCATE(n+1,1);

                 PRINT(MenuPoint[DisplayPoint].DisplayString);

                

                 if ((DisplayPoint) == UserChoose)

                     {

                         BOX(1,n*16+1,126,(n+1)*16-2,1,1);

                     }

                

                     DisplayPoint +=1;

                     if ((DisplayPoint) == (MaxItems))

                     {

                         DisplayPoint = 0;

                     }

                

             }

         }

        //BOX(0,0,127,63,2,2);

}



/**************************************************************

*  函数说明:获得键值函数                                     *

**************************************************************/

short GetKeyNum(void)

{

    short TempKeyNum = 0;

        TempKeyNum = F_Key_Scan();       //获取按键值

        *P_IOA_Dir = 0x01ff;

        *P_IOA_Attrib = 0x01ff;      

        *P_IOA_Data = 0x01ff;

            

        return TempKeyNum;



}



/**************************************************************

*  函数说明:主函数                                           *

**************************************************************/

int main()

{                     

    short KeyNum = 0xff;

   

    SystemInitialation();            //系统初始化

   

    ShowMenu();

        while(1)

        {

            ClearWatchDog();             //喂狗

            KeyNum = GetKeyNum();        //获取按键值



            /*******************目录操作*********************/

            

            /***************************************

            *   [按键说明]                         *

            *  ----------------------------------- *

            *   [K1]            UP(向上)           *

            *   [K5]            Down(向下)         *

            *   [K2]            Esc(后退)          *

            *   [K6]            Enter(确定)        *

            *   [K3]            返回根目录         *

            ***************************************/

            if (KeyNum != 0xff)

            {

             ShowMenu();

                 switch(KeyNum)

                 {

                     case UP:

                         UserChoose --;

                         if (UserChoose < 0)

                         {

                             UserChoose = MaxItems-1;

                         }

                         break;

                     case Esc:

                         if (MenuPoint[0].ParentMenus != &Null)

                         {

                             MenuPoint = MenuPoint[0].ParentMenus;

                             UserChoose = 0;

                             DisplayStart = 0;

                         }

                         break;

                     case Down:

                         UserChoose ++;

                         if (UserChoose == MaxItems)

                         {

                             UserChoose = 0;

                         }

                         

                         break;

                     case Enter:

                         if (MenuPoint[UserChoose].Subs != NullSubs)

                         {

                             (*MenuPoint[UserChoose].Subs)();

                         }

                         else if (MenuPoint[UserChoose].ChildrenMenus != &Null)

                         {

                             MenuPoint = MenuPoint[UserChoose].ChildrenMenus;

                             UserChoose = 0;

                             DisplayStart = 0;

                         }

                         break;

                     case Reset:

                         MenuPoint = MainMenu;

                         UserChoose = 0;

                         DisplayStart = 0;

                         break;

                 }

                

                 if ((UserChoose < DisplayStart) || (UserChoose > (DisplayStart+3)))

                 {

                    DisplayStart = UserChoose;

                 }

                

                 CLS

                 ShowMenu();

                

            }

            /*******************目录操作*********************/

        }

       



}



过多的废话我就不多说了,大家看了就清楚了哈。

顺便提示下,其实用在串口中的指令系统也是一种菜单,用的好的话,可以用菜单的结构来描述很复杂的指令系统哦。
-----此内容被Gorgon Meducer于2006-04-19,02:57:07编辑过

出0入296汤圆

 楼主| 发表于 2006-4-19 03:11:51 | 显示全部楼层
ADDENDUM TWO:  THE GAME

    附录二:游戏

    除了菜单,牵扯到GUI最多的恐怕就是游戏了。贪食蛇就是本文要介绍的一个典型例子。有多少人注意过贪食蛇的数据结构?我想大部分玩过的人可能都没有仔细端详一下这一个神奇的游戏其实是FIFO(先入先出)的典型——是的,贪食蛇应该是队列数据结构的最理想演示和说明。用户所要做的只不过是控制一个二维数组空间中,队列的生长方向,我们所要控制的只有两个指针,队尾(蛇头)指针的运动方向,需要关心的只是在队尾(蛇头)指针吃掉一个豆子以后,延迟一次队头指针(蛇尾)的清除动作。说得再具体一点就是,一个二维数组,蛇的身体记录着队尾的前进方向(用四个数字),豆子用255表示,如果队尾(蛇头)指针发现下面一个位置的数组元素为255即豆子,那么就放弃一次队头的清空操作(相当于蛇长长了),如果发现前面的元素不是255而且也不是0那么显然撞倒东西了,GAME OVER。

    数据结构分析清楚了,那么一切都明了了。对于GUI来说,只不过根据数组的关系在屏幕上棉花方框框而已……没有什么可以神秘的,有兴趣的人可以自己做一个来耍耍。我忙完了阿莫的设计项目会集中力量另起新帖子——探讨下贪食蛇的编写。



    附上文曲星上BASIC编写的贪食蛇,抛砖引玉哈

   

(文件头部——界面的显示)

10 GRAPH:CLEAR:CLS

20 BOX 1,0,160,80,0,1:LOCATE 2,4:PRINT "RiverPigFamily";

25 LOCATE 4,5:PRINT "For SuperMan"

26 LOCATE 3,6:PRINT "闪电贪食蛇";:POKE 199,0

30 IF PEEK(199)=0 THEN GOTO 30 ELSE POKE 199,0:CLEAR:CLS:HEADX=20:HEADY=10:GOSUB 3000



(游戏的初始变量设定)

40 TAILX=24:TAILY=10

50 DIM BOXS(40,20):BOXS(21,10)=1:BOXS(22,10)=1:BOXS(23,10)=1:BOXS(24,10)=1:GOSUB 2000

60 DIRECT=1:REM 1 LEFT 2 RIGHT 3 UP 4 DOWN

70 POKE 199,0:EAT=1:N=-1

80 BOX 80,40,95,43,1,1



==============================闪电贪食蛇的主体=================================

90 A=PEEK(199)

91 BOX 1,0,160,80,0,1

95 HLX=HEADX:HLY=HEADY

100 IF A<>0 THEN POKE 199,0:GOSUB 1000

        110 IF DIRECT=1 THEN HEADX=HEADX-1:IF HEADX<0 THEN GOSUB 1500

        120 IF DIRECT=2 THEN HEADX=HEADX+1:IF HEADX>39 THEN GOSUB 1500

        130 REM

        140 IF DIRECT=3 THEN HEADY=HEADY-1:IF HEADY<0 THEN GOSUB 1500

        150 IF DIRECT=4 THEN HEADY=HEADY+1:IF HEADY>19 THEN GOSUB 1500

160 BOXS(HLX,HLY)=DIRECT:FOR C=1 TO SPEED:NEXT C

170 IF BOXS(HEADX,HEADY)=9 THEN EAT=1:GOTO 250

175 IF BOXS(HEADX,HEADY)<>0 THEN GOTO 1500

180 TDIRECT=BOXS(TAILX,TAILY)

190 BOXS(TAILX,TAILY)=0:

        200 IF TDIRECT=1 THEN TAILX=TAILX-1

        210 IF TDIRECT=2 THEN TAILX=TAILX+1

        220 IF TDIRECT=3 THEN TAILY=TAILY-1

        230 IF TDIRECT=4 THEN TAILY=TAILY+1

240 BOX TAILX*4,TAILY*4,TAILX*4+3,TAILY*4+3,1,0

250 BOX HEADX*4,HEADY*4,HEADX*4+3,HEADY*4+3,1,1:IF EAT=0 THEN GOTO 290 ELSE EAT=0

260 N=N+1:X=INT(RND(1)*40)

270 Y=INT(RND(1)*20)

280 IF BOXS(X,Y)<>0 THEN GOTO 260 ELSE BOXS(X,Y)=9

290 BOX X*4,Y*4,X*4+3,Y*4+3,0,1

300 GOTO 90

==============================闪电贪食蛇的主体=================================



(按键判断)

1000 IF A=151 AND DIRECT<>2 THEN DIRECT=1:GOTO 1050

1010 IF A=150 AND DIRECT<>1 THEN DIRECT=2:GOTO 1050

1020 IF A=148 AND DIRECT<>4 THEN DIRECT=3:GOTO 1050

1030 IF A=149 AND DIRECT<>3 THEN DIRECT=4:GOTO 1050

1040 POKE 199,0

1045 IF PEEK(199)=0 THEN GOTO 1045 ELSE POKE 199,0

1050 RETURN



(游戏的分数统计部分)

1500 CLS:BOX 1,0,160,80,0,1:LOCATE 2,6:PRINT "GAME  OVER";:LOCATE 4,2:PRINT "总分:";

1510 PRINT 1000+N*(HARDR*50+SPEEDR*10);" 共:";N;"个";

1515 N=1000+N*(HARDR*50+SPEEDR*10):POKE 199,0

1518 A=PEEK(199):IF A=0 THEN GOTO 1518 ELSE POKE 199,0

1520 OPEN "SN_WINER" FOR APPEND AS #1

1530 WRITE#1,"RiverPigFamily"

1540 CLOSE#1 :NUM=1000:NAMES$="PLEAR"

1550 OPEN "SN_WINER" FOR INPUT AS #1

1560 INPUT#1,TEMP$:IF TEMP$="RiverPigFamily" THEN CLOSE#1:GOTO 1580

1570 NAMES$=TEMP$:INPUT#1,NUM :CLOSE#1

1580 IF N<=NUM THEN GOTO 1630 ELSE NUM=N

1590 CLS:BOX 1,0,160,80,0,1

1600 POKE 947,0:LOCATE 3,2:

1610 INPUT "YOUR NAME:";NAMES$ :POKE 947,128

1620 IF LEN(NAMES$)>9 THEN NAMES$=LEFT$(NAMES$,9)

1625 IF NAMES$="" THEN NAMES$="PLEAR"

1630 OPEN "SN_WINER" FOR OUTPUT AS #1

1640 WRITE#1,NAMES$,NUM:CLOSE#1

1650 CLS:BOX 1,0,160,80,0,1

1660 LOCATE 2,4:PRINT "最高分:";

1670 LOCATE 3,3:PRINT RIGHT$("        "+NAMES$,9);" ";RIGHT$(STR$(NUM+1000000),6);

1680 POKE 199,0

1690 IF PEEK(199)=0 THEN GOTO 1690 ELSE POKE 199,0:CLS:GOTO 20



(一个简易的地图生成器)

2000 FOR D=1 TO 18 STEP HARD

2010 FOR F=2 TO 39 STEP HARD

2020 BOXS(F,D)=1

2030 BOX F*4,D*4,F*4+3,D*4+3,1,1

2040 NEXT F

2050 NEXT D

2060 RETURN



(一个简易的菜单生成部分)

3000 POKE 947,0:CLS:LOCATE 3,2

3010 INPUT "难度(1-6)";HARDR:HARDR=INT(HARDR)

3020 IF HARDR<1 OR HARDR>6 THEN HARDR=1

3030 HARD=2^(7-HARDR)

3040 CLS:LOCATE 3,1

3050 INPUT "速度(1-20)";SPEEDR:SPEEDR=INT(SPEEDR)

3060 IF SPEEDR<1 OR SPEEDR>20 THEN SPEEDR=10

3070 SPEED=(20-SPEEDR)*10

3080 POKE 947,128:CLS:RETURN

出0入296汤圆

 楼主| 发表于 2006-4-19 03:14:46 | 显示全部楼层
-------------------------------------------------------------------

全文(完)SIAP!

出0入0汤圆

发表于 2006-4-19 08:58:37 | 显示全部楼层
Gorgon Meducer 傻孩子 ,辛苦了!

阁下算是给大家深入浅出地讲了在液晶开发的方方面面,从液晶驱动到GUI用户界面,从汉字,字符到菜单结构,这些正是大家在玩液晶的时候所急需补充的养分。能把自己肚里的知识合盘托出,的确是一种无私奉献的精神!



开发液晶需要这样几步

1:调通液晶驱动程序,可以显示基本点线笔画。

2; 定义好字符,汉字字模结构,菜单结构。

3:将自定义字库装入flah,ram。



允许我打断一下,请教 Gorgon Meducer 阁下,



"typedef struct    // 汉字字模显示数据结构  

{  

   char Index[2];  //两个 字节存放一个汉字  

   char Msk[12][2];//存放点阵信息  

}typFNT_GB12;



---------------------------------

从这个结构体来看,要想把你的这个结构体写入flash,只要在

typeFNT_GB12定义的变量定义前加一个const就可以了

比方说

const typeFNT_GB12 DIC[65535];

系统会自动把字库刷进代码段。不过,这位兄弟的存储方法比较占空间哈……  "



阁下肯定有这方面的经验,在avr gcc中

const typeFNT_GB12 DIC[65535]; 好像不能将DIC【65535】装入flash吧,

const typeFNT_GB12 DIC[65535] PROGMEM; 这样才对,但是我这样做了,发现系统并没有将数组正确的装入flash,代码如下:



这是头文件



// hzku.h

//

#ifndef __HZKU_H

#define __HZKU_H

#include "includes.h"

#include "avr/pgmspace.h"  // 使用flash程序区



typedef struct              // 汉字字模显示数据结构

{

        uchar Index[2];  // 两个 字节存放一个汉字

        uchar Msk[24];   // 存放点阵信息

}typFNT_GB12;



extern const typFNT_GB12  GB_12[8] PROGMEM;  





#endif //__HZKU_H



在头文件中声明GB_12[8]为PROGMEM,然后在源文件中赋值如下





源文件





#include "includes.h"



const  typFNT_GB12  GB_12[8] PROGMEM =

{

               

       

                "进",

                0x42,0x40,

                0x22,0x40,

                0x22,0x40,

                0x0F,0xF0,

                0xE2,0x40,

                0x22,0x40,

                0x2F,0xF0,

                0x22,0x40,

                0x24,0x40,

                0x28,0x40,

                0x50,0x00,

                0x8F,0xF0,

               

                "入",

                0x18,0x00,

                0x04,0x00,

                0x04,0x00,

                0x04,0x00,

                0x0A,0x00,

                0x0A,0x00,

                0x09,0x00,

                0x11,0x00,

                0x10,0x80,

                0x20,0x40,

                0xC0,0x30,

                0x00,0x00,

                "系",

                0x01,0xE0,

                0x7E,0x00,

                0x04,0x00,

                0x08,0x80,

                0x3F,0x00,

                0x04,0x80,

                0x08,0x40,

                0x3F,0xE0,

                0x02,0x00,

                0x22,0x40,

                0x42,0x20,

                0x06,0x00,

                "统",

                0x21,0x00,

                0x20,0x80,

                0x47,0xF0,

                0x51,0x00,

                0xF2,0x20,

                0x27,0xF0,

                0x42,0x80,

                0xF2,0x80,

                0x02,0x80,

                0x1A,0x90,

                0xE4,0x90,

                0x48,0x70,

                  ......

                  ......

}



如此这样可以编译,但是调试时候发现, 通过观察变量,GB_12[8]并没有被正确赋值,请教Gorgon Meducer 及各位有过将汉字信息写入avr flash经验的高手,应该如何正确写入呢???????????

出0入296汤圆

 楼主| 发表于 2006-4-19 09:40:20 | 显示全部楼层
我使用的是ICC,似乎不会出现这样的问题哈。

不知道GCC支持在声明前面加入flash关键字来实现把信息存入Flash中——ICC是可以的。

我可能帮不了你。

出0入0汤圆

发表于 2006-4-24 17:51:47 | 显示全部楼层
看看芯艺的<<AVR单片机GCC程序设计>>,介绍flash操作很详细.

出0入0汤圆

发表于 2006-4-27 01:37:39 | 显示全部楼层
首先感谢楼主废了这么大劲做完了这一道大餐,可以说是色香味俱全啊,谢谢啦!

楼主,不怕您笑话,我觉得98楼到99楼之间应该还有一些东西要说没有说,您自己以为呢?

出0入0汤圆

发表于 2006-4-27 02:00:51 | 显示全部楼层
真不错~~~

出0入296汤圆

 楼主| 发表于 2006-4-27 09:20:18 | 显示全部楼层
105楼的兄弟,能不能说的再明确些呢?呵呵……我的确有点想匆匆结束的意思——被你发现了。

出0入0汤圆

发表于 2006-4-27 10:25:09 | 显示全部楼层
不错 又添内容了

出0入0汤圆

发表于 2006-10-2 22:45:32 | 显示全部楼层
谢谢Gorgon Meducer大侠赐教,



我们都受益非浅,如果在次的每个avr使用者都能像你一样慷慨相授,



中国的电子行业发展将会提速数十倍!!
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-3-29 10:30

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表