独闷闷网

 找回密码
 立即注册
搜索
楼主: jianhong_wu
打印 上一主题 下一主题
收起左侧

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

[复制链接]
81#
 楼主| 发表于 2014-9-9 09:35:25 | 显示全部楼层
第六十六节:单片机外部中断的基础。

开场白:
外部中断是单片机非常重要的内部资源,应用很广,它是单片机的高速开关感应器输入接口,它可以检测脉冲输入,可以接收红外遥控器的输入信号,可以检测高速运转的车轮或者电机圆周运动的反馈信号,可以检测输液器里瞬间即逝的水滴信号,可以接收模拟串口的数据信息,等等。
    这一节要教大家两个知识点:
   第一个:外部中断的初始化代码和中断函数的基本程序模板。
   第二个:当系统存在两种中断以上时,如何设置外部中断0为最高优先级,实现中断嵌套功能。

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

(1)硬件平台:
    基于坚鸿51单片机学习板。用S1按键作为模拟外部中断0的下降沿脉冲输入。原来S1按键是直接连接到P0^0口的,因此必须通过跳线把P0^0口连接到单片机外部中断0专用IO口P3^2上,只需把P0^0和P3^2的两根黄颜色跳冒去掉,通过一根线把P0^0和P3^2相互连接起来即可。这时每按下一次S1按键,就会给P3^2口产生一个下降沿的脉冲,然后程序会自动跳到中断函数中执行一次。

(2)实现功能:
    用数码管低4位显示记录当前的下降沿脉冲数。用S1按键经过跳线后模拟外部中断0的下降沿输入,每按一次数码管就会显示往上累加的脉冲数。由于按键按下去的时候有抖动,也就按一次可能产生几个脉冲,所以按一次往往看到数据一次加了三四个,这种实验现象都是正常的。

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

  2. #define const_voice_short  40   //蜂鸣器短叫的持续时间
  3. #define const_key_time1  20    //按键去抖动延时的时间

  4. void initial_myself();   
  5. void initial_peripheral();
  6. void delay_short(unsigned int uiDelayShort);
  7. void delay_long(unsigned int uiDelaylong);
  8. //驱动数码管的74HC595
  9. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);  
  10. void display_drive(); //显示数码管字模的驱动函数
  11. void display_service(); //显示的窗口菜单服务程序
  12. //驱动LED的74HC595
  13. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
  14. void T0_time();  //定时中断函数
  15. void INT0_int();//外部0中断函数



  16. sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平
  17. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口


  18. sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
  19. sbit dig_hc595_st_dr=P2^1;  
  20. sbit dig_hc595_ds_dr=P2^2;  


  21. unsigned char ucDigShow8;  //第8位数码管要显示的内容
  22. unsigned char ucDigShow7;  //第7位数码管要显示的内容
  23. unsigned char ucDigShow6;  //第6位数码管要显示的内容
  24. unsigned char ucDigShow5;  //第5位数码管要显示的内容
  25. unsigned char ucDigShow4;  //第4位数码管要显示的内容
  26. unsigned char ucDigShow3;  //第3位数码管要显示的内容
  27. unsigned char ucDigShow2;  //第2位数码管要显示的内容
  28. unsigned char ucDigShow1;  //第1位数码管要显示的内容

  29. unsigned char ucDigDot8;  //数码管8的小数点是否显示的标志
  30. unsigned char ucDigDot7;  //数码管7的小数点是否显示的标志
  31. unsigned char ucDigDot6;  //数码管6的小数点是否显示的标志
  32. unsigned char ucDigDot5;  //数码管5的小数点是否显示的标志
  33. unsigned char ucDigDot4;  //数码管4的小数点是否显示的标志
  34. unsigned char ucDigDot3;  //数码管3的小数点是否显示的标志
  35. unsigned char ucDigDot2;  //数码管2的小数点是否显示的标志
  36. unsigned char ucDigDot1;  //数码管1的小数点是否显示的标志
  37. unsigned char ucDigShowTemp=0; //临时中间变量
  38. unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量

  39. unsigned char ucWd1Update=1; //窗口1更新显示标志

  40. unsigned char ucWd=1;  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。本程序只有一个显示窗口
  41. unsigned int  uiPluseCnt=0;  //本程序中累加中断脉冲数的变量

  42. unsigned char ucTemp1=0;  //中间过渡变量
  43. unsigned char ucTemp2=0;  //中间过渡变量
  44. unsigned char ucTemp3=0;  //中间过渡变量
  45. unsigned char ucTemp4=0;  //中间过渡变量

  46. //根据原理图得出的共阴数码管字模表
  47. code unsigned char dig_table[]=
  48. {
  49. 0x3f,  //0       序号0
  50. 0x06,  //1       序号1
  51. 0x5b,  //2       序号2
  52. 0x4f,  //3       序号3
  53. 0x66,  //4       序号4
  54. 0x6d,  //5       序号5
  55. 0x7d,  //6       序号6
  56. 0x07,  //7       序号7
  57. 0x7f,  //8       序号8
  58. 0x6f,  //9       序号9
  59. 0x00,  //无      序号10
  60. 0x40,  //-       序号11
  61. 0x73,  //P       序号12
  62. };
  63. void main()
  64.   {
  65.    initial_myself();  
  66.    delay_long(100);   
  67.    initial_peripheral();
  68.    while(1)  
  69.    {
  70.        display_service(); //显示的窗口菜单服务程序
  71.    }
  72. }


  73. void display_service() //显示的窗口菜单服务程序
  74. {

  75.    switch(ucWd)  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
  76.    {
  77.        case 1:   //显示第一个窗口的数据  本系统中只有一个显示窗口
  78.             if(ucWd1Update==1)  //窗口1要全部更新显示
  79.             {
  80.                ucWd1Update=0;  //及时清零标志,避免一直进来扫描
  81.                ucDigShow8=10;  //第8位数码管显示无
  82.                ucDigShow7=10;  //第7位数码管显示无
  83.                ucDigShow6=10;  //第6位数码管显示无
  84.                ucDigShow5=10;  //第5位数码管显示无

  85.               //先分解数据
  86.                ucTemp4=uiPluseCnt/1000;     
  87.                ucTemp3=uiPluseCnt%1000/100;
  88.                ucTemp2=uiPluseCnt%100/10;
  89.                ucTemp1=uiPluseCnt%10;
  90.   
  91.              //再过渡需要显示的数据到缓冲变量里,让过渡的时间越短越好

  92.               //以下增加的if判断就是略作修改,把整个4位数据中高位为0的去掉不显示。
  93.                if(uiPluseCnt<1000)   
  94.                {
  95.                    ucDigShow4=10;  //如果小于1000,千位显示无
  96.                }
  97.                else
  98.                {
  99.                   ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
  100.                }
  101.                if(uiPluseCnt<100)
  102.                {
  103.                   ucDigShow3=10;  //如果小于100,百位显示无
  104.                }
  105.                else
  106.                {
  107.                   ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
  108.                }
  109.                if(uiPluseCnt<10)
  110.                {
  111.                   ucDigShow2=10;  //如果小于10,十位显示无
  112.                }
  113.                else
  114.                {
  115.                   ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
  116.                }
  117.                ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
  118.             }
  119.             break;
  120.    
  121.     }
  122.    

  123. }


  124. void display_drive()  
  125. {
  126.    //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
  127.    switch(ucDisplayDriveStep)
  128.    {
  129.       case 1:  //显示第1位
  130.            ucDigShowTemp=dig_table[ucDigShow1];
  131.                    if(ucDigDot1==1)
  132.                    {
  133.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  134.                    }
  135.            dig_hc595_drive(ucDigShowTemp,0xfe);
  136.                break;
  137.       case 2:  //显示第2位
  138.            ucDigShowTemp=dig_table[ucDigShow2];
  139.                    if(ucDigDot2==1)
  140.                    {
  141.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  142.                    }
  143.            dig_hc595_drive(ucDigShowTemp,0xfd);
  144.                break;
  145.       case 3:  //显示第3位
  146.            ucDigShowTemp=dig_table[ucDigShow3];
  147.                    if(ucDigDot3==1)
  148.                    {
  149.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  150.                    }
  151.            dig_hc595_drive(ucDigShowTemp,0xfb);
  152.                break;
  153.       case 4:  //显示第4位
  154.            ucDigShowTemp=dig_table[ucDigShow4];
  155.                    if(ucDigDot4==1)
  156.                    {
  157.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  158.                    }
  159.            dig_hc595_drive(ucDigShowTemp,0xf7);
  160.                break;
  161.       case 5:  //显示第5位
  162.            ucDigShowTemp=dig_table[ucDigShow5];
  163.                    if(ucDigDot5==1)
  164.                    {
  165.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  166.                    }
  167.            dig_hc595_drive(ucDigShowTemp,0xef);
  168.                break;
  169.       case 6:  //显示第6位
  170.            ucDigShowTemp=dig_table[ucDigShow6];
  171.                    if(ucDigDot6==1)
  172.                    {
  173.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  174.                    }
  175.            dig_hc595_drive(ucDigShowTemp,0xdf);
  176.                break;
  177.       case 7:  //显示第7位
  178.            ucDigShowTemp=dig_table[ucDigShow7];
  179.                    if(ucDigDot7==1)
  180.                    {
  181.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  182.            }
  183.            dig_hc595_drive(ucDigShowTemp,0xbf);
  184.                break;
  185.       case 8:  //显示第8位
  186.            ucDigShowTemp=dig_table[ucDigShow8];
  187.                    if(ucDigDot8==1)
  188.                    {
  189.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  190.                    }
  191.            dig_hc595_drive(ucDigShowTemp,0x7f);
  192.                break;
  193.    }
  194.    ucDisplayDriveStep++;
  195.    if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
  196.    {
  197.      ucDisplayDriveStep=1;
  198.    }

  199. }

  200. //数码管的74HC595驱动函数
  201. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
  202. {
  203.    unsigned char i;
  204.    unsigned char ucTempData;
  205.    dig_hc595_sh_dr=0;
  206.    dig_hc595_st_dr=0;
  207.    ucTempData=ucDigStatusTemp16_09;  //先送高8位
  208.    for(i=0;i<8;i++)
  209.    {
  210.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  211.          else dig_hc595_ds_dr=0;
  212.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  213.          delay_short(1);
  214.          dig_hc595_sh_dr=1;
  215.          delay_short(1);
  216.          ucTempData=ucTempData<<1;
  217.    }
  218.    ucTempData=ucDigStatusTemp08_01;  //再先送低8位
  219.    for(i=0;i<8;i++)
  220.    {
  221.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  222.          else dig_hc595_ds_dr=0;
  223.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  224.          delay_short(1);
  225.          dig_hc595_sh_dr=1;
  226.          delay_short(1);
  227.          ucTempData=ucTempData<<1;
  228.    }
  229.    dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  230.    delay_short(1);
  231.    dig_hc595_st_dr=1;
  232.    delay_short(1);
  233.    dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
  234.    dig_hc595_st_dr=0;
  235.    dig_hc595_ds_dr=0;
  236. }

  237. void T0_time() interrupt 1   //定时器中断函数
  238. {
  239.   TF0=0;  //清除中断标志
  240.   TR0=0; //关中断

  241.   display_drive();  //数码管字模的驱动函数

  242.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  243.   TL0=0x0b;
  244.   TR0=1;  //开中断
  245. }


  246. /* 注释一:
  247. * 用坚鸿51学习板中的S1按键作为模拟外部中断0的下降沿脉冲输入。
  248. * 原来S1按键是直接连接到P0^0口的,因此必须通过跳线把P0^0口连接到
  249. * 单片机外部中断0专用IO口P3^2上,只需把P0^0和P3^2的两个黄颜色跳冒去掉,通过一根
  250. * 线把P0^0和P3^2相互连接起来即可。这时每按下一次S1按键,就会给P3^2口
  251. * 产生一个下降沿的脉冲,然后程序会自动跳到以下中断函数中执行一次。
  252. * 由于按键按下去的时候有抖动,也就按一次可能产生几个脉冲,所以按一次往往看到数据一次加了三四个,
  253. * 这种实验现象都是正常的。
  254. */

  255. void  INT0_int(void) interrupt 0  //INT0外部中断函数
  256. {
  257.    EX0=0;   //禁止外部0中断 这个只是我个人的编程习惯,也可以不关闭

  258.    uiPluseCnt++;  //累计外部中断下降沿的脉冲数
  259.    ucWd1Update=1;  //窗口1更新显示

  260.    EX0=1;  //打开外部0中断
  261. }

  262. void delay_short(unsigned int uiDelayShort)
  263. {
  264.    unsigned int i;  
  265.    for(i=0;i<uiDelayShort;i++)
  266.    {
  267.      ;   //一个分号相当于执行一条空语句
  268.    }
  269. }

  270. void delay_long(unsigned int uiDelayLong)
  271. {
  272.    unsigned int i;
  273.    unsigned int j;
  274.    for(i=0;i<uiDelayLong;i++)
  275.    {
  276.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  277.           {
  278.              ; //一个分号相当于执行一条空语句
  279.           }
  280.    }
  281. }

  282. void initial_myself()  //初始化单片机
  283. {
  284. /* 注释二:
  285. * 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
  286. * 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
  287. * 坚鸿51学习板的S1就是本程序中用到的一个独立按键。S1经过跳线后
  288. * 连接到单片机的外部中断专用接口P3^2上,用来模拟外部下降沿脉冲输入。
  289. */
  290.   key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平
  291.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  292.   TMOD=0x01;  //设置定时器0为工作方式1
  293.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  294.   TL0=0x0b;
  295. }

  296. void initial_peripheral() //初始化外围
  297. {

  298.    ucDigDot8=0;   //小数点全部不显示
  299.    ucDigDot7=0;  
  300.    ucDigDot6=0;
  301.    ucDigDot5=0;  
  302.    ucDigDot4=0;
  303.    ucDigDot3=0;  
  304.    ucDigDot2=0;
  305.    ucDigDot1=0;

  306.    EX0=1; //允许外部中断0
  307.    IT0=1;  //下降沿触发外部中断0   如果是0代表低电平中断

  308. /* 注释三:
  309. * 注意,由于本系统中用了2个中断,一个是定时中断,一个是外部中断,
  310. * 因此必须设置IP寄存器,让外部中断0为最高优先级,让外部中断0可以打断
  311. * 定时中断。
  312. */

  313.    IP=0x01; //设置外部中断0为最高优先级,可以打断低优先级中断服务。实现中断嵌套功能

  314.    EA=1;     //开总中断
  315.    ET0=1;    //允许定时中断
  316.    TR0=1;    //启动定时中断


  317. }
复制代码

总结陈词:
    这节讲了外部中断的基本程序模板,下一节我会讲一个外部中断的实际应用项目例子。欲知详情,请听下回分解----利用外部中断实现模拟串口数据的收发。

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

乐于分享,勇于质疑!
82#
 楼主| 发表于 2014-9-13 15:26:50 | 显示全部楼层
第六十七节:利用外部中断实现模拟串口数据的收发。

开场白:
     鸿哥曾经亲自用外部中断做过红外遥控器的数据接收,步进电机圆周运动的光电反馈信号检测,输液器里瞬间即逝的水滴信号,以及本节的模拟串口数据的接收,其实这些项目的原理都大同小异,会一样即可触类旁通其它的。
    这一节要教大家四个知识点:
   第一个:如何利用外部中断实现模拟串口数据的收发。
   第二个:在退出外部中断函数时,必须通过软件把外部中断标志位IE0清零,否则在接收到的数据包最后面会多收到一个无效的字节0xFF。
   第三个:实际做项目的时候,尽量利用单片机内部自带的集成串口,不到万不得已尽量不要用自制的模拟串口,如果非要用本节讲的模拟串口,那么一次接收的数据包不要太长,尽可能越短越好,因为自己做的模拟串口在稳定性上肯定比不上单片机自带的串口。这种模拟串口在批量生产时容易因为晶振的误差,以及外界各地温度的温差而影响产品的一致性,是有隐患的。
第四个:用模拟串口时,尽量不要选用动态数码管的显示方案,因为单片机在收发串口数据时,只能专心干一件事,此时不能中途被动态数码管扫描程序占用。而动态数码管得不到均匀扫描,就会产生略微闪烁的现象瑕疵。

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

(1)硬件平台:
    基于坚鸿51单片机学习板。当把程序下载到单片机之后,要做以下跳线处理:
   单片机原来的P3.1引脚是TI串口输出引脚,P3.0是RI串口输入引脚,分别把P3.1和P3.0的黄颜色跳冒去掉,同时也把外部中断0的引脚P3.2和一根IO口P1.0引脚的换颜色跳冒去掉,把P3.2跳冒的右针连接到P3.0跳冒的左针,作为模拟串口的接收数据线。把P1.0跳冒的右针连接到P3.1跳冒的左针,作为模拟串口的发送数据线。

(2)实现功能:
    波特率是:9600 。
通过电脑串口调试助手模拟上位机,往单片机任意发送一串不超过10个的数据包,单片机如实地返回接收到的整包数据给上位机。

例如:
(a)上位机发送数据:01 02 03 04 05 06 07 08 09 0A
单片机返回:    01 02 03 04 05 06 07 08 09 0A

(b)上位机发送数据: 05 07 EE A8 F9
单片机返回:     05 07 EE A8 F9


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

  2. #define const_voice_short  40   //蜂鸣器短叫的持续时间

  3. #define const_rc_size  20  //接收串口中断数据的缓冲区数组大小

  4. #define const_receive_time  5  //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完,这个时间根据实际情况来调整大小


  5. /* 注释一:
  6. * 以下时序脉冲延时参数我是在keil uVision2 平台下,Memory Model在small模式,Code Rom Size在Large模式下编译的,
  7. * 如果在不同keil版本,不同的模式下,编译出来的程序有可能此参数会不一样。
  8. * 以下的时序脉冲延时参数是需要一步一步慢慢调的。我一开始的时候先编写一个简单的发送数据测试程序,
  9. * 先确调试出合适的发送时序延时数据。然后再编写串口接收数据的程序,从而调试出接收时序的延时参数。
  10. * 比如:我第一步发送数据的测试程序是这样的:
  11. void main()
  12.   {
  13.    initial_myself();  
  14.    delay_long(100);   
  15.    initial_peripheral();
  16.    while(1)  
  17.    {
  18.   //      usart_service();  //串口服务程序
  19.        eusart_send(0x08);    //测试程序,让它不断发送数据给上位机观察,确保发送延时时序的参数准确性
  20.            delay_long(300);

  21.        eusart_send(0xE5);    //测试程序,让它不断发送数据给上位机观察,确保发送延时时序的参数准确性
  22.            delay_long(300);
  23.    }

  24. }
  25. */


  26. #define const_t_1  10  //发送时序延时1  第一步先调出此数据
  27. #define const_t_2  9  //发送时序延时2   第一步先调出此数据


  28. #define const_r_1  7  //接收时序延时1   第二步再调出此数据
  29. #define const_r_2  9 //接收时序延时2  第二步再调出此数据

  30. void initial_myself(void);   
  31. void initial_peripheral(void);
  32. void delay_long(unsigned int uiDelaylong);
  33. void delay_short(unsigned int uiDelayShort);
  34. void delay_minimum(unsigned char ucDelayMinimum);  //细分度最小的延时,用char类型一个字节

  35. void T0_time(void);  //定时中断函数
  36. void INT0_int(void);  //外部0中断函数,在本系统中是模拟串口的接收中断函数。
  37. void usart_service(void);  //串口服务程序,在main函数里


  38. void eusart_send(unsigned char ucSendData);
  39. unsigned char read_eusart_byte();//从串口读一个字节


  40. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
  41. sbit ti_dr=P1^0;  //模拟串口发送数据的IO口
  42. sbit ri_sr=P3^2;  //模拟串口接收数据的IO口 也是外部中断0的复用IO口
  43. unsigned int  uiSendCnt=0;     //用来识别串口是否接收完一串数据的计时器
  44. unsigned char ucSendLock=1;    //串口服务程序的自锁变量,每次接收完一串数据只处理一次
  45. unsigned int  uiRcregTotal=0;  //代表当前缓冲区已经接收了多少个数据
  46. unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组

  47. unsigned char ucTest=0;

  48. void main()
  49.   {
  50.    initial_myself();  
  51.    delay_long(100);   
  52.    initial_peripheral();
  53.    while(1)  
  54.    {
  55.         usart_service();  //串口服务程序

  56.    }

  57. }



  58. void usart_service(void)  //串口服务程序,在main函数里
  59. {

  60.      unsigned char i=0;   

  61.      if(uiSendCnt>=const_receive_time&&ucSendLock==1) //说明超过了一定的时间内,再也没有新数据从串口来
  62.      {

  63.             ucSendLock=0;    //处理一次就锁起来,不用每次都进来,除非有新接收的数据

  64.             //下面的代码进入数据协议解析和数据处理的阶段


  65.                         for(i=0;i<uiRcregTotal;i++)  //返回全部接收到的数据包
  66.                         {
  67.                            eusart_send(ucRcregBuf[i]);
  68.                         }
  69.          
  70.                                          
  71.             uiRcregTotal=0;  //清空缓冲的下标,方便下次重新从0下标开始接受新数据
  72.   
  73.      }
  74.                         
  75. }


  76. //往串口发送一个字节
  77. void eusart_send(unsigned char ucSendData) //往上位机发送一个字节的函数
  78. {
  79.      unsigned char i=8;
  80.      EA=0;     //关总中断
  81.      ti_dr=0; //发送启始位
  82.          delay_minimum(const_t_1); //发送时序延时1   delay_minimum是本程序细分度最小的延时
  83.      while(i--)
  84.      {
  85.          ti_dr=ucSendData&0x01;      //先传低位
  86.              delay_minimum(const_t_2); //发送时序延时2   delay_minimum是本程序细分度最小的延时
  87.          ucSendData=ucSendData>>1;
  88.      }

  89.      ti_dr=1;  //发送结束位
  90.      delay_short(400);  //每个字节之间的延时,这里非常关键,也是最容易出错的地方。延时的大小请根据实际项目来调整
  91.      EA=1;     //开总中断
  92. }


  93. //从串口读取一个字节
  94. unsigned char read_eusart_byte()
  95. {
  96.     unsigned char  ucReadData=0;
  97.     unsigned char  i=8;


  98.          delay_minimum(const_r_1);  //接收时序延时1 。作用是等过起始位  delay_minimum是本程序细分度最小的延时
  99.      while(i--)
  100.      {
  101.          ucReadData >>=1;
  102.          if(ri_sr==1)
  103.                  {  
  104.                      ucReadData|=0x80;      //先收低位
  105.                  }

  106.          if(ri_sr==0) //此处空指令,是为了让驱动时序的时间保持一致性
  107.                  {  
  108.                      ;
  109.                  }

  110.              delay_minimum(const_r_2);    //接收时序延时2    delay_minimum是本程序细分度最小的延时   
  111.      }

  112.      return ucReadData;
  113. }




  114. void T0_time(void) interrupt 1    //定时中断
  115. {
  116.   TF0=0;  //清除中断标志
  117.   TR0=0; //关中断


  118.   if(uiSendCnt<const_receive_time)   //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完
  119.   {
  120.       uiSendCnt++;    //表面上这个数据不断累加,但是在串口中断里,每接收一个字节它都会被清零,除非这个中间没有串口数据过来
  121.       ucSendLock=1;     //开自锁标志
  122.   }



  123.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  124.   TL0=0x0b;
  125.   TR0=1;  //开中断
  126. }


  127.                              
  128. void  INT0_int(void) interrupt 0  //INT0外部中断函数
  129. {
  130.    EX0=0;   //禁止外部0中断 这个只是我个人的编程习惯,也可以不关闭

  131.    ++uiRcregTotal;
  132.    if(uiRcregTotal>const_rc_size)  //超过缓冲区
  133.    {
  134.            uiRcregTotal=const_rc_size;
  135.    }
  136.    ucRcregBuf[uiRcregTotal-1]=read_eusart_byte();   //将串口接收到的数据缓存到接收缓冲区里
  137.    uiSendCnt=0;  //及时喂狗,虽然main函数那边不断在累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个中断都被清零。

  138. /* 注释二:
  139. * 注意,此处必须把IE0中断标志清零,否则在接收到的数据包最后面会多收到一个无效的字节0xFF。
  140. */

  141.    IE0=0;  //外部中断0标志位清零,必须的!

  142.    EX0=1;  //打开外部0中断
  143. }


  144. void delay_long(unsigned int uiDelayLong)
  145. {
  146.    unsigned int i;
  147.    unsigned int j;
  148.    for(i=0;i<uiDelayLong;i++)
  149.    {
  150.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  151.           {
  152.              ; //一个分号相当于执行一条空语句
  153.           }
  154.    }
  155. }

  156. void delay_short(unsigned int uiDelayShort)
  157. {
  158.    unsigned int i;  
  159.    for(i=0;i<uiDelayShort;i++)
  160.    {
  161.      ;   //一个分号相当于执行一条空语句
  162.    }
  163. }


  164. /* 注释三:
  165. * 由于IO口模拟的串口时序要求很高,所以用的延时函数尽可能细分度越高越好,以下用一个字节的延时计时器
  166. */
  167. void delay_minimum(unsigned char ucDelayMinimum)  //细分度最小的延时,用char类型一个字节
  168. {
  169.    unsigned char i;  
  170.    for(i=0;i<ucDelayMinimum;i++)
  171.    {
  172.      ;   //一个分号相当于执行一条空语句
  173.    }

  174. }

  175. void initial_myself(void)  //第一区 初始化单片机
  176. {

  177.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  178.   //配置定时器
  179.   TMOD=0x01;  //设置定时器0为工作方式1
  180.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  181.   TL0=0x0b;


  182. }

  183. void initial_peripheral(void) //第二区 初始化外围
  184. {

  185.    EX0=1; //允许外部中断0
  186.    IT0=1;  //下降沿触发外部中断0   如果是0代表低电平中断
  187.    IP=0x01; //设置外部中断0为最高优先级,可以打断低优先级中断服务。实现中断嵌套功能

  188.    EA=1;     //开总中断
  189.    ET0=1;    //允许定时中断
  190.    TR0=1;    //启动定时中断

  191. }
复制代码

总结陈词:
这节讲完了外部中断的应用例子,下一节我会开始讲单片机C语言的多文件编程技巧。很多人也把多文件编程称作模块化编程,其实我觉得叫多文件编程会更加符合实际一些。多文件编程有两个最大的好处,一个是给我们的程序增加了目录,方便我们查找。另外一个好处是方便移植别人已经做好的功能程序模块,利用这个特点,特别适合团队一起做大型项目。很多初学者刚开始学多文件编程时,会经常遇到重复定义等问题,想知道怎么解决这些问题吗?欲知详情,请听下回分解----单片机C语言的多文件编程技巧。

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

乐于分享,勇于质疑!
83#
 楼主| 发表于 2014-9-14 21:08:05 | 显示全部楼层
waphaoyun 发表于 2014-9-14 20:35
鸿哥辛苦了,  我表示一直在看,

感谢你的鼓励。
乐于分享,勇于质疑!
84#
 楼主| 发表于 2014-9-17 13:39:45 | 显示全部楼层
第六十八节:单片机C语言的多文件编程技巧。
开场白:
很多人也把多文件编程称作模块化编程,其实我觉得叫多文件编程会更加符合实际一些。多文件编程有两个最大的好处,一个是给我们的程序增加了目录,方便我们查找。另外一个好处是方便移植别人已经做好的功能程序模块,利用这个特点,特别适合团队一起做大型项目。很多初学者刚开始学多文件编程时,会经常遇到重复定义等问题,想知道怎么解决这些问题吗?只要按照以下鸿哥教的规则来做,这些问题就不存在了。
第一个:每个文件保持成双成对出现。每个.c源文件必须有一个.h头文件跟它对应,每个.h头文件必须有一个.c源文件跟它对应。比如:main.c与main.h,delay.c与 delay.h。

第二个:.c源文件只负责函数的定义和变量的定义,但是不负责函数的声明和变量的声明。比如:
unsigned char ucLedStep=0; //这个是全局变量的定义
void led_flicker()   //这个是函数的定义
{
   //…里面是具体代码内容
  }

第三个:.h头文件只负责函数的声明和变量的声明,以及常量和IO口的宏定义,但是不负责函数的定义和变量的定义。比如:
#define const_time_level 200  //这个是常量的宏定义
sbit led_dr=P3^5;    //这个是IO口的宏定义
void led_flicker();     //这个是函数的声明
extern unsigned char ucLedStep;   //这个是全局变量的声明,不能赋初始值
第四个:每个.h头文件都必须固定以#ifndef,#define,#endif语句为模板,此模板是用来避免编译时由于重复包含头文件里面的内容而导致出错。其中标志变量_XXX_鸿哥建议用它本身的文件名称加前后下划线_。
比如:
#ifndef _LED_   //标志变量_LED_是用它本身的文件名称命名
#define _LED_   //标志变量_LED_是用它本身的文件名称命名
#define const_time_level 200  //这个是常量的宏定义
sbit led_dr=P3^5;    //这个是IO口的宏定义
void led_flicker();     //这个是函数的声明
extern unsigned char ucLedStep;   //这个是全局变量的声明,不能赋初始值
#endif  
第五个:每个.h头文件里都必须声明它对应的.c源文件里的所有定义函数和全局变量,注意:.c源文件里所有的全局变量都要在它所对应的.h头文件里声明一次,不仅仅是函数,这个地方很容易被人忽略。
比如:在led.h头文件中:
void led_flicker();     //这个是函数的声明,因为在这个函数在led.c文件里定义了。
   extern unsigned char ucLedStep;   //这个是全局变量的声明,不许赋初值
第六个:每个.c源文件里都必须包含两个文件,一个是单片机的系统头文件REG52.H,另外一个是它自己本身的头文件比如initial.h.剩下其它的头文件看实际情况来决定是否调用,我们用到了哪些文件的函数,全局变量或者宏定义,就需要调用对应的头文件。
比如:在initial.c源文件中:
#include"REG52.H"  //必须包含的单片机系统头文件
#include"initial.h"  //必须包含它本身的头文件
/* 注释:
   由于本源文件中用到了led_dr的语句,而led_dr是在led.h文件里宏定义的,所以必须把led.h也包含进来
*/  
#include"led.h"  //由于本源文件中用到了led_dr的语句,所以必须把led.h也包含进来
void initial_myself()  //这个是函数定义
{
  led_dr=0;  //led_dr是在led文件里定义和声明的
}
第七个:声明一个全局变量必须加extern关键字,同时千万不能在声明全局变量的时候赋初始值,比如:
extern unsigned char ucLedStep=0; //这样是绝对错误的。
extern unsigned char ucLedStep; //这个是全局变量的声明,这个才是正确的
第八个:对于函数与全局变量的声明,编译器都不分配内存空间。对于函数与全局变量的定义,编译器都分配内存空间。函数与全局变量的定义只能在一个.c源文件中出现一次,而函数与全局变量的声明可以在多个.h文件中出现。

具体内容,请看源代码讲解,本程序例程是直接把前面第四节一个源文件更改成多文件编程方式。

1)硬件平台:
   
基于朱兆祺51单片机学习板。把前面第四节一个源文件更改成多文件编程方式。

(2)实现功能:跟前面第四节的功能一模一样,让一个LED闪烁。
   
(3)keil多文件编程的截图预览:
4)整个源代码讲解工程文件下载:
more_file_4.rar (23.69 KB, 下载次数: 2763)
5)源代码讲解如下(注意,以下代码不能直接放到一个源文件里编译)
/*以下是 main.h 的内容*/

/* 注释一:
  每个头文件都是固定以#ifndef,#define,#endif
  为模板,其中标志变量_XXX_我建议用它本身的文件名称加前后下划线_。
  此标志变量名称是用来预防多次包含出错的,详细讲解请看注释二。
  每个头文件只做函数的声明和变量的声明,以及常量和IO口的宏定义,不做
  函数的定义与变量的定义。
*/  
#ifndef _MAIN_   //标志变量_MAIN_是用它本身的文件名称命名
#define _MAIN_   //标志变量_MAIN_是用它本身的文件名称命名

void main();  //这个是函数的声明

#endif  

/* 注释二:
以上语句
#ifndef  
#define
插入其它内容...
#endif

类似于把_MAIN_看成是一个标志变量
if(_MAIN_==0)  // 相当于#ifndef _MAIN_
{
    _MAIN_=1;  // 相当于#define _MAIN_
     插入其它内容...

}               //相当于#endif

目的是通过一个标志位变量的赋值,让编译器在编译的时候,只包含一次此头文件,避免多次包含出错
*/  

/*------分割线--------------------------------------------------*/

/*以下是 main.c 的内容*/

/* 注释一:
  每个源文件都必须包含两个文件,一个是单片机的系统头文件REG52.H,
  另外一个是它自己本身的头文件main.h.剩下其它的头文件看实际情况来
  决定是否调用,我们用到了哪些文件的函数,全局变量或者宏定义,就需要调用对应的头文件。
  每个源文件只做函数的定义和变量的定义,不做函数的声明和变量的声明。
*/  

#include "REG52.H"  //必须包含的单片机系统头文件
#include "main.h"  //必须包含它本身的头文件

/* 注释二:
   (1)由于本源文件中调用initial_myself()和initial_peripheral()函数,而这两个函数
      都是在initial文件里定义和声明的,所以必须把initial.h也包含进来。
   (2)由于本源文件中调用delay_long(100)函数,而这个函数
      是在delay文件里定义和声明的,所以必须把delay.h也包含进来。
   (2)由于本源文件中调用led_flicker()函数,而这个函数
      是在led文件里定义和声明的,所以必须把led.h也包含进来。
*/  


#include "initial.h"  //由于本源文件中用到了initial_myself()和initial_peripheral()函数,所以必须把initial.h也包含进来
#include "delay.h"  //由于本源文件中用到了delay_long(100)函数,所以必须把delay.h也包含进来
#include "led.h"  //由于本源文件中用到了led_flicker()函数,所以必须把led.h也包含进来

void main()  //这个是函数的定义
  {
   initial_myself();  
   delay_long(100);   
   initial_peripheral();
   while(1)   
   {
      led_flicker();   
   }

}

/*------分割线--------------------------------------------------*/

/*以下是 delay.h 的内容*/


#ifndef _DELAY_   //标志变量_DELAY_是用它本身的文件名称命名
#define _DELAY_   //标志变量_DELAY_是用它本身的文件名称命名

void delay_long(unsigned int uiDelaylong); //这个是函数的声明,每一个源文件里的函数都要在它的头文件里声明


#endif  

/*------分割线--------------------------------------------------*/

/*以下是 delay.c 的内容*/


#include "REG52.H"  //必须包含的单片机系统头文件
#include "delay.h"  //必须包含它本身的头文件


void delay_long(unsigned int uiDelayLong)  //这个是函数的定义
{
   unsigned int i;   //这个是局部变量的定义
   unsigned int j;   //这个是局部变量的定义
   for(i=0;i<uiDelayLong;i++)
   {
      for(j=0;j<500;j++)
          {
             ;
          }
   }
}
/*------分割线--------------------------------------------------*/
/*以下是 initial.h 的内容*/

#ifndef _INITIAL_   //标志变量_INITIAL_是用它本身的文件名称命名
#define _INITIAL_   //标志变量_INITIAL_是用它本身的文件名称命名

void initial_myself();    //这个是函数声明,每一个源文件里的函数都要在它的头文件里声明
void initial_peripheral(); //这个是函数声明,每一个源文件里的函数都要在它的头文件里声明

#endif  

/*------分割线--------------------------------------------------*/
/*以下是 initial.c 的内容*/


#include "REG52.H"  //必须包含的单片机系统头文件
#include "initial.h"  //必须包含它本身的头文件

/* 注释一:
   由于本源文件中用到了led_dr的语句,而led_dr是在led文件里宏定义的,所以必须把led.h也包含进来
*/  
#include "led.h"  //由于本源文件中用到了led_dr的语句,所以必须把led.h也包含进来

void initial_myself()  //这个是函数定义
{


  TMOD=0x01;  //以下能直接用TMOD,TH0,TL0,EA,ET0,TR0这些寄存器关键字,是因为包含了REG52.H头文件

  TH0=0xf8;  
  TL0=0x2f;   

  led_dr=0;  //led_dr是在led文件里定义和声明的
}


void initial_peripheral() //这个是函数定义
{
  EA=1;     
  ET0=1;   
  TR0=1;   

}

/*------分割线--------------------------------------------------*/
/*以下是 interrupt.h 的内容*/


#ifndef _INTERRUPT_   //标志变量_INTERRUPT_是用它本身的文件名称命名
#define _INTERRUPT_   //标志变量_INTERRUPT_是用它本身的文件名称命名

void T0_time();  //这个是函数声明,每一个源文件里的函数都要在它的头文件里声明

/* 注释一:
声明一个外部全局变量必须加extern关键字,同时千万不能在声明全局变量的时候赋初始值,比如:
extern unsigned int uiTimeCnt=0; 这样是绝对错误的。
*/  
extern unsigned int uiTimeCnt; //这个是全局变量的声明,不能赋初始值


#endif  

/*------分割线--------------------------------------------------*/
/*以下是 interrupt.c 的内容*/


#include "REG52.H"  //必须包含的单片机系统头文件
#include "interrupt.h"  //必须包含它本身的头文件

unsigned int uiTimeCnt=0; //这个是全局变量的定义,可以赋初值


void T0_time() interrupt 1  //这个是函数定义
{
  TF0=0;   //以下能直接用TF0,TR0,TH0,TL0这些寄存器关键字,是因为包含了REG52.H头文件
  TR0=0;

  if(uiTimeCnt<0xffff)
  {
      uiTimeCnt++;
  }

  TH0=0xf8;  
  TL0=0x2f;
  TR0=1;
}


/*------分割线--------------------------------------------------*/
/*以下是 led.h 的内容*/


#ifndef _LED_   //标志变量_LED_是用它本身的文件名称命名
#define _LED_   //标志变量_LED_是用它本身的文件名称命名


#define const_time_level 200   //宏定义都放在头文件里

/* 注释一:
  IO口的宏定义也放在头文件里,
  如果是PIC单片机,以下IO口定义相当于宏定义 #define  led_dr LATBbits.LATB4等语句
*/  
sbit led_dr=P3^5; //如果是PIC单片机,相当于宏定义 #define  led_dr LATBbits.LATB4等语句

void led_flicker();   //这个是函数的声明,每一个源文件里的函数都要在它的头文件里声明

/* 注释三:
声明一个全局变量必须加extern关键字,同时千万不能在声明全局变量的时候赋初始值,比如:
extern unsigned char ucLedStep=0; 这样是绝对错误的。
*/  
extern unsigned char ucLedStep; //这个是全局变量的声明



#endif  

/*------分割线--------------------------------------------------*/
/*以下是 led.c 的内容*/

#include "REG52.H"  //必须包含的单片机系统头文件
#include "led.h"  //必须包含它本身的头文件


/* 注释一:
   由于本源文件中用到了uiTimeCnt全局变量,而uiTimeCnt是在interrupt文件里声明和定义的,
   所以必须把interrupt.h也包含进来
*/  
#include "interrupt.h"  //必须包含它本身的头文件

unsigned char ucLedStep=0; //这个是全局变量的定义,可以赋初值

void led_flicker()   //这个是函数的定义
{
  switch(ucLedStep)
  {
     case 0:

         if(uiTimeCnt>=const_time_level)
         {

             ET0=0;  //以下能直接用ET0寄存器关键字,是因为包含了REG52.H头文件
             uiTimeCnt=0; //uiTimeCnt此变量是在interrupt文件里声明和定义的,所以必须把interrupt.h也包含进来
             ET0=1;
             led_dr=1;  //此IO口定义已经在led.h头文件中定义了
             ucLedStep=1; //切换到下一个步骤
         }
         break;
     case 1:
         if(uiTimeCnt>=const_time_level)
         {
             ET0=0;
             uiTimeCnt=0;
             ET0=1;  
             led_dr=0;   
             ucLedStep=0; //返回到上一个步骤
         }
         break;
  
  }

}


/*------分割线--------------------------------------------------*/

总结陈词:
下一节开始讲液晶屏显示方面的内容。欲知详情,请听下回分解----带字库12864液晶屏的常用点阵字体程序。

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

乐于分享,勇于质疑!
85#
 楼主| 发表于 2014-9-18 12:51:27 | 显示全部楼层
第六十九节:使用static关键字可以减少全局变量的使用

开场白:
本来这一节打算开始讲液晶屏的,但是昨天经过网友“任军”的点拨,我发现了一个惊天秘密,原来static关键字是这么好的东西我却错过了那么多年。以前就有一些网友抱怨,鸿哥的程序好是好,就是全局变量满天飞,当时我觉得我也没招呀,C语言就全局变量和局部变量,单单靠局部变量肯定不行,局部变量每次进入函数内部数值都会被初始化改变,所以我在很多场合也只能靠全局变量了。但是自从昨天知道了static关键字的秘密后,我恍然大悟,很多场合只要在局部变量前面加上static关键字,就可以大大减少全局变量的使用了。
这一节要教会大家一个知识点:
  大家都知道,普通的局部变量在每次程序执行到函数内部的时候,数值都会被重新初始化,数值会发生变化,不能保持之前的数值。但是在局部变量加上static关键字后,系统在刚上电的时候就已经把带static的局部变量赋初始值了,从此程序每次进入函数内部,都不会初始化带static关键字的局部变量,它会保持最近一次被程序执行更改的数值不变,像全局变量一样。跟全局变量唯一的差别是,带static关键字的局部变量的作用域仅仅在函数内部,而普通全局变量的作用域是整个工程。

本程序例程是直接在第八节程序上修改,大大减少了全局变量的使用。具体内容,请看源代码讲解。

(1)硬件平台:
     基于坚鸿51单片机学习板。用矩阵键盘中的S1和S5号键作为独立按键,记得把输出线P0.4一直输出低电平,模拟独立按键的触发地GND。

(2)实现功能:跟前面第八节的功能一模一样,有两个独立按键,每按一个独立按键,蜂鸣器发出“滴”的一声后就停。
   
(3)源代码讲解如下:

#include "REG52.H"

#define const_voice_short  40  

#define const_key_time1  20   
#define const_key_time2  20   

void initial_myself();   
void initial_peripheral();
void delay_long(unsigned int uiDelaylong);
void T0_time();
void key_service();
void key_scan();

sbit key_sr1=P0^0;
sbit key_sr2=P0^1;
sbit key_gnd_dr=P0^4;

sbit beep_dr=P2^7;

unsigned char ucKeySec=0;   //一些需要在不同函数之间使用的核心变量,只能用全局变量
unsigned int  uiVoiceCnt=0;  //一些需要在不同函数之间使用的核心变量,只能用全局变量

void main()
  {
   initial_myself();  
   delay_long(100);   
   initial_peripheral();
   while(1)  
   {
       key_service();
   }

}

void key_scan()
{  
/* 注释一:
   (1)大家都知道,普通的局部变量在每次程序执行到函数内部的时候,数值都会被重新初始化,
   数值会发生变化,不能保持之前的数值。
   (2)但是在局部变量加上static关键字后,系统在刚上电的时候就已经把带static的局部变量
   赋初始值了,从此程序每次进入函数内部,都不会初始化带static关键字的局部变量,它会保持
   最近一次被程序执行更改的数值不变,像全局变量一样。跟全局变量唯一的差别是,带static关键字
   的局部变量的作用域仅仅在函数内部,而普通全局变量的作用域是整个工程。
   (3)以下这些变量我原来在第八节是用普通全局变量的,现在改成用static的局部变量了,减少了全局变量
   的使用,让程序阅读起来更加简洁。大家也可以试试把以下变量的static去掉试试,结果会发现去掉了static后,
   按键就不会被触发了。
*/

static unsigned int  uiKeyTimeCnt1=0; //带static的局部变量
static unsigned char ucKeyLock1=0;   
static unsigned int  uiKeyTimeCnt2=0;
static unsigned char ucKeyLock2=0;


  if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  {
     ucKeyLock1=0; //按键自锁标志清零
         uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  }
  else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  {
     uiKeyTimeCnt1++; //累加定时中断次数
     if(uiKeyTimeCnt1>const_key_time1)
     {
        uiKeyTimeCnt1=0;
        ucKeyLock1=1;  //自锁按键置位,避免一直触发
        ucKeySec=1;    //触发1号键
     }
  }

  if(key_sr2==1)
  {
     ucKeyLock2=0;
         uiKeyTimeCnt2=0;
  }
  else if(ucKeyLock2==0)
  {
     uiKeyTimeCnt2++;
     if(uiKeyTimeCnt2>const_key_time2)
     {
        uiKeyTimeCnt2=0;
        ucKeyLock2=1;
        ucKeySec=2;   
     }
  }

}


void key_service()
{
  switch(ucKeySec)
  {
    case 1:

          uiVoiceCnt=const_voice_short;
          ucKeySec=0;
          break;        
    case 2:
          uiVoiceCnt=const_voice_short;
          ucKeySec=0;
          break;                    
  }               
}



void T0_time() interrupt 1
{
  TF0=0;
  TR0=0;

  key_scan();

  if(uiVoiceCnt!=0)
  {
     uiVoiceCnt--;
     beep_dr=0;  
  }
  else
  {
     ;
     beep_dr=1;
  }


  TH0=0xf8;   
  TL0=0x2f;
  TR0=1;
}


void delay_long(unsigned int uiDelayLong)
{
   unsigned int i;
   unsigned int j;
   for(i=0;i<uiDelayLong;i++)
   {
      for(j=0;j<500;j++)  
      {
         ;
      }
   }
}


void initial_myself()
{


  key_gnd_dr=0;

  beep_dr=1;

  TMOD=0x01;  

  TH0=0xf8;   
  TL0=0x2f;

}
void initial_peripheral()
{
  EA=1;     
  ET0=1;   
  TR0=1;   
}
总结陈词:
下一节开始讲液晶屏显示方面的内容。欲知详情,请听下回分解----带字库12864液晶屏的常用点阵字体程序。

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


乐于分享,勇于质疑!
86#
 楼主| 发表于 2014-9-22 00:43:18 | 显示全部楼层
KR770906 发表于 2014-9-21 08:58
贴合实战,膜拜鸿哥。

感谢支持。
乐于分享,勇于质疑!
87#
 楼主| 发表于 2014-9-24 14:09:35 | 显示全部楼层
本帖最后由 jianhong_wu 于 2014-11-16 21:26 编辑

第七十节:深入讲解液晶屏的构字过程。

开场白:
    液晶屏模块本身带控制芯片,驱动液晶屏的本质就是单片机通过串行或者并行方式,根据芯片资料指定的协议跟液晶芯片进行通讯的过程。这个详细的通讯协议驱动程序厂家都会免费提供的,也可以在网上找到大量的示范程序。那么我们最应该关注的核心是什么?我认为最核心的是要理清楚程序坐标与实际显示坐标之间的关系规律。本程序不使用模块自带的字库,而是使用自己构造的字库,目的就是为了让读者理解更底层的字模显示。
这一节要教会大家三个知识点:
第一个:对于驱动芯片是st7920的12864液晶屏,它的真实坐标体系的本质是256x32的点阵液晶屏。
第二个:鸿哥刻意在驱动显示函数里增加了大延时函数,目的是通过慢镜头回放,让大家观察到横向取模的字是如何一个字节一个字节构建而成的。
第三个:数组带const关键字,表示数据常量存放在ROM程序区,不占用RAM的变量。

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

(1)硬件平台:
     基于坚鸿51单片机学习板。

(2)实现功能:开机上电后,可以观察到0x01,0x02,0x03,0x04这4个显示数字在不同的排列方式下,出现在不同的液晶屏显示位置。也可以观察到“馒头”这两个字是如何一个字节一个字节构建而成的,加深理解字模数组跟显示现象的关系。
   
(3)源代码讲解如下:
#include "REG52.H"


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

void SendByteToLcd(unsigned char ucData);  //发送一个字节数据到液晶模块
void SPIWrite(unsigned char ucWData, unsigned char ucWRS); //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动
void WriteCommand(unsigned char ucCommand); //发送一个字节的命令给液晶模块
void LCDWriteData(unsigned char ucData);   //发送一个字节的数据给液晶模块
void LCDInit(void);  //初始化  函数内部包括液晶模块的复位
void display_lattice(unsigned int x,unsigned int y,const unsigned char  *ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount); //显示任意点阵函数
void display_clear(void); // 清屏

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

/* 注释一:
* 数组带const关键字,表示数据常量存放在ROM程序区,不占用RAM的变量
*/
const unsigned char Hz1616_man[]= /*馒   横向取模  16X16点阵 网上有很多免费的字模软件生成字模数组 */
{
0x21,0xF8,0x21,0x08,0x21,0xF8,0x3D,0x08,0x45,0xF8,0x48,0x00,0x83,0xFC,0x22,0x94,
0x23,0xFC,0x20,0x00,0x21,0xF8,0x20,0x90,0x28,0x60,0x30,0x90,0x23,0x0E,0x00,0x00,
};

const unsigned char Hz1616_tou[]= /*头   横向取模  16X16点阵 网上有很多免费的字模软件生成字模数组 */
{
0x00,0x80,0x10,0x80,0x0C,0x80,0x04,0x80,0x10,0x80,0x0C,0x80,0x08,0x80,0x00,0x80,
0xFF,0xFE,0x00,0x80,0x01,0x40,0x02,0x20,0x04,0x30,0x08,0x18,0x10,0x0C,0x20,0x08,
};

/* 注释二:
* 为了方便观察字模的数字与显示的关系,以下3个数组的本质是完全一样的,只是排列不一样而已。
*/
const unsigned char Byte_1[]=  //4横,1列
{
0x01,0x02,0x03,0x04,
};

const unsigned char Byte_2[]= //2横,2列
{
0x01,0x02,
0x03,0x04,
};

const unsigned char Byte_3[]= //1横,4列
{
0x01,
0x02,
0x03,
0x04,
};


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

    display_clear(); // 清屏

        display_lattice(0,0,Byte_1,0,4,1);    //显示<4横,1列>的数组数字
        display_lattice(0,16,Byte_1,1,4,1);   //显示<4横,1列>的数组数字 反显

        display_lattice(7,0,Byte_2,0,2,2);   //显示<2横,2列>的数组数字
        display_lattice(7,16,Byte_2,1,2,2);  //显示<2横,2列>的数组数字 反显

        display_lattice(8,0,Byte_3,0,1,4);  //显示<1横,4列>的数组数字
        display_lattice(8,16,Byte_3,1,1,4); //显示<1横,4列>的数组数字 反显

        display_lattice(14,0,Hz1616_man,0,2,16);  //显示<馒>字
        display_lattice(15,0,Hz1616_tou,0,2,16);  //显示<头>字
        display_lattice(14,16,Hz1616_man,1,2,16); //显示<馒>字 反显
        display_lattice(15,16,Hz1616_tou,1,2,16); //显示<头>字 反显
    while(1)  
    {
       ;
    }

}

/* 注释三:真实坐标体系的本质。
* 从坐标体系的角度来看,本液晶屏表面上是128x64的液晶屏,实际上可以看做是256x32的液晶屏。
* 把256x32的液晶屏分左右两半,把左半屏128x32放在上面,把右半屏128x32放下面,就合并成了
* 一个128x64的液晶屏。由于液晶模块内部控制器的原因,虽然横向有256个点阵,但是我们的x轴
* 坐标没办法精确到每个点,只能以16个点(2个字节)为一个单位,因此256个点的x轴坐标范围是0至15。
* 而y轴的坐标可以精确到每个点为一行,所以32个点的y轴坐标范围是0至31.
*/

void display_clear(void) // 清屏
{   

        unsigned char x,y;
  //  WriteCommand(0x34);  //关显示缓冲指令            
    WriteCommand(0x36); //这次为了观察每个数字在显示屏上的关系,所以把这个显示缓冲的命令提前打开,下一节放到本函数最后
         y=0;
        while(y<32)  //y轴的范围0至31
    {
                 WriteCommand(y+0x80);        //垂直地址
         WriteCommand(0x80);          //水平地址
         for(x=0;x<32;x++)  //256个横向点,有32个字节
         {  
            LCDWriteData(0x00);
             }
                 y++;
    }


}


/* 注释四:本节的核心函数,读者尤其要搞懂x_amount和y_amount对应的显示关系。
* 第1,2个参数x,y是坐标体系。x的范围是0至15,y的范围是0至31.
* 第3个参数*ucArray是字模的数组。
* 第4个参数ucFbFlag是反白显示标志。0代表正常显示,1代表反白显示。
* 第5,6个参数x_amount,y_amount分别代表字模数组的横向有多少个字节,纵向有几横。
* 本函数后面故意增加一个长延时delay_short(30000),是为了方便读者观察横向取模的
* 字是如何一个字节一个字节构建而成的。
*/
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 j=0;
   unsigned int i=0;
   unsigned char ucTemp;
//  WriteCommand(0x34);   //关显示缓冲指令   
   WriteCommand(0x36);  //这次为了观察每个数字在显示屏上的关系,所以把这个显示缓冲的命令提前打开,下一节放到本函数最后
   for(j=0;j<y_amount;j++) //y_amount代表y轴有多少横
   {
       WriteCommand(y+j+0x80);        //垂直地址
       WriteCommand(x+0x80);          //水平地址
       for(i=0;i<x_amount;i++) //x_amount代表x轴有多少列
       {
         ucTemp=ucArray[j*x_amount+i];
             if(ucFbFlag==1)  //反白显示
                 {
            ucTemp=~ucTemp;
         }
             LCDWriteData(ucTemp);
                 delay_short(30000);  //本函数故意增加这个长延时,是为了方便读者观察横向取模的字是如何一个字节一个字节构建而成的。
      }
   }

}

/* 注释五:
* 以下是液晶屏模块的驱动程序,我觉得没有什么好讲的,因为我是直接在网上寻找现成的驱动时序修改而成。
* 它的本质就是单片机跟这个液晶模块芯片进行串行通信。
*/
void SendByteToLcd(unsigned char ucData)  //发送一个字节数据到液晶模块
{
        unsigned char i;
        for ( i = 0; i < 8; i++ )
        {
                if ( (ucData << i) & 0x80 )
                {
                        LCDSID_dr = 1;
                }
                else
                {
                        LCDSID_dr = 0;
                }
                LCDCLK_dr = 0;
                LCDCLK_dr = 1;
        }
}

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


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

        LCDCS_dr = 0;
        LCDCS_dr = 1;
        SPIWrite(ucCommand, 0);
        delay_short(90);
}

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

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



void delay_short(unsigned int uiDelayShort) //延时函数
{
   unsigned int i;  
   for(i=0;i<uiDelayShort;i++)
   {
     ;  
   }
}

总结陈词:
这节重点讲了液晶屏的构字过程,下节将会在本节的基础上,略作修改,显示常用的不同点阵字模。欲知详情,请听下回分解-----液晶屏的字符,16点阵,24点阵和32点阵的显示程序。

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

乐于分享,勇于质疑!
88#
 楼主| 发表于 2014-9-25 11:09:37 | 显示全部楼层
KR770906 发表于 2014-9-25 08:43
鸿哥,ds1302芯片内置万年历和电池,好像不用判断闰月,直接对时就可以使用了。不知对否?

(1)DS1302没有内置电池。
(2)从DS1302读取的数据是不用判断闰月的,但是你往里面写入修改的时间数据时,建议还是判断一下,因为假如你不判断就写入一个错误的时间,我不知道ds1302内部会怎么处理。
乐于分享,勇于质疑!
89#
 楼主| 发表于 2014-9-25 11:10:27 | 显示全部楼层
第七十一节:液晶屏的字符,16点阵,24点阵和32点阵的显示程序。
开场白:
这一节要教会大家二个知识点:
第一个:如何利用任意点阵字体显示函数display_lattice来显示8x16的字符,16点阵汉字,24点阵汉字和32点阵汉字。
第二个:纠正上一节的一个小错误。C51编译器跟其它单片机的编译器有点不一样。想把常量数据保存在ROM程序存储区里并不是用const关键字,而是是用code关键字。

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

(1)硬件平台:
    基于坚鸿51单片机学习板。

2)实现功能:开机上电后,可以看到液晶屏分别显示32点阵,24点阵和16点阵的“馒头”两个字,还有“V5”这两个8x16点阵的字符。
   
3)源代码讲解如下:
#include "REG52.H"

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

void SendByteToLcd(unsigned char ucData);  //发送一个字节数据到液晶模块
void SPIWrite(unsigned char ucWData, unsigned char ucWRS); //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动
void WriteCommand(unsigned char ucCommand); //发送一个字节的命令给液晶模块
void LCDWriteData(unsigned char ucData);   //发送一个字节的数据给液晶模块
void LCDInit(void);  //初始化  函数内部包括液晶模块的复位
void display_lattice(unsigned int x,unsigned int y,const unsigned char  *ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount); //显示任意点阵函数
void display_clear(void); // 清屏

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

/* 注释一:
* 纠正上一节的一个小错误。C51编译器跟其它的编译器有点不一样。
* 存在ROM程序存储区里的常量数据并不是用const关键字,而是是用code关键字。
*/
code unsigned char Hz3232_man[]= /*馒   横向取模  32x32点阵 */
{
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x07,0x03,0x00,0x0F,0x87,0xFF,0x80,
0x0F,0x07,0x03,0x80,0x0E,0x07,0x03,0x80,0x0E,0x37,0xFF,0x80,0x1C,0x7F,0x03,0x80,
0x1F,0xFF,0x03,0x80,0x18,0x77,0xFF,0x00,0x38,0xE0,0x00,0xC0,0x36,0xDF,0xFF,0xF0,
0x77,0x9C,0xCE,0xE0,0x67,0x1C,0xCE,0xE0,0xC7,0x1C,0xCE,0xE0,0x07,0x1C,0xCE,0xE0,
0x07,0x1F,0xFF,0xE0,0x07,0x18,0x00,0x00,0x07,0x00,0x03,0x80,0x07,0x0F,0xFF,0xC0,
0x07,0x71,0x8F,0x00,0x07,0xE0,0xDE,0x00,0x07,0xC0,0xFC,0x00,0x07,0x80,0x78,0x00,
0x0F,0x01,0xFE,0x00,0x07,0x03,0x8F,0xE0,0x00,0x1E,0x03,0xF0,0x00,0xF8,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
};


code unsigned char Hz3232_tou[]= /*头   横向取模  32x32点阵 */
{
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0xC0,0x00,0x00,0x03,0xE0,0x00,
0x03,0xC3,0xC0,0x00,0x00,0xF3,0x80,0x00,0x00,0x7B,0x80,0x00,0x00,0x7B,0x80,0x00,
0x00,0x3B,0x80,0x00,0x0E,0x03,0x80,0x00,0x07,0x83,0x80,0x00,0x03,0xC3,0x80,0x00,
0x01,0xE3,0x80,0x00,0x01,0xE3,0x80,0x00,0x00,0xC3,0x80,0x00,0x00,0x03,0x81,0xE0,
0x7F,0xFF,0xFF,0xF0,0x00,0x07,0x80,0x30,0x00,0x07,0x00,0x00,0x00,0x07,0x80,0x00,
0x00,0x0E,0xE0,0x00,0x00,0x1E,0x7C,0x00,0x00,0x3C,0x1F,0x00,0x00,0x78,0x0F,0xC0,
0x00,0xF0,0x03,0xC0,0x03,0xC0,0x01,0xE0,0x0F,0x00,0x00,0xE0,0x78,0x00,0x00,0x00,
0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
};

code unsigned char Hz2424_man[]= /*馒   横向取模  24x24点阵 */
{
0x00,0x00,0x00,0x00,0x00,0x00,0x0C,0x18,0x30,0x1E,0x1F,0xF8,0x1C,0x1C,0x38,0x1C,
0x1F,0xF8,0x19,0xFC,0x38,0x3F,0xFF,0xF8,0x31,0x98,0x30,0x7B,0xE0,0x0E,0x6F,0x7F,
0xFE,0x6E,0x76,0xEE,0xCC,0x76,0xEE,0x0C,0x7F,0xFE,0x0C,0x70,0x0C,0x0C,0x00,0x38,
0x0C,0x3F,0xF8,0x0D,0xCE,0x70,0x0F,0x87,0xE0,0x0F,0x03,0x80,0x1E,0x07,0xE0,0x0C,
0x1C,0x7E,0x01,0xF0,0x1F,0x00,0x00,0x00,
};


code unsigned char Hz2424_tou[]= /*头   横向取模  24x24点阵 */
{
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0E,0x00,0x06,0x0F,0x00,0x07,0x8E,0x00,0x01,
0xEE,0x00,0x00,0xEE,0x00,0x00,0xEC,0x00,0x1C,0x0C,0x00,0x0F,0x0C,0x00,0x07,0x9C,
0x00,0x03,0x9C,0x00,0x00,0x1C,0x0C,0x00,0x1C,0x1E,0x7F,0xFF,0xF6,0x00,0x1C,0x00,
0x00,0x3C,0x00,0x00,0x3F,0x80,0x00,0x71,0xE0,0x00,0xE0,0xF8,0x01,0xC0,0x3C,0x07,
0x00,0x1C,0x3C,0x00,0x0C,0x70,0x00,0x00,
};


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

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

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

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



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

    display_clear(); // 清屏

        display_lattice(0,0,Hz3232_man,0,4,32);  //显示32点阵的<馒>字
        display_lattice(2,0,Hz3232_tou,0,4,32);  //显示32点阵的<头>字

        display_lattice(4,0,Hz2424_man,0,3,24);  //显示24点阵的<馒>字
        display_lattice(6,0,Hz2424_tou,0,3,24);  //显示24点阵的<头>字

        display_lattice(8,0,Hz1616_man,0,2,16);  //显示16点阵的<馒>字
        display_lattice(9,0,Hz1616_tou,0,2,16);  //显示16点阵的<头>字

        display_lattice(11,0,Zf816_V,0,1,16);  //显示8x16点阵的<V>字符
        display_lattice(12,0,Zf816_5,0,1,16);  //显示8x16点阵的<5>字符

    while(1)  
    {
       ;
    }

}



void display_clear(void) // 清屏
{   

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

}


/* 注释二:本节的核心函数,读者尤其要搞懂x_amount和y_amount对应的显示关系。
* 第1,2个参数x,y是坐标体系。x的范围是0至15,y的范围是0至31.
* 第3个参数*ucArray是字模的数组。
* 第4个参数ucFbFlag是反白显示标志。0代表正常显示,1代表反白显示。
* 第5,6个参数x_amount,y_amount分别代表字模数组的横向有多少个字节,纵向有几横。
*/
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 j=0;
   unsigned int i=0;
   unsigned char ucTemp;
   WriteCommand(0x34);  //关显示缓冲指令            
   WriteCommand(0x34);  //关显示缓冲指令  故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的
   for(j=0;j<y_amount;j++) //y_amount代表y轴有多少横
   {
       WriteCommand(y+j+0x80);        //垂直地址
       WriteCommand(x+0x80);          //水平地址
       for(i=0;i<x_amount;i++) //x_amount代表x轴有多少列
       {
         ucTemp=ucArray[j*x_amount+i];
             if(ucFbFlag==1)  //反白显示
                 {
            ucTemp=~ucTemp;
         }
             LCDWriteData(ucTemp);
          //         delay_short(30000);  //把上一节这个延时函数去掉,加快刷屏速度
      }
   }
   WriteCommand(0x36); //开显示缓冲指令
}


void SendByteToLcd(unsigned char ucData)  //发送一个字节数据到液晶模块
{
        unsigned char i;
        for ( i = 0; i < 8; i++ )
        {
                if ( (ucData << i) & 0x80 )
                {
                        LCDSID_dr = 1;
                }
                else
                {
                        LCDSID_dr = 0;
                }
                LCDCLK_dr = 0;
                LCDCLK_dr = 1;
        }
}

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


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

        LCDCS_dr = 0;
        LCDCS_dr = 1;
        SPIWrite(ucCommand, 0);
        delay_short(90);
}

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

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



void delay_short(unsigned int uiDelayShort) //延时函数
{
   unsigned int i;  
   for(i=0;i<uiDelayShort;i++)
   {
     ;  
   }
}


总结陈词:
    我们现在讲的字体显示都是横向的,如果某个项目要把整个液晶屏顺时针旋转90度,要求像对联一样纵向显示一串字体的时候,该怎么办?我前两个月就遇到了这样的项目,当时我的做法就是把字体的字库数组通过算法旋转90度就达到了目的。这种算法程序是怎样编写的?欲知详情,请听下回分解-----把字体顺时针旋转90度显示的算法程序。

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

乐于分享,勇于质疑!
90#
 楼主| 发表于 2014-10-6 12:29:46 | 显示全部楼层
又一个暑假 发表于 2014-10-6 12:14
鸿哥下次能不能把把整个工程文件夹压缩好附在后面传上来啊,在这里复制代码自己在建个工程挺懒的

我的都是一个源文件的,复制很简单的。你的请求被驳回了。
乐于分享,勇于质疑!
91#
 楼主| 发表于 2014-10-10 12:38:36 | 显示全部楼层
第七十二节:在液晶屏中把字体顺时针旋转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. }
复制代码

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

乐于分享,勇于质疑!
92#
 楼主| 发表于 2014-10-10 13:45:53 | 显示全部楼层
海~~ 发表于 2014-10-10 12:48
非常有用啊,鸿哥。

那当然,我分享的都是实际项目的经验,不是精华我不发。
乐于分享,勇于质疑!
93#
 楼主| 发表于 2014-10-12 10:54:10 | 显示全部楼层
KR770906 发表于 2014-10-11 21:30
十一大假后鸿哥又出手了,呵呵。

一直会有的,根本停不下来。
乐于分享,勇于质疑!
94#
 楼主| 发表于 2014-10-13 10:45:24 | 显示全部楼层
第七十三节:在液晶屏中把字体镜像显示的算法程序。

开场白:
有的项目会要求把字体或者图像进行镜像显示处理,这一节把这个算法教给大家。
    这个算法的本质是:
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行,行不行?当然可以。但是需要我们编写额外的算法程序。这种算法程序是怎样编写的?欲知详情,请听下回分解-----在液晶屏中让字体可以跨区域无缝对接显示的算法程序。

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

乐于分享,勇于质疑!
95#
 楼主| 发表于 2014-10-16 15:05:10 | 显示全部楼层
第七十四节:在液晶屏中让字体可以跨区域无缝对接显示的算法程序。
开场白:
细心的网友会发现,这种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个点阵为单位进行移动显示的算法程序。
(未完待续,下节更精彩,不要走开哦)

乐于分享,勇于质疑!
96#
 楼主| 发表于 2014-10-19 10:03:11 | 显示全部楼层
第七十五节:在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个窗口里通过移动光标来设置不同参数的液晶屏菜单程序。

(未完待续,下节更精彩,不要走开哦)
乐于分享,勇于质疑!
97#
 楼主| 发表于 2014-10-23 11:31:33 | 显示全部楼层
第七十六节:如何把一个任意数值的变量显示在液晶屏上。

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

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

(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个窗口里通过移动光标来设置不同参数的液晶屏菜单程序。

(未完待续,下节更精彩,不要走开哦)
乐于分享,勇于质疑!
98#
 楼主| 发表于 2014-10-26 09:21:20 | 显示全部楼层
本帖最后由 jianhong_wu 于 2014-10-28 17:25 编辑

第七十七节:在1个窗口里通过移动光标来设置不同参数的液晶屏菜单程序。

开场白:
    这一节要教会大家两个知识点:
第一个知识点:我在前面讲数码管显示的时候就提出了一个 “一二级菜单显示理论”:凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。把每一个窗口的内容分为两种类型,一种类型是那些不用经常刷新显示的内容,只有在切换窗口的时候才需要更新的,这种内容放在整屏更新显示的括号里,比如清屏操作等内容。另外一种是那些经常需要刷新显示的内容,这种内容放在局部更新显示的括号里。
第二个知识点:按键如何跟液晶屏显示有机的结合起来?只要遵循鸿哥总结出来的一个规律“在不同的窗口下,根据不同的局部变量来操作不同的参数”,这样再复杂的人机交互程序都会显得很简单清晰。

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

(1)硬件平台:基于坚鸿51单片机学习板。加按键对应S1键,减按键对应S5键,切换“光标”移动按键对应S9键,设置参数按键对应S13键。

(2)实现功能:
     通过按键设置4个不同的参数。
    有1个窗口。每个窗口显示4个参数。每个参数的范围是从0到99。
   有4个按键:
(a)        一个是设置参数S13按键,按下此按键,液晶屏的第一行会出现反显的光标,表示进入设置参数模式,再次按下此按键,反显光标会消失,表示退出设置参数模式。
(b)        一个是移动光标S9按键,在进入设置参数的模式下,依次按下此按键,液晶屏上的光标会从上往下移动,表示选中不同的参数。
(c)        一个是减数S5按键,在设置参数模式下,依次按下此按键,被选中的参数会逐渐减小。
(d)        一个是加数S1按键,在设置参数模式下,依次按下此按键,被选中的参数会逐渐加大。

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


  2. #define const_voice_short  40   //蜂鸣器短叫的持续时间

  3. #define const_key_time1  20    //按键去抖动延时的时间
  4. #define const_key_time2  20    //按键去抖动延时的时间
  5. #define const_key_time3  20    //按键去抖动延时的时间
  6. #define const_key_time4  20    //按键去抖动延时的时间


  7. sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
  8. sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
  9. sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键
  10. sbit key_sr4=P0^3; //对应朱兆祺学习板的S13键

  11. sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平

  12. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口

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

  17. void SendByteToLcd(unsigned char ucData);  //发送一个字节数据到液晶模块
  18. void SPIWrite(unsigned char ucWData, unsigned char ucWRS); //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动
  19. void WriteCommand(unsigned char ucCommand); //发送一个字节的命令给液晶模块
  20. void LCDWriteData(unsigned char ucData);   //发送一个字节的数据给液晶模块
  21. void LCDInit(void);  //初始化  函数内部包括液晶模块的复位
  22. void display_clear(unsigned char ucFillDate); // 清屏 全部显示空填充0x00   全部显示点阵用0xff
  23. 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);//把字模插入画布.
  24. 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); //显示任意点阵函数
  25. unsigned char *number_to_matrix(unsigned char  ucBitNumber); //把一位数字转换成字模首地址的函数
  26. void delay_short(unsigned int uiDelayshort); //延时
  27. void delay_long(unsigned int uiDelayLong);

  28. void T0_time(); //定时中断函数
  29. void key_service(void); //按键服务的应用程序
  30. void key_scan(void);//按键扫描函数 放在定时中断里

  31. void initial_myself();   
  32. void initial_peripheral();


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

  35. code unsigned char Zf816_0[]=
  36. {
  37. /*--  文字:  0  --*/
  38. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  39. 0x00,0x00,0x00,0x18,0x24,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x24,0x18,0x00,0x00,
  40. };

  41. code unsigned char Zf816_1[]=
  42. {
  43. /*--  文字:  1  --*/
  44. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  45. 0x00,0x00,0x00,0x10,0x70,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x7C,0x00,0x00,
  46. };

  47. code unsigned char Zf816_2[]=
  48. {
  49. /*--  文字:  2  --*/
  50. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  51. 0x00,0x00,0x00,0x3C,0x42,0x42,0x42,0x04,0x04,0x08,0x10,0x20,0x42,0x7E,0x00,0x00,
  52. };

  53. code unsigned char Zf816_3[]=
  54. {
  55. /*--  文字:  3  --*/
  56. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  57. 0x00,0x00,0x00,0x3C,0x42,0x42,0x04,0x18,0x04,0x02,0x02,0x42,0x44,0x38,0x00,0x00,
  58. };

  59. code unsigned char Zf816_4[]=
  60. {
  61. /*--  文字:  4  --*/
  62. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  63. 0x00,0x00,0x00,0x04,0x0C,0x14,0x24,0x24,0x44,0x44,0x7E,0x04,0x04,0x1E,0x00,0x00,
  64. };

  65. code unsigned char Zf816_5[]=
  66. {
  67. /*--  文字:  5  --*/
  68. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  69. 0x00,0x00,0x00,0x7E,0x40,0x40,0x40,0x58,0x64,0x02,0x02,0x42,0x44,0x38,0x00,0x00,
  70. };

  71. code unsigned char Zf816_6[]=
  72. {
  73. /*--  文字:  6  --*/
  74. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  75. 0x00,0x00,0x00,0x1C,0x24,0x40,0x40,0x58,0x64,0x42,0x42,0x42,0x24,0x18,0x00,0x00,
  76. };


  77. code unsigned char Zf816_7[]=
  78. {
  79. /*--  文字:  7  --*/
  80. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  81. 0x00,0x00,0x00,0x7E,0x44,0x44,0x08,0x08,0x10,0x10,0x10,0x10,0x10,0x10,0x00,0x00,
  82. };

  83. code unsigned char Zf816_8[]=
  84. {
  85. /*--  文字:  8  --*/
  86. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  87. 0x00,0x00,0x00,0x3C,0x42,0x42,0x42,0x24,0x18,0x24,0x42,0x42,0x42,0x3C,0x00,0x00,
  88. };

  89. code unsigned char Zf816_9[]=
  90. {
  91. /*--  文字:  9  --*/
  92. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  93. 0x00,0x00,0x00,0x18,0x24,0x42,0x42,0x42,0x26,0x1A,0x02,0x02,0x24,0x38,0x00,0x00,
  94. };


  95. code unsigned char Zf816_nc[]=  //空字模
  96. {
  97. 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  98. };

  99. code unsigned char Zf816_mao_hao[]=  //冒号
  100. {
  101. /*--  文字:  :  --*/
  102. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  103. 0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,
  104. };

  105. code unsigned char Hz1616_yi[]=
  106. {
  107. /*--  文字:  一  --*/
  108. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  109. 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x7F,0xFE,
  110. 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  111. };

  112. code unsigned char Hz1616_er[]=
  113. {
  114. /*--  文字:  二  --*/
  115. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  116. 0x00,0x00,0x00,0x10,0x3F,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  117. 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x7F,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,
  118. };

  119. code unsigned char Hz1616_san[]=
  120. {
  121. /*--  文字:  三  --*/
  122. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  123. 0x00,0x00,0x00,0x00,0x7F,0xFC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3F,0xF8,
  124. 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7F,0xFE,0x00,0x00,0x00,0x00,
  125. };

  126. code unsigned char Hz1616_si[]=
  127. {
  128. /*--  文字:  四  --*/
  129. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  130. 0x00,0x00,0x7F,0xFC,0x44,0x84,0x44,0x84,0x44,0x84,0x44,0x84,0x44,0x84,0x44,0x84,
  131. 0x48,0x84,0x48,0x7C,0x50,0x04,0x60,0x04,0x40,0x04,0x7F,0xFC,0x40,0x04,0x00,0x00,
  132. };

  133. code unsigned char Hz1616_chuang[]=
  134. {
  135. /*--  文字:  窗  --*/
  136. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  137. 0x01,0x00,0x00,0x80,0x7F,0xFE,0x40,0x22,0x09,0x18,0x12,0x06,0x7F,0xF8,0x11,0x08,
  138. 0x13,0xE8,0x14,0x48,0x1A,0x88,0x11,0x08,0x12,0x88,0x14,0x08,0x1F,0xF8,0x10,0x08,
  139. };

  140. code unsigned char Hz1616_kou[]=
  141. {
  142. /*--  文字:  口  --*/
  143. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  144. 0x00,0x00,0x00,0x00,0x3F,0xF8,0x20,0x08,0x20,0x08,0x20,0x08,0x20,0x08,0x20,0x08,
  145. 0x20,0x08,0x20,0x08,0x20,0x08,0x3F,0xF8,0x20,0x08,0x20,0x08,0x00,0x00,0x00,0x00,
  146. };

  147. code unsigned char Hz1616_hang[]=
  148. {
  149. /*--  文字:  行  --*/
  150. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  151. 0x08,0x00,0x1C,0x00,0x31,0xFC,0x40,0x00,0x88,0x00,0x0C,0x00,0x1B,0xFE,0x30,0x20,
  152. 0x50,0x20,0x90,0x20,0x10,0x20,0x10,0x20,0x10,0x20,0x10,0x20,0x10,0xA0,0x10,0x40,
  153. };


  154. unsigned char ucCanvasBuffer[]= //画布显示数组。注意,这里没有code关键字,是全局变量。初始化全部填充0x00
  155. {
  156. 0x00,0x00,0x00,0x00,  //上半屏
  157. 0x00,0x00,0x00,0x00,
  158. 0x00,0x00,0x00,0x00,
  159. 0x00,0x00,0x00,0x00,
  160. 0x00,0x00,0x00,0x00,
  161. 0x00,0x00,0x00,0x00,
  162. 0x00,0x00,0x00,0x00,
  163. 0x00,0x00,0x00,0x00,

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

  165. 0x00,0x00,0x00,0x00,  //下半屏
  166. 0x00,0x00,0x00,0x00,
  167. 0x00,0x00,0x00,0x00,
  168. 0x00,0x00,0x00,0x00,
  169. 0x00,0x00,0x00,0x00,
  170. 0x00,0x00,0x00,0x00,
  171. 0x00,0x00,0x00,0x00,
  172. 0x00,0x00,0x00,0x00,
  173. };



  174. unsigned char ucKeySec=0;   //被触发的按键编号
  175. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器


  176. unsigned char ucWd=1; //窗口变量
  177. unsigned char ucPart=0; //局部变量 0代表没有选中任何一行,其它数值1到4代表选中某一行


  178. unsigned char ucWd1Update=1; //窗口1的整屏更新显示变量      1代表更新显示,响应函数内部会清零
  179. unsigned char ucWd1Part1Update=0; //窗口1的第1行局部更新显示变量  1代表更新显示,响应函数内部会清零
  180. unsigned char ucWd1Part2Update=0; //窗口1的第2行局部更新显示变量  1代表更新显示,响应函数内部会清零
  181. unsigned char ucWd1Part3Update=0; //窗口1的第3行局部更新显示变量  1代表更新显示,响应函数内部会清零
  182. unsigned char ucWd1Part4Update=0; //窗口1的第4行局部更新显示变量  1代表更新显示,响应函数内部会清零



  183. unsigned char ucData_1_1=8;  //第1个窗口第1行的被设置数据
  184. unsigned char ucData_1_2=9;  //第1个窗口第2行的被设置数据
  185. unsigned char ucData_1_3=10;  //第1个窗口第3行的被设置数据
  186. unsigned char ucData_1_4=11;  //第1个窗口第4行的被设置数据

  187. void main()
  188.   {
  189.         initial_myself();      //第一区,上电后马上初始化
  190.         delay_long(100);       //一线,延时线。延时一段时间
  191.         initial_peripheral();  //第二区,上电后延时一段时间再初始化

  192.         while(1)   //第三区
  193.         {
  194.                     key_service(); //按键服务的应用程序
  195.             lcd_display_service(); //应用层面的液晶屏显示程序
  196.         }

  197. }


  198. void initial_myself()  //第一区 上电后马上初始化
  199. {
  200. /* 注释一:
  201. * 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
  202. * 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
  203. * 坚鸿51学习板的S1和S5两个按键就是本程序中用到的两个独立按键。
  204. */
  205.    key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平
  206.    beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  207.    TMOD=0x01;  //设置定时器0为工作方式1

  208.    TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  209.    TL0=0x2f;
  210. }
  211. void initial_peripheral() //第二区 上电后延时一段时间再初始化
  212. {
  213.     LCDInit(); //初始化12864 内部包含液晶模块的复位


  214.     EA=1;     //开总中断
  215.     ET0=1;    //允许定时中断
  216.     TR0=1;    //启动定时中断

  217. }


  218. void T0_time() interrupt 1
  219. {
  220.   TF0=0;  //清除中断标志
  221.   TR0=0; //关中断

  222.   key_scan(); //按键扫描函数

  223.   if(uiVoiceCnt!=0)
  224.   {
  225.      uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  226.          beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  227.   }
  228.   else
  229.   {
  230.      ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  231.            beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  232.   }


  233.   TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  234.   TL0=0x2f;
  235.   TR0=1;  //开中断
  236. }



  237. void key_scan(void)//按键扫描函数 放在定时中断里
  238. {  


  239.   static unsigned int  uiKeyTimeCnt1=0; //按键去抖动延时计数器
  240.   static unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志

  241.   static unsigned int  uiKeyTimeCnt2=0; //按键去抖动延时计数器
  242.   static unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志

  243.   static unsigned int  uiKeyTimeCnt3=0; //按键去抖动延时计数器
  244.   static unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志

  245.   static unsigned int  uiKeyTimeCnt4=0; //按键去抖动延时计数器
  246.   static unsigned char ucKeyLock4=0; //按键触发后自锁的变量标志

  247.   if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  248.   {
  249.      ucKeyLock1=0; //按键自锁标志清零
  250.      uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  251.   }
  252.   else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  253.   {
  254.      uiKeyTimeCnt1++; //累加定时中断次数
  255.      if(uiKeyTimeCnt1>const_key_time1)
  256.      {
  257.         uiKeyTimeCnt1=0;
  258.         ucKeyLock1=1;  //自锁按键置位,避免一直触发
  259.         ucKeySec=1;    //触发1号键
  260.      }
  261.   }

  262.   if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  263.   {
  264.      ucKeyLock2=0; //按键自锁标志清零
  265.      uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  266.   }
  267.   else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
  268.   {
  269.      uiKeyTimeCnt2++; //累加定时中断次数
  270.      if(uiKeyTimeCnt2>const_key_time2)
  271.      {
  272.         uiKeyTimeCnt2=0;
  273.         ucKeyLock2=1;  //自锁按键置位,避免一直触发
  274.         ucKeySec=2;    //触发2号键
  275.      }
  276.   }

  277.   if(key_sr3==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  278.   {
  279.      ucKeyLock3=0; //按键自锁标志清零
  280.      uiKeyTimeCnt3=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  281.   }
  282.   else if(ucKeyLock3==0)//有按键按下,且是第一次被按下
  283.   {
  284.      uiKeyTimeCnt3++; //累加定时中断次数
  285.      if(uiKeyTimeCnt3>const_key_time3)
  286.      {
  287.         uiKeyTimeCnt3=0;
  288.         ucKeyLock3=1;  //自锁按键置位,避免一直触发
  289.         ucKeySec=3;    //触发3号键
  290.      }
  291.   }

  292.   if(key_sr4==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  293.   {
  294.      ucKeyLock4=0; //按键自锁标志清零
  295.      uiKeyTimeCnt4=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  296.   }
  297.   else if(ucKeyLock4==0)//有按键按下,且是第一次被按下
  298.   {
  299.      uiKeyTimeCnt4++; //累加定时中断次数
  300.      if(uiKeyTimeCnt4>const_key_time4)
  301.      {
  302.         uiKeyTimeCnt4=0;
  303.         ucKeyLock4=1;  //自锁按键置位,避免一直触发
  304.         ucKeySec=4;    //触发4号键
  305.      }
  306.   }

  307. }


  308. void key_service(void) //按键服务的应用程序
  309. {
  310.   switch(ucKeySec) //按键服务状态切换
  311.   {
  312.     case 1:// 加按键 对应朱兆祺学习板的S1键
  313.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  314.           {
  315.               case 1:
  316.                    switch(ucPart)  //在窗口1下,根据不同的局部变量来设置不同的参数
  317.                    {
  318.                           case 0:   //无光标显示的状态 此处的case 0可以省略

  319.                                 break;
  320.                           case 1:   //设置第1行参数
  321.                                 ucData_1_1++;
  322.                                                                 if(ucData_1_1>99)
  323.                                                                 {
  324.                                                                    ucData_1_1=99;
  325.                                                                 }
  326.                                 ucWd1Part1Update=1; //1代表更新显示,响应函数内部会清零
  327.                                 break;
  328.                           case 2:   //设置第2行参数
  329.                                 ucData_1_2++;
  330.                                                                 if(ucData_1_2>99)
  331.                                                                 {
  332.                                                                    ucData_1_2=99;
  333.                                                                 }
  334.                                 ucWd1Part2Update=1; //1代表更新显示,响应函数内部会清零
  335.                                 break;
  336.                           case 3:   //设置第3行参数
  337.                                 ucData_1_3++;
  338.                                                                 if(ucData_1_3>99)
  339.                                                                 {
  340.                                                                    ucData_1_3=99;
  341.                                                                 }
  342.                                 ucWd1Part3Update=1; //1代表更新显示,响应函数内部会清零
  343.                                 break;
  344.                           case 4:   //设置第4行参数
  345.                                 ucData_1_4++;
  346.                                                                 if(ucData_1_4>99)
  347.                                                                 {
  348.                                                                    ucData_1_4=99;
  349.                                                                 }
  350.                                 ucWd1Part4Update=1; //1代表更新显示,响应函数内部会清零
  351.                                 break;


  352.                    }
  353.                    break;
  354.          
  355.           }     
  356.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  357.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  358.           break;   
  359.    
  360.     case 2:// 减按键 对应朱兆祺学习板的S5键
  361.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  362.           {
  363.               case 1:
  364.                    switch(ucPart)  //在窗口1下,根据不同的局部变量来设置不同的参数
  365.                    {
  366.                           case 0:   //无光标显示的状态 此处的case 0可以省略

  367.                                 break;
  368.                           case 1:   //设置第1行参数
  369.                                 ucData_1_1--;
  370.                                                                 if(ucData_1_1>99) //一直减到最后,单片机C语言编译器有一个特征,0减去1会溢出变成255(0xff)
  371.                                                                 {
  372.                                                                    ucData_1_1=0;
  373.                                                                 }
  374.                                 ucWd1Part1Update=1; //1代表更新显示,响应函数内部会清零
  375.                                 break;
  376.                           case 2:   //设置第2行参数
  377.                                 ucData_1_2--;
  378.                                                                 if(ucData_1_2>99) //一直减到最后,单片机C语言编译器有一个特征,0减去1会溢出变成255(0xff)
  379.                                                                 {
  380.                                                                    ucData_1_2=0;
  381.                                                                 }
  382.                                 ucWd1Part2Update=1; //1代表更新显示,响应函数内部会清零
  383.                                 break;
  384.                           case 3:   //设置第3行参数
  385.                                 ucData_1_3--;
  386.                                                                 if(ucData_1_3>99) //一直减到最后,单片机C语言编译器有一个特征,0减去1会溢出变成255(0xff)
  387.                                                                 {
  388.                                                                    ucData_1_3=0;
  389.                                                                 }
  390.                                 ucWd1Part3Update=1; //1代表更新显示,响应函数内部会清零
  391.                                 break;
  392.                           case 4:   //设置第4行参数
  393.                                 ucData_1_4--;
  394.                                                                 if(ucData_1_4>99) //一直减到最后,单片机C语言编译器有一个特征,0减去1会溢出变成255(0xff)
  395.                                                                 {
  396.                                                                    ucData_1_4=0;
  397.                                                                 }
  398.                                 ucWd1Part4Update=1; //1代表更新显示,响应函数内部会清零
  399.                                 break;


  400.                    }
  401.                    break;
  402.          
  403.           }     
  404.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  405.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  406.           break;  

  407.     case 3:// 切换"光标"移动按键 对应朱兆祺学习板的S9键
  408.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  409.           {
  410.               case 1:
  411.                    switch(ucPart)  //在窗口1下,根据不同的局部变量来设置不同的参数
  412.                    {
  413.                           case 0:   //无光标显示的状态 此处的case 0可以省略

  414.                                 break;
  415.                           case 1:   //设置第1行参数
  416.                                 ucPart=2; //光标切换到下一行
  417.                                 ucWd1Part1Update=1; //更新显示原来那一行,目的是更新反显光标的状态
  418.                                 ucWd1Part2Update=1; //更新显示下一行,    目的是更新反显光标的状态
  419.                                 break;
  420.                           case 2:   //设置第2行参数
  421.                                 ucPart=3; //光标切换到下一行
  422.                                 ucWd1Part2Update=1; //更新显示原来那一行,目的是更新反显光标的状态
  423.                                 ucWd1Part3Update=1; //更新显示下一行,    目的是更新反显光标的状态
  424.                                 break;
  425.                           case 3:   //设置第3行参数
  426.                                 ucPart=4; //光标切换到下一行
  427.                                 ucWd1Part3Update=1; //更新显示原来那一行,目的是更新反显光标的状态
  428.                                 ucWd1Part4Update=1; //更新显示下一行,    目的是更新反显光标的状态
  429.                                 break;
  430.                           case 4:   //设置第4行参数
  431.                                 ucPart=1; //光标返回到最上面第一行
  432.                                 ucWd1Part4Update=1; //更新显示原来那一行,目的是更新反显光标的状态
  433.                                 ucWd1Part1Update=1; //更新显示最上面第一行,    目的是更新反显光标的状态
  434.                                 break;


  435.                    }
  436.                    break;
  437.          
  438.           }         
  439.         
  440.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  441.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  442.           break;      
  443.    
  444.     case 4: // 设置按键  对应朱兆祺学习板的S13键,按一次进入设置状态,出现反显光标。再按一次推出设置状态,消除反显光标
  445.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  446.           {
  447.               case 1:
  448.                    switch(ucPart)  //在窗口1下,根据不同的局部变量来设置不同的参数
  449.                    {

  450.                           case 0:   //无光标显示的状态
  451.                                 ucPart=1; //光标显示第一行,进入设置模式
  452.                                 ucWd1Part1Update=1; //更新显示
  453.                                 break;
  454.                           case 1:   //设置第1行参数
  455.                                 ucPart=0; //无光标显示,退出设置模式
  456.                                 ucWd1Part1Update=1; //更新显示
  457.                                 break;
  458.                           case 2:   //设置第2行参数
  459.                                 ucPart=0; //无光标显示,退出设置模式
  460.                                 ucWd1Part2Update=1; //更新显示
  461.                                 break;
  462.                           case 3:   //设置第3行参数
  463.                                 ucPart=0; //无光标显示,退出设置模式
  464.                                 ucWd1Part3Update=1; //更新显示
  465.                                 break;
  466.                           case 4:   //设置第4行参数
  467.                                 ucPart=0; //无光标显示,退出设置模式
  468.                                 ucWd1Part4Update=1; //更新显示
  469.                                 break;


  470.                    }
  471.                    break;
  472.          
  473.           }   

  474.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  475.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  476.           break;         

  477.   }               
  478. }


  479. unsigned char *number_to_matrix(unsigned char  ucBitNumber)
  480. {
  481.     unsigned char *p_ucAnyNumber;  //此指针根据ucBitNumber数值的大小,分别调用不同的字库。

  482.         switch(ucBitNumber)  //根据ucBitNumber数值的大小,分别调用不同的字库。
  483.         {
  484.             case 0:
  485.              p_ucAnyNumber=Zf816_0;
  486.                      break;
  487.             case 1:
  488.              p_ucAnyNumber=Zf816_1;
  489.                      break;
  490.             case 2:
  491.              p_ucAnyNumber=Zf816_2;
  492.                      break;
  493.             case 3:
  494.              p_ucAnyNumber=Zf816_3;
  495.                      break;
  496.             case 4:
  497.              p_ucAnyNumber=Zf816_4;
  498.                      break;
  499.             case 5:
  500.              p_ucAnyNumber=Zf816_5;
  501.                      break;
  502.             case 6:
  503.              p_ucAnyNumber=Zf816_6;
  504.                      break;
  505.             case 7:
  506.              p_ucAnyNumber=Zf816_7;
  507.                      break;
  508.             case 8:
  509.              p_ucAnyNumber=Zf816_8;
  510.                      break;
  511.             case 9:
  512.              p_ucAnyNumber=Zf816_9;
  513.                      break;
  514.             case 10:
  515.              p_ucAnyNumber=Zf816_nc;
  516.                      break;
  517.                 default:   //如果上面的条件都不符合,那么默认指向空字模
  518.              p_ucAnyNumber=Zf816_nc;
  519.                      break;
  520.         }

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



  523. void lcd_display_service(void) //应用层面的液晶屏显示程序
  524. {
  525.     unsigned char ucAnyNumber_1; //分解变量的个位
  526.     unsigned char ucAnyNumber_10; //分解变量的十位


  527.     unsigned char *p_ucAnyNumber_1; //经过数字转换成字模后,分解变量的个位字模首地址
  528.     unsigned char *p_ucAnyNumber_10; //经过数字转换成字模后,分解变量的十位字模首地址

  529.         unsigned char ucCursorFlag;  //光标标志,也就是反显的标志,它是根据局部变量ucPart来定的

  530.     switch(ucWd)  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
  531.     {
  532.         case 1:   //显示窗口1的数据

  533. /* 注释二:
  534. * 把每一个窗口的内容分为两种类型,一种类型是那些不用经常刷新显示的内容,只有在切换窗口的时候
  535. * 才需要更新,这种内容放在整屏更新显示的括号里,比如清屏操作等内容。另外一种是那些经常需要
  536. * 刷新显示的内容,这种内容放在局部更新显示的括号里。
  537. */
  538.                       if(ucWd1Update==1)  //窗口1整屏更新,里面只放那些不用经常刷新显示的内容
  539.                           {
  540.                              ucWd1Update=0;  //及时清零,避免一直更新

  541.                  ucWd1Part1Update=1; //激活窗口1的第1行局部更新显示变量,这里在前面数码管显示框架上有所改进
  542.                  ucWd1Part2Update=1; //激活窗口1的第2行局部更新显示变量,这里在前面数码管显示框架上有所改进
  543.                  ucWd1Part3Update=1; //激活窗口1的第3行局部更新显示变量,这里在前面数码管显示框架上有所改进
  544.                  ucWd1Part4Update=1; //激活窗口1的第4行局部更新显示变量,这里在前面数码管显示框架上有所改进

  545.                  display_clear(0x00); // 清屏操作, 全部显示空填充0x00,全部显示点阵用0xff。
  546.                  clear_all_canvas();  //把画布全部清零
  547.                  insert_buffer_to_canvas(0,0,Zf816_mao_hao,0,1,16);//把冒号的字模插入画布

  548.                  display_lattice(0,0,Hz1616_yi,0,2,16,0);    //一窗口一行,这些内容不用经常更新,只有在切换窗口的时候才更新显示
  549.                  display_lattice(1,0,Hz1616_chuang,0,2,16,0);   
  550.                  display_lattice(2,0,Hz1616_kou,0,2,16,0);   
  551.                  display_lattice(3,0,Hz1616_yi,0,2,16,0);
  552.                  display_lattice(4,0,Hz1616_hang,0,2,16,0);

  553.                  display_lattice(0,16,Hz1616_yi,0,2,16,0);    //一窗口二行
  554.                  display_lattice(1,16,Hz1616_chuang,0,2,16,0);   
  555.                  display_lattice(2,16,Hz1616_kou,0,2,16,0);   
  556.                  display_lattice(3,16,Hz1616_er,0,2,16,0);
  557.                  display_lattice(4,16,Hz1616_hang,0,2,16,0);

  558.                  display_lattice(8,0,Hz1616_yi,0,2,16,0);    //一窗口三行
  559.                  display_lattice(9,0,Hz1616_chuang,0,2,16,0);   
  560.                  display_lattice(10,0,Hz1616_kou,0,2,16,0);   
  561.                  display_lattice(11,0,Hz1616_san,0,2,16,0);
  562.                  display_lattice(12,0,Hz1616_hang,0,2,16,0);

  563.                  display_lattice(8,16,Hz1616_yi,0,2,16,0);    //一窗口四行
  564.                  display_lattice(9,16,Hz1616_chuang,0,2,16,0);   
  565.                  display_lattice(10,16,Hz1616_kou,0,2,16,0);   
  566.                  display_lattice(11,16,Hz1616_si,0,2,16,0);
  567.                  display_lattice(12,16,Hz1616_hang,0,2,16,0);

  568.                           }

  569. /* 注释三:
  570. * 注意!我前面讲数码管显示的时候有一句话讲错了,我那时说<局部更新应该写在整屏更新之前>,这是不对的。
  571. * 按照现在的显示程序框架<即整屏显示更新括号里包含了所有局部变量的激活>,应该是<整屏更新应该写在局部更新之前>
  572. * 这样才对。
  573. */
  574.                           if(ucWd1Part1Update==1) //窗口1的第1行局部更新显示变量,里面放一些经常需要刷新显示的内容
  575.                           {
  576.                              ucWd1Part1Update=0; //及时清零,避免一直更新

  577.                      if(ucPart==1) //被选中
  578.                                  {
  579.                                     ucCursorFlag=1; //反显 显示
  580.                                  }
  581.                      else //没被选中
  582.                                  {
  583.                                     ucCursorFlag=0; //正常 显示
  584.                                  }

  585.                  if(ucData_1_1>=10) //有2位数以上
  586.                  {
  587.                     ucAnyNumber_10=ucData_1_1/10;  //十位
  588.                  }
  589.                  else //否则显示空
  590.                  {
  591.                     ucAnyNumber_10=10;  //在下面的转换函数中,代码10表示空字模
  592.                  }

  593.                  ucAnyNumber_1=ucData_1_1%10/1;  //个位

  594.    
  595.                  p_ucAnyNumber_10=number_to_matrix(ucAnyNumber_10); //把数字转换成字模首地址
  596.                  p_ucAnyNumber_1=number_to_matrix(ucAnyNumber_1); //把数字转换成字模首地址


  597.                  insert_buffer_to_canvas(2,0,p_ucAnyNumber_10,ucCursorFlag,1,16);//把十的字模插入画布
  598.                  insert_buffer_to_canvas(3,0,p_ucAnyNumber_1,ucCursorFlag,1,16);//把个的字模插入画布

  599.                  display_lattice(5,0,ucCanvasBuffer,0,4,16,0);   //显示整屏的画布,最后的参数0是偏移量

  600.                           
  601.                           }

  602.                           if(ucWd1Part2Update==1) //窗口1的第2行局部更新显示变量,里面放一些经常需要刷新显示的内容
  603.                           {
  604.                              ucWd1Part2Update=0; //及时清零,避免一直更新

  605.                      if(ucPart==2) //被选中
  606.                                  {
  607.                                     ucCursorFlag=1; //反显 显示
  608.                                  }
  609.                      else //没被选中
  610.                                  {
  611.                                     ucCursorFlag=0; //正常 显示
  612.                                  }

  613.                  if(ucData_1_2>=10) //有2位数以上
  614.                  {
  615.                     ucAnyNumber_10=ucData_1_2/10;  //十位
  616.                  }
  617.                  else //否则显示空
  618.                  {
  619.                     ucAnyNumber_10=10;  //在下面的转换函数中,代码10表示空字模
  620.                  }

  621.                  ucAnyNumber_1=ucData_1_2%10/1;  //个位

  622.    
  623.                  p_ucAnyNumber_10=number_to_matrix(ucAnyNumber_10); //把数字转换成字模首地址
  624.                  p_ucAnyNumber_1=number_to_matrix(ucAnyNumber_1); //把数字转换成字模首地址


  625.                  insert_buffer_to_canvas(2,0,p_ucAnyNumber_10,ucCursorFlag,1,16);//把十的字模插入画布
  626.                  insert_buffer_to_canvas(3,0,p_ucAnyNumber_1,ucCursorFlag,1,16);//把个的字模插入画布

  627.                  display_lattice(5,16,ucCanvasBuffer,0,4,16,0);   //显示整屏的画布,最后的参数0是偏移量
  628.                           
  629.                           }

  630.                           if(ucWd1Part3Update==1) //窗口1的第3行局部更新显示变量,里面放一些经常需要刷新显示的内容
  631.                           {
  632.                              ucWd1Part3Update=0; //及时清零,避免一直更新

  633.                      if(ucPart==3) //被选中
  634.                                  {
  635.                                     ucCursorFlag=1; //反显 显示
  636.                                  }
  637.                      else //没被选中
  638.                                  {
  639.                                     ucCursorFlag=0; //正常 显示
  640.                                  }

  641.                  if(ucData_1_3>=10) //有2位数以上
  642.                  {
  643.                     ucAnyNumber_10=ucData_1_3/10;  //十位
  644.                  }
  645.                  else //否则显示空
  646.                  {
  647.                     ucAnyNumber_10=10;  //在下面的转换函数中,代码10表示空字模
  648.                  }

  649.                  ucAnyNumber_1=ucData_1_3%10/1;  //个位

  650.    
  651.                  p_ucAnyNumber_10=number_to_matrix(ucAnyNumber_10); //把数字转换成字模首地址
  652.                  p_ucAnyNumber_1=number_to_matrix(ucAnyNumber_1); //把数字转换成字模首地址


  653.                  insert_buffer_to_canvas(2,0,p_ucAnyNumber_10,ucCursorFlag,1,16);//把十的字模插入画布
  654.                  insert_buffer_to_canvas(3,0,p_ucAnyNumber_1,ucCursorFlag,1,16);//把个的字模插入画布

  655.                  display_lattice(13,0,ucCanvasBuffer,0,4,16,0);   //显示整屏的画布,最后的参数0是偏移量
  656.                           
  657.                           }

  658.                           if(ucWd1Part4Update==1) //窗口1的第4行局部更新显示变量,里面放一些经常需要刷新显示的内容
  659.                           {
  660.                              ucWd1Part4Update=0; //及时清零,避免一直更新

  661.                      if(ucPart==4) //被选中
  662.                                  {
  663.                                     ucCursorFlag=1; //反显 显示
  664.                                  }
  665.                      else //没被选中
  666.                                  {
  667.                                     ucCursorFlag=0; //正常 显示
  668.                                  }

  669.                  if(ucData_1_4>=10) //有2位数以上
  670.                  {
  671.                     ucAnyNumber_10=ucData_1_4/10;  //十位
  672.                  }
  673.                  else //否则显示空
  674.                  {
  675.                     ucAnyNumber_10=10;  //在下面的转换函数中,代码10表示空字模
  676.                  }

  677.                  ucAnyNumber_1=ucData_1_4%10/1;  //个位

  678.    
  679.                  p_ucAnyNumber_10=number_to_matrix(ucAnyNumber_10); //把数字转换成字模首地址
  680.                  p_ucAnyNumber_1=number_to_matrix(ucAnyNumber_1); //把数字转换成字模首地址


  681.                  insert_buffer_to_canvas(2,0,p_ucAnyNumber_10,ucCursorFlag,1,16);//把十的字模插入画布
  682.                  insert_buffer_to_canvas(3,0,p_ucAnyNumber_1,ucCursorFlag,1,16);//把个的字模插入画布

  683.                  display_lattice(13,16,ucCanvasBuffer,0,4,16,0);   //显示整屏的画布,最后的参数0是偏移量                          
  684.                           }

  685.               break;
  686.         //本程序只有1个窗口,所以只有一个case 1,如果要增加窗口,就直接增加 case 2, case 3...        
  687.     }


  688. }



  689. void clear_all_canvas(void)  //把画布全部清零
  690. {
  691.    unsigned int j=0;
  692.    unsigned int i=0;

  693.    for(j=0;j<16;j++)  //这里的16表示画布有16行
  694.    {
  695.       for(i=0;i<4;i++) //这里的4表示画布每行有4个字节
  696.       {
  697.                   ucCanvasBuffer[j*4+i]=0x00;
  698.       }
  699.    }         

  700. }





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

  703.     unsigned char x,y;
  704.     WriteCommand(0x34);  //关显示缓冲指令            
  705.     WriteCommand(0x34);  //关显示缓冲指令  故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的
  706.     y=0;
  707.     while(y<32)  //y轴的范围0至31
  708.     {
  709.          WriteCommand(y+0x80);        //垂直地址
  710.          WriteCommand(0x80);          //水平地址
  711.          for(x=0;x<32;x++)  //256个横向点,有32个字节
  712.          {  
  713.             LCDWriteData(ucFillDate);
  714.          }
  715.          y++;
  716.     }
  717.     WriteCommand(0x36); //开显示缓冲指令

  718. }

  719. /* 注释四:
  720. * 把字模插入画布的函数.
  721. * 这是本节的核心函数,读者尤其要搞懂x_amount和y_amount对应的显示关系。
  722. * 第1,2个参数x,y是在画布中的坐标体系。
  723. * x的范围是0至3,因为画布的横向只要4个字节。y的范围是0至15,因为画布的纵向只有16行。
  724. * 第3个参数*ucArray是字模的数组。
  725. * 第4个参数ucFbFlag是反白显示标志。0代表正常显示,1代表反白显示。
  726. * 第5,6个参数x_amount,y_amount分别代表字模数组的横向有多少个字节,纵向有几横。
  727. */
  728. 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)
  729. {
  730.    unsigned int j=0;
  731.    unsigned int i=0;
  732.    unsigned char ucTemp;
  733.    for(j=0;j<y_amount;j++)
  734.    {
  735.       for(i=0;i<x_amount;i++)
  736.       {
  737.                    ucTemp=ucArray[j*x_amount+i];
  738.                    if(ucFbFlag==0)
  739.                    {
  740.               ucCanvasBuffer[(y+j)*4+x+i]=ucTemp; //这里的4代表画布每一行只有4个字节
  741.                    }
  742.                    else
  743.                    {
  744.               ucCanvasBuffer[(y+j)*4+x+i]=~ucTemp; //这里的4代表画布每一行只有4个字节
  745.                    }
  746.       }
  747.    }         

  748. }

  749. /* 注释五:
  750. * 显示任意点阵函数.
  751. * 注意,本函数在前几节的基础上多增加了第7个参数uiOffSetAddr,它是偏移地址。
  752. * 对于这个函数,读者尤其要搞懂x_amount和y_amount对应的显示关系。
  753. * 第1,2个参数x,y是坐标体系。x的范围是0至15,y的范围是0至31.
  754. * 第3个参数*ucArray是字模的数组。
  755. * 第4个参数ucFbFlag是反白显示标志。0代表正常显示,1代表反白显示。
  756. * 第5,6个参数x_amount,y_amount分别代表字模数组的横向有多少个字节,纵向有几横。
  757. * 第7个参数uiOffSetAddr是偏移地址,代表字模数组的从第几个数据开始显示。
  758. */
  759. 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)
  760. {
  761.    unsigned int j=0;
  762.    unsigned int i=0;
  763.    unsigned char ucTemp;

  764. //注意,要把以下两行指令屏蔽,否则屏幕在更新显示时会整屏闪动
  765. //  WriteCommand(0x34);  //关显示缓冲指令            
  766. //  WriteCommand(0x34);  //关显示缓冲指令  故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的
  767.    for(j=0;j<y_amount;j++) //y_amount代表y轴有多少横
  768.    {
  769.        WriteCommand(y+j+0x80);        //垂直地址
  770.        WriteCommand(x+0x80);          //水平地址
  771.        for(i=0;i<x_amount;i++) //x_amount代表x轴有多少列
  772.        {
  773.            ucTemp=ucArray[j*x_amount+i+uiOffSetAddr]; //uiOffSetAddr是字模数组的偏移地址
  774.            if(ucFbFlag==1)  //反白显示
  775.            {
  776.                ucTemp=~ucTemp;
  777.            }
  778.            LCDWriteData(ucTemp);
  779.           //         delay_short(30000);  //把上一节这个延时函数去掉,加快刷屏速度
  780.       }
  781.    }
  782.    WriteCommand(0x36); //开显示缓冲指令
  783. }




  784. void SendByteToLcd(unsigned char ucData)  //发送一个字节数据到液晶模块
  785. {
  786.         unsigned char i;
  787.         for ( i = 0; i < 8; i++ )
  788.         {
  789.                 if ( (ucData << i) & 0x80 )
  790.                 {
  791.                         LCDSID_dr = 1;
  792.                 }
  793.                 else
  794.                 {
  795.                         LCDSID_dr = 0;
  796.                 }
  797.                 LCDCLK_dr = 0;
  798.                 LCDCLK_dr = 1;
  799.         }
  800. }

  801. void SPIWrite(unsigned char ucWData, unsigned char ucWRS) //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动
  802. {
  803.         SendByteToLcd( 0xf8 + (ucWRS << 1) );
  804.         SendByteToLcd( ucWData & 0xf0 );
  805.         SendByteToLcd( (ucWData << 4) & 0xf0);
  806. }


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

  809.         LCDCS_dr = 0;
  810.         LCDCS_dr = 1;
  811.         SPIWrite(ucCommand, 0);
  812.         delay_short(90);
  813. }

  814. void LCDWriteData(unsigned char ucData)  //发送一个字节的数据给液晶模块
  815. {
  816.         LCDCS_dr = 0;
  817.         LCDCS_dr = 1;
  818.         SPIWrite(ucData, 1);
  819. }

  820. void LCDInit(void) //初始化  函数内部包括液晶模块的复位
  821. {
  822.         LCDRST_dr = 1;  //复位
  823.         LCDRST_dr = 0;
  824.         LCDRST_dr = 1;
  825. }



  826. void delay_short(unsigned int uiDelayShort) //延时函数
  827. {
  828.    unsigned int i;  
  829.    for(i=0;i<uiDelayShort;i++)
  830.    {
  831.      ;  
  832.    }
  833. }


  834. void delay_long(unsigned int uiDelayLong)
  835. {
  836.    unsigned int i;
  837.    unsigned int j;
  838.    for(i=0;i<uiDelayLong;i++)
  839.    {
  840.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  841.           {
  842.              ; //一个分号相当于执行一条空语句
  843.           }
  844.    }
  845. }
复制代码

总结陈词:
这一节讲了在一个窗口里设置不同的参数,如果有几个窗口的情况下,该如何编程?欲知详情,请听下回分解-----在多个窗口里通过移动光标来设置不同参数的液晶屏菜单程序。

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

乐于分享,勇于质疑!
99#
 楼主| 发表于 2014-10-28 14:33:31 | 显示全部楼层
本帖最后由 jianhong_wu 于 2014-10-28 17:32 编辑

第七十八节:在多个窗口里通过移动光标来设置不同参数的液晶屏菜单程序。

开场白:
上一节讲了1个窗口下如何设置参数的菜单程序,这一节多增加1个窗口变成2个窗口,看看它们两个窗口之间是如何通过按键程序进行切换的。继续巩固上一节教给大家的两个知识点:
   第一个知识点:我在前面讲数码管显示的时候就提出了一个 “一二级菜单显示理论”:凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。把每一个窗口的内容分为两种类型,一种类型是那些不用经常刷新显示的内容,只有在切换窗口的时候才需要更新的,这种内容放在整屏更新显示的括号里,比如清屏操作等内容。另外一种是那些经常需要刷新显示的内容,这种内容放在局部更新显示的括号里。
    第二个知识点:按键如何跟液晶屏显示有机的结合起来?只要遵循鸿哥总结出来的一个规律“在不同的窗口下,根据不同的局部变量来操作不同的参数”,这样再复杂的人机交互程序都会显得很简单清晰。

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

(1)硬件平台:基于坚鸿51单片机学习板。加按键对应S1键,减按键对应S5键,切换“光标”移动按键对应S9键,设置参数按键对应S13键。

(2)实现功能:
     通过按键设置8个不同的参数。
    有2个窗口。每个窗口显示4个参数。每个参数的范围是从0到99。
    有4个按键:
(a)          一个是设置参数S13按键,按下此按键,液晶屏的第1个窗口第一行会出现反显的光标,表示进入设置参数模式,再次按下此按键,反显光标会消失,并且强行切换到第1个窗口,表示退出设置参数模式。
(b)         一个是移动光标S9按键,在进入设置参数的模式下,依次按下此按键,液晶屏上的光标会从上往下移动,表示选中不同的参数。当移动到每个窗口最下边那一行时,再按下此按键会进行切换窗口的操作。
(c)          一个是减数S5按键,在设置参数模式下,依次按下此按键,被选中的参数会逐渐减小。
(d)         一个是加数S1按键,在设置参数模式下,依次按下此按键,被选中的参数会逐渐加大。
3)源代码讲解如下:
  1. #include "REG52.H"

  2. /* 注释一:
  3. * 本程序用到的变量比较多,所以在keil编译模式里要设置一下编译模式memory model,
  4. * 否则编译会出错.右键单击Target选择“Options for Target'Target1'”就会出来一个框
  5. * 在memory model中选择compact:variables in pdata 就可以了。
  6. */

  7. #define const_voice_short  40   //蜂鸣器短叫的持续时间

  8. #define const_key_time1  20    //按键去抖动延时的时间
  9. #define const_key_time2  20    //按键去抖动延时的时间
  10. #define const_key_time3  20    //按键去抖动延时的时间
  11. #define const_key_time4  20    //按键去抖动延时的时间


  12. sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
  13. sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
  14. sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键
  15. sbit key_sr4=P0^3; //对应朱兆祺学习板的S13键

  16. sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平

  17. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口

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

  22. void SendByteToLcd(unsigned char ucData);  //发送一个字节数据到液晶模块
  23. void SPIWrite(unsigned char ucWData, unsigned char ucWRS); //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动
  24. void WriteCommand(unsigned char ucCommand); //发送一个字节的命令给液晶模块
  25. void LCDWriteData(unsigned char ucData);   //发送一个字节的数据给液晶模块
  26. void LCDInit(void);  //初始化  函数内部包括液晶模块的复位
  27. void display_clear(unsigned char ucFillDate); // 清屏 全部显示空填充0x00   全部显示点阵用0xff
  28. 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);//把字模插入画布.
  29. 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); //显示任意点阵函数
  30. unsigned char *number_to_matrix(unsigned char  ucBitNumber); //把一位数字转换成字模首地址的函数
  31. void delay_short(unsigned int uiDelayshort); //延时
  32. void delay_long(unsigned int uiDelayLong);

  33. void T0_time(); //定时中断函数
  34. void key_service(void); //按键服务的应用程序
  35. void key_scan(void);//按键扫描函数 放在定时中断里

  36. void initial_myself();   
  37. void initial_peripheral();


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

  40. void wd1(void);//窗口1显示的内容
  41. void wd2(void);//窗口2显示的内容

  42. code unsigned char Zf816_0[]=
  43. {
  44. /*--  文字:  0  --*/
  45. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  46. 0x00,0x00,0x00,0x18,0x24,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x24,0x18,0x00,0x00,
  47. };

  48. code unsigned char Zf816_1[]=
  49. {
  50. /*--  文字:  1  --*/
  51. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  52. 0x00,0x00,0x00,0x10,0x70,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x7C,0x00,0x00,
  53. };

  54. code unsigned char Zf816_2[]=
  55. {
  56. /*--  文字:  2  --*/
  57. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  58. 0x00,0x00,0x00,0x3C,0x42,0x42,0x42,0x04,0x04,0x08,0x10,0x20,0x42,0x7E,0x00,0x00,
  59. };

  60. code unsigned char Zf816_3[]=
  61. {
  62. /*--  文字:  3  --*/
  63. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  64. 0x00,0x00,0x00,0x3C,0x42,0x42,0x04,0x18,0x04,0x02,0x02,0x42,0x44,0x38,0x00,0x00,
  65. };

  66. code unsigned char Zf816_4[]=
  67. {
  68. /*--  文字:  4  --*/
  69. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  70. 0x00,0x00,0x00,0x04,0x0C,0x14,0x24,0x24,0x44,0x44,0x7E,0x04,0x04,0x1E,0x00,0x00,
  71. };

  72. code unsigned char Zf816_5[]=
  73. {
  74. /*--  文字:  5  --*/
  75. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  76. 0x00,0x00,0x00,0x7E,0x40,0x40,0x40,0x58,0x64,0x02,0x02,0x42,0x44,0x38,0x00,0x00,
  77. };

  78. code unsigned char Zf816_6[]=
  79. {
  80. /*--  文字:  6  --*/
  81. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  82. 0x00,0x00,0x00,0x1C,0x24,0x40,0x40,0x58,0x64,0x42,0x42,0x42,0x24,0x18,0x00,0x00,
  83. };


  84. code unsigned char Zf816_7[]=
  85. {
  86. /*--  文字:  7  --*/
  87. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  88. 0x00,0x00,0x00,0x7E,0x44,0x44,0x08,0x08,0x10,0x10,0x10,0x10,0x10,0x10,0x00,0x00,
  89. };

  90. code unsigned char Zf816_8[]=
  91. {
  92. /*--  文字:  8  --*/
  93. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  94. 0x00,0x00,0x00,0x3C,0x42,0x42,0x42,0x24,0x18,0x24,0x42,0x42,0x42,0x3C,0x00,0x00,
  95. };

  96. code unsigned char Zf816_9[]=
  97. {
  98. /*--  文字:  9  --*/
  99. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  100. 0x00,0x00,0x00,0x18,0x24,0x42,0x42,0x42,0x26,0x1A,0x02,0x02,0x24,0x38,0x00,0x00,
  101. };


  102. code unsigned char Zf816_nc[]=  //空字模
  103. {
  104. 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  105. };

  106. code unsigned char Zf816_mao_hao[]=  //冒号
  107. {
  108. /*--  文字:  :  --*/
  109. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  110. 0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,
  111. };

  112. code unsigned char Hz1616_yi[]=
  113. {
  114. /*--  文字:  一  --*/
  115. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  116. 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x7F,0xFE,
  117. 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  118. };

  119. code unsigned char Hz1616_er[]=
  120. {
  121. /*--  文字:  二  --*/
  122. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  123. 0x00,0x00,0x00,0x10,0x3F,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  124. 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x7F,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,
  125. };

  126. code unsigned char Hz1616_san[]=
  127. {
  128. /*--  文字:  三  --*/
  129. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  130. 0x00,0x00,0x00,0x00,0x7F,0xFC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3F,0xF8,
  131. 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7F,0xFE,0x00,0x00,0x00,0x00,
  132. };

  133. code unsigned char Hz1616_si[]=
  134. {
  135. /*--  文字:  四  --*/
  136. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  137. 0x00,0x00,0x7F,0xFC,0x44,0x84,0x44,0x84,0x44,0x84,0x44,0x84,0x44,0x84,0x44,0x84,
  138. 0x48,0x84,0x48,0x7C,0x50,0x04,0x60,0x04,0x40,0x04,0x7F,0xFC,0x40,0x04,0x00,0x00,
  139. };

  140. code unsigned char Hz1616_chuang[]=
  141. {
  142. /*--  文字:  窗  --*/
  143. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  144. 0x01,0x00,0x00,0x80,0x7F,0xFE,0x40,0x22,0x09,0x18,0x12,0x06,0x7F,0xF8,0x11,0x08,
  145. 0x13,0xE8,0x14,0x48,0x1A,0x88,0x11,0x08,0x12,0x88,0x14,0x08,0x1F,0xF8,0x10,0x08,
  146. };

  147. code unsigned char Hz1616_kou[]=
  148. {
  149. /*--  文字:  口  --*/
  150. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  151. 0x00,0x00,0x00,0x00,0x3F,0xF8,0x20,0x08,0x20,0x08,0x20,0x08,0x20,0x08,0x20,0x08,
  152. 0x20,0x08,0x20,0x08,0x20,0x08,0x3F,0xF8,0x20,0x08,0x20,0x08,0x00,0x00,0x00,0x00,
  153. };

  154. code unsigned char Hz1616_hang[]=
  155. {
  156. /*--  文字:  行  --*/
  157. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  158. 0x08,0x00,0x1C,0x00,0x31,0xFC,0x40,0x00,0x88,0x00,0x0C,0x00,0x1B,0xFE,0x30,0x20,
  159. 0x50,0x20,0x90,0x20,0x10,0x20,0x10,0x20,0x10,0x20,0x10,0x20,0x10,0xA0,0x10,0x40,
  160. };


  161. unsigned char ucCanvasBuffer[]= //画布显示数组。注意,这里没有code关键字,是全局变量。初始化全部填充0x00
  162. {
  163. 0x00,0x00,0x00,0x00,  //上半屏
  164. 0x00,0x00,0x00,0x00,
  165. 0x00,0x00,0x00,0x00,
  166. 0x00,0x00,0x00,0x00,
  167. 0x00,0x00,0x00,0x00,
  168. 0x00,0x00,0x00,0x00,
  169. 0x00,0x00,0x00,0x00,
  170. 0x00,0x00,0x00,0x00,

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

  172. 0x00,0x00,0x00,0x00,  //下半屏
  173. 0x00,0x00,0x00,0x00,
  174. 0x00,0x00,0x00,0x00,
  175. 0x00,0x00,0x00,0x00,
  176. 0x00,0x00,0x00,0x00,
  177. 0x00,0x00,0x00,0x00,
  178. 0x00,0x00,0x00,0x00,
  179. 0x00,0x00,0x00,0x00,
  180. };



  181. unsigned char ucKeySec=0;   //被触发的按键编号
  182. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器


  183. unsigned char ucWd=1; //窗口变量
  184. unsigned char ucPart=0; //局部变量 0代表没有选中任何一行,其它数值1到4代表选中某一行


  185. unsigned char ucWd1Update=1; //窗口1的整屏更新显示变量      1代表更新显示,响应函数内部会清零
  186. unsigned char ucWd1Part1Update=0; //窗口1的第1行局部更新显示变量  1代表更新显示,响应函数内部会清零
  187. unsigned char ucWd1Part2Update=0; //窗口1的第2行局部更新显示变量  1代表更新显示,响应函数内部会清零
  188. unsigned char ucWd1Part3Update=0; //窗口1的第3行局部更新显示变量  1代表更新显示,响应函数内部会清零
  189. unsigned char ucWd1Part4Update=0; //窗口1的第4行局部更新显示变量  1代表更新显示,响应函数内部会清零

  190. unsigned char ucWd2Update=0; //窗口2的整屏更新显示变量      1代表更新显示,响应函数内部会清零
  191. unsigned char ucWd2Part1Update=0; //窗口2的第1行局部更新显示变量  1代表更新显示,响应函数内部会清零
  192. unsigned char ucWd2Part2Update=0; //窗口2的第2行局部更新显示变量  1代表更新显示,响应函数内部会清零
  193. unsigned char ucWd2Part3Update=0; //窗口2的第3行局部更新显示变量  1代表更新显示,响应函数内部会清零
  194. unsigned char ucWd2Part4Update=0; //窗口2的第4行局部更新显示变量  1代表更新显示,响应函数内部会清零

  195. unsigned char ucData_1_1=8;  //第1个窗口第1行的被设置数据
  196. unsigned char ucData_1_2=9;  //第1个窗口第2行的被设置数据
  197. unsigned char ucData_1_3=10;  //第1个窗口第3行的被设置数据
  198. unsigned char ucData_1_4=11;  //第1个窗口第4行的被设置数据

  199. unsigned char ucData_2_1=12;  //第2个窗口第1行的被设置数据
  200. unsigned char ucData_2_2=13;  //第2个窗口第2行的被设置数据
  201. unsigned char ucData_2_3=14;  //第2个窗口第3行的被设置数据
  202. unsigned char ucData_2_4=15;  //第2个窗口第4行的被设置数据

  203. void main()
  204.   {
  205.         initial_myself();      //第一区,上电后马上初始化
  206.         delay_long(100);       //一线,延时线。延时一段时间
  207.         initial_peripheral();  //第二区,上电后延时一段时间再初始化

  208.         while(1)   //第三区
  209.         {
  210.                     key_service(); //按键服务的应用程序
  211.             lcd_display_service(); //应用层面的液晶屏显示程序
  212.         }

  213. }


  214. void initial_myself()  //第一区 上电后马上初始化
  215. {
  216. /* 注释二:
  217. * 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
  218. * 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
  219. * 坚鸿51学习板的S1和S5两个按键就是本程序中用到的两个独立按键。
  220. */
  221.    key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平
  222.    beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  223.    TMOD=0x01;  //设置定时器0为工作方式1

  224.    TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  225.    TL0=0x2f;
  226. }
  227. void initial_peripheral() //第二区 上电后延时一段时间再初始化
  228. {
  229.     LCDInit(); //初始化12864 内部包含液晶模块的复位


  230.     EA=1;     //开总中断
  231.     ET0=1;    //允许定时中断
  232.     TR0=1;    //启动定时中断

  233. }


  234. void T0_time() interrupt 1
  235. {
  236.   TF0=0;  //清除中断标志
  237.   TR0=0; //关中断

  238.   key_scan(); //按键扫描函数

  239.   if(uiVoiceCnt!=0)
  240.   {
  241.      uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  242.          beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  243.   }
  244.   else
  245.   {
  246.      ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  247.            beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  248.   }


  249.   TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  250.   TL0=0x2f;
  251.   TR0=1;  //开中断
  252. }



  253. void key_scan(void)//按键扫描函数 放在定时中断里
  254. {  


  255.   static unsigned int  uiKeyTimeCnt1=0; //按键去抖动延时计数器
  256.   static unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志

  257.   static unsigned int  uiKeyTimeCnt2=0; //按键去抖动延时计数器
  258.   static unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志

  259.   static unsigned int  uiKeyTimeCnt3=0; //按键去抖动延时计数器
  260.   static unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志

  261.   static unsigned int  uiKeyTimeCnt4=0; //按键去抖动延时计数器
  262.   static unsigned char ucKeyLock4=0; //按键触发后自锁的变量标志

  263.   if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  264.   {
  265.      ucKeyLock1=0; //按键自锁标志清零
  266.      uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  267.   }
  268.   else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  269.   {
  270.      uiKeyTimeCnt1++; //累加定时中断次数
  271.      if(uiKeyTimeCnt1>const_key_time1)
  272.      {
  273.         uiKeyTimeCnt1=0;
  274.         ucKeyLock1=1;  //自锁按键置位,避免一直触发
  275.         ucKeySec=1;    //触发1号键
  276.      }
  277.   }

  278.   if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  279.   {
  280.      ucKeyLock2=0; //按键自锁标志清零
  281.      uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  282.   }
  283.   else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
  284.   {
  285.      uiKeyTimeCnt2++; //累加定时中断次数
  286.      if(uiKeyTimeCnt2>const_key_time2)
  287.      {
  288.         uiKeyTimeCnt2=0;
  289.         ucKeyLock2=1;  //自锁按键置位,避免一直触发
  290.         ucKeySec=2;    //触发2号键
  291.      }
  292.   }

  293.   if(key_sr3==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  294.   {
  295.      ucKeyLock3=0; //按键自锁标志清零
  296.      uiKeyTimeCnt3=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  297.   }
  298.   else if(ucKeyLock3==0)//有按键按下,且是第一次被按下
  299.   {
  300.      uiKeyTimeCnt3++; //累加定时中断次数
  301.      if(uiKeyTimeCnt3>const_key_time3)
  302.      {
  303.         uiKeyTimeCnt3=0;
  304.         ucKeyLock3=1;  //自锁按键置位,避免一直触发
  305.         ucKeySec=3;    //触发3号键
  306.      }
  307.   }

  308.   if(key_sr4==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  309.   {
  310.      ucKeyLock4=0; //按键自锁标志清零
  311.      uiKeyTimeCnt4=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  312.   }
  313.   else if(ucKeyLock4==0)//有按键按下,且是第一次被按下
  314.   {
  315.      uiKeyTimeCnt4++; //累加定时中断次数
  316.      if(uiKeyTimeCnt4>const_key_time4)
  317.      {
  318.         uiKeyTimeCnt4=0;
  319.         ucKeyLock4=1;  //自锁按键置位,避免一直触发
  320.         ucKeySec=4;    //触发4号键
  321.      }
  322.   }

  323. }


  324. void key_service(void) //按键服务的应用程序
  325. {
  326.   switch(ucKeySec) //按键服务状态切换
  327.   {
  328.     case 1:// 加按键 对应朱兆祺学习板的S1键
  329.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  330.           {
  331.               case 1:  //窗口1
  332.                    switch(ucPart)  //在窗口1下,根据不同的局部变量来设置不同的参数
  333.                    {
  334.                           case 0:   //无光标显示的状态 此处的case 0可以省略

  335.                                 break;
  336.                           case 1:   //设置第1行参数
  337.                                 ucData_1_1++;
  338.                                 if(ucData_1_1>99)
  339.                                 {
  340.                                    ucData_1_1=99;
  341.                                 }
  342.                                 ucWd1Part1Update=1; //1代表更新显示,响应函数内部会清零
  343.                                 break;
  344.                           case 2:   //设置第2行参数
  345.                                 ucData_1_2++;
  346.                                 if(ucData_1_2>99)
  347.                                 {
  348.                                    ucData_1_2=99;
  349.                                 }
  350.                                 ucWd1Part2Update=1; //1代表更新显示,响应函数内部会清零
  351.                                 break;
  352.                           case 3:   //设置第3行参数
  353.                                 ucData_1_3++;
  354.                                 if(ucData_1_3>99)
  355.                                 {
  356.                                    ucData_1_3=99;
  357.                                 }
  358.                                 ucWd1Part3Update=1; //1代表更新显示,响应函数内部会清零
  359.                                 break;
  360.                           case 4:   //设置第4行参数
  361.                                 ucData_1_4++;
  362.                                 if(ucData_1_4>99)
  363.                                 {
  364.                                    ucData_1_4=99;
  365.                                 }
  366.                                 ucWd1Part4Update=1; //1代表更新显示,响应函数内部会清零
  367.                                 break;


  368.                    }
  369.                    break;
  370.               case 2:  //窗口2
  371.                    switch(ucPart)  //在窗口2下,根据不同的局部变量来设置不同的参数
  372.                    {
  373.                           case 0:   //无光标显示的状态 此处的case 0可以省略

  374.                                 break;
  375.                           case 1:   //设置第1行参数
  376.                                 ucData_2_1++;
  377.                                 if(ucData_2_1>99)
  378.                                 {
  379.                                    ucData_2_1=99;
  380.                                 }
  381.                                 ucWd2Part1Update=1; //1代表更新显示,响应函数内部会清零
  382.                                 break;
  383.                           case 2:   //设置第2行参数
  384.                                 ucData_2_2++;
  385.                                 if(ucData_2_2>99)
  386.                                 {
  387.                                    ucData_2_2=99;
  388.                                 }
  389.                                 ucWd2Part2Update=1; //1代表更新显示,响应函数内部会清零
  390.                                 break;
  391.                           case 3:   //设置第3行参数
  392.                                 ucData_2_3++;
  393.                                 if(ucData_2_3>99)
  394.                                 {
  395.                                    ucData_2_3=99;
  396.                                 }
  397.                                 ucWd2Part3Update=1; //1代表更新显示,响应函数内部会清零
  398.                                 break;
  399.                           case 4:   //设置第4行参数
  400.                                 ucData_2_4++;
  401.                                 if(ucData_2_4>99)
  402.                                 {
  403.                                    ucData_2_4=99;
  404.                                 }
  405.                                 ucWd2Part4Update=1; //1代表更新显示,响应函数内部会清零
  406.                                 break;


  407.                    }
  408.                    break;         
  409.           }     
  410.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  411.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  412.           break;   
  413.    
  414.     case 2:// 减按键 对应朱兆祺学习板的S5键
  415.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  416.           {
  417.               case 1:  //窗口1
  418.                    switch(ucPart)  //在窗口1下,根据不同的局部变量来设置不同的参数
  419.                    {
  420.                           case 0:   //无光标显示的状态 此处的case 0可以省略

  421.                                 break;
  422.                           case 1:   //设置第1行参数
  423.                                 ucData_1_1--;
  424.                                 if(ucData_1_1>99) //一直减到最后,单片机C语言编译器有一个特征,0减去1会溢出变成255(0xff)
  425.                                 {
  426.                                    ucData_1_1=0;
  427.                                 }
  428.                                 ucWd1Part1Update=1; //1代表更新显示,响应函数内部会清零
  429.                                 break;
  430.                           case 2:   //设置第2行参数
  431.                                 ucData_1_2--;
  432.                                 if(ucData_1_2>99) //一直减到最后,单片机C语言编译器有一个特征,0减去1会溢出变成255(0xff)
  433.                                 {
  434.                                    ucData_1_2=0;
  435.                                 }
  436.                                 ucWd1Part2Update=1; //1代表更新显示,响应函数内部会清零
  437.                                 break;
  438.                           case 3:   //设置第3行参数
  439.                                 ucData_1_3--;
  440.                                 if(ucData_1_3>99) //一直减到最后,单片机C语言编译器有一个特征,0减去1会溢出变成255(0xff)
  441.                                 {
  442.                                    ucData_1_3=0;
  443.                                 }
  444.                                 ucWd1Part3Update=1; //1代表更新显示,响应函数内部会清零
  445.                                 break;
  446.                           case 4:   //设置第4行参数
  447.                                 ucData_1_4--;
  448.                                 if(ucData_1_4>99) //一直减到最后,单片机C语言编译器有一个特征,0减去1会溢出变成255(0xff)
  449.                                 {
  450.                                    ucData_1_4=0;
  451.                                 }
  452.                                 ucWd1Part4Update=1; //1代表更新显示,响应函数内部会清零
  453.                                 break;


  454.                    }
  455.                    break;
  456.               case 2:  //窗口2
  457.                    switch(ucPart)  //在窗口2下,根据不同的局部变量来设置不同的参数
  458.                    {
  459.                           case 0:   //无光标显示的状态 此处的case 0可以省略

  460.                                 break;
  461.                           case 1:   //设置第1行参数
  462.                                 ucData_2_1--;
  463.                                 if(ucData_2_1>99) //一直减到最后,单片机C语言编译器有一个特征,0减去1会溢出变成255(0xff)
  464.                                 {
  465.                                    ucData_2_1=0;
  466.                                 }
  467.                                 ucWd2Part1Update=1; //1代表更新显示,响应函数内部会清零
  468.                                 break;
  469.                           case 2:   //设置第2行参数
  470.                                 ucData_2_2--;
  471.                                 if(ucData_2_2>99) //一直减到最后,单片机C语言编译器有一个特征,0减去1会溢出变成255(0xff)
  472.                                 {
  473.                                    ucData_2_2=0;
  474.                                 }
  475.                                 ucWd2Part2Update=1; //1代表更新显示,响应函数内部会清零
  476.                                 break;
  477.                           case 3:   //设置第3行参数
  478.                                 ucData_2_3--;
  479.                                 if(ucData_2_3>99) //一直减到最后,单片机C语言编译器有一个特征,0减去1会溢出变成255(0xff)
  480.                                 {
  481.                                    ucData_2_3=0;
  482.                                 }
  483.                                 ucWd2Part3Update=1; //1代表更新显示,响应函数内部会清零
  484.                                 break;
  485.                           case 4:   //设置第4行参数
  486.                                 ucData_2_4--;
  487.                                 if(ucData_2_4>99) //一直减到最后,单片机C语言编译器有一个特征,0减去1会溢出变成255(0xff)
  488.                                 {
  489.                                    ucData_2_4=0;
  490.                                 }
  491.                                 ucWd2Part4Update=1; //1代表更新显示,响应函数内部会清零
  492.                                 break;


  493.                    }
  494.                    break;         
  495.           }     
  496.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  497.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  498.           break;  

  499.     case 3:// 切换"光标"移动按键 对应朱兆祺学习板的S9键
  500.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  501.           {
  502.               case 1: //窗口1
  503.                    switch(ucPart)  //在窗口1下,根据不同的局部变量来设置不同的参数
  504.                    {
  505.                           case 0:   //无光标显示的状态 此处的case 0可以省略

  506.                                 break;
  507.                           case 1:   //设置第1行参数
  508.                                 ucPart=2; //光标切换到下一行
  509.                                 ucWd1Part1Update=1; //更新显示原来那一行,目的是更新反显光标的状态
  510.                                 ucWd1Part2Update=1; //更新显示下一行,    目的是更新反显光标的状态
  511.                                 break;
  512.                           case 2:   //设置第2行参数
  513.                                 ucPart=3; //光标切换到下一行
  514.                                 ucWd1Part2Update=1; //更新显示原来那一行,目的是更新反显光标的状态
  515.                                 ucWd1Part3Update=1; //更新显示下一行,    目的是更新反显光标的状态
  516.                                 break;
  517.                           case 3:   //设置第3行参数
  518.                                 ucPart=4; //光标切换到下一行
  519.                                 ucWd1Part3Update=1; //更新显示原来那一行,目的是更新反显光标的状态
  520.                                 ucWd1Part4Update=1; //更新显示下一行,    目的是更新反显光标的状态
  521.                                 break;
  522.                           case 4:   //设置第4行参数
  523.                                                         ucWd=2;  //切换到第2个窗口
  524.                                 ucPart=1; //光标返回到最上面第一行
  525.                                                                 ucWd2Update=1; //窗口2整屏更新
  526.                                 break;


  527.                    }
  528.                    break;
  529.               case 2: //窗口2
  530.                    switch(ucPart)  //在窗口2下,根据不同的局部变量来设置不同的参数
  531.                    {
  532.                           case 0:   //无光标显示的状态 此处的case 0可以省略

  533.                                 break;
  534.                           case 1:   //设置第1行参数
  535.                                 ucPart=2; //光标切换到下一行
  536.                                 ucWd2Part1Update=1; //更新显示原来那一行,目的是更新反显光标的状态
  537.                                 ucWd2Part2Update=1; //更新显示下一行,    目的是更新反显光标的状态
  538.                                 break;
  539.                           case 2:   //设置第2行参数
  540.                                 ucPart=3; //光标切换到下一行
  541.                                 ucWd2Part2Update=1; //更新显示原来那一行,目的是更新反显光标的状态
  542.                                 ucWd2Part3Update=1; //更新显示下一行,    目的是更新反显光标的状态
  543.                                 break;
  544.                           case 3:   //设置第3行参数
  545.                                 ucPart=4; //光标切换到下一行
  546.                                 ucWd2Part3Update=1; //更新显示原来那一行,目的是更新反显光标的状态
  547.                                 ucWd2Part4Update=1; //更新显示下一行,    目的是更新反显光标的状态
  548.                                 break;
  549.                           case 4:   //设置第4行参数
  550.                                                         ucWd=1;  //切换到第1个窗口
  551.                                 ucPart=1; //光标返回到最上面第一行
  552.                                                                 ucWd1Update=1; //窗口1整屏更新
  553.                                 break;


  554.                    }
  555.                    break;         
  556.           }         
  557.         
  558.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  559.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  560.           break;      
  561.    
  562.     case 4: // 设置按键  对应朱兆祺学习板的S13键,按一次进入设置状态,出现反显光标。再按一次推出设置状态,消除反显光标,并且强行切换到第1个窗口
  563.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  564.           {
  565.               case 1:  //窗口1
  566.                    switch(ucPart)  //在窗口1下,根据不同的局部变量来设置不同的参数
  567.                    {

  568.                           case 0:   //无光标显示的状态
  569.                                 ucPart=1; //光标显示第一行,进入设置模式
  570.                                 ucWd1Part1Update=1; //更新显示
  571.                                 break;
  572.                           case 1:   //设置第1行参数
  573.                                 ucPart=0; //无光标显示,退出设置模式
  574.                                 ucWd1Part1Update=1; //更新显示
  575.                                 break;
  576.                           case 2:   //设置第2行参数
  577.                                 ucPart=0; //无光标显示,退出设置模式
  578.                                 ucWd1Part2Update=1; //更新显示
  579.                                 break;
  580.                           case 3:   //设置第3行参数
  581.                                 ucPart=0; //无光标显示,退出设置模式
  582.                                 ucWd1Part3Update=1; //更新显示
  583.                                 break;
  584.                           case 4:   //设置第4行参数
  585.                                 ucPart=0; //无光标显示,退出设置模式
  586.                                 ucWd1Part4Update=1; //更新显示
  587.                                 break;


  588.                    }
  589.                    break;
  590.               case 2:  //窗口2
  591.                    switch(ucPart)  //在窗口2下,根据不同的局部变量来设置不同的参数
  592.                    {

  593.                           case 0:   //无光标显示的状态
  594.                                                         ucWd=1; //强行切换到第1个窗口
  595.                                 ucPart=1; //光标显示第一行,进入设置模式
  596.                                                                 ucWd1Update=1; //窗口1整屏更新
  597.                                 break;
  598.                           case 1:   //设置第1行参数
  599.                                                                   ucWd=1; //强行切换到第1个窗口
  600.                                 ucPart=0; //无光标显示,退出设置模式
  601.                                                                 ucWd1Update=1; //窗口1整屏更新
  602.                                 break;
  603.                           case 2:   //设置第2行参数
  604.                                                                   ucWd=1; //强行切换到第1个窗口
  605.                                 ucPart=0; //无光标显示,退出设置模式
  606.                                                                 ucWd1Update=1; //窗口1整屏更新
  607.                                 break;
  608.                           case 3:   //设置第3行参数
  609.                                                               ucWd=1; //强行切换到第1个窗口
  610.                                 ucPart=0; //无光标显示,退出设置模式
  611.                                                                 ucWd1Update=1; //窗口1整屏更新
  612.                                 break;
  613.                           case 4:   //设置第4行参数
  614.                                                                   ucWd=1; //强行切换到第1个窗口
  615.                                 ucPart=0; //无光标显示,退出设置模式
  616.                                                                 ucWd1Update=1; //窗口1整屏更新
  617.                                 break;


  618.                    }
  619.                    break;         
  620.           }   

  621.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  622.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  623.           break;         

  624.   }               
  625. }


  626. unsigned char *number_to_matrix(unsigned char  ucBitNumber)
  627. {
  628.     unsigned char *p_ucAnyNumber;  //此指针根据ucBitNumber数值的大小,分别调用不同的字库。

  629.         switch(ucBitNumber)  //根据ucBitNumber数值的大小,分别调用不同的字库。
  630.         {
  631.             case 0:
  632.              p_ucAnyNumber=Zf816_0;
  633.                      break;
  634.             case 1:
  635.              p_ucAnyNumber=Zf816_1;
  636.                      break;
  637.             case 2:
  638.              p_ucAnyNumber=Zf816_2;
  639.                      break;
  640.             case 3:
  641.              p_ucAnyNumber=Zf816_3;
  642.                      break;
  643.             case 4:
  644.              p_ucAnyNumber=Zf816_4;
  645.                      break;
  646.             case 5:
  647.              p_ucAnyNumber=Zf816_5;
  648.                      break;
  649.             case 6:
  650.              p_ucAnyNumber=Zf816_6;
  651.                      break;
  652.             case 7:
  653.              p_ucAnyNumber=Zf816_7;
  654.                      break;
  655.             case 8:
  656.              p_ucAnyNumber=Zf816_8;
  657.                      break;
  658.             case 9:
  659.              p_ucAnyNumber=Zf816_9;
  660.                      break;
  661.             case 10:
  662.              p_ucAnyNumber=Zf816_nc;
  663.                      break;
  664.                 default:   //如果上面的条件都不符合,那么默认指向空字模
  665.              p_ucAnyNumber=Zf816_nc;
  666.                      break;
  667.         }

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



  670. void lcd_display_service(void) //应用层面的液晶屏显示程序
  671. {

  672.     switch(ucWd)  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
  673.     {
  674.         case 1:  
  675.               wd1();  //窗口1显示的内容
  676.               break;
  677.         case 2:  
  678.               wd2();  //窗口2显示的内容
  679.               break;
  680.         //本程序只有2个窗口,所以只有一个case 1,如果要增加窗口,就直接增加 case 2, case 3...        
  681.     }

  682. }


  683. void wd1(void)  //窗口1显示的内容
  684. {
  685.     unsigned char ucAnyNumber_1; //分解变量的个位
  686.     unsigned char ucAnyNumber_10; //分解变量的十位


  687.     unsigned char *p_ucAnyNumber_1; //经过数字转换成字模后,分解变量的个位字模首地址
  688.     unsigned char *p_ucAnyNumber_10; //经过数字转换成字模后,分解变量的十位字模首地址

  689.     unsigned char ucCursorFlag;  //光标标志,也就是反显的标志,它是根据局部变量ucPart来定的

  690. /* 注释三:
  691. * 把每一个窗口的内容分为两种类型,一种类型是那些不用经常刷新显示的内容,只有在切换窗口的时候
  692. * 才需要更新,这种内容放在整屏更新显示的括号里,比如清屏操作等内容。另外一种是那些经常需要
  693. * 刷新显示的内容,这种内容放在局部更新显示的括号里。
  694. */
  695.     if(ucWd1Update==1)  //窗口1整屏更新,里面只放那些不用经常刷新显示的内容
  696.     {
  697.         ucWd1Update=0;  //及时清零,避免一直更新

  698.         ucWd1Part1Update=1; //激活窗口1的第1行局部更新显示变量,这里在前面数码管显示框架上有所改进
  699.         ucWd1Part2Update=1; //激活窗口1的第2行局部更新显示变量,这里在前面数码管显示框架上有所改进
  700.         ucWd1Part3Update=1; //激活窗口1的第3行局部更新显示变量,这里在前面数码管显示框架上有所改进
  701.         ucWd1Part4Update=1; //激活窗口1的第4行局部更新显示变量,这里在前面数码管显示框架上有所改进

  702.         display_clear(0x00); // 清屏操作, 全部显示空填充0x00,全部显示点阵用0xff。
  703.         clear_all_canvas();  //把画布全部清零
  704.         insert_buffer_to_canvas(0,0,Zf816_mao_hao,0,1,16);//把冒号的字模插入画布

  705.         display_lattice(0,0,Hz1616_yi,0,2,16,0);    //一窗口一行,这些内容不用经常更新,只有在切换窗口的时候才更新显示
  706.         display_lattice(1,0,Hz1616_chuang,0,2,16,0);   
  707.         display_lattice(2,0,Hz1616_kou,0,2,16,0);   
  708.         display_lattice(3,0,Hz1616_yi,0,2,16,0);
  709.         display_lattice(4,0,Hz1616_hang,0,2,16,0);

  710.         display_lattice(0,16,Hz1616_yi,0,2,16,0);    //一窗口二行
  711.         display_lattice(1,16,Hz1616_chuang,0,2,16,0);   
  712.         display_lattice(2,16,Hz1616_kou,0,2,16,0);   
  713.         display_lattice(3,16,Hz1616_er,0,2,16,0);
  714.         display_lattice(4,16,Hz1616_hang,0,2,16,0);

  715.         display_lattice(8,0,Hz1616_yi,0,2,16,0);    //一窗口三行
  716.         display_lattice(9,0,Hz1616_chuang,0,2,16,0);   
  717.         display_lattice(10,0,Hz1616_kou,0,2,16,0);   
  718.         display_lattice(11,0,Hz1616_san,0,2,16,0);
  719.         display_lattice(12,0,Hz1616_hang,0,2,16,0);

  720.         display_lattice(8,16,Hz1616_yi,0,2,16,0);    //一窗口四行
  721.         display_lattice(9,16,Hz1616_chuang,0,2,16,0);   
  722.         display_lattice(10,16,Hz1616_kou,0,2,16,0);   
  723.         display_lattice(11,16,Hz1616_si,0,2,16,0);
  724.         display_lattice(12,16,Hz1616_hang,0,2,16,0);

  725.     }

  726. /* 注释四:
  727. * 注意!我前面讲数码管显示的时候有一句话讲错了,我那时说<局部更新应该写在整屏更新之前>,这是不对的。
  728. * 按照现在的显示程序框架<即整屏显示更新括号里包含了所有局部变量的激活>,应该是<整屏更新应该写在局部更新之前>
  729. * 这样才对。
  730. */
  731.     if(ucWd1Part1Update==1) //窗口1的第1行局部更新显示变量,里面放一些经常需要刷新显示的内容
  732.     {
  733.         ucWd1Part1Update=0; //及时清零,避免一直更新

  734.         if(ucPart==1) //被选中
  735.         {
  736.              ucCursorFlag=1; //反显 显示
  737.         }
  738.         else //没被选中
  739.         {
  740.              ucCursorFlag=0; //正常 显示
  741.         }

  742.         if(ucData_1_1>=10) //有2位数以上
  743.         {
  744.              ucAnyNumber_10=ucData_1_1/10;  //十位
  745.         }
  746.         else //否则显示空
  747.         {
  748.              ucAnyNumber_10=10;  //在下面的转换函数中,代码10表示空字模
  749.         }

  750.         ucAnyNumber_1=ucData_1_1%10/1;  //个位

  751.    
  752.         p_ucAnyNumber_10=number_to_matrix(ucAnyNumber_10); //把数字转换成字模首地址
  753.         p_ucAnyNumber_1=number_to_matrix(ucAnyNumber_1); //把数字转换成字模首地址


  754.         insert_buffer_to_canvas(2,0,p_ucAnyNumber_10,ucCursorFlag,1,16);//把十的字模插入画布
  755.         insert_buffer_to_canvas(3,0,p_ucAnyNumber_1,ucCursorFlag,1,16);//把个的字模插入画布

  756.         display_lattice(5,0,ucCanvasBuffer,0,4,16,0);   //显示整屏的画布,最后的参数0是偏移量

  757.                           
  758.     }

  759.     if(ucWd1Part2Update==1) //窗口1的第2行局部更新显示变量,里面放一些经常需要刷新显示的内容
  760.     {
  761.          ucWd1Part2Update=0; //及时清零,避免一直更新

  762.          if(ucPart==2) //被选中
  763.          {
  764.              ucCursorFlag=1; //反显 显示
  765.          }
  766.          else //没被选中
  767.          {
  768.              ucCursorFlag=0; //正常 显示
  769.          }

  770.          if(ucData_1_2>=10) //有2位数以上
  771.          {
  772.              ucAnyNumber_10=ucData_1_2/10;  //十位
  773.          }
  774.          else //否则显示空
  775.          {
  776.              ucAnyNumber_10=10;  //在下面的转换函数中,代码10表示空字模
  777.          }

  778.          ucAnyNumber_1=ucData_1_2%10/1;  //个位

  779.    
  780.          p_ucAnyNumber_10=number_to_matrix(ucAnyNumber_10); //把数字转换成字模首地址
  781.          p_ucAnyNumber_1=number_to_matrix(ucAnyNumber_1); //把数字转换成字模首地址


  782.          insert_buffer_to_canvas(2,0,p_ucAnyNumber_10,ucCursorFlag,1,16);//把十的字模插入画布
  783.          insert_buffer_to_canvas(3,0,p_ucAnyNumber_1,ucCursorFlag,1,16);//把个的字模插入画布

  784.          display_lattice(5,16,ucCanvasBuffer,0,4,16,0);   //显示整屏的画布,最后的参数0是偏移量
  785.                           
  786.      }

  787.      if(ucWd1Part3Update==1) //窗口1的第3行局部更新显示变量,里面放一些经常需要刷新显示的内容
  788.      {
  789.          ucWd1Part3Update=0; //及时清零,避免一直更新

  790.          if(ucPart==3) //被选中
  791.          {
  792.              ucCursorFlag=1; //反显 显示
  793.          }
  794.          else //没被选中
  795.          {
  796.              ucCursorFlag=0; //正常 显示
  797.          }

  798.          if(ucData_1_3>=10) //有2位数以上
  799.          {
  800.              ucAnyNumber_10=ucData_1_3/10;  //十位
  801.          }
  802.          else //否则显示空
  803.          {
  804.              ucAnyNumber_10=10;  //在下面的转换函数中,代码10表示空字模
  805.          }

  806.          ucAnyNumber_1=ucData_1_3%10/1;  //个位

  807.    
  808.          p_ucAnyNumber_10=number_to_matrix(ucAnyNumber_10); //把数字转换成字模首地址
  809.          p_ucAnyNumber_1=number_to_matrix(ucAnyNumber_1); //把数字转换成字模首地址


  810.          insert_buffer_to_canvas(2,0,p_ucAnyNumber_10,ucCursorFlag,1,16);//把十的字模插入画布
  811.          insert_buffer_to_canvas(3,0,p_ucAnyNumber_1,ucCursorFlag,1,16);//把个的字模插入画布

  812.          display_lattice(13,0,ucCanvasBuffer,0,4,16,0);   //显示整屏的画布,最后的参数0是偏移量
  813.                           
  814.      }

  815.      if(ucWd1Part4Update==1) //窗口1的第4行局部更新显示变量,里面放一些经常需要刷新显示的内容
  816.      {
  817.          ucWd1Part4Update=0; //及时清零,避免一直更新

  818.          if(ucPart==4) //被选中
  819.          {
  820.              ucCursorFlag=1; //反显 显示
  821.          }
  822.          else //没被选中
  823.          {
  824.              ucCursorFlag=0; //正常 显示
  825.          }

  826.          if(ucData_1_4>=10) //有2位数以上
  827.          {
  828.              ucAnyNumber_10=ucData_1_4/10;  //十位
  829.          }
  830.          else //否则显示空
  831.          {
  832.              ucAnyNumber_10=10;  //在下面的转换函数中,代码10表示空字模
  833.          }

  834.          ucAnyNumber_1=ucData_1_4%10/1;  //个位

  835.    
  836.          p_ucAnyNumber_10=number_to_matrix(ucAnyNumber_10); //把数字转换成字模首地址
  837.          p_ucAnyNumber_1=number_to_matrix(ucAnyNumber_1); //把数字转换成字模首地址


  838.          insert_buffer_to_canvas(2,0,p_ucAnyNumber_10,ucCursorFlag,1,16);//把十的字模插入画布
  839.          insert_buffer_to_canvas(3,0,p_ucAnyNumber_1,ucCursorFlag,1,16);//把个的字模插入画布

  840.          display_lattice(13,16,ucCanvasBuffer,0,4,16,0);   //显示整屏的画布,最后的参数0是偏移量                          
  841.      }


  842. }


  843. void wd2(void)  //窗口2显示的内容
  844. {
  845.     unsigned char ucAnyNumber_1; //分解变量的个位
  846.     unsigned char ucAnyNumber_10; //分解变量的十位


  847.     unsigned char *p_ucAnyNumber_1; //经过数字转换成字模后,分解变量的个位字模首地址
  848.     unsigned char *p_ucAnyNumber_10; //经过数字转换成字模后,分解变量的十位字模首地址

  849.     unsigned char ucCursorFlag;  //光标标志,也就是反显的标志,它是根据局部变量ucPart来定的

  850.     if(ucWd2Update==1)  //窗口2整屏更新,里面只放那些不用经常刷新显示的内容
  851.     {
  852.         ucWd2Update=0;  //及时清零,避免一直更新

  853.         ucWd2Part1Update=1; //激活窗口2的第1行局部更新显示变量,这里在前面数码管显示框架上有所改进
  854.         ucWd2Part2Update=1; //激活窗口2的第2行局部更新显示变量,这里在前面数码管显示框架上有所改进
  855.         ucWd2Part3Update=1; //激活窗口2的第3行局部更新显示变量,这里在前面数码管显示框架上有所改进
  856.         ucWd2Part4Update=1; //激活窗口2的第4行局部更新显示变量,这里在前面数码管显示框架上有所改进

  857.         display_clear(0x00); // 清屏操作, 全部显示空填充0x00,全部显示点阵用0xff。
  858.         clear_all_canvas();  //把画布全部清零
  859.         insert_buffer_to_canvas(0,0,Zf816_mao_hao,0,1,16);//把冒号的字模插入画布

  860.         display_lattice(0,0,Hz1616_er,0,2,16,0);    //二窗口一行,这些内容不用经常更新,只有在切换窗口的时候才更新显示
  861.         display_lattice(1,0,Hz1616_chuang,0,2,16,0);   
  862.         display_lattice(2,0,Hz1616_kou,0,2,16,0);   
  863.         display_lattice(3,0,Hz1616_yi,0,2,16,0);
  864.         display_lattice(4,0,Hz1616_hang,0,2,16,0);

  865.         display_lattice(0,16,Hz1616_er,0,2,16,0);    //二窗口二行
  866.         display_lattice(1,16,Hz1616_chuang,0,2,16,0);   
  867.         display_lattice(2,16,Hz1616_kou,0,2,16,0);   
  868.         display_lattice(3,16,Hz1616_er,0,2,16,0);
  869.         display_lattice(4,16,Hz1616_hang,0,2,16,0);

  870.         display_lattice(8,0,Hz1616_er,0,2,16,0);    //二窗口三行
  871.         display_lattice(9,0,Hz1616_chuang,0,2,16,0);   
  872.         display_lattice(10,0,Hz1616_kou,0,2,16,0);   
  873.         display_lattice(11,0,Hz1616_san,0,2,16,0);
  874.         display_lattice(12,0,Hz1616_hang,0,2,16,0);

  875.         display_lattice(8,16,Hz1616_er,0,2,16,0);    //二窗口四行
  876.         display_lattice(9,16,Hz1616_chuang,0,2,16,0);   
  877.         display_lattice(10,16,Hz1616_kou,0,2,16,0);   
  878.         display_lattice(11,16,Hz1616_si,0,2,16,0);
  879.         display_lattice(12,16,Hz1616_hang,0,2,16,0);

  880.     }

  881.     if(ucWd2Part1Update==1) //窗口2的第1行局部更新显示变量,里面放一些经常需要刷新显示的内容
  882.     {
  883.         ucWd2Part1Update=0; //及时清零,避免一直更新

  884.         if(ucPart==1) //被选中
  885.         {
  886.              ucCursorFlag=1; //反显 显示
  887.         }
  888.         else //没被选中
  889.         {
  890.              ucCursorFlag=0; //正常 显示
  891.         }

  892.         if(ucData_2_1>=10) //有2位数以上
  893.         {
  894.              ucAnyNumber_10=ucData_2_1/10;  //十位
  895.         }
  896.         else //否则显示空
  897.         {
  898.              ucAnyNumber_10=10;  //在下面的转换函数中,代码10表示空字模
  899.         }

  900.         ucAnyNumber_1=ucData_2_1%10/1;  //个位

  901.    
  902.         p_ucAnyNumber_10=number_to_matrix(ucAnyNumber_10); //把数字转换成字模首地址
  903.         p_ucAnyNumber_1=number_to_matrix(ucAnyNumber_1); //把数字转换成字模首地址


  904.         insert_buffer_to_canvas(2,0,p_ucAnyNumber_10,ucCursorFlag,1,16);//把十的字模插入画布
  905.         insert_buffer_to_canvas(3,0,p_ucAnyNumber_1,ucCursorFlag,1,16);//把个的字模插入画布

  906.         display_lattice(5,0,ucCanvasBuffer,0,4,16,0);   //显示整屏的画布,最后的参数0是偏移量

  907.                           
  908.     }

  909.     if(ucWd2Part2Update==1) //窗口2的第2行局部更新显示变量,里面放一些经常需要刷新显示的内容
  910.     {
  911.          ucWd2Part2Update=0; //及时清零,避免一直更新

  912.          if(ucPart==2) //被选中
  913.          {
  914.              ucCursorFlag=1; //反显 显示
  915.          }
  916.          else //没被选中
  917.          {
  918.              ucCursorFlag=0; //正常 显示
  919.          }

  920.          if(ucData_2_2>=10) //有2位数以上
  921.          {
  922.              ucAnyNumber_10=ucData_2_2/10;  //十位
  923.          }
  924.          else //否则显示空
  925.          {
  926.              ucAnyNumber_10=10;  //在下面的转换函数中,代码10表示空字模
  927.          }

  928.          ucAnyNumber_1=ucData_2_2%10/1;  //个位

  929.    
  930.          p_ucAnyNumber_10=number_to_matrix(ucAnyNumber_10); //把数字转换成字模首地址
  931.          p_ucAnyNumber_1=number_to_matrix(ucAnyNumber_1); //把数字转换成字模首地址


  932.          insert_buffer_to_canvas(2,0,p_ucAnyNumber_10,ucCursorFlag,1,16);//把十的字模插入画布
  933.          insert_buffer_to_canvas(3,0,p_ucAnyNumber_1,ucCursorFlag,1,16);//把个的字模插入画布

  934.          display_lattice(5,16,ucCanvasBuffer,0,4,16,0);   //显示整屏的画布,最后的参数0是偏移量
  935.                           
  936.      }

  937.      if(ucWd2Part3Update==1) //窗口2的第3行局部更新显示变量,里面放一些经常需要刷新显示的内容
  938.      {
  939.          ucWd2Part3Update=0; //及时清零,避免一直更新

  940.          if(ucPart==3) //被选中
  941.          {
  942.              ucCursorFlag=1; //反显 显示
  943.          }
  944.          else //没被选中
  945.          {
  946.              ucCursorFlag=0; //正常 显示
  947.          }

  948.          if(ucData_2_3>=10) //有2位数以上
  949.          {
  950.              ucAnyNumber_10=ucData_2_3/10;  //十位
  951.          }
  952.          else //否则显示空
  953.          {
  954.              ucAnyNumber_10=10;  //在下面的转换函数中,代码10表示空字模
  955.          }

  956.          ucAnyNumber_1=ucData_2_3%10/1;  //个位

  957.    
  958.          p_ucAnyNumber_10=number_to_matrix(ucAnyNumber_10); //把数字转换成字模首地址
  959.          p_ucAnyNumber_1=number_to_matrix(ucAnyNumber_1); //把数字转换成字模首地址


  960.          insert_buffer_to_canvas(2,0,p_ucAnyNumber_10,ucCursorFlag,1,16);//把十的字模插入画布
  961.          insert_buffer_to_canvas(3,0,p_ucAnyNumber_1,ucCursorFlag,1,16);//把个的字模插入画布

  962.          display_lattice(13,0,ucCanvasBuffer,0,4,16,0);   //显示整屏的画布,最后的参数0是偏移量
  963.                           
  964.      }

  965.      if(ucWd2Part4Update==1) //窗口2的第4行局部更新显示变量,里面放一些经常需要刷新显示的内容
  966.      {
  967.          ucWd2Part4Update=0; //及时清零,避免一直更新

  968.          if(ucPart==4) //被选中
  969.          {
  970.              ucCursorFlag=1; //反显 显示
  971.          }
  972.          else //没被选中
  973.          {
  974.              ucCursorFlag=0; //正常 显示
  975.          }

  976.          if(ucData_2_4>=10) //有2位数以上
  977.          {
  978.              ucAnyNumber_10=ucData_2_4/10;  //十位
  979.          }
  980.          else //否则显示空
  981.          {
  982.              ucAnyNumber_10=10;  //在下面的转换函数中,代码10表示空字模
  983.          }

  984.          ucAnyNumber_1=ucData_2_4%10/1;  //个位

  985.    
  986.          p_ucAnyNumber_10=number_to_matrix(ucAnyNumber_10); //把数字转换成字模首地址
  987.          p_ucAnyNumber_1=number_to_matrix(ucAnyNumber_1); //把数字转换成字模首地址


  988.          insert_buffer_to_canvas(2,0,p_ucAnyNumber_10,ucCursorFlag,1,16);//把十的字模插入画布
  989.          insert_buffer_to_canvas(3,0,p_ucAnyNumber_1,ucCursorFlag,1,16);//把个的字模插入画布

  990.          display_lattice(13,16,ucCanvasBuffer,0,4,16,0);   //显示整屏的画布,最后的参数0是偏移量                          
  991.      }


  992. }


  993. void clear_all_canvas(void)  //把画布全部清零
  994. {
  995.    unsigned int j=0;
  996.    unsigned int i=0;

  997.    for(j=0;j<16;j++)  //这里的16表示画布有16行
  998.    {
  999.       for(i=0;i<4;i++) //这里的4表示画布每行有4个字节
  1000.       {
  1001.                   ucCanvasBuffer[j*4+i]=0x00;
  1002.       }
  1003.    }         

  1004. }





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

  1007.     unsigned char x,y;
  1008.     WriteCommand(0x34);  //关显示缓冲指令            
  1009.     WriteCommand(0x34);  //关显示缓冲指令  故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的
  1010.     y=0;
  1011.     while(y<32)  //y轴的范围0至31
  1012.     {
  1013.          WriteCommand(y+0x80);        //垂直地址
  1014.          WriteCommand(0x80);          //水平地址
  1015.          for(x=0;x<32;x++)  //256个横向点,有32个字节
  1016.          {  
  1017.             LCDWriteData(ucFillDate);
  1018.          }
  1019.          y++;
  1020.     }
  1021.     WriteCommand(0x36); //开显示缓冲指令

  1022. }

  1023. /* 注释五:
  1024. * 把字模插入画布的函数.
  1025. * 这是本节的核心函数,读者尤其要搞懂x_amount和y_amount对应的显示关系。
  1026. * 第1,2个参数x,y是在画布中的坐标体系。
  1027. * x的范围是0至3,因为画布的横向只要4个字节。y的范围是0至15,因为画布的纵向只有16行。
  1028. * 第3个参数*ucArray是字模的数组。
  1029. * 第4个参数ucFbFlag是反白显示标志。0代表正常显示,1代表反白显示。
  1030. * 第5,6个参数x_amount,y_amount分别代表字模数组的横向有多少个字节,纵向有几横。
  1031. */
  1032. 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)
  1033. {
  1034.    unsigned int j=0;
  1035.    unsigned int i=0;
  1036.    unsigned char ucTemp;
  1037.    for(j=0;j<y_amount;j++)
  1038.    {
  1039.       for(i=0;i<x_amount;i++)
  1040.       {
  1041.                    ucTemp=ucArray[j*x_amount+i];
  1042.                    if(ucFbFlag==0)
  1043.                    {
  1044.               ucCanvasBuffer[(y+j)*4+x+i]=ucTemp; //这里的4代表画布每一行只有4个字节
  1045.                    }
  1046.                    else
  1047.                    {
  1048.               ucCanvasBuffer[(y+j)*4+x+i]=~ucTemp; //这里的4代表画布每一行只有4个字节
  1049.                    }
  1050.       }
  1051.    }         

  1052. }

  1053. /* 注释六:
  1054. * 显示任意点阵函数.
  1055. * 注意,本函数在前几节的基础上多增加了第7个参数uiOffSetAddr,它是偏移地址。
  1056. * 对于这个函数,读者尤其要搞懂x_amount和y_amount对应的显示关系。
  1057. * 第1,2个参数x,y是坐标体系。x的范围是0至15,y的范围是0至31.
  1058. * 第3个参数*ucArray是字模的数组。
  1059. * 第4个参数ucFbFlag是反白显示标志。0代表正常显示,1代表反白显示。
  1060. * 第5,6个参数x_amount,y_amount分别代表字模数组的横向有多少个字节,纵向有几横。
  1061. * 第7个参数uiOffSetAddr是偏移地址,代表字模数组的从第几个数据开始显示。
  1062. */
  1063. 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)
  1064. {
  1065.    unsigned int j=0;
  1066.    unsigned int i=0;
  1067.    unsigned char ucTemp;

  1068. //注意,要把以下两行指令屏蔽,否则屏幕在更新显示时会整屏闪动
  1069. //  WriteCommand(0x34);  //关显示缓冲指令            
  1070. //  WriteCommand(0x34);  //关显示缓冲指令  故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的
  1071.    for(j=0;j<y_amount;j++) //y_amount代表y轴有多少横
  1072.    {
  1073.        WriteCommand(y+j+0x80);        //垂直地址
  1074.        WriteCommand(x+0x80);          //水平地址
  1075.        for(i=0;i<x_amount;i++) //x_amount代表x轴有多少列
  1076.        {
  1077.            ucTemp=ucArray[j*x_amount+i+uiOffSetAddr]; //uiOffSetAddr是字模数组的偏移地址
  1078.            if(ucFbFlag==1)  //反白显示
  1079.            {
  1080.                ucTemp=~ucTemp;
  1081.            }
  1082.            LCDWriteData(ucTemp);
  1083.           //         delay_short(30000);  //把上一节这个延时函数去掉,加快刷屏速度
  1084.       }
  1085.    }
  1086.    WriteCommand(0x36); //开显示缓冲指令
  1087. }




  1088. void SendByteToLcd(unsigned char ucData)  //发送一个字节数据到液晶模块
  1089. {
  1090.         unsigned char i;
  1091.         for ( i = 0; i < 8; i++ )
  1092.         {
  1093.                 if ( (ucData << i) & 0x80 )
  1094.                 {
  1095.                         LCDSID_dr = 1;
  1096.                 }
  1097.                 else
  1098.                 {
  1099.                         LCDSID_dr = 0;
  1100.                 }
  1101.                 LCDCLK_dr = 0;
  1102.                 LCDCLK_dr = 1;
  1103.         }
  1104. }

  1105. void SPIWrite(unsigned char ucWData, unsigned char ucWRS) //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动
  1106. {
  1107.         SendByteToLcd( 0xf8 + (ucWRS << 1) );
  1108.         SendByteToLcd( ucWData & 0xf0 );
  1109.         SendByteToLcd( (ucWData << 4) & 0xf0);
  1110. }


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

  1113.         LCDCS_dr = 0;
  1114.         LCDCS_dr = 1;
  1115.         SPIWrite(ucCommand, 0);
  1116.         delay_short(90);
  1117. }

  1118. void LCDWriteData(unsigned char ucData)  //发送一个字节的数据给液晶模块
  1119. {
  1120.         LCDCS_dr = 0;
  1121.         LCDCS_dr = 1;
  1122.         SPIWrite(ucData, 1);
  1123. }

  1124. void LCDInit(void) //初始化  函数内部包括液晶模块的复位
  1125. {
  1126.         LCDRST_dr = 1;  //复位
  1127.         LCDRST_dr = 0;
  1128.         LCDRST_dr = 1;
  1129. }



  1130. void delay_short(unsigned int uiDelayShort) //延时函数
  1131. {
  1132.    unsigned int i;  
  1133.    for(i=0;i<uiDelayShort;i++)
  1134.    {
  1135.      ;  
  1136.    }
  1137. }


  1138. void delay_long(unsigned int uiDelayLong)
  1139. {
  1140.    unsigned int i;
  1141.    unsigned int j;
  1142.    for(i=0;i<uiDelayLong;i++)
  1143.    {
  1144.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  1145.           {
  1146.              ; //一个分号相当于执行一条空语句
  1147.           }
  1148.    }
  1149. }
复制代码

总结陈词:
这一节讲了在多个窗口里设置不同的参数。还有一种常见的项目,要求把第1窗口是用来作为主菜单,主菜单里面有3个子菜单,可以通过移动光标进入不同的子菜单窗口进行参数设置,这类项目该如何编程?欲知详情,请听下回分解-----通过主菜单移动光标来进入子菜单窗口的液晶屏程序。

(未完待续,下节更精彩,不要走开哦)
乐于分享,勇于质疑!
100#
 楼主| 发表于 2014-10-28 15:35:21 | 显示全部楼层
又一个暑假 发表于 2014-10-28 15:15
鸿哥的液晶更新有点多节啦,得开始研究了

不要急,一节一节来慢慢消化。
乐于分享,勇于质疑!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|独闷闷网 ( 粤ICP备12007667号-2 )

GMT+8, 2024-5-7 00:56 , Processed in 0.339986 second(s), 17 queries .

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