安德鲁

[转载].教你在12864上打点(基于ST7920控制器).[C][C51][LCD]

0
阅读(3596)

 

转者注:AIHHLI 咚冬兄写得比较生动,值得一读;对其他的点阵屏的研究有很大的参考价值。

 

基于ST7920控制的12864液晶用于字符显示很方便的,但网友说用它显示图形并不合适,原因就是它绘图时先要关闭显示,绘完后又要打开, 速度会较慢。我没有用过别的液晶,手中只有这一款,摆弄了几天,掌握了一点东西,写出来共享。
首先,我们知道,图形都是由像素点组成的, 绘图的基础其实就是画点。只要我们能点亮液晶的任意一个像素点,那么绘图就不是什么难事了。万丈高楼平地起嘛,先要做的,当然是要打好基础。

ST7920 提供了用于绘图的GDRAM(graph display RAM)。共 64×32 个字节的空间(由扩充指令设定绘图 RAM 地址),最多可以控制 256×64点阵的二维绘图缓冲空间。在它的Datasheet给出了GDRAM的坐标地址对照表:


用坐标表示,就是这样:


它的横坐标每一个地址都是16 位的。共16个地址,256位。
很明显,它能控制256*64像素的液晶屏,而我们的只是128*64像素液晶屏,显然只用到它的一部 分。
我刚开始以为它对应屏幕的绘图RAM是这样分布的(如红色部分):


结果栽了大根头,后来终于弄明白,原来它对应 屏幕的GDRAM是这样分布的:


只要我们清楚了它的GDRAM和屏幕上像素点 的映射(对应)关系,点亮对应的像素点就容易多了。要点亮某一个像素点,就是将这个像素点在GDRAM中对应的位置1,这个相信没人会不知道吧?
我 们先讨论一下思路,再一步步写代码。我觉得,思路要比代码重要的多,只要你的思路通了,正确了,那么写出代码肯定会很容易。
首先,给你 x,y的坐标,要你点亮一个点,要怎么做呢?从上面的图我们知道,它是分为两个半屏的,首先,我们要确定这个点是在上半屏还是下半屏,然后确定它是在那一 行(纵坐标Y),再确定它是在哪一个字节的哪一个位(也就是确定它在那一列,即横坐标X)。这些都确定后我们就定位到某一个具体的位上了,只就将这个位置 1,就OK了。
下面我们边写代码边讨论。
因为这里仅仅是讨论如何在12864上打点的,而不是给12864写一个驱 动,所以对于基本的数据读写函数,我们不做讨论,这里假设已经有了如下基本函数:

1 void lcd_write_cmd(unsigned char);   //lcd 命令写
2 void lcd_write_data(unsigned char);  //lcd 数据写
3 unsigend char lcd_read_data(void);   //lcd 数据读

好了,就这些了。
为了方便,我们定义如下宏:

1 #define  BASIC_SET   0x00          //基本指令集,后面的数字查数据手册,下同。
2 #define  EXTEND_SET  0x00           //扩展指令集
3 #define  DRAW_ON     0x00           //绘图显示开
4 #define  DRAW_OFF    0x00           //绘图显示关

我们现在开始写点亮某一个点的函数:

01 void lcd_set_dot(unsigned char x, unsigned char y)
02 {
03   unsigned char x_byet, x_bit;        //在横坐标的哪一个字节,哪一个位
04   unsigned char y_byte, y_bit;
05   x_byte = x / 16;                    //算出它在哪一 个字节(地址)
06                                       //注意一个地址是16位的
07   x_bit = x % 16;                     //算出它在哪 一个位
08   y_byte = y /32;                     //y是没在哪个字节这个说 法
09                                       //这里只是确定它在上半屏还是下半屏
10                                       //0:上半屏 1:下半屏
11   y_bit = y % 32;                     //y_bit 确定它是在第几行
12   lcd_write_cmd(EXTEND_SET);          //扩展指令集
13   lcd_write_cmd(DRAW_OFF);            //绘图显示关闭
14   lcd_write_cmd(0x80 + y_bit);        //先写垂直地址
15                                       //具体参照数据手册
16   lcd_write_cmd(0x80 + x_byte + 8 * y_byte);   //水平坐标
17                                                //下半屏的水平坐标起始地址为0x88
18                                                //(+8*y_byte)就是用来确定在上半屏还是下半屏
19   if (x_bit < 8)                               //如果 x_bit位数小于8
20   {
21       lcd_write_data(0x01 << (7 - x_bit));     //写高字节。因为坐标是从左向右的
22                                                //而GDRAM高位在左,底位在右
23       lcd_write_data(0x00);                    //低字节全部填0
24   }
25   else
26   {
27       lcd_write_data(0x00);                    //高字节全部填0
28       lcd_write_data(0x01 << (15 - x_bit));
29   }
30   lcd_write_cmd(DRAW_ON);                     //打开绘图显示
31   lcd_write_cmd(BASIC_SET);                   //回到基本指令集,毕竟ST7920是以字符为主的
32   return ;
33 }


基本画点函数算是完成了,但是我们如果使用这个函数,就会发现问题。你且用它沿横坐标画几个连续的点试试,肯定不是你想要的结果。
出现问题的原因是因为我们画点时对其余的位全部填0处理了,造成对原来的信息的破坏。所以我们要读出要写的那个地址原来的数据,再进行加工,写回 去就可以解决问题了。
改进后的代码:

01 void lcd_set_dot(unsigned char x, unsigned char y)
02 {
03   unsigned char x_byet, x_bit;        //在横坐标的哪一个字节,哪一个位
04   unsigned char y_byte, y_bit;
05   unsigned char tmph, tmpl;           //定义两个临时变 量,用于存放读出来的数据
06   x &= 0x7F;
07   y &= 0x3F;
08   x_byte = x / 16;                    //算出它在哪一 个字节(地址)
09                                       //注意一个地址是16位的
10   x_bit = x&0x0F;                     //算 出它在哪一个位
11   y_byte = y /32;                     //y是没在哪个字节这个说 法
12                                       //这里只是确定它在上半屏还是下半屏
13                                       //0:上半屏 1:下半屏
14   y_bit = y&0x3F;                     //y_bit 确定它是在第几行
15   lcd_write_cmd(EXTEND_SET);          //扩展指令集
16   lcd_write_cmd(DRAW_OFF);            //绘图显示关闭
17   lcd_write_cmd(0x80 + y_bit);        //先写垂直地址(最高位必须为1)
18                                       //具体参照数据手册
19   lcd_write_cmd(0x80 + x_byte + 8 * y_byte);   //水平坐标
20                                                //下半屏的水平坐标起始地址为0x88
21                                                //(+8*y_byte)就是用来确定
22                                                //在上半屏还是下半屏
23   lcd_read_data();                  //先空读一次
24   tmph = lcd_read_data();           //读高位
25   tmpl = lcd_read_data();
26   lcd_write_cmd(0x80 + y_bit);       //读操作会改变AC,所以重新设置一次
27   lcd_write_cmd(0x80 + x_byte + 8 * y_byte);
28   if (x_bit < 8)                             //如果 x_bit位数小于8
29   {
30      lcd_write_data(tmph | (0x01 << (7 - x_bit)));  //写高字节。因为坐标是从左向右的
31                                                     //而GDRAM高位在左,底位在右
32       lcd_write_data(tmpl);                         //原数据送回
33   }
34   else
35   {
36       lcd_write_data(tmph);                         //原数据送回
37       lcd_write_data(tmpl | (0x01 << (15 - x_bit)));
38   }
39   lcd_write_cmd(DRAW_ON);       //打开绘图显示
40   lcd_write_cmd(BASIC_SET);     //回到基本指令集,毕竟ST7920是以字符为主的
41   return ;
42 }


画点函数到此就完成了,剩下的事情就是对函数的优化了。例如对入口参数的检查,对乘除法的优化等等。