[51单片机] 从业将近十年!手把手教你单片机程序框架(连载)

[复制链接]
294134|913
langgao183 发表于 2014-10-9 22:05 | 显示全部楼层
  key_scan();为什么放在中断?我觉得中断程序越少越好
 楼主| jianhong_wu 发表于 2014-10-10 12:57 | 显示全部楼层
langgao183 发表于 2014-10-9 22:05
key_scan();为什么放在中断?我觉得中断程序越少越好

不一定非要放中断里,看情况吧。我大部分的项目都是放在中断里,因为我的按键扫描虽然代码多,但是不耗时间,放在中断里很好用,已经应用在大量的项目上了。但是我建议,如果一些实时性非常高的项目,还是不要放在中断里。
 楼主| jianhong_wu 发表于 2014-10-10 12:59 | 显示全部楼层
第七十二节:在液晶屏中把字体顺时针旋转90度显示的算法程序。
开场白:
我曾经遇到过这样的项目,客户由于外壳结果的原因,故意把液晶屏物理位置逆时针旋转了90度,在这种情况下,如果按之前的显示驱动就会发现字体也跟着倒了过来,影响了阅读。当时我的解决办法就是把字体的字库数组通过算法顺时针旋转90度就达到了目的。这一节把这个算法教给大家。
这个算法的本质是:请看以下附图1,附图2,附图3.
第一步:旋转90度的本质,就是把原来横向取模改成纵向去模。先把代表每一行16个点阵数的2个char型数据合并成1个int型数据。
第二步:再把每一列的16个点阵按2个字节分别取到一个数组里,就是纵向取模的过程了。
具体内容,请看源代码讲解。



(1)硬件平台:
    基于朱兆祺51单片机学习板。

2)实现功能:把液晶屏物理位置逆时针旋转了90度,开机上电后,可以看到液晶屏像对联的显示顺序一样,从上往下分别显示“馒头V5”四个字。
   
3)源代码讲解如下:
  1. #include "REG52.H"

  2. sbit  LCDCS_dr  = P1^6;  //片选线
  3. sbit  LCDSID_dr = P1^7;  //串行数据线
  4. sbit  LCDCLK_dr = P3^2;  //串行时钟线
  5. sbit  LCDRST_dr = P3^4;  //复位线

  6. void SendByteToLcd(unsigned char ucData);  //发送一个字节数据到液晶模块
  7. void SPIWrite(unsigned char ucWData, unsigned char ucWRS); //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动
  8. void WriteCommand(unsigned char ucCommand); //发送一个字节的命令给液晶模块
  9. void LCDWriteData(unsigned char ucData);   //发送一个字节的数据给液晶模块
  10. void LCDInit(void);  //初始化  函数内部包括液晶模块的复位
  11. void display_lattice(unsigned int x,unsigned int y,const unsigned char  *ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount); //显示任意点阵函数
  12. void display_clear(void); // 清屏
  13. void hz1616_s90(const unsigned char  *p_ucHz,unsigned char *p_ucResult);  //把16x16汉字字模顺时针旋转90度的转换函数
  14. void hz816_s90(const unsigned char  *p_ucHz,unsigned char *p_ucResult);  //把8x16字符字模顺时针旋转90度的转换函数

  15. void delay_short(unsigned int uiDelayshort); //延时

  16. code unsigned char Hz1616_man[]= /*馒   横向取模  16X16点阵 */
  17. {
  18. 0x21,0xF8,0x21,0x08,0x21,0xF8,0x3D,0x08,0x45,0xF8,0x48,0x00,0x83,0xFC,0x22,0x94,
  19. 0x23,0xFC,0x20,0x00,0x21,0xF8,0x20,0x90,0x28,0x60,0x30,0x90,0x23,0x0E,0x00,0x00,
  20. };

  21. code unsigned char Hz1616_tou[]= /*头   横向取模  16X16点阵 */
  22. {
  23. 0x00,0x80,0x10,0x80,0x0C,0x80,0x04,0x80,0x10,0x80,0x0C,0x80,0x08,0x80,0x00,0x80,
  24. 0xFF,0xFE,0x00,0x80,0x01,0x40,0x02,0x20,0x04,0x30,0x08,0x18,0x10,0x0C,0x20,0x08,
  25. };


  26. code unsigned char Zf816_V[]= /*V   横向取模  8x16点阵 */
  27. {
  28. 0x00,0x00,0x00,0xE7,0x42,0x42,0x44,0x24,0x24,0x28,0x28,0x18,0x10,0x10,0x00,0x00,
  29. };

  30. code unsigned char Zf816_5[]= /*5   横向取模  8x16点阵 */
  31. {
  32. 0x00,0x00,0x00,0x7E,0x40,0x40,0x40,0x58,0x64,0x02,0x02,0x42,0x44,0x38,0x00,0x00,
  33. };


  34. unsigned char ucBufferResult[32]; //用于临时存放转换结束后的字模数组

  35. void main()
  36.   {
  37.         LCDInit(); //初始化12864 内部包含液晶模块的复位

  38.         display_clear(); // 清屏

  39. /* 注释一:
  40. * (1)把原来的液晶屏物理位置逆时针旋转90度后,从上往下阅读,类似对联的阅读习惯。所以请注意坐标体系参数的变化。
  41. * (2)为了让字符居中显示,请注意在显示V和5两个字符时坐标体系的变化。
  42. * (3)字符8x16经过旋转处理后,变成了16x8,在调用display_lattice函数时,要注意修改响应的参数。
  43. */

  44.         hz1616_s90(Hz1616_man,ucBufferResult);  //把<馒>字顺时针旋转90度放到ucBufferResult临时变量里。
  45.         display_lattice(7,0,ucBufferResult,0,2,16);  //显示旋转90度后的<馒>字

  46.         hz1616_s90(Hz1616_tou,ucBufferResult);  //把<头>字顺时针旋转90度放到ucBufferResult临时变量里。
  47.         display_lattice(6,0,ucBufferResult,0,2,16);  //显示旋转90度后的<头>字


  48.         hz816_s90(Zf816_V,ucBufferResult);  //把<V>字符顺时针旋转90度放到ucBufferResult临时变量里。
  49.         display_lattice(5,4,ucBufferResult,0,2,8);  //显示旋转90度后的<V>字符。注意在最后两个个参数,2表示每一行有2个字节,8表示8列。第二个坐标参数4是为了偏移居中显示。

  50.         hz816_s90(Zf816_5,ucBufferResult);  //把<5>字符顺时针旋转90度放到ucBufferResult临时变量里。
  51.         display_lattice(4,4,ucBufferResult,0,2,8);  //显示旋转90度后的<5>字符。注意在最后两个个参数,2表示每一行有2个字节,8表示8列。第二个坐标参数4是为了偏移居中显示。


  52.     while(1)  
  53.     {
  54.        ;
  55.     }

  56. }



  57. void display_clear(void) // 清屏
  58. {   

  59.     unsigned char x,y;
  60.     WriteCommand(0x34);  //关显示缓冲指令            
  61.     WriteCommand(0x34);  //关显示缓冲指令  故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的
  62.     y=0;
  63.     while(y<32)  //y轴的范围0至31
  64.     {
  65.          WriteCommand(y+0x80);        //垂直地址
  66.          WriteCommand(0x80);          //水平地址
  67.          for(x=0;x<32;x++)  //256个横向点,有32个字节
  68.          {  
  69.             LCDWriteData(0x00);
  70.          }
  71.          y++;
  72.     }
  73.     WriteCommand(0x36); //开显示缓冲指令

  74. }


  75. /* 注释二:
  76. * 把16x16汉字字模顺时针旋转90度的步骤:请看附图1,附图2,附图3.
  77. * 第一步:旋转90度的本质,就是把原来横向取模改成纵向去模。先把代表每一行16个点阵数的2个char型数据合并成1个int型数据。
  78. * 第二步:再把每一列的16个点阵按2个字节分别取到一个数组里,就是纵向取模的过程了。以下程序int型数据每取8个数据的最高位,
  79. * 就左移一次,本质就是纵向取模的过程。
  80. */
  81. void hz1616_s90(const unsigned char  *p_ucHz,unsigned char *p_ucResult)  //把16x16汉字字模顺时针旋转90度的转换函数
  82. {
  83.          unsigned char a;
  84.          unsigned char b;
  85.          unsigned char c;
  86.      unsigned int uiBuffer[16];  //注意,是int类型数据,一个数据包含2个字节。
  87.        
  88.          for(a=0;a<16;a++) //把原来以字节为单位的字库每一行的2个字节合并成1个int型数据。放到一个包含16个int类型的数组里,为旋转90度算法处理做准备
  89.          {
  90.          uiBuffer[a]=p_ucHz[a*2];
  91.                  uiBuffer[a]=uiBuffer[a]<<8;
  92.                  uiBuffer[a]=uiBuffer[a]+p_ucHz[a*2+1];
  93.      }
  94.          
  95.          c=0;
  96.          for(a=0;a<16;a++)  //这里的16代表16列
  97.          {
  98.                  for(b=0;b<8;b++)   //每一列中有16个点,有2个字节,这里的8代表第一个字节的8个位或点。
  99.                  {
  100.                           p_ucResult[c]=p_ucResult[c]<<1;   
  101.               p_ucResult[c]=p_ucResult[c]&0xfe;                 
  102.               if(uiBuffer[15-b]>=0x8000)    //注意,int类型数据的判断是0x8000,char型的是0x80
  103.                       {
  104.                  p_ucResult[c]=p_ucResult[c]+1;
  105.               }
  106.                       uiBuffer[15-b]=uiBuffer[15-b]<<1;
  107.          }
  108.                  c++;
  109.                  
  110.                  for(b=0;b<8;b++) //每一列中有16个点,有2个字节,这里的8代表第二个字节的8个位或点。
  111.                  {
  112.                           p_ucResult[c]=p_ucResult[c]<<1;  
  113.               p_ucResult[c]=p_ucResult[c]&0xfe;                                          
  114.               if(uiBuffer[7-b]>=0x8000)      
  115.                       {
  116.                    p_ucResult[c]=p_ucResult[c]+1;
  117.               }
  118.                              uiBuffer[7-b]=uiBuffer[7-b]<<1;
  119.          }
  120.                  c++;
  121.     }
  122.          
  123. }


  124. /* 注释三:
  125. * 把8x16字符字模顺时针旋转90度的步骤:
  126. * 第一步:旋转90度的本质,就是把原来横向取模改成纵向去模。由于原来的字库存放在带code关键字的ROM区,只能读不能写,所以
  127. * 先把原来的字模数组读取出来,放到一个变量缓冲区里。
  128. * 第二步:再把每一列的16个点阵按2个字节分别取到一个数组里,就是纵向取模的过程了。以下程序int型数据每取8个数据的最高位,
  129.    就左移一次,本质就是纵向取模的过程。
  130. */
  131. void hz816_s90(const unsigned char  *p_ucHz,unsigned char *p_ucResult)  //把8x16字符字模顺时针旋转90度的转换函数
  132. {
  133.          unsigned char a;
  134.          unsigned char b;
  135.          unsigned char c;
  136.      unsigned char uiBuffer[16]; //注意,跟16x16点阵不一样,这里是char数据。因为横向的只有8个点
  137.        
  138.          for(a=0;a<16;a++) //把存放在ROM的字库放到一个16个char类型的数组里
  139.          {
  140.          uiBuffer[a]=p_ucHz[a];
  141.      }
  142.          
  143.          c=0;
  144.          for(a=0;a<8;a++)  //这里的8代表8列
  145.          {
  146.                  for(b=0;b<8;b++)  //每一列中有16个点,有2个字节,这里的8代表第一个字节的8个位或点。
  147.                  {
  148.                           p_ucResult[c]=p_ucResult[c]<<1;
  149.               p_ucResult[c]=p_ucResult[c]&0xfe;                                         
  150.               if(uiBuffer[15-b]>=0x80)   //注意,int类型数据的判断是0x8000,char型的是0x80
  151.                         {
  152.                   p_ucResult[c]=p_ucResult[c]+1;
  153.               }
  154.                       uiBuffer[15-b]=uiBuffer[15-b]<<1;
  155.          }
  156.                c++;
  157.                  
  158.                  for(b=0;b<8;b++)  //每一列中有16个点,有2个字节,这里的8代表第二个字节的8个位或点。
  159.                  {
  160.                           p_ucResult[c]=p_ucResult[c]<<1;
  161.               p_ucResult[c]=p_ucResult[c]&0xfe;                                         
  162.               if(uiBuffer[7-b]>=0x80)   //注意,int类型数据的判断是0x8000,char型的是0x80
  163.                           {
  164.                  p_ucResult[c]=p_ucResult[c]+1;
  165.               }
  166.                           uiBuffer[7-b]=uiBuffer[7-b]<<1;
  167.          }
  168.                  c++;
  169.      }
  170.          
  171. }



  172. /* 注释四:本节的核心函数,读者尤其要搞懂x_amount和y_amount对应的显示关系。
  173. * 第1,2个参数x,y是坐标体系。x的范围是0至15,y的范围是0至31.
  174. * 第3个参数*ucArray是字模的数组。
  175. * 第4个参数ucFbFlag是反白显示标志。0代表正常显示,1代表反白显示。
  176. * 第5,6个参数x_amount,y_amount分别代表字模数组的横向有多少个字节,纵向有几横。
  177. */
  178. void display_lattice(unsigned int x,unsigned int y,const unsigned char  *ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount)
  179. {
  180.    unsigned int j=0;
  181.    unsigned int i=0;
  182.    unsigned char ucTemp;
  183.    WriteCommand(0x34);  //关显示缓冲指令            
  184.    WriteCommand(0x34);  //关显示缓冲指令  故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的
  185.    for(j=0;j<y_amount;j++) //y_amount代表y轴有多少横
  186.    {
  187.        WriteCommand(y+j+0x80);        //垂直地址
  188.        WriteCommand(x+0x80);          //水平地址
  189.        for(i=0;i<x_amount;i++) //x_amount代表x轴有多少列
  190.        {
  191.          ucTemp=ucArray[j*x_amount+i];
  192.              if(ucFbFlag==1)  //反白显示
  193.                  {
  194.             ucTemp=~ucTemp;
  195.          }
  196.              LCDWriteData(ucTemp);
  197.           //         delay_short(30000);  //把上一节这个延时函数去掉,加快刷屏速度
  198.       }
  199.    }
  200.    WriteCommand(0x36); //开显示缓冲指令
  201. }


  202. void SendByteToLcd(unsigned char ucData)  //发送一个字节数据到液晶模块
  203. {
  204.         unsigned char i;
  205.         for ( i = 0; i < 8; i++ )
  206.         {
  207.                 if ( (ucData << i) & 0x80 )
  208.                 {
  209.                         LCDSID_dr = 1;
  210.                 }
  211.                 else
  212.                 {
  213.                         LCDSID_dr = 0;
  214.                 }
  215.                 LCDCLK_dr = 0;
  216.                 LCDCLK_dr = 1;
  217.         }
  218. }

  219. void SPIWrite(unsigned char ucWData, unsigned char ucWRS) //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动
  220. {
  221.         SendByteToLcd( 0xf8 + (ucWRS << 1) );
  222.         SendByteToLcd( ucWData & 0xf0 );
  223.         SendByteToLcd( (ucWData << 4) & 0xf0);
  224. }


  225. void WriteCommand(unsigned char ucCommand) //发送一个字节的命令给液晶模块
  226. {

  227.         LCDCS_dr = 0;
  228.         LCDCS_dr = 1;
  229.         SPIWrite(ucCommand, 0);
  230.         delay_short(90);
  231. }

  232. void LCDWriteData(unsigned char ucData)  //发送一个字节的数据给液晶模块
  233. {
  234.         LCDCS_dr = 0;
  235.         LCDCS_dr = 1;
  236.         SPIWrite(ucData, 1);
  237. }

  238. void LCDInit(void) //初始化  函数内部包括液晶模块的复位
  239. {
  240.         LCDRST_dr = 1;  //复位
  241.         LCDRST_dr = 0;
  242.         LCDRST_dr = 1;
  243. }



  244. void delay_short(unsigned int uiDelayShort) //延时函数
  245. {
  246.    unsigned int i;  
  247.    for(i=0;i<uiDelayShort;i++)
  248.    {
  249.      ;  
  250.    }
  251. }

总结陈词:
    有的项目会要求把字体或者图像进行镜像显示处理,这种算法程序是怎样编写的?欲知详情,请听下回分解-----在液晶屏中把字体镜像显示的算法程序。
(未完待续,下节更精彩,不要走开哦)

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

×
逝去的堕落青春 发表于 2014-10-12 13:04 | 显示全部楼层
jianhong_wu 发表于 2014-9-18 12:59
第六十九节:使用static关键字可以减少全局变量的使用。

开场白:

个人觉得,在函数内使用static定义变量时,应该在函数的开头对该变量做异常的处理机制。如:
void Working(void)
{
    static uint8_t Work_Step=0;
   
    if(某种情况)
       Work_Step=0;
/////////////////////////////////////////
    Your code
}
这样做实现外部程序对该变量进行强制初始化动作。

这样方便统一将大量函数中的大量static 变量进行异常处理。

以上纯属个人习惯,分享一下,因为我在这方面吃过亏.........
 楼主| jianhong_wu 发表于 2014-10-12 18:08 | 显示全部楼层
逝去的堕落青春 发表于 2014-10-12 13:04
个人觉得,在函数内使用static定义变量时,应该在函数的开头对该变量做异常的处理机制。如:
void Workin ...

非常好的建议。感谢你的分享。
 楼主| jianhong_wu 发表于 2014-10-13 10:56 | 显示全部楼层
第七十三节:在液晶屏中把字体镜像显示的算法程序。

开场白:
有的项目会要求把字体或者图像进行镜像显示处理,这一节把这个算法教给大家。
    这个算法的本质是:
16x16点阵的图像或者字体有16行,每行有2个字节,如果把这2个字节看成是一个16位int型数据,那么就是要这个数据从原来左边是高位,右边是低位的顺序颠倒过来。本程序没有把2个字节合并成一个int型数据,而是直接在一个字节数据内把高低位顺序颠倒过来,然后把第1字节数据跟第2字节数据交换。
8x16点阵的图像或者字体有16行,每行有1个字节,把这个数据从原来左边是高位,右边是低位的顺序颠倒过来。
具体内容,请看源代码讲解。

(1)硬件平台:
     基于朱兆祺51单片机学习板。

(2)实现功能:开机上电后,从上往下分别显示“馒头V5”四个字以及右边镜像后的“馒头V5”四个字。
   
(3)源代码讲解如下:
  1. #include "REG52.H"

  2. sbit  LCDCS_dr  = P1^6;  //片选线
  3. sbit  LCDSID_dr = P1^7;  //串行数据线
  4. sbit  LCDCLK_dr = P3^2;  //串行时钟线
  5. sbit  LCDRST_dr = P3^4;  //复位线

  6. void SendByteToLcd(unsigned char ucData);  //发送一个字节数据到液晶模块
  7. void SPIWrite(unsigned char ucWData, unsigned char ucWRS); //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动
  8. void WriteCommand(unsigned char ucCommand); //发送一个字节的命令给液晶模块
  9. void LCDWriteData(unsigned char ucData);   //发送一个字节的数据给液晶模块
  10. void LCDInit(void);  //初始化  函数内部包括液晶模块的复位
  11. void display_lattice(unsigned int x,unsigned int y,const unsigned char  *ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount); //显示任意点阵函数
  12. void display_clear(void); // 清屏
  13. void hz1616_mirror(const unsigned char  *p_ucHz,unsigned char *p_ucResult);  //把16x16点阵字库镜像
  14. void hz816_mirror(const unsigned char  *p_ucHz,unsigned char *p_ucResult);  //把8x16点阵字库镜像

  15. void delay_short(unsigned int uiDelayshort); //延时

  16. code unsigned char Hz1616_man[]= /*馒   横向取模  16X16点阵 */
  17. {
  18. 0x21,0xF8,0x21,0x08,0x21,0xF8,0x3D,0x08,0x45,0xF8,0x48,0x00,0x83,0xFC,0x22,0x94,
  19. 0x23,0xFC,0x20,0x00,0x21,0xF8,0x20,0x90,0x28,0x60,0x30,0x90,0x23,0x0E,0x00,0x00,
  20. };

  21. code unsigned char Hz1616_tou[]= /*头   横向取模  16X16点阵 */
  22. {
  23. 0x00,0x80,0x10,0x80,0x0C,0x80,0x04,0x80,0x10,0x80,0x0C,0x80,0x08,0x80,0x00,0x80,
  24. 0xFF,0xFE,0x00,0x80,0x01,0x40,0x02,0x20,0x04,0x30,0x08,0x18,0x10,0x0C,0x20,0x08,
  25. };


  26. code unsigned char Zf816_V[]= /*V   横向取模  8x16点阵 */
  27. {
  28. 0x00,0x00,0x00,0xE7,0x42,0x42,0x44,0x24,0x24,0x28,0x28,0x18,0x10,0x10,0x00,0x00,
  29. };

  30. code unsigned char Zf816_5[]= /*5   横向取模  8x16点阵 */
  31. {
  32. 0x00,0x00,0x00,0x7E,0x40,0x40,0x40,0x58,0x64,0x02,0x02,0x42,0x44,0x38,0x00,0x00,
  33. };


  34. unsigned char ucBufferResult[32]; //用于临时存放转换结束后的字模数组

  35. void main()
  36.   {
  37.         LCDInit(); //初始化12864 内部包含液晶模块的复位

  38.         display_clear(); // 清屏

  39.         display_lattice(0,0,Hz1616_man,0,2,16);  //显示镜像前的<馒>字
  40.         hz1616_mirror(Hz1616_man,ucBufferResult);  //把<馒>字镜像后放到ucBufferResult临时变量里。
  41.         display_lattice(1,0,ucBufferResult,0,2,16);  //显示镜像后的<馒>字


  42.         display_lattice(0,16,Hz1616_tou,0,2,16);  //显示镜像前的<头>字
  43.         hz1616_mirror(Hz1616_tou,ucBufferResult);  //把<头>字镜像后放到ucBufferResult临时变量里。
  44.         display_lattice(1,16,ucBufferResult,0,2,16);  //显示镜像后的<头>字

  45.         display_lattice(8,0,Zf816_V,0,1,16);  //显示镜像前的<V>字符
  46.         hz816_mirror(Zf816_V,ucBufferResult);  //把<V>字符镜像后放到ucBufferResult临时变量里。
  47.         display_lattice(9,0,ucBufferResult,0,1,16);  //显示镜像后的<V>字符

  48.         display_lattice(8,16,Zf816_5,0,1,16);  //显示镜像前的<5>字符
  49.         hz816_mirror(Zf816_5,ucBufferResult);  //把<5>字符镜像后放到ucBufferResult临时变量里。
  50.         display_lattice(9,16,ucBufferResult,0,1,16);  //显示镜像后的<5>字符

  51.         while(1)  
  52.         {
  53.              ;
  54.         }

  55. }



  56. void display_clear(void) // 清屏
  57. {   

  58.     unsigned char x,y;
  59.     WriteCommand(0x34);  //关显示缓冲指令            
  60.     WriteCommand(0x34);  //关显示缓冲指令  故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的
  61.     y=0;
  62.     while(y<32)  //y轴的范围0至31
  63.     {
  64.          WriteCommand(y+0x80);        //垂直地址
  65.          WriteCommand(0x80);          //水平地址
  66.          for(x=0;x<32;x++)  //256个横向点,有32个字节
  67.          {  
  68.             LCDWriteData(0x00);
  69.          }
  70.          y++;
  71.     }
  72.     WriteCommand(0x36); //开显示缓冲指令

  73. }

  74. /* 注释一:
  75. * 16x16点阵镜像的本质:
  76. * 16x16点阵有16行,每行有2个字节,如果把这2个字节看成是一个16位int型数据,
  77. * 那么就是要这个数据从原来左边是高位,右边是低位的顺序颠倒过来。本程序没有把2个字节
  78. * 合并成一个int型数据,而是直接在一个字节数据内把高低位顺序颠倒过来,然后把第1字节数据跟第2字节数据交换。
  79. */
  80. void hz1616_mirror(const unsigned char  *p_ucHz,unsigned char *p_ucResult)  //把16x16点阵字库镜像的函数
  81. {
  82.          unsigned char a;
  83.          unsigned char b;
  84.          unsigned char c;
  85.          unsigned char d;
  86.        
  87.          for(a=0;a<16;a++) //这里16代表有16行。每一行有2个字节。把每一个字节看做一列,这里先把第1列字节的数据从原来左边是高位,右边是低位的顺序颠倒过来,相当于镜像。
  88.          {
  89.             b=p_ucHz[a*2+0];  //这里的2代表16x16点阵每行有2列字节,0代表从第1列开始。
  90.             c=0;
  91.             for(d=0;d<8;d++)  //把一个字节调换顺序
  92.                 {
  93.                c=c>>1;
  94.            if((b&0x80)==0x80)
  95.                    {
  96.              c=c|0x80;
  97.            }
  98.                    b=b<<1;
  99.         }                 
  100.         p_ucResult[a*2+1]=c;   //注意,因为是镜像,所以要把颠倒顺序后的字节从原来是第1列的调换到第2列         
  101.      }
  102.          
  103.          for(a=0;a<16;a++)  //这里16代表有16行。每一行有2个字节。把每一个字节看做一列,这里先把第2列字节的数据从原来左边是高位,右边是低位的顺序颠倒过来,相当于镜像。
  104.          {
  105.             b=p_ucHz[a*2+1];   //这里的2代表16x16点阵每行有2列字节,1代表从第2列开始。
  106.                  
  107.             c=0;
  108.             for(d=0;d<8;d++)  //把一个字节调换顺序
  109.                 {
  110.                         c=c>>1;
  111.             if((b&0x80)==0x80)
  112.                         {
  113.               c=c|0x80;
  114.             }
  115.                         b=b<<1;
  116.          }

  117.          p_ucResult[a*2+0]=c;         //注意,因为是镜像,所以要把颠倒顺序后的字节从原来是第2列的调换到第1列        

  118.                  
  119.      }
  120.          

  121. }



  122. /* 注释二:
  123. * 8x16点阵镜像的本质:
  124. * 8x16点阵有16行,每行有1个字节,把这个数据从原来左边是高位,右边是低位的顺序颠倒过来。
  125. */
  126. void hz816_mirror(const unsigned char  *p_ucHz,unsigned char *p_ucResult)  //把8x16点阵字库镜像的函数
  127. {
  128.          unsigned char a;
  129.          unsigned char b;
  130.          unsigned char c;
  131.          unsigned char d;
  132.        
  133.          for(a=0;a<16;a++) //这里16代表有16行。每一行有1个字节。这里先把每一行字节的数据从原来左边是高位,右边是低位的顺序颠倒过来,相当于镜像。
  134.          {
  135.             b=p_ucHz[a*1+0];  //这里的1代表8x16点阵每行有1列字节,0代表从第1列开始。
  136.             c=0;
  137.             for(d=0;d<8;d++)  //把一个字节调换顺序
  138.                 {
  139.                c=c>>1;
  140.            if((b&0x80)==0x80)
  141.                    {
  142.              c=c|0x80;
  143.            }
  144.                    b=b<<1;
  145.         }                 
  146.         p_ucResult[a*1+0]=c;   //注意,因为每一行只有一列,所以不用像16x16点阵那样把第1列跟第2列对调交换。
  147.      }
  148.          
  149. }



  150. /* 注释三:本节的核心函数,读者尤其要搞懂x_amount和y_amount对应的显示关系。
  151. * 第1,2个参数x,y是坐标体系。x的范围是0至15,y的范围是0至31.
  152. * 第3个参数*ucArray是字模的数组。
  153. * 第4个参数ucFbFlag是反白显示标志。0代表正常显示,1代表反白显示。
  154. * 第5,6个参数x_amount,y_amount分别代表字模数组的横向有多少个字节,纵向有几横。
  155. */
  156. void display_lattice(unsigned int x,unsigned int y,const unsigned char  *ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount)
  157. {
  158.    unsigned int j=0;
  159.    unsigned int i=0;
  160.    unsigned char ucTemp;
  161.    WriteCommand(0x34);  //关显示缓冲指令            
  162.    WriteCommand(0x34);  //关显示缓冲指令  故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的
  163.    for(j=0;j<y_amount;j++) //y_amount代表y轴有多少横
  164.    {
  165.        WriteCommand(y+j+0x80);        //垂直地址
  166.        WriteCommand(x+0x80);          //水平地址
  167.        for(i=0;i<x_amount;i++) //x_amount代表x轴有多少列
  168.        {
  169.          ucTemp=ucArray[j*x_amount+i];
  170.              if(ucFbFlag==1)  //反白显示
  171.                  {
  172.             ucTemp=~ucTemp;
  173.          }
  174.              LCDWriteData(ucTemp);
  175.           //         delay_short(30000);  //把上一节这个延时函数去掉,加快刷屏速度
  176.       }
  177.    }
  178.    WriteCommand(0x36); //开显示缓冲指令
  179. }


  180. void SendByteToLcd(unsigned char ucData)  //发送一个字节数据到液晶模块
  181. {
  182.         unsigned char i;
  183.         for ( i = 0; i < 8; i++ )
  184.         {
  185.                 if ( (ucData << i) & 0x80 )
  186.                 {
  187.                         LCDSID_dr = 1;
  188.                 }
  189.                 else
  190.                 {
  191.                         LCDSID_dr = 0;
  192.                 }
  193.                 LCDCLK_dr = 0;
  194.                 LCDCLK_dr = 1;
  195.         }
  196. }

  197. void SPIWrite(unsigned char ucWData, unsigned char ucWRS) //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动
  198. {
  199.         SendByteToLcd( 0xf8 + (ucWRS << 1) );
  200.         SendByteToLcd( ucWData & 0xf0 );
  201.         SendByteToLcd( (ucWData << 4) & 0xf0);
  202. }


  203. void WriteCommand(unsigned char ucCommand) //发送一个字节的命令给液晶模块
  204. {

  205.         LCDCS_dr = 0;
  206.         LCDCS_dr = 1;
  207.         SPIWrite(ucCommand, 0);
  208.         delay_short(90);
  209. }

  210. void LCDWriteData(unsigned char ucData)  //发送一个字节的数据给液晶模块
  211. {
  212.         LCDCS_dr = 0;
  213.         LCDCS_dr = 1;
  214.         SPIWrite(ucData, 1);
  215. }

  216. void LCDInit(void) //初始化  函数内部包括液晶模块的复位
  217. {
  218.         LCDRST_dr = 1;  //复位
  219.         LCDRST_dr = 0;
  220.         LCDRST_dr = 1;
  221. }



  222. void delay_short(unsigned int uiDelayShort) //延时函数
  223. {
  224.    unsigned int i;  
  225.    for(i=0;i<uiDelayShort;i++)
  226.    {
  227.      ;  
  228.    }
  229. }
总结陈词:
    细心的网友一定会发现,这种12864液晶屏普遍有个毛病,在坐标轴x,y方向上不能完全做到以一个点阵为单位进行随心所欲的显示,比如横向的至少是一个字节8个点阵为单位,而第1,2行跟第3,4行又做不到无缝对接显示,假如我要把汉字一半显示在第2行一半显示在第3行,行不行?当然可以。但是需要我们编写额外的算法程序。这种算法程序是怎样编写的?欲知详情,请听下回分解-----在液晶屏中让字体可以跨区域无缝对接显示的算法程序。

(未完待续,下节更精彩,不要走开哦)

 楼主| jianhong_wu 发表于 2014-10-16 15:09 | 显示全部楼层
第七十四节:在液晶屏中让字体可以跨区域无缝对接显示的算法程序。

开场白:
细心的网友会发现,这种12864液晶屏在显示自造字库时普遍有个毛病,在坐标轴x方向上是以每16个点阵为一个单位的,如果显示两个8x16字符”V”和”5”,虽然它们的x坐标轴是相邻的,但是实际显示的效果是中间隔了8个点阵。另外,这种12864液晶屏是由上半屏和下半屏组成的,软件上的坐标体系并没有做到跟物理的坐标体系一致,需要转换的。如果我们想把一个整体字符的一半显示在上半屏,另一半显示在下半屏,那怎么办?
这一节就要教给大家这个算法程序:
为了实现跨区域无缝显示,就先在某个区域显示一块画布,我们只要在这块画布数组中插入字模数组,就可以达到跨区域无缝显示的目的。

具体内容,请看源代码讲解。

(1)硬件平台:
     基于朱兆祺51单片机学习板。

(2)实现功能:开机上电后,看到液晶屏所有的点阵都显示。正中间露出一小方块空白的32x16点阵画布,从左到右分别显示“V5”两个字符。这两个字符是紧紧挨在一起的,中间并没有8个点阵的空格,同时这两个字符的上半部分显示在上半屏,下半部分显示在下半屏。实现了真正的跨区域无缝对接显示。

(3)源代码讲解如下:
  1. #include "REG52.H"

  2. sbit  LCDCS_dr  = P1^6;  //片选线
  3. sbit  LCDSID_dr = P1^7;  //串行数据线
  4. sbit  LCDCLK_dr = P3^2;  //串行时钟线
  5. sbit  LCDRST_dr = P3^4;  //复位线

  6. void SendByteToLcd(unsigned char ucData);  //发送一个字节数据到液晶模块
  7. void SPIWrite(unsigned char ucWData, unsigned char ucWRS); //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动
  8. void WriteCommand(unsigned char ucCommand); //发送一个字节的命令给液晶模块
  9. void LCDWriteData(unsigned char ucData);   //发送一个字节的数据给液晶模块
  10. void LCDInit(void);  //初始化  函数内部包括液晶模块的复位
  11. void display_clear(unsigned char ucFillDate); // 清屏 全部显示空填充0x00   全部显示点阵用0xff
  12. void insert_buffer_to_canvas(unsigned int x,unsigned int y,const unsigned char  *ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount);//把字模插入画布.
  13. void display_lattice(unsigned int x,unsigned int y,const unsigned char  *ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount,unsigned int uiOffSetAddr); //显示任意点阵函数
  14. void delay_short(unsigned int uiDelayshort); //延时

  15. code unsigned char Zf816_V[]= /*V   横向取模  8x16点阵 每一行只要1个字节,共16行 */
  16. {
  17. 0x00,
  18. 0x00,
  19. 0x00,
  20. 0xE7,
  21. 0x42,
  22. 0x42,
  23. 0x44,
  24. 0x24,
  25. 0x24,
  26. 0x28,
  27. 0x28,
  28. 0x18,
  29. 0x10,
  30. 0x10,
  31. 0x00,
  32. 0x00,
  33. };

  34. code unsigned char Zf816_5[]= /*5   横向取模  8x16点阵 每一行只要1个字节,共16行 */
  35. {
  36. 0x00,
  37. 0x00,
  38. 0x00,
  39. 0x7E,
  40. 0x40,
  41. 0x40,
  42. 0x40,
  43. 0x58,
  44. 0x64,
  45. 0x02,
  46. 0x02,
  47. 0x42,
  48. 0x44,
  49. 0x38,
  50. 0x00,
  51. 0x00,
  52. };


  53. /* 注释一:
  54. * 为了实现跨区域无缝显示,就先在某个区域显示一块画布,我们只要在这块画布数组中插入字模数组,
  55. * 就可以达到跨区域无缝显示的目的。根据上几节的介绍,12864液晶屏由上下两半屏组成,以下这块画布
  56. * 显示在上半屏和下半屏之间。横向4个字节,纵向16行。其中上半屏显示8行,下半屏显示8行。注意,这个数组
  57. * 不带code关键字,是全局变量,这样可读可写。画布的横向x坐标范围是0至3,因为画布的横向只要4个字节。
  58. * 画布的纵向y坐标范围是0至15,因为画布的纵向只有16行。
  59. */
  60. unsigned char ucCanvasBuffer[]= //画布显示数组。注意,这里没有code关键字,是全局变量。初始化全部填充0x00
  61. {
  62. 0x00,0x00,0x00,0x00,  //上半屏
  63. 0x00,0x00,0x00,0x00,
  64. 0x00,0x00,0x00,0x00,
  65. 0x00,0x00,0x00,0x00,
  66. 0x00,0x00,0x00,0x00,
  67. 0x00,0x00,0x00,0x00,
  68. 0x00,0x00,0x00,0x00,
  69. 0x00,0x00,0x00,0x00,

  70. //------------上半屏和下半屏的分割线-----------

  71. 0x00,0x00,0x00,0x00,  //下半屏
  72. 0x00,0x00,0x00,0x00,
  73. 0x00,0x00,0x00,0x00,
  74. 0x00,0x00,0x00,0x00,
  75. 0x00,0x00,0x00,0x00,
  76. 0x00,0x00,0x00,0x00,
  77. 0x00,0x00,0x00,0x00,
  78. 0x00,0x00,0x00,0x00,
  79. };



  80. void main()
  81.   {
  82.         LCDInit(); //初始化12864 内部包含液晶模块的复位

  83.         display_clear(0xff); // 清屏 全部显示空填充0x00   全部显示点阵用0xff

  84.         insert_buffer_to_canvas(0,0,Zf816_V,0,1,16);//把<V>的字模插入画布
  85.         insert_buffer_to_canvas(1,0,Zf816_5,0,1,16);//把<5>的字模插入画布

  86.         display_lattice(3,24,ucCanvasBuffer,0,4,8,0);   //显示上半屏的画布,最后的参数0是偏移量
  87.         display_lattice(11,0,ucCanvasBuffer,0,4,8,32);  //显示下半屏的画布,最后的参数32是偏移量


  88.         while(1)  
  89.         {
  90.              ;
  91.         }

  92. }



  93. void display_clear(unsigned char ucFillDate) // 清屏  全部显示空填充0x00   全部显示点阵用0xff
  94. {   

  95.     unsigned char x,y;
  96.     WriteCommand(0x34);  //关显示缓冲指令            
  97.     WriteCommand(0x34);  //关显示缓冲指令  故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的
  98.     y=0;
  99.     while(y<32)  //y轴的范围0至31
  100.     {
  101.          WriteCommand(y+0x80);        //垂直地址
  102.          WriteCommand(0x80);          //水平地址
  103.          for(x=0;x<32;x++)  //256个横向点,有32个字节
  104.          {  
  105.             LCDWriteData(ucFillDate);
  106.          }
  107.          y++;
  108.     }
  109.     WriteCommand(0x36); //开显示缓冲指令

  110. }

  111. /* 注释二:
  112. * 把字模插入画布的函数.
  113. * 这是本节的核心函数,读者尤其要搞懂x_amount和y_amount对应的显示关系。
  114. * 第1,2个参数x,y是在画布中的坐标体系。
  115. * x的范围是0至3,因为画布的横向只要4个字节。y的范围是0至15,因为画布的纵向只有16行。
  116. * 第3个参数*ucArray是字模的数组。
  117. * 第4个参数ucFbFlag是反白显示标志。0代表正常显示,1代表反白显示。
  118. * 第5,6个参数x_amount,y_amount分别代表字模数组的横向有多少个字节,纵向有几横。
  119. */
  120. void insert_buffer_to_canvas(unsigned int x,unsigned int y,const unsigned char  *ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount)
  121. {
  122.    unsigned int j=0;
  123.    unsigned int i=0;
  124.    unsigned char ucTemp;
  125.    for(j=0;j<y_amount;j++)
  126.    {
  127.       for(i=0;i<x_amount;i++)
  128.       {
  129.                    ucTemp=ucArray[j*x_amount+i];
  130.                    if(ucFbFlag==0)
  131.                    {
  132.               ucCanvasBuffer[(y+j)*4+x+i]=ucTemp; //这里的4代表画布每一行只有4个字节
  133.                    }
  134.                    else
  135.                    {
  136.               ucCanvasBuffer[(y+j)*4+x+i]=~ucTemp; //这里的4代表画布每一行只有4个字节
  137.                    }
  138.       }
  139.    }         

  140. }

  141. /* 注释三:
  142. * 显示任意点阵函数.
  143. * 注意,本函数在前几节的基础上多增加了第7个参数uiOffSetAddr,它是偏移地址。
  144. * 对于这个函数,读者尤其要搞懂x_amount和y_amount对应的显示关系。
  145. * 第1,2个参数x,y是坐标体系。x的范围是0至15,y的范围是0至31.
  146. * 第3个参数*ucArray是字模的数组。
  147. * 第4个参数ucFbFlag是反白显示标志。0代表正常显示,1代表反白显示。
  148. * 第5,6个参数x_amount,y_amount分别代表字模数组的横向有多少个字节,纵向有几横。
  149. * 第7个参数uiOffSetAddr是偏移地址,代表字模数组的从第几个数据开始显示。
  150. */
  151. void display_lattice(unsigned int x,unsigned int y,const unsigned char  *ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount,unsigned int uiOffSetAddr)
  152. {
  153.    unsigned int j=0;
  154.    unsigned int i=0;
  155.    unsigned char ucTemp;
  156.    WriteCommand(0x34);  //关显示缓冲指令            
  157.    WriteCommand(0x34);  //关显示缓冲指令  故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的
  158.    for(j=0;j<y_amount;j++) //y_amount代表y轴有多少横
  159.    {
  160.        WriteCommand(y+j+0x80);        //垂直地址
  161.        WriteCommand(x+0x80);          //水平地址
  162.        for(i=0;i<x_amount;i++) //x_amount代表x轴有多少列
  163.        {
  164.            ucTemp=ucArray[j*x_amount+i+uiOffSetAddr]; //uiOffSetAddr是字模数组的偏移地址
  165.            if(ucFbFlag==1)  //反白显示
  166.            {
  167.                ucTemp=~ucTemp;
  168.            }
  169.            LCDWriteData(ucTemp);
  170.           //         delay_short(30000);  //把上一节这个延时函数去掉,加快刷屏速度
  171.       }
  172.    }
  173.    WriteCommand(0x36); //开显示缓冲指令
  174. }


  175. void SendByteToLcd(unsigned char ucData)  //发送一个字节数据到液晶模块
  176. {
  177.         unsigned char i;
  178.         for ( i = 0; i < 8; i++ )
  179.         {
  180.                 if ( (ucData << i) & 0x80 )
  181.                 {
  182.                         LCDSID_dr = 1;
  183.                 }
  184.                 else
  185.                 {
  186.                         LCDSID_dr = 0;
  187.                 }
  188.                 LCDCLK_dr = 0;
  189.                 LCDCLK_dr = 1;
  190.         }
  191. }

  192. void SPIWrite(unsigned char ucWData, unsigned char ucWRS) //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动
  193. {
  194.         SendByteToLcd( 0xf8 + (ucWRS << 1) );
  195.         SendByteToLcd( ucWData & 0xf0 );
  196.         SendByteToLcd( (ucWData << 4) & 0xf0);
  197. }


  198. void WriteCommand(unsigned char ucCommand) //发送一个字节的命令给液晶模块
  199. {

  200.         LCDCS_dr = 0;
  201.         LCDCS_dr = 1;
  202.         SPIWrite(ucCommand, 0);
  203.         delay_short(90);
  204. }

  205. void LCDWriteData(unsigned char ucData)  //发送一个字节的数据给液晶模块
  206. {
  207.         LCDCS_dr = 0;
  208.         LCDCS_dr = 1;
  209.         SPIWrite(ucData, 1);
  210. }

  211. void LCDInit(void) //初始化  函数内部包括液晶模块的复位
  212. {
  213.         LCDRST_dr = 1;  //复位
  214.         LCDRST_dr = 0;
  215.         LCDRST_dr = 1;
  216. }



  217. void delay_short(unsigned int uiDelayShort) //延时函数
  218. {
  219.    unsigned int i;  
  220.    for(i=0;i<uiDelayShort;i++)
  221.    {
  222.      ;  
  223.    }
  224. }
总结陈词:
    经过这一节的算法处理后,字符终于可以在x轴上紧紧挨着显示了。也就是把原来x坐标是16个点阵为一个单位,改成了以8个点阵为一个单位。如果要求以1个点阵为单位显示,那该怎么办?这个还真有点难度,因为横向的最小显示单位就是一个字节8个点,不过鸿哥在下一节中照样有办法实现这个功能。欲知详情,请听下回分解-----在12864液晶屏中让字体以1个点阵为单位进行移动显示的算法程序。

(未完待续,下节更精彩,不要走开哦)

评论

这个段代码谁能再清晰的解释一下吗  发表于 2018-9-6 14:01
c=0; for(a=0;a<16;a++) //这里的16代表16列 { for(b=0;b<8;b++) //每一列中有16个点,有2个字节,这里的8代表第一个字节的8个位或点。 { p_ucResult[c]=p_ucResult[c]<<1; p_ucResult[c]  发表于 2018-9-6 14:00
armxu 发表于 2014-10-17 15:56 来自手机 | 显示全部楼层
没有源码,能发上来吗?
 楼主| jianhong_wu 发表于 2014-10-19 10:05 | 显示全部楼层
第七十五节:在12864液晶屏中让字体以1个点阵为单位进行移动显示的算法程序。

开场白:
    假设有一个固定的四方形透明窗口,在窗口里面放了一张画布,只要想办法让这个画布
往右边拖动,那么画布里面的内容就会跟着画布整体往右边移动,这个就是能以1个点阵为单位进行移动显示的本质。同理,这个画布有16行,每行有4个字节,我们只要把每行4个字节看作是一个首尾连接的二进制数据,把每一行的二进制数据每次整体往右边移动一位,就相当于移动一个点阵了。这一节就要把这个算法教给大家。

具体内容,请看源代码讲解。

(1)硬件平台:
    基于朱兆祺51单片机学习板。

(2)实现功能:开机上电后,能看到正中间显示的两个字符“V5”整体以1个点阵为单位向右边慢慢移动。

(3)源代码讲解如下:
  1. #include "REG52.H"

  2. #define const_MoveTime 400  //每移动一位后的延时时间

  3. sbit  LCDCS_dr  = P1^6;  //片选线
  4. sbit  LCDSID_dr = P1^7;  //串行数据线
  5. sbit  LCDCLK_dr = P3^2;  //串行时钟线
  6. sbit  LCDRST_dr = P3^4;  //复位线

  7. void SendByteToLcd(unsigned char ucData);  //发送一个字节数据到液晶模块
  8. void SPIWrite(unsigned char ucWData, unsigned char ucWRS); //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动
  9. void WriteCommand(unsigned char ucCommand); //发送一个字节的命令给液晶模块
  10. void LCDWriteData(unsigned char ucData);   //发送一个字节的数据给液晶模块
  11. void LCDInit(void);  //初始化  函数内部包括液晶模块的复位
  12. void display_clear(unsigned char ucFillDate); // 清屏 全部显示空填充0x00   全部显示点阵用0xff
  13. void insert_buffer_to_canvas(unsigned int x,unsigned int y,const unsigned char  *ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount);//把字模插入画布.
  14. void display_lattice(unsigned int x,unsigned int y,const unsigned char  *ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount,unsigned int uiOffSetAddr); //显示任意点阵函数
  15. void delay_short(unsigned int uiDelayshort); //延时

  16. void move_service(void); //整体画布移动的应用程序
  17. void lcd_display_service(void); //应用层面的液晶屏显示程序
  18. void move_canvas_to_one_bit(void);  //把画布整体往右边移动一个点阵
  19. void clear_all_canvas(void);  //把画布全部清零

  20. void T0_time(void);  //定时中断函数

  21. code unsigned char Zf816_V[]= /*V   横向取模  8x16点阵 每一行只要1个字节,共16行 */
  22. {
  23. 0x00,
  24. 0x00,
  25. 0x00,
  26. 0xE7,
  27. 0x42,
  28. 0x42,
  29. 0x44,
  30. 0x24,
  31. 0x24,
  32. 0x28,
  33. 0x28,
  34. 0x18,
  35. 0x10,
  36. 0x10,
  37. 0x00,
  38. 0x00,
  39. };

  40. code unsigned char Zf816_5[]= /*5   横向取模  8x16点阵 每一行只要1个字节,共16行 */
  41. {
  42. 0x00,
  43. 0x00,
  44. 0x00,
  45. 0x7E,
  46. 0x40,
  47. 0x40,
  48. 0x40,
  49. 0x58,
  50. 0x64,
  51. 0x02,
  52. 0x02,
  53. 0x42,
  54. 0x44,
  55. 0x38,
  56. 0x00,
  57. 0x00,
  58. };


  59. /* 注释一:
  60. * 为了实现跨区域无缝显示,就先在某个区域显示一块画布,我们只要在这块画布数组中插入字模数组,
  61. * 就可以达到跨区域无缝显示的目的。根据上几节的介绍,12864液晶屏由上下两半屏组成,以下这块画布
  62. * 显示在上半屏和下半屏之间。横向4个字节,纵向16行。其中上半屏显示8行,下半屏显示8行。注意,这个数组
  63. * 不带code关键字,是全局变量,这样可读可写。画布的横向x坐标范围是0至3,因为画布的横向只要4个字节。
  64. * 画布的纵向y坐标范围是0至15,因为画布的纵向只有16行。
  65. */
  66. unsigned char ucCanvasBuffer[]= //画布显示数组。注意,这里没有code关键字,是全局变量。初始化全部填充0x00
  67. {
  68. 0x00,0x00,0x00,0x00,  //上半屏
  69. 0x00,0x00,0x00,0x00,
  70. 0x00,0x00,0x00,0x00,
  71. 0x00,0x00,0x00,0x00,
  72. 0x00,0x00,0x00,0x00,
  73. 0x00,0x00,0x00,0x00,
  74. 0x00,0x00,0x00,0x00,
  75. 0x00,0x00,0x00,0x00,

  76. //------------上半屏和下半屏的分割线-----------

  77. 0x00,0x00,0x00,0x00,  //下半屏
  78. 0x00,0x00,0x00,0x00,
  79. 0x00,0x00,0x00,0x00,
  80. 0x00,0x00,0x00,0x00,
  81. 0x00,0x00,0x00,0x00,
  82. 0x00,0x00,0x00,0x00,
  83. 0x00,0x00,0x00,0x00,
  84. 0x00,0x00,0x00,0x00,
  85. };

  86. unsigned char ucDisplayUpdate=1;  //更新显示变量
  87. unsigned char ucMoveStepReset=0;  //这个变量是为了方便外部程序初始化应用程序内部后缀为step的步骤变量

  88. unsigned char ucMoveTimeStart=0; //定时器的开关标志  也相当于原子锁或互斥量的功能
  89. unsigned int uiMoveTime=0;  //定时器累计时间

  90. void main()
  91.   {

  92.         LCDInit(); //初始化12864 内部包含液晶模块的复位
  93.         display_clear(0xff); // 清屏 全部显示空填充0x00   全部显示点阵用0xff


  94.         TMOD=0x01;  //设置定时器0为工作方式1
  95.         TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  96.         TL0=0x2f;
  97.         EA=1;     //开总中断
  98.         ET0=1;    //允许定时中断
  99.         TR0=1;    //启动定时中断


  100.         while(1)  
  101.         {
  102.            move_service(); //整体画布移动的应用程序
  103.            lcd_display_service(); //应用层面的液晶屏显示程序
  104.         }

  105. }



  106. void move_service(void) //整体画布移动的应用程序
  107. {
  108.    static unsigned char ucMoveStep=0; //运行步骤。前面加关键字static表示上电后这个变量只初始化一次,以后每次进出函数此变量不会重新初始化,保存之前的更改数值不变。
  109.    static unsigned char ucMoveCnt=0; //统计当前已经往左边移动了多少位。关键字static表示此变量上电后只初始化一次,不会每次进入函数都初始化。

  110.    if(ucMoveStepReset==1)  //运行步骤的复位标志,此段代码结构方便外部程序初始化函数内部的步骤变量ucMoveStep
  111.    {
  112.       ucMoveStepReset=0; //及时把复位标志清零。避免一直处于复位的状态、

  113.           ucMoveStep=0; //运行步骤变量被外部程序通过复位标志初始化。
  114.    }

  115.    switch(ucMoveStep)
  116.    {
  117.       case 0:
  118.                clear_all_canvas();  //把画布全部清零
  119.            insert_buffer_to_canvas(0,0,Zf816_V,0,1,16);//把<V>的字模插入画布
  120.            insert_buffer_to_canvas(1,0,Zf816_5,0,1,16);//把<5>的字模插入画布
  121.            ucDisplayUpdate=1; //更新液晶屏显示
  122.                   
  123.                    uiMoveTime=0;  //定时器清零
  124.                    ucMoveTimeStart=1; //开定时器     也相当于原子锁或互斥量的功能
  125.                    ucMoveCnt=0; //统计当前已经往左边移动了多少位
  126.                    ucMoveStep=1; //切换到下一个运行步骤

  127.                break;

  128.       case 1:
  129.                if(uiMoveTime>const_MoveTime)  //延时一定的时间后
  130.                    {
  131.                               ucMoveTimeStart=0; //关定时器    也相当于原子锁或互斥量的功能
  132.                        uiMoveTime=0;  //定时器清零

  133.                    if(ucMoveCnt<16)
  134.                        {
  135.                           ucMoveCnt++;
  136.                   move_canvas_to_one_bit(); //把画布整体往左边移动一个点阵
  137.                   ucDisplayUpdate=1; //更新液晶屏显示
  138.                                   ucMoveTimeStart=1; //开定时器   也相当于原子锁或互斥量的功能

  139.                        }
  140.                        else
  141.                        {
  142.                                   ucMoveStep=0; //移动了16个点阵后,返回上一个运行步骤,把字模重新插入画布
  143.                        }



  144.            }
  145.                break;
  146.    }

  147. }


  148. void lcd_display_service(void) //应用层面的液晶屏显示程序
  149. {
  150.     if(ucDisplayUpdate==1)  //需要更新显示
  151.     {
  152.        ucDisplayUpdate=0;  //及时把标志清零,避免一直处于不断更新的状态。


  153.        display_lattice(3,24,ucCanvasBuffer,0,4,8,0);   //显示上半屏的画布,最后的参数0是偏移量
  154.        display_lattice(11,0,ucCanvasBuffer,0,4,8,32);  //显示下半屏的画布,最后的参数32是偏移量
  155.     }
  156. }

  157. /* 注释二:
  158. * 假设有一个固定的四方形透明窗口,在窗口里面放了一张画布,只要想办法让这个画布
  159. * 往右边拖动,那么画布里面的内容就会跟着画布整体往右边移动,这个就是能以1个点阵为单位进行移动显示的本质。
  160. * 同理,这个画布有16行,每行有4个字节,我们只要把每行4个字节看作是一个首尾连接的二进制数据,
  161. * 把每一行的二进制数据每次整体往右边移动一位,就相当于移动一个点阵了。
  162. */

  163. void move_canvas_to_one_bit(void)  //把画布整体往右边移动一个点阵
  164. {
  165.    unsigned int j=0;
  166.    unsigned int i=0;
  167.    unsigned char ucBitH;  //临时保存一个字节中的最高位
  168.    unsigned char ucBitL;  //临时保存一个字节中的最低位

  169.    for(j=0;j<16;j++)  //这里的16表示画布有16行
  170.    {
  171.       ucBitH=0;   
  172.           ucBitL=0;   
  173.       for(i=0;i<4;i++) //这里的4表示画布每行有4个字节
  174.       {
  175.                   if((ucCanvasBuffer[j*4+i]&0x01)==0x01)  //临时保存一个字节中的最低位
  176.                   {
  177.                      ucBitL=1;
  178.                   }
  179.                   else
  180.                   {
  181.                      ucBitL=0;
  182.                   }
  183.                   ucCanvasBuffer[j*4+i]=ucCanvasBuffer[j*4+i]>>1;  //一行中的一个字节右移一位

  184.                   if(ucBitH==1)   //原来左边相邻的字节最低位移动到了当前字节的最高位
  185.                   {
  186.              ucCanvasBuffer[j*4+i]=ucCanvasBuffer[j*4+i]|0x80; //把最高位补上
  187.                   }
  188.           ucBitH=ucBitL;  //把当前的最低位赋值给最高位,为下一个相邻字节做准备。
  189.       }
  190.    }         

  191. }


  192. void clear_all_canvas(void)  //把画布全部清零
  193. {
  194.    unsigned int j=0;
  195.    unsigned int i=0;

  196.    for(j=0;j<16;j++)  //这里的16表示画布有16行
  197.    {
  198.       for(i=0;i<4;i++) //这里的4表示画布每行有4个字节
  199.       {
  200.                   ucCanvasBuffer[j*4+i]=0x00;
  201.       }
  202.    }         

  203. }


  204. void T0_time(void) interrupt 1  //定时中断函数
  205. {
  206.   TF0=0;  //清除中断标志
  207.   TR0=0; //关中断

  208.   if(ucMoveTimeStart==1) //已经开了定时器  也相当于原子锁或互斥量的功能
  209.   {
  210.       uiMoveTime++; //定时器累加计时开始
  211.   }

  212.   TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  213.   TL0=0x2f;
  214.   TR0=1;  //开中断
  215. }



  216. void display_clear(unsigned char ucFillDate) // 清屏  全部显示空填充0x00   全部显示点阵用0xff
  217. {   

  218.     unsigned char x,y;
  219.     WriteCommand(0x34);  //关显示缓冲指令            
  220.     WriteCommand(0x34);  //关显示缓冲指令  故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的
  221.     y=0;
  222.     while(y<32)  //y轴的范围0至31
  223.     {
  224.          WriteCommand(y+0x80);        //垂直地址
  225.          WriteCommand(0x80);          //水平地址
  226.          for(x=0;x<32;x++)  //256个横向点,有32个字节
  227.          {  
  228.             LCDWriteData(ucFillDate);
  229.          }
  230.          y++;
  231.     }
  232.     WriteCommand(0x36); //开显示缓冲指令

  233. }

  234. /* 注释三:
  235. * 把字模插入画布的函数.
  236. * 这是本节的核心函数,读者尤其要搞懂x_amount和y_amount对应的显示关系。
  237. * 第1,2个参数x,y是在画布中的坐标体系。
  238. * x的范围是0至3,因为画布的横向只要4个字节。y的范围是0至15,因为画布的纵向只有16行。
  239. * 第3个参数*ucArray是字模的数组。
  240. * 第4个参数ucFbFlag是反白显示标志。0代表正常显示,1代表反白显示。
  241. * 第5,6个参数x_amount,y_amount分别代表字模数组的横向有多少个字节,纵向有几横。
  242. */
  243. void insert_buffer_to_canvas(unsigned int x,unsigned int y,const unsigned char  *ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount)
  244. {
  245.    unsigned int j=0;
  246.    unsigned int i=0;
  247.    unsigned char ucTemp;
  248.    for(j=0;j<y_amount;j++)
  249.    {
  250.       for(i=0;i<x_amount;i++)
  251.       {
  252.                    ucTemp=ucArray[j*x_amount+i];
  253.                    if(ucFbFlag==0)
  254.                    {
  255.               ucCanvasBuffer[(y+j)*4+x+i]=ucTemp; //这里的4代表画布每一行只有4个字节
  256.                    }
  257.                    else
  258.                    {
  259.               ucCanvasBuffer[(y+j)*4+x+i]=~ucTemp; //这里的4代表画布每一行只有4个字节
  260.                    }
  261.       }
  262.    }         

  263. }

  264. /* 注释四:
  265. * 显示任意点阵函数.
  266. * 注意,本函数在前几节的基础上多增加了第7个参数uiOffSetAddr,它是偏移地址。
  267. * 对于这个函数,读者尤其要搞懂x_amount和y_amount对应的显示关系。
  268. * 第1,2个参数x,y是坐标体系。x的范围是0至15,y的范围是0至31.
  269. * 第3个参数*ucArray是字模的数组。
  270. * 第4个参数ucFbFlag是反白显示标志。0代表正常显示,1代表反白显示。
  271. * 第5,6个参数x_amount,y_amount分别代表字模数组的横向有多少个字节,纵向有几横。
  272. * 第7个参数uiOffSetAddr是偏移地址,代表字模数组的从第几个数据开始显示。
  273. */
  274. void display_lattice(unsigned int x,unsigned int y,const unsigned char  *ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount,unsigned int uiOffSetAddr)
  275. {
  276.    unsigned int j=0;
  277.    unsigned int i=0;
  278.    unsigned char ucTemp;

  279. //注意,要把以下两行指令屏蔽,否则屏幕在更新显示时会整屏闪动
  280. //  WriteCommand(0x34);  //关显示缓冲指令            
  281. //  WriteCommand(0x34);  //关显示缓冲指令  故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的
  282.    for(j=0;j<y_amount;j++) //y_amount代表y轴有多少横
  283.    {
  284.        WriteCommand(y+j+0x80);        //垂直地址
  285.        WriteCommand(x+0x80);          //水平地址
  286.        for(i=0;i<x_amount;i++) //x_amount代表x轴有多少列
  287.        {
  288.            ucTemp=ucArray[j*x_amount+i+uiOffSetAddr]; //uiOffSetAddr是字模数组的偏移地址
  289.            if(ucFbFlag==1)  //反白显示
  290.            {
  291.                ucTemp=~ucTemp;
  292.            }
  293.            LCDWriteData(ucTemp);
  294.           //         delay_short(30000);  //把上一节这个延时函数去掉,加快刷屏速度
  295.       }
  296.    }
  297.    WriteCommand(0x36); //开显示缓冲指令
  298. }




  299. void SendByteToLcd(unsigned char ucData)  //发送一个字节数据到液晶模块
  300. {
  301.         unsigned char i;
  302.         for ( i = 0; i < 8; i++ )
  303.         {
  304.                 if ( (ucData << i) & 0x80 )
  305.                 {
  306.                         LCDSID_dr = 1;
  307.                 }
  308.                 else
  309.                 {
  310.                         LCDSID_dr = 0;
  311.                 }
  312.                 LCDCLK_dr = 0;
  313.                 LCDCLK_dr = 1;
  314.         }
  315. }

  316. void SPIWrite(unsigned char ucWData, unsigned char ucWRS) //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动
  317. {
  318.         SendByteToLcd( 0xf8 + (ucWRS << 1) );
  319.         SendByteToLcd( ucWData & 0xf0 );
  320.         SendByteToLcd( (ucWData << 4) & 0xf0);
  321. }


  322. void WriteCommand(unsigned char ucCommand) //发送一个字节的命令给液晶模块
  323. {

  324.         LCDCS_dr = 0;
  325.         LCDCS_dr = 1;
  326.         SPIWrite(ucCommand, 0);
  327.         delay_short(90);
  328. }

  329. void LCDWriteData(unsigned char ucData)  //发送一个字节的数据给液晶模块
  330. {
  331.         LCDCS_dr = 0;
  332.         LCDCS_dr = 1;
  333.         SPIWrite(ucData, 1);
  334. }

  335. void LCDInit(void) //初始化  函数内部包括液晶模块的复位
  336. {
  337.         LCDRST_dr = 1;  //复位
  338.         LCDRST_dr = 0;
  339.         LCDRST_dr = 1;
  340. }



  341. void delay_short(unsigned int uiDelayShort) //延时函数
  342. {
  343.    unsigned int i;  
  344.    for(i=0;i<uiDelayShort;i++)
  345.    {
  346.      ;  
  347.    }
  348. }
总结陈词:
从下一节开始讲大家关注已久的液晶屏菜单程序。欲知详情,请听下回分解-----在1个窗口里通过移动光标来设置不同参数的液晶屏菜单程序。

(未完待续,下节更精彩,不要走开哦)
armxu 发表于 2014-10-19 14:31 来自手机 | 显示全部楼层
不知为何手机看不到源码。
 楼主| jianhong_wu 发表于 2014-10-19 16:20 | 显示全部楼层
armxu 发表于 2014-10-19 14:31
不知为何手机看不到源码。

可能跟论坛系统软件有关系。你用电脑就可以看到。
蓝魔大师 发表于 2014-10-19 17:48 | 显示全部楼层
学习学习,,,,,,,,,,
 楼主| jianhong_wu 发表于 2014-10-23 11:39 | 显示全部楼层
第七十六节:如何把一个任意数值的变量显示在液晶屏上。

开场白:
本来这一节打算开始讲液晶屏的菜单程序,但是我担心跳跃太大,恐怕很多初学者跟不上,所以多插入这一节讲讲后面菜单程序中经常用到的基本功能,如何把一个任意数值的变量显示在液晶屏上。我们需要做一个变量转换成字模的函数,以后只要调用这个转换函数就可以了。这一节就要把这个转换函数教给大家。

具体内容,请看源代码讲解。

(1)硬件平台:
    基于朱兆祺51单片机学习板。

(2)实现功能:我们定义一个char型的全局变量,把它默认初始化为218,开机上电后,能看到正中间恰好显示这个全局变量的数值218。大家也可以试着更改它的默认初始值,只要不超过char型最大数值255范围,我们就会看到它上电后显示的就是这个初始值。

(3)源代码讲解如下:
  1. #include "REG52.H"

  2. sbit  LCDCS_dr  = P1^6;  //片选线
  3. sbit  LCDSID_dr = P1^7;  //串行数据线
  4. sbit  LCDCLK_dr = P3^2;  //串行时钟线
  5. sbit  LCDRST_dr = P3^4;  //复位线

  6. void SendByteToLcd(unsigned char ucData);  //发送一个字节数据到液晶模块
  7. void SPIWrite(unsigned char ucWData, unsigned char ucWRS); //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动
  8. void WriteCommand(unsigned char ucCommand); //发送一个字节的命令给液晶模块
  9. void LCDWriteData(unsigned char ucData);   //发送一个字节的数据给液晶模块
  10. void LCDInit(void);  //初始化  函数内部包括液晶模块的复位
  11. void display_clear(unsigned char ucFillDate); // 清屏 全部显示空填充0x00   全部显示点阵用0xff
  12. void insert_buffer_to_canvas(unsigned int x,unsigned int y,const unsigned char  *ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount);//把字模插入画布.
  13. void display_lattice(unsigned int x,unsigned int y,const unsigned char  *ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount,unsigned int uiOffSetAddr); //显示任意点阵函数
  14. unsigned char *number_to_matrix(unsigned char  ucBitNumber); //把一位数字转换成字模首地址的函数
  15. void delay_short(unsigned int uiDelayshort); //延时
  16. void delay_long(unsigned int uiDelayLong);


  17. void initial_myself();   
  18. void initial_peripheral();


  19. void lcd_display_service(void); //应用层面的液晶屏显示程序
  20. void clear_all_canvas(void);  //把画布全部清零

  21. code unsigned char Zf816_0[]=
  22. {
  23. /*--  文字:  0  --*/
  24. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  25. 0x00,0x00,0x00,0x18,0x24,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x24,0x18,0x00,0x00,
  26. };

  27. code unsigned char Zf816_1[]=
  28. {
  29. /*--  文字:  1  --*/
  30. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  31. 0x00,0x00,0x00,0x10,0x70,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x7C,0x00,0x00,
  32. };

  33. code unsigned char Zf816_2[]=
  34. {
  35. /*--  文字:  2  --*/
  36. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  37. 0x00,0x00,0x00,0x3C,0x42,0x42,0x42,0x04,0x04,0x08,0x10,0x20,0x42,0x7E,0x00,0x00,
  38. };

  39. code unsigned char Zf816_3[]=
  40. {
  41. /*--  文字:  3  --*/
  42. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  43. 0x00,0x00,0x00,0x3C,0x42,0x42,0x04,0x18,0x04,0x02,0x02,0x42,0x44,0x38,0x00,0x00,
  44. };

  45. code unsigned char Zf816_4[]=
  46. {
  47. /*--  文字:  4  --*/
  48. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  49. 0x00,0x00,0x00,0x04,0x0C,0x14,0x24,0x24,0x44,0x44,0x7E,0x04,0x04,0x1E,0x00,0x00,
  50. };

  51. code unsigned char Zf816_5[]=
  52. {
  53. /*--  文字:  5  --*/
  54. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  55. 0x00,0x00,0x00,0x7E,0x40,0x40,0x40,0x58,0x64,0x02,0x02,0x42,0x44,0x38,0x00,0x00,
  56. };

  57. code unsigned char Zf816_6[]=
  58. {
  59. /*--  文字:  6  --*/
  60. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  61. 0x00,0x00,0x00,0x1C,0x24,0x40,0x40,0x58,0x64,0x42,0x42,0x42,0x24,0x18,0x00,0x00,
  62. };


  63. code unsigned char Zf816_7[]=
  64. {
  65. /*--  文字:  7  --*/
  66. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  67. 0x00,0x00,0x00,0x7E,0x44,0x44,0x08,0x08,0x10,0x10,0x10,0x10,0x10,0x10,0x00,0x00,
  68. };

  69. code unsigned char Zf816_8[]=
  70. {
  71. /*--  文字:  8  --*/
  72. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  73. 0x00,0x00,0x00,0x3C,0x42,0x42,0x42,0x24,0x18,0x24,0x42,0x42,0x42,0x3C,0x00,0x00,
  74. };

  75. code unsigned char Zf816_9[]=
  76. {
  77. /*--  文字:  9  --*/
  78. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  79. 0x00,0x00,0x00,0x18,0x24,0x42,0x42,0x42,0x26,0x1A,0x02,0x02,0x24,0x38,0x00,0x00,
  80. };


  81. code unsigned char Zf816_nc[]=  //空字模
  82. {
  83. 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  84. };


  85. /* 注释一:
  86. * 为了实现跨区域无缝显示,就先在某个区域显示一块画布,我们只要在这块画布数组中插入字模数组,
  87. * 就可以达到跨区域无缝显示的目的。根据上几节的介绍,12864液晶屏由上下两半屏组成,以下这块画布
  88. * 显示在上半屏和下半屏之间。横向4个字节,纵向16行。其中上半屏显示8行,下半屏显示8行。注意,这个数组
  89. * 不带code关键字,是全局变量,这样可读可写。画布的横向x坐标范围是0至3,因为画布的横向只要4个字节。
  90. * 画布的纵向y坐标范围是0至15,因为画布的纵向只有16行。
  91. */
  92. unsigned char ucCanvasBuffer[]= //画布显示数组。注意,这里没有code关键字,是全局变量。初始化全部填充0x00
  93. {
  94. 0x00,0x00,0x00,0x00,  //上半屏
  95. 0x00,0x00,0x00,0x00,
  96. 0x00,0x00,0x00,0x00,
  97. 0x00,0x00,0x00,0x00,
  98. 0x00,0x00,0x00,0x00,
  99. 0x00,0x00,0x00,0x00,
  100. 0x00,0x00,0x00,0x00,
  101. 0x00,0x00,0x00,0x00,

  102. //------------上半屏和下半屏的分割线-----------

  103. 0x00,0x00,0x00,0x00,  //下半屏
  104. 0x00,0x00,0x00,0x00,
  105. 0x00,0x00,0x00,0x00,
  106. 0x00,0x00,0x00,0x00,
  107. 0x00,0x00,0x00,0x00,
  108. 0x00,0x00,0x00,0x00,
  109. 0x00,0x00,0x00,0x00,
  110. 0x00,0x00,0x00,0x00,
  111. };


  112. unsigned char ucDisplayUpdate=1;  //更新显示变量


  113. /* 注释二:
  114. * 以下变量就是本程序的任意变量,网友可以自己更改它的大小来测试本程序,不要超过255.
  115. */
  116. unsigned char ucAnyNumber=218;  //任意变量默认初始化为218。


  117. void main()
  118.   {
  119.         initial_myself();      //第一区,上电后马上初始化
  120.         delay_long(100);       //一线,延时线。延时一段时间
  121.         initial_peripheral();  //第二区,上电后延时一段时间再初始化

  122.         while(1)   //第三区
  123.         {
  124.             lcd_display_service(); //应用层面的液晶屏显示程序
  125.         }

  126. }


  127. void initial_myself()  //第一区 上电后马上初始化
  128. {
  129.     ;
  130. }
  131. void initial_peripheral() //第二区 上电后延时一段时间再初始化
  132. {
  133.     LCDInit(); //初始化12864 内部包含液晶模块的复位
  134.     display_clear(0xff); // 清屏 全部显示空填充0x00   全部显示点阵用0xff
  135. }



  136. /* 注释三:
  137. * 本程序的核心转换函数。
  138. * 是可以把一位任意数字变量的函数转换成对应的字模,由于字模是数组,所以返回的是指针,代表字模数组的首地址。
  139. */
  140. unsigned char *number_to_matrix(unsigned char  ucBitNumber)
  141. {
  142.     unsigned char *p_ucAnyNumber;  //此指针根据ucBitNumber数值的大小,分别调用不同的字库。

  143.         switch(ucBitNumber)  //根据ucBitNumber数值的大小,分别调用不同的字库。
  144.         {
  145.             case 0:
  146.              p_ucAnyNumber=Zf816_0;
  147.                      break;
  148.             case 1:
  149.              p_ucAnyNumber=Zf816_1;
  150.                      break;
  151.             case 2:
  152.              p_ucAnyNumber=Zf816_2;
  153.                      break;
  154.             case 3:
  155.              p_ucAnyNumber=Zf816_3;
  156.                      break;
  157.             case 4:
  158.              p_ucAnyNumber=Zf816_4;
  159.                      break;
  160.             case 5:
  161.              p_ucAnyNumber=Zf816_5;
  162.                      break;
  163.             case 6:
  164.              p_ucAnyNumber=Zf816_6;
  165.                      break;
  166.             case 7:
  167.              p_ucAnyNumber=Zf816_7;
  168.                      break;
  169.             case 8:
  170.              p_ucAnyNumber=Zf816_8;
  171.                      break;
  172.             case 9:
  173.              p_ucAnyNumber=Zf816_9;
  174.                      break;
  175.             case 10:
  176.              p_ucAnyNumber=Zf816_nc;
  177.                      break;
  178.                 default:   //如果上面的条件都不符合,那么默认指向空字模
  179.              p_ucAnyNumber=Zf816_nc;
  180.                      break;
  181.         }

  182.     return p_ucAnyNumber;  //返回转换结束后的指针
  183. }


  184. void lcd_display_service(void) //应用层面的液晶屏显示程序
  185. {
  186.     static unsigned char ucAnyNumber_1; //分解变量的个位
  187.     static unsigned char ucAnyNumber_10; //分解变量的十位
  188.     static unsigned char ucAnyNumber_100; //分解变量的百位

  189.     static unsigned char *p_ucAnyNumber_1; //经过数字转换成字模后,分解变量的个位字模首地址
  190.     static unsigned char *p_ucAnyNumber_10; //经过数字转换成字模后,分解变量的十位字模首地址
  191.     static unsigned char *p_ucAnyNumber_100; //经过数字转换成字模后,分解变量的百位字模首地址

  192.     if(ucDisplayUpdate==1)  //需要更新显示
  193.     {
  194.        ucDisplayUpdate=0;  //及时把标志清零,避免一直处于不断更新的状态。

  195.            if(ucAnyNumber>=100) //有3位数以上
  196.            {
  197.            ucAnyNumber_100=ucAnyNumber/100; //百位
  198.        }
  199.            else //否则显示空
  200.            {
  201.                ucAnyNumber_100=10;  //在下面的转换函数中,代码10表示空字模
  202.            }

  203.            if(ucAnyNumber>=10) //有2位数以上
  204.            {
  205.            ucAnyNumber_10=ucAnyNumber%100/10;  //十位
  206.        }
  207.            else //否则显示空
  208.            {
  209.                ucAnyNumber_10=10;  //在下面的转换函数中,代码10表示空字模
  210.            }

  211.        ucAnyNumber_1=ucAnyNumber%10/1;  //个位

  212.            p_ucAnyNumber_100=number_to_matrix(ucAnyNumber_100); //把数字转换成字模首地址      
  213.            p_ucAnyNumber_10=number_to_matrix(ucAnyNumber_10); //把数字转换成字模首地址
  214.            p_ucAnyNumber_1=number_to_matrix(ucAnyNumber_1); //把数字转换成字模首地址

  215.        clear_all_canvas();  //把画布全部清零
  216.        insert_buffer_to_canvas(0,0,p_ucAnyNumber_100,0,1,16);//把百位的字模插入画布
  217.        insert_buffer_to_canvas(1,0,p_ucAnyNumber_10,0,1,16);//把十的字模插入画布
  218.        insert_buffer_to_canvas(2,0,p_ucAnyNumber_1,0,1,16);//把个的字模插入画布

  219.        display_lattice(3,24,ucCanvasBuffer,0,4,8,0);   //显示上半屏的画布,最后的参数0是偏移量
  220.        display_lattice(11,0,ucCanvasBuffer,0,4,8,32);  //显示下半屏的画布,最后的参数32是偏移量
  221.     }
  222. }



  223. void clear_all_canvas(void)  //把画布全部清零
  224. {
  225.    unsigned int j=0;
  226.    unsigned int i=0;

  227.    for(j=0;j<16;j++)  //这里的16表示画布有16行
  228.    {
  229.       for(i=0;i<4;i++) //这里的4表示画布每行有4个字节
  230.       {
  231.                   ucCanvasBuffer[j*4+i]=0x00;
  232.       }
  233.    }         

  234. }





  235. void display_clear(unsigned char ucFillDate) // 清屏  全部显示空填充0x00   全部显示点阵用0xff
  236. {   

  237.     unsigned char x,y;
  238.     WriteCommand(0x34);  //关显示缓冲指令            
  239.     WriteCommand(0x34);  //关显示缓冲指令  故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的
  240.     y=0;
  241.     while(y<32)  //y轴的范围0至31
  242.     {
  243.          WriteCommand(y+0x80);        //垂直地址
  244.          WriteCommand(0x80);          //水平地址
  245.          for(x=0;x<32;x++)  //256个横向点,有32个字节
  246.          {  
  247.             LCDWriteData(ucFillDate);
  248.          }
  249.          y++;
  250.     }
  251.     WriteCommand(0x36); //开显示缓冲指令

  252. }

  253. /* 注释四:
  254. * 把字模插入画布的函数.
  255. * 这是本节的核心函数,读者尤其要搞懂x_amount和y_amount对应的显示关系。
  256. * 第1,2个参数x,y是在画布中的坐标体系。
  257. * x的范围是0至3,因为画布的横向只要4个字节。y的范围是0至15,因为画布的纵向只有16行。
  258. * 第3个参数*ucArray是字模的数组。
  259. * 第4个参数ucFbFlag是反白显示标志。0代表正常显示,1代表反白显示。
  260. * 第5,6个参数x_amount,y_amount分别代表字模数组的横向有多少个字节,纵向有几横。
  261. */
  262. void insert_buffer_to_canvas(unsigned int x,unsigned int y,const unsigned char  *ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount)
  263. {
  264.    unsigned int j=0;
  265.    unsigned int i=0;
  266.    unsigned char ucTemp;
  267.    for(j=0;j<y_amount;j++)
  268.    {
  269.       for(i=0;i<x_amount;i++)
  270.       {
  271.                    ucTemp=ucArray[j*x_amount+i];
  272.                    if(ucFbFlag==0)
  273.                    {
  274.               ucCanvasBuffer[(y+j)*4+x+i]=ucTemp; //这里的4代表画布每一行只有4个字节
  275.                    }
  276.                    else
  277.                    {
  278.               ucCanvasBuffer[(y+j)*4+x+i]=~ucTemp; //这里的4代表画布每一行只有4个字节
  279.                    }
  280.       }
  281.    }         

  282. }

  283. /* 注释五:
  284. * 显示任意点阵函数.
  285. * 注意,本函数在前几节的基础上多增加了第7个参数uiOffSetAddr,它是偏移地址。
  286. * 对于这个函数,读者尤其要搞懂x_amount和y_amount对应的显示关系。
  287. * 第1,2个参数x,y是坐标体系。x的范围是0至15,y的范围是0至31.
  288. * 第3个参数*ucArray是字模的数组。
  289. * 第4个参数ucFbFlag是反白显示标志。0代表正常显示,1代表反白显示。
  290. * 第5,6个参数x_amount,y_amount分别代表字模数组的横向有多少个字节,纵向有几横。
  291. * 第7个参数uiOffSetAddr是偏移地址,代表字模数组的从第几个数据开始显示。
  292. */
  293. void display_lattice(unsigned int x,unsigned int y,const unsigned char  *ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount,unsigned int uiOffSetAddr)
  294. {
  295.    unsigned int j=0;
  296.    unsigned int i=0;
  297.    unsigned char ucTemp;

  298. //注意,要把以下两行指令屏蔽,否则屏幕在更新显示时会整屏闪动
  299. //  WriteCommand(0x34);  //关显示缓冲指令            
  300. //  WriteCommand(0x34);  //关显示缓冲指令  故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的
  301.    for(j=0;j<y_amount;j++) //y_amount代表y轴有多少横
  302.    {
  303.        WriteCommand(y+j+0x80);        //垂直地址
  304.        WriteCommand(x+0x80);          //水平地址
  305.        for(i=0;i<x_amount;i++) //x_amount代表x轴有多少列
  306.        {
  307.            ucTemp=ucArray[j*x_amount+i+uiOffSetAddr]; //uiOffSetAddr是字模数组的偏移地址
  308.            if(ucFbFlag==1)  //反白显示
  309.            {
  310.                ucTemp=~ucTemp;
  311.            }
  312.            LCDWriteData(ucTemp);
  313.           //         delay_short(30000);  //把上一节这个延时函数去掉,加快刷屏速度
  314.       }
  315.    }
  316.    WriteCommand(0x36); //开显示缓冲指令
  317. }




  318. void SendByteToLcd(unsigned char ucData)  //发送一个字节数据到液晶模块
  319. {
  320.         unsigned char i;
  321.         for ( i = 0; i < 8; i++ )
  322.         {
  323.                 if ( (ucData << i) & 0x80 )
  324.                 {
  325.                         LCDSID_dr = 1;
  326.                 }
  327.                 else
  328.                 {
  329.                         LCDSID_dr = 0;
  330.                 }
  331.                 LCDCLK_dr = 0;
  332.                 LCDCLK_dr = 1;
  333.         }
  334. }

  335. void SPIWrite(unsigned char ucWData, unsigned char ucWRS) //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动
  336. {
  337.         SendByteToLcd( 0xf8 + (ucWRS << 1) );
  338.         SendByteToLcd( ucWData & 0xf0 );
  339.         SendByteToLcd( (ucWData << 4) & 0xf0);
  340. }


  341. void WriteCommand(unsigned char ucCommand) //发送一个字节的命令给液晶模块
  342. {

  343.         LCDCS_dr = 0;
  344.         LCDCS_dr = 1;
  345.         SPIWrite(ucCommand, 0);
  346.         delay_short(90);
  347. }

  348. void LCDWriteData(unsigned char ucData)  //发送一个字节的数据给液晶模块
  349. {
  350.         LCDCS_dr = 0;
  351.         LCDCS_dr = 1;
  352.         SPIWrite(ucData, 1);
  353. }

  354. void LCDInit(void) //初始化  函数内部包括液晶模块的复位
  355. {
  356.         LCDRST_dr = 1;  //复位
  357.         LCDRST_dr = 0;
  358.         LCDRST_dr = 1;
  359. }



  360. void delay_short(unsigned int uiDelayShort) //延时函数
  361. {
  362.    unsigned int i;  
  363.    for(i=0;i<uiDelayShort;i++)
  364.    {
  365.      ;  
  366.    }
  367. }


  368. void delay_long(unsigned int uiDelayLong)
  369. {
  370.    unsigned int i;
  371.    unsigned int j;
  372.    for(i=0;i<uiDelayLong;i++)
  373.    {
  374.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  375.           {
  376.              ; //一个分号相当于执行一条空语句
  377.           }
  378.    }
  379. }
总结陈词:
有了这一节的基础,我们继续循序渐进,下一节将会讲到液晶屏的菜单程序。欲知详情,请听下回分解-----在1个窗口里通过移动光标来设置不同参数的液晶屏菜单程序。

(未完待续,下节更精彩,不要走开哦)
lsc201100 发表于 2014-10-24 09:01 | 显示全部楼层
      复杂啊,眼花。
jimiy123 发表于 2014-10-24 11:39 | 显示全部楼层
鸿哥,我是学自动化的,刚毕业,也学习单片机,在学校里面单片机没怎么学好,工作后,感觉到压力很大,不知道该怎么办,自己买了STC51单片机在摸索,也没人带我,就自己一个人学习,连接,敲代码,仿真,我今年都24了,现在把大学里的大学里的单片机拿过来再学习一遍,感觉有点小小的迷茫,现在在这家公司,2500一个月,硬件设施也不齐全,我是很想做项目搞技术,真想有人能指导我一下,带带我,看您发了那么多,都不知道该从哪里去学,也不知道从头学来未来怎么样,将来从事单片机行业的前景怎么样?
鸿哥,您是过来人,能指导一下我吗?
 楼主| jianhong_wu 发表于 2014-10-24 14:42 | 显示全部楼层
本帖最后由 jianhong_wu 于 2014-10-24 14:43 编辑
jimiy123 发表于 2014-10-24 11:39
鸿哥,我是学自动化的,刚毕业,也学习单片机,在学校里面单片机没怎么学好,工作后,感觉到压力很大,不知 ...

(1)如何学好单片机?我还是建议你买一套朱兆祺的51学习板,然后跟着我的连载帖子去学,只要把我的按键,数码管,跑马灯,串口搞懂了,基本上你就会掌握了做项目的核心程序框架。朱兆祺的51学习板你可以在淘宝上搜索到。
(2)单片机的前景怎么样?行行出状元,单片机毕竟是一个技术工具,它的前途有多大取决你把它应用在哪些行业,取决于你在什么样的公司岗位。但是我觉得单片机是电子行业的基础与核心,如果从事电子行业,单片机还是必须要懂的。
chen125318 发表于 2014-10-25 12:52 | 显示全部楼层
我现在大四 正在找工作 学的电子信息工程  现在找工作遇到一个大问题就是我英语四级没过 很多公司简历都过不了 好伤心 单片机我也算是很熟悉 一些比赛题目基本一个人就能稳稳的搞定 stm32也在学 但是这个简历都直接被刷了 又比较内向不太会表达 面试发挥又差展示不出自己的东西 哎 最近好痛苦 说出来发泄发泄好受一点
cjseng 发表于 2014-10-25 12:59 | 显示全部楼层
chen125318 发表于 2014-10-25 12:52
我现在大四 正在找工作 学的电子信息工程  现在找工作遇到一个大问题就是我英语四级没过 很多公司简历都过 ...

找工作要看英语4级成绩吗?你难道要去外资公司。
chen125318 发表于 2014-10-25 13:05 | 显示全部楼层
cjseng 发表于 2014-10-25 12:59
找工作要看英语4级成绩吗?你难道要去外资公司。

国企央企基本硬性要求四级,比较大的私企跟国企央企一样。其他的小企业有不要求四级的但是要求你能力够强
 楼主| jianhong_wu 发表于 2014-10-25 13:27 | 显示全部楼层
chen125318 发表于 2014-10-25 12:52
我现在大四 正在找工作 学的电子信息工程  现在找工作遇到一个大问题就是我英语四级没过 很多公司简历都过 ...

我06年毕业的时候英语四级对毕业生找工作是很重要的,那时我大二的时候英语就过了四级。对于搞电子行业的人来说,英语的阅读能力确实太重要了。即使不为了证书,英语能力还是要过关的。另外,也有一些公司不看四级证书的,尤其是一些小公司。找工作很多时候也看运气,运气来了,可能就找到适合自己的。看开点,不要太难过。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 在线客服 返回列表 返回顶部