这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 嵌入式开发 » MCU » 从业将近十年!手把手教你单片机程序框架(连载)

共88条 8/9 |‹ 4 5 6 7 8 9 跳转至
菜鸟
2014-09-13 15:40:21     打赏
71楼

第六十七节:利用外部中断实现模拟串口数据的收发。


开场白:

     鸿哥曾经亲自用外部中断做过红外遥控器的数据接收,步进电机圆周运动的光电反馈信号检测,输液器里瞬间即逝的水滴信号,以及本节的模拟串口数据的接收,其实这些项目的原理都大同小异,会一样即可触类旁通其它的。
    这一节要教大家四个知识点:
   第一个:如何利用外部中断实现模拟串口数据的收发。

   第二个:在退出外部中断函数时,必须通过软件把外部中断标志位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)源代码讲解如下:

#include "REG52.H"

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

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

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


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

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

}
 */


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


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

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

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


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


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

unsigned char ucTest=0;

void main() 
  {
   initial_myself();  
   delay_long(100);   
   initial_peripheral(); 
   while(1)  
   { 
        usart_service();  //串口服务程序

   }

}



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

     unsigned char i=0;   

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

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

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


			for(i=0;i<uiRcregTotal;i++)  //返回全部接收到的数据包
			{
			   eusart_send(ucRcregBuf[i]); 
			}
         
                                         
            uiRcregTotal=0;  //清空缓冲的下标,方便下次重新从0下标开始接受新数据
  
     }
                         
}


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

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


//从串口读取一个字节
unsigned char read_eusart_byte()
{
    unsigned char  ucReadData=0;
    unsigned char  i=8;


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

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

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

     return ucReadData;
}




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


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



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


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

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

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

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

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


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 delay_short(unsigned int uiDelayShort) 
{
   unsigned int i;  
   for(i=0;i<uiDelayShort;i++)
   {
     ;   //一个分号相当于执行一条空语句
   }
}


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

}

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

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

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


}

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

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

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

}

 

总结陈词: 

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

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




菜鸟
2014-09-17 14:31:07     打赏
72楼

第六十八节:单片机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)源代码讲解如下(注意,以下代码不能直接放到一个源文件里编译):

/*以下是 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液晶屏的常用点阵字体程序。

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




菜鸟
2014-09-18 13:04:22     打赏
73楼

第六十九节:使用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液晶屏的常用点阵字体程序。

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




菜鸟
2014-09-24 14:34:02     打赏
74楼

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

 

开场白:

    液晶屏模块本身带控制芯片,驱动液晶屏的本质就是单片机通过串行或者并行方式,根据芯片资料指定的协议跟液晶芯片进行通讯的过程。这个详细的通讯协议驱动程序厂家都会免费提供的,也可以在网上找到大量的示范程序。那么我们最应该关注的核心是什么?我认为最核心的是要理清楚程序坐标与实际显示坐标之间的关系规律。本程序不使用模块自带的字库,而是使用自己构造的字库,目的就是为了让读者理解更底层的字模显示。

这一节要教会大家三个知识点:

第一个:对于驱动芯片是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点阵的显示程序。

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




菜鸟
2014-09-25 11:50:18     打赏
75楼

第七十一节:液晶屏的字符,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度显示的算法程序。

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




菜鸟
2014-10-10 13:13:58     打赏
76楼

第七十二节:在液晶屏中把字体顺时针旋转90度显示的算法程序。

 

开场白:

我曾经遇到过这样的项目,客户由于外壳结果的原因,故意把液晶屏物理位置逆时针旋转了90度,在这种情况下,如果按之前的显示驱动就会发现字体也跟着倒了过来,影响了阅读。当时我的解决办法就是把字体的字库数组通过算法顺时针旋转90度就达到了目的。这一节把这个算法教给大家。

 这个算法的本质是:请看以下附图1,附图2,附图3.

 第一步:旋转90度的本质,就是把原来横向取模改成纵向去模。先把代表每一行16个点阵数的2char型数据合并成1int型数据。

 第二步:再把每一列的16个点阵按2个字节分别取到一个数组里,就是纵向取模的过程了。


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


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


(2)实现功能:把液晶屏物理位置逆时针旋转了90度,开机上电后,可以看到液晶屏像对联的显示顺序一样,从上往下分别显示“馒头V5”四个字。

  

(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 hz1616_s90(const unsigned char  *p_ucHz,unsigned char *p_ucResult);  //把16x16汉字字模顺时针旋转90度的转换函数
void hz816_s90(const unsigned char  *p_ucHz,unsigned char *p_ucResult);  //把8x16字符字模顺时针旋转90度的转换函数

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

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,
};


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

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

        display_clear(); // 清屏

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

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

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


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

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


    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); //开显示缓冲指令

}


/* 注释二:
 * 把16x16汉字字模顺时针旋转90度的步骤:请看附图1,附图2,附图3.
 * 第一步:旋转90度的本质,就是把原来横向取模改成纵向去模。先把代表每一行16个点阵数的2个char型数据合并成1个int型数据。
 * 第二步:再把每一列的16个点阵按2个字节分别取到一个数组里,就是纵向取模的过程了。以下程序int型数据每取8个数据的最高位,
 * 就左移一次,本质就是纵向取模的过程。
 */
void hz1616_s90(const unsigned char  *p_ucHz,unsigned char *p_ucResult)  //把16x16汉字字模顺时针旋转90度的转换函数
{
	 unsigned char a;
	 unsigned char b;
	 unsigned char c;
     unsigned int uiBuffer[16];  //注意,是int类型数据,一个数据包含2个字节。
	
	 for(a=0;a<16;a++) //把原来以字节为单位的字库每一行的2个字节合并成1个int型数据。放到一个包含16个int类型的数组里,为旋转90度算法处理做准备
	 {
         uiBuffer[a]=p_ucHz[a*2];
		 uiBuffer[a]=uiBuffer[a]<<8;
		 uiBuffer[a]=uiBuffer[a]+p_ucHz[a*2+1];
     }
	 
	 c=0;
	 for(a=0;a<16;a++)  //这里的16代表16列
	 {
		 for(b=0;b<8;b++)   //每一列中有16个点,有2个字节,这里的8代表第一个字节的8个位或点。
		 {
			  p_ucResult[c]=p_ucResult[c]<<1;   
              p_ucResult[c]=p_ucResult[c]&0xfe;		 
              if(uiBuffer[15-b]>=0x8000)    //注意,int类型数据的判断是0x8000,char型的是0x80
		      {
                 p_ucResult[c]=p_ucResult[c]+1;
              }
		      uiBuffer[15-b]=uiBuffer[15-b]<<1; 
         }
		 c++;
		 
		 for(b=0;b<8;b++) //每一列中有16个点,有2个字节,这里的8代表第二个字节的8个位或点。
		 {
			  p_ucResult[c]=p_ucResult[c]<<1;  
              p_ucResult[c]=p_ucResult[c]&0xfe;		 			 
              if(uiBuffer[7-b]>=0x8000)      
		      {
                   p_ucResult[c]=p_ucResult[c]+1;
              }
		   	  uiBuffer[7-b]=uiBuffer[7-b]<<1;
         }
		 c++;
    }
	 
}


/* 注释三:
 * 把8x16字符字模顺时针旋转90度的步骤:
 * 第一步:旋转90度的本质,就是把原来横向取模改成纵向去模。由于原来的字库存放在带code关键字的ROM区,只能读不能写,所以
 * 先把原来的字模数组读取出来,放到一个变量缓冲区里。
 * 第二步:再把每一列的16个点阵按2个字节分别取到一个数组里,就是纵向取模的过程了。以下程序int型数据每取8个数据的最高位,
   就左移一次,本质就是纵向取模的过程。
 */
void hz816_s90(const unsigned char  *p_ucHz,unsigned char *p_ucResult)  //把8x16字符字模顺时针旋转90度的转换函数
{
	 unsigned char a;
	 unsigned char b;
	 unsigned char c;
     unsigned char uiBuffer[16]; //注意,跟16x16点阵不一样,这里是char数据。因为横向的只有8个点
	
	 for(a=0;a<16;a++) //把存放在ROM的字库放到一个16个char类型的数组里
	 {
         uiBuffer[a]=p_ucHz[a];
     }
	 
	 c=0;
	 for(a=0;a<8;a++)  //这里的8代表8列
	 {
		 for(b=0;b<8;b++)  //每一列中有16个点,有2个字节,这里的8代表第一个字节的8个位或点。
		 {
			  p_ucResult[c]=p_ucResult[c]<<1;
              p_ucResult[c]=p_ucResult[c]&0xfe;					 
              if(uiBuffer[15-b]>=0x80)   //注意,int类型数据的判断是0x8000,char型的是0x80
	  	      {
                  p_ucResult[c]=p_ucResult[c]+1;
              }
		      uiBuffer[15-b]=uiBuffer[15-b]<<1;
         }
  	     c++;
		 
		 for(b=0;b<8;b++)  //每一列中有16个点,有2个字节,这里的8代表第二个字节的8个位或点。
		 {
			  p_ucResult[c]=p_ucResult[c]<<1;
              p_ucResult[c]=p_ucResult[c]&0xfe;					 
              if(uiBuffer[7-b]>=0x80)   //注意,int类型数据的判断是0x8000,char型的是0x80
			  {
                 p_ucResult[c]=p_ucResult[c]+1;
              }
			  uiBuffer[7-b]=uiBuffer[7-b]<<1;
         }
		 c++;
     }
	 
}



/* 注释四:本节的核心函数,读者尤其要搞懂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++)
   {
     ;  
   }
}

 


总结陈词: 

    有的项目会要求把字体或者图像进行镜像显示处理,这种算法程序是怎样编写的?欲知详情,请听下回分解-----在液晶屏中把字体镜像显示的算法程序。

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




菜鸟
2014-10-13 11:00:53     打赏
77楼

第七十三节:在液晶屏中把字体镜像显示的算法程序。


开场白:

有的项目会要求把字体或者图像进行镜像显示处理,这一节把这个算法教给大家。

    这个算法的本质是:

16x16点阵的图像或者字体有16行,每行有2个字节,如果把这2个字节看成是一个16int型数据,那么就是要这个数据从原来左边是高位,右边是低位的顺序颠倒过来。本程序没有把2个字节合并成一个int型数据,而是直接在一个字节数据内把高低位顺序颠倒过来,然后把第1字节数据跟第2字节数据交换。

8x16点阵的图像或者字体有16行,每行有1个字节,把这个数据从原来左边是高位,右边是低位的顺序颠倒过来。

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


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


(2)实现功能:开机上电后,从上往下分别显示“馒头V5”四个字以及右边镜像后的“馒头V5”四个字。

  

(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 hz1616_mirror(const unsigned char  *p_ucHz,unsigned char *p_ucResult);  //把16x16点阵字库镜像
void hz816_mirror(const unsigned char  *p_ucHz,unsigned char *p_ucResult);  //把8x16点阵字库镜像

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

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,
};


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

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

        display_clear(); // 清屏

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


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

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

        display_lattice(8,16,Zf816_5,0,1,16);  //显示镜像前的<5>字符
        hz816_mirror(Zf816_5,ucBufferResult);  //把<5>字符镜像后放到ucBufferResult临时变量里。
        display_lattice(9,16,ucBufferResult,0,1,16);  //显示镜像后的<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); //开显示缓冲指令

}

/* 注释一:
 * 16x16点阵镜像的本质:
 * 16x16点阵有16行,每行有2个字节,如果把这2个字节看成是一个16位int型数据,
 * 那么就是要这个数据从原来左边是高位,右边是低位的顺序颠倒过来。本程序没有把2个字节
 * 合并成一个int型数据,而是直接在一个字节数据内把高低位顺序颠倒过来,然后把第1字节数据跟第2字节数据交换。
 */
void hz1616_mirror(const unsigned char  *p_ucHz,unsigned char *p_ucResult)  //把16x16点阵字库镜像的函数
{
	 unsigned char a;
	 unsigned char b;
	 unsigned char c;
	 unsigned char d;
	
	 for(a=0;a<16;a++) //这里16代表有16行。每一行有2个字节。把每一个字节看做一列,这里先把第1列字节的数据从原来左边是高位,右边是低位的顺序颠倒过来,相当于镜像。
	 {
	    b=p_ucHz[a*2+0];  //这里的2代表16x16点阵每行有2列字节,0代表从第1列开始。
	    c=0;
	    for(d=0;d<8;d++)  //把一个字节调换顺序
		{
	       c=c>>1;
           if((b&0x80)==0x80)
		   {
             c=c|0x80;
           }
		   b=b<<1;
        }		 
        p_ucResult[a*2+1]=c;   //注意,因为是镜像,所以要把颠倒顺序后的字节从原来是第1列的调换到第2列	 
     }
	 
	 for(a=0;a<16;a++)  //这里16代表有16行。每一行有2个字节。把每一个字节看做一列,这里先把第2列字节的数据从原来左边是高位,右边是低位的顺序颠倒过来,相当于镜像。
	 {
	    b=p_ucHz[a*2+1];   //这里的2代表16x16点阵每行有2列字节,1代表从第2列开始。
		 
	    c=0;
	    for(d=0;d<8;d++)  //把一个字节调换顺序
		{
			c=c>>1;
            if((b&0x80)==0x80)
			{
              c=c|0x80;
            }
			b=b<<1;
         }

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

		 
     }
	 

}



/* 注释二:
 * 8x16点阵镜像的本质:
 * 8x16点阵有16行,每行有1个字节,把这个数据从原来左边是高位,右边是低位的顺序颠倒过来。
 */
void hz816_mirror(const unsigned char  *p_ucHz,unsigned char *p_ucResult)  //把8x16点阵字库镜像的函数
{
	 unsigned char a;
	 unsigned char b;
	 unsigned char c;
	 unsigned char d;
	
	 for(a=0;a<16;a++) //这里16代表有16行。每一行有1个字节。这里先把每一行字节的数据从原来左边是高位,右边是低位的顺序颠倒过来,相当于镜像。
	 {
	    b=p_ucHz[a*1+0];  //这里的1代表8x16点阵每行有1列字节,0代表从第1列开始。
	    c=0;
	    for(d=0;d<8;d++)  //把一个字节调换顺序
		{
	       c=c>>1;
           if((b&0x80)==0x80)
		   {
             c=c|0x80;
           }
		   b=b<<1;
        }		 
        p_ucResult[a*1+0]=c;   //注意,因为每一行只有一列,所以不用像16x16点阵那样把第1列跟第2列对调交换。
     }
	 
}



/* 注释三:本节的核心函数,读者尤其要搞懂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++)
   {
     ;  
   }
}

 


总结陈词: 

    细心的网友一定会发现,这种12864液晶屏普遍有个毛病,在坐标轴x,y方向上不能完全做到以一个点阵为单位进行随心所欲的显示,比如横向的至少是一个字节8个点阵为单位,而第1,2行跟第3,4行又做不到无缝对接显示,假如我要把汉字一半显示在第2行一半显示在第3行,行不行?当然可以。但是需要我们编写额外的算法程序。这种算法程序是怎样编写的?欲知详情,请听下回分解-----在液晶屏中让字体可以跨区域无缝对接显示的算法程序。

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




菜鸟
2014-10-16 15:18:31     打赏
78楼

第七十四节:在液晶屏中让字体可以跨区域无缝对接显示的算法程序。


开场白:

细心的网友会发现,这种12864液晶屏在显示自造字库时普遍有个毛病,在坐标轴x方向上是以每16个点阵为一个单位的,如果显示两个8x16字符”V””5”,虽然它们的x坐标轴是相邻的,但是实际显示的效果是中间隔了8个点阵。另外,这种12864液晶屏是由上半屏和下半屏组成的,软件上的坐标体系并没有做到跟物理的坐标体系一致,需要转换的。如果我们想把一个整体字符的一半显示在上半屏,另一半显示在下半屏,那怎么办?

这一节就要教给大家这个算法程序:

为了实现跨区域无缝显示,就先在某个区域显示一块画布,我们只要在这块画布数组中插入字模数组,就可以达到跨区域无缝显示的目的。


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


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


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


(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_clear(unsigned char ucFillDate); // 清屏 全部显示空填充0x00   全部显示点阵用0xff
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);//把字模插入画布.
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); //显示任意点阵函数
void delay_short(unsigned int uiDelayshort); //延时

code unsigned char Zf816_V[]= /*V   横向取模  8x16点阵 每一行只要1个字节,共16行 */
{
0x00,
0x00,
0x00,
0xE7,
0x42,
0x42,
0x44,
0x24,
0x24,
0x28,
0x28,
0x18,
0x10,
0x10,
0x00,
0x00,
};

code unsigned char Zf816_5[]= /*5   横向取模  8x16点阵 每一行只要1个字节,共16行 */
{
0x00,
0x00,
0x00,
0x7E,
0x40,
0x40,
0x40,
0x58,
0x64,
0x02,
0x02,
0x42,
0x44,
0x38,
0x00,
0x00,
};


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

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

0x00,0x00,0x00,0x00,  //下半屏
0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,
};



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

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

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

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


        while(1)  
        { 
             ;
        }

}



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

    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(ucFillDate);
         }
         y++;
    }
    WriteCommand(0x36); //开显示缓冲指令

}

/* 注释二:
 * 把字模插入画布的函数.
 * 这是本节的核心函数,读者尤其要搞懂x_amount和y_amount对应的显示关系。
 * 第1,2个参数x,y是在画布中的坐标体系。
 * x的范围是0至3,因为画布的横向只要4个字节。y的范围是0至15,因为画布的纵向只有16行。
 * 第3个参数*ucArray是字模的数组。
 * 第4个参数ucFbFlag是反白显示标志。0代表正常显示,1代表反白显示。
 * 第5,6个参数x_amount,y_amount分别代表字模数组的横向有多少个字节,纵向有几横。
 */
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)
{
   unsigned int j=0;
   unsigned int i=0;
   unsigned char ucTemp;
   for(j=0;j<y_amount;j++)
   {
      for(i=0;i<x_amount;i++)
      {
		   ucTemp=ucArray[j*x_amount+i];
		   if(ucFbFlag==0)
		   {
              ucCanvasBuffer[(y+j)*4+x+i]=ucTemp; //这里的4代表画布每一行只有4个字节
		   }
		   else
		   {
              ucCanvasBuffer[(y+j)*4+x+i]=~ucTemp; //这里的4代表画布每一行只有4个字节
		   }
      }
   }	 

}

/* 注释三:
 * 显示任意点阵函数.
 * 注意,本函数在前几节的基础上多增加了第7个参数uiOffSetAddr,它是偏移地址。
 * 对于这个函数,读者尤其要搞懂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分别代表字模数组的横向有多少个字节,纵向有几横。
 * 第7个参数uiOffSetAddr是偏移地址,代表字模数组的从第几个数据开始显示。
 */
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)
{
   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+uiOffSetAddr]; //uiOffSetAddr是字模数组的偏移地址
           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++)
   {
     ;  
   }
}

 


总结陈词: 

    经过这一节的算法处理后,字符终于可以在x轴上紧紧挨着显示了。也就是把原来x坐标是16个点阵为一个单位,改成了以8个点阵为一个单位。如果要求以1个点阵为单位显示,那该怎么办?这个还真有点难度,因为横向的最小显示单位就是一个字节8个点,不过鸿哥在下一节中照样有办法实现这个功能。欲知详情,请听下回分解-----在12864液晶屏中让字体以1个点阵为单位进行移动显示的算法程序。

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




菜鸟
2014-10-19 10:17:39     打赏
79楼
 

第七十五节:在12864液晶屏中让字体以1个点阵为单位进行移动显示的算法程序。

开场白:

    假设有一个固定的四方形透明窗口,在窗口里面放了一张画布,只要想办法让这个画布

 往右边拖动,那么画布里面的内容就会跟着画布整体往右边移动,这个就是能以1个点阵为单位进行移动显示的本质。同理,这个画布有16行,每行有4个字节,我们只要把每行4个字节看作是一个首尾连接的二进制数据,把每一行的二进制数据每次整体往右边移动一位,就相当于移动一个点阵了。这一节就要把这个算法教给大家。

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


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


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

3)源代码讲解如下:

#include "REG52.H"

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

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_clear(unsigned char ucFillDate); // 清屏 全部显示空填充0x00   全部显示点阵用0xff
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);//把字模插入画布.
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); //显示任意点阵函数
void delay_short(unsigned int uiDelayshort); //延时

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

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

code unsigned char Zf816_V[]= /*V   横向取模  8x16点阵 每一行只要1个字节,共16行 */
{
0x00,
0x00,
0x00,
0xE7,
0x42,
0x42,
0x44,
0x24,
0x24,
0x28,
0x28,
0x18,
0x10,
0x10,
0x00,
0x00,
};

code unsigned char Zf816_5[]= /*5   横向取模  8x16点阵 每一行只要1个字节,共16行 */
{
0x00,
0x00,
0x00,
0x7E,
0x40,
0x40,
0x40,
0x58,
0x64,
0x02,
0x02,
0x42,
0x44,
0x38,
0x00,
0x00,
};


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

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

0x00,0x00,0x00,0x00,  //下半屏
0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,
};

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

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

void main() 
  {

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


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


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

}



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

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

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

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

	       break;

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

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

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



           }
	       break;
   }

}


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


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

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

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

   for(j=0;j<16;j++)  //这里的16表示画布有16行
   {
      ucBitH=0;   
	  ucBitL=0;   
      for(i=0;i<4;i++) //这里的4表示画布每行有4个字节
      {
		  if((ucCanvasBuffer[j*4+i]&0x01)==0x01)  //临时保存一个字节中的最低位
		  {
		     ucBitL=1;
		  }
		  else
		  {
		     ucBitL=0;
		  }
		  ucCanvasBuffer[j*4+i]=ucCanvasBuffer[j*4+i]>>1;  //一行中的一个字节右移一位

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

}


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

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

}


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

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

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



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

    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(ucFillDate);
         }
         y++;
    }
    WriteCommand(0x36); //开显示缓冲指令

}

/* 注释三:
 * 把字模插入画布的函数.
 * 这是本节的核心函数,读者尤其要搞懂x_amount和y_amount对应的显示关系。
 * 第1,2个参数x,y是在画布中的坐标体系。
 * x的范围是0至3,因为画布的横向只要4个字节。y的范围是0至15,因为画布的纵向只有16行。
 * 第3个参数*ucArray是字模的数组。
 * 第4个参数ucFbFlag是反白显示标志。0代表正常显示,1代表反白显示。
 * 第5,6个参数x_amount,y_amount分别代表字模数组的横向有多少个字节,纵向有几横。
 */
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)
{
   unsigned int j=0;
   unsigned int i=0;
   unsigned char ucTemp;
   for(j=0;j<y_amount;j++)
   {
      for(i=0;i<x_amount;i++)
      {
                   ucTemp=ucArray[j*x_amount+i];
                   if(ucFbFlag==0)
                   {
              ucCanvasBuffer[(y+j)*4+x+i]=ucTemp; //这里的4代表画布每一行只有4个字节
                   }
                   else
                   {
              ucCanvasBuffer[(y+j)*4+x+i]=~ucTemp; //这里的4代表画布每一行只有4个字节
                   }
      }
   }         

}

/* 注释四:
 * 显示任意点阵函数.
 * 注意,本函数在前几节的基础上多增加了第7个参数uiOffSetAddr,它是偏移地址。
 * 对于这个函数,读者尤其要搞懂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分别代表字模数组的横向有多少个字节,纵向有几横。
 * 第7个参数uiOffSetAddr是偏移地址,代表字模数组的从第几个数据开始显示。
 */
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)
{
   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+uiOffSetAddr]; //uiOffSetAddr是字模数组的偏移地址
           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++)
   {
     ;  
   }
}

 

总结陈词:

从下一节开始讲大家关注已久的液晶屏菜单程序。欲知详情,请听下回分解-----1个窗口里通过移动光标来设置不同参数的液晶屏菜单程序。

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


菜鸟
2014-10-23 11:42:43     打赏
80楼

第七十六节:如何把一个任意数值的变量显示在液晶屏上。


开场白:

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

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

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

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


(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_clear(unsigned char ucFillDate); // 清屏 全部显示空填充0x00   全部显示点阵用0xff
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);//把字模插入画布.
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); //显示任意点阵函数
unsigned char *number_to_matrix(unsigned char  ucBitNumber); //把一位数字转换成字模首地址的函数
void delay_short(unsigned int uiDelayshort); //延时
void delay_long(unsigned int uiDelayLong);


void initial_myself();    
void initial_peripheral();


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

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

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

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

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

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

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

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


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

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

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


code unsigned char Zf816_nc[]=  //空字模
{
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
};


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

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

0x00,0x00,0x00,0x00,  //下半屏
0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,
};


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


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


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

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

}


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



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

	switch(ucBitNumber)  //根据ucBitNumber数值的大小,分别调用不同的字库。
	{ 
	    case 0:
             p_ucAnyNumber=Zf816_0;
		     break;
	    case 1:
             p_ucAnyNumber=Zf816_1;
		     break;
	    case 2:
             p_ucAnyNumber=Zf816_2;
		     break;
	    case 3:
             p_ucAnyNumber=Zf816_3;
		     break;
	    case 4:
             p_ucAnyNumber=Zf816_4;
		     break;
	    case 5:
             p_ucAnyNumber=Zf816_5;
		     break;
	    case 6:
             p_ucAnyNumber=Zf816_6;
		     break;
	    case 7:
             p_ucAnyNumber=Zf816_7;
		     break;
	    case 8:
             p_ucAnyNumber=Zf816_8;
		     break;
	    case 9:
             p_ucAnyNumber=Zf816_9;
		     break;
	    case 10:
             p_ucAnyNumber=Zf816_nc;
		     break;
		default:   //如果上面的条件都不符合,那么默认指向空字模
             p_ucAnyNumber=Zf816_nc;
		     break;
	}

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


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

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

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

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

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

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

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

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

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



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

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

}





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

    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(ucFillDate);
         }
         y++;
    }
    WriteCommand(0x36); //开显示缓冲指令

}

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

}

/* 注释五:
 * 显示任意点阵函数.
 * 注意,本函数在前几节的基础上多增加了第7个参数uiOffSetAddr,它是偏移地址。
 * 对于这个函数,读者尤其要搞懂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分别代表字模数组的横向有多少个字节,纵向有几横。
 * 第7个参数uiOffSetAddr是偏移地址,代表字模数组的从第几个数据开始显示。
 */
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)
{
   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+uiOffSetAddr]; //uiOffSetAddr是字模数组的偏移地址
           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++)
   {
     ;  
   }
}


void delay_long(unsigned int uiDelayLong)
{
   unsigned int i;
   unsigned int j;
   for(i=0;i<uiDelayLong;i++)
   {
      for(j=0;j<500;j++)  //内嵌循环的空指令数量
          {
             ; //一个分号相当于执行一条空语句
          }
   }
}

 

总结陈词:

有了这一节的基础,我们继续循序渐进,下一节将会讲到液晶屏的菜单程序。欲知详情,请听下回分解-----在1个窗口里通过移动光标来设置不同参数的液晶屏菜单程序。

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


共88条 8/9 |‹ 4 5 6 7 8 9 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]