LCD12864经典驱动(详细注释)

#include "bsp.h"
/*
    RW = 1 :读数据
    RW = 0 : 写数据
    RS = 1 :数据D0-D7与显示RAM交互
    RS = 0 :数据D0-D7与指令寄存器交互
    E  = 1 :读写是能有效(即可以读写)操作的基础
    E :下降沿:锁定数据
    CS1 = 0:选择LCD的前64位显示
    CS2 = 0:选择LCD的后64位显示
*/



/*
 *LCD检测忙函数
 *在RS=0,RW=1模式下
*/
void chekbusy12864(void)
{
    uchar dat;            //定义uchar变量,接收数据
    EX0 = 0;              //禁止外部中断0
    LCD_RS_OUT = 0;       //指令模式 
    LCD_RW_OUT = 1;       //读数据
    do
    {
        P0 = 0x00;        //初始化数据端口
        LCD_E_OUT = 1;    //使能,此语句执行后可以对指令寄存器进行指定操作,此处执行后P0口已经读出了指令寄存器的内容
        dat = P0 & 0x80;  //判断P0的最高位数据(将最高为标为第8位即数据手册BF位,BF = 0空闲,BF=1忙)
        LCD_E_OUT = 0;    //E出现一个下降沿所存P0数据
    }while(dat != 0x00);  //如果dat != 0x00为真,继续do-while循环,也就是说P0的最高为不为1时,退出do-while循环
    EX0=1;                //允许外部中断0
}


/*
 *LCD选屏函数
 *输入参数为0时:选择左半屏
 *输入参数为1时:选择右半屏
 *输入参数为2时:选择双屏
*/
void CHOOSE_12864_SCREEN(uchar i)  /*i是要写的屏.0是左屏,1是右屏,2是双屏;*/
{                                  /*此处在硬件上运行时i的电平全部与程序相反;*/
    switch (i)                           
    {
        case 0: 
        {
            LCD_CS1_OUT=0;
            LCD_CS2_OUT=1;
        }break;                     //比如此处如果要在电路上运行则应该改为CS=1;LCD_CS2_OUT=0;   
        case 1: 
        {
            LCD_CS1_OUT=1;
            LCD_CS2_OUT=0;
        }break;
        case 2: 
        {
            LCD_CS1_OUT=0;
            LCD_CS2_OUT=0;
        }break;
        default: break; 
    }
}

/*
 *LCD写指令函数
 *在RS=0,RW=0模式下
*/
void LCD_12864_CMD(uchar cmd)                //写命令
{
    chekbusy12864();     //调用lcd检忙函数,知道不忙的时候结束这个函数,继续执行后面内容
    EX0=0;               //关闭外部中断0
    LCD_RS_OUT=0;        //指令模式
    LCD_RW_OUT=0;        //写模式
    LCD_E_OUT=1;         //E使能读写
    P0 = cmd;            //将输入参数cmd写入P0口,此时P0口立马将数据写入内部指令存储区
    LCD_E_OUT=0;         //E出现下降沿。所存P0口,P0口不在改变
    EX0=1;               //开启外部中断0
}

/*
 *LCD写数据函数
 *在RS=1,RW=0模式下
*/
void  LCD_12864_DAT(uchar dat)
{
    chekbusy12864();     //调用lcd检忙函数,知道不忙的时候结束这个函数,继续执行后面内容
    EX0 = 0;             //关闭外部中断0
    LCD_RS_OUT = 1;      //数据模式
    LCD_RW_OUT = 0;      //写模式
    LCD_E_OUT  = 1;      //E使能读写
    P0 = dat;            //将输入参数cmd写入P0口,此时P0口立马将数据写入内部数据存储区
    LCD_E_OUT=0;         //E出现下降沿。所存P0口,P0口不在改变
    EX0=1;               //开启外部中断0
}


/*
 *LCD清屏函数
 *调用写数据和写指令函数
 *此处和实际LCD12864有区别
 *
*/
void CLEAR_12864_SCREEN(void)     //此处分左右屏清屏,左右两屏每一屏都是8页
{
    uchar page;  //定义页面变量
    uchar row;   //定义行变量
    for(page = 0xb8; page < 0xc0; page++)
    {
        LCD_12864_CMD(page);      //1011 1000 - 1100 0000,page0~page7
        LCD_12864_CMD(0x40);      //0100 0000 - 0111 1111从每个page的00 0000地址开始到11 1111结束 0~63共64位,每写一位,地址自动移向下一位
        for(row=0; row<64; row++) //此处row只实现计数功能,实际地址移动是在向地址写入数据后自动实现的
        {
            LCD_12864_DAT(0x00);  //对12864所有地址全部写零,此处每写一个,row地址自动加1
        }
    }
}

/*
 *LCD初始化函数
 *
 */
void Init_12864_HS(void)
{
    chekbusy12864();              //调用检忙函数
    LCD_12864_CMD(0xc0);          //1100 0000 设置显示起始行,此处起始行位后6位,第7位和第6位11表示设置起始行这个功能
    LCD_12864_CMD(0x3f);          //0011 1111 设置屏幕显示开关
}



/*
 *LCD显示8x16点函数
 *
*/
/*
    指令格式
    01-- ---- : 设置列地址    0100 0000:0x40
    1011 1--- : 设置行地址    1011 1000: 0xb8
    8page 64row
*/
void Display_8_point(uchar ch, uchar row, uchar page, uchar *adr)
{
    uchar i;                        //定义循环变量i
    CHOOSE_12864_SCREEN(ch);        //此处最好选择左右两屏需要显示的屏幕

    page = page << 1;               //程序中采用的位移运算代替乘法运算,这样可以大大降低处理器的负担
    row = row << 3;                 //此处移位的原因

    LCD_12864_CMD(row + 0x40);      //0100 0000 + row
    LCD_12864_CMD(page + 0xb8);     //1011 1000 + page

    for(i = 0; i < 8; i++)
    {
        LCD_12864_DAT(*(adr + i));  //adr是数组的首地址
    }

    LCD_12864_CMD(row + 0x40);
    LCD_12864_CMD(page + 0xb9);

    for(i = 8; i < 16; i++)         //此处i表示数据数组的下标,8x16显示,数据数组是每16位一组
    {
        LCD_12864_DAT(*(adr + i));
    }
}


/*
 *LCD显示16x16点函数
 *
*/
void Display_16_point(uchar ch, uchar row, uchar page, uchar *adr)
{
    uchar i;
    CHOOSE_12864_SCREEN(ch);

    page = page << 1;                                           
    row = row << 3;                  //此处移位的原因可能是需要保留一部分不显示

    LCD_12864_CMD(row + 0x40);       //0100 0000
    LCD_12864_CMD(page + 0xb8);      //1011 1000
    for(i = 0; i < 16; i++)          //16x16显示,数据数组每32位一组
    {
        LCD_12864_DAT(*(adr + i));
    }

    LCD_12864_CMD(row + 0x40);
    LCD_12864_CMD(page + 0xb9);
    for(i = 16; i < 32; i++)
    {
        LCD_12864_DAT(*(adr + i));         //adr是数组的首地址
    }
} 


/*
 *LCD数据读取函数
 *
*/
uchar DAT_READ_12864(uchar page, uchar arrange)  //page页地址.arrange列地址)
{
    uchar dat;                        //定义变量
    chekbusy12864();                  //调用检忙函数
    EA = 0;                           //关闭总中断
    LCD_12864_CMD(page + 0xb8);       //调用写指令函数写入页地址指令
    LCD_12864_CMD(arrange + 0x40);    //调用写指令函数写入列地址指令
    EX0 = 0;                          //关闭外部中断0                      
    P0  = 0xff;                       //初始化数据端口P0
    LCD_RW_OUT = 1;                   //设置读模式
    LCD_RS_OUT = 1;                   //设置数据模式
    LCD_E_OUT  = 1;                   
    LCD_E_OUT  = 0;                   //12864读数据时第二次读才有效,第一次读取的值不采集
    LCD_E_OUT  = 1;                   //二次使能有效
    dat = P0;                         //独处P0口的数据读出8位数据
    LCD_E_OUT = 0;                    //所存P0口
    EX0=1;                            //打开外部中断0
    EA = 1;                           //打开总中断

    return(dat);                      //将读出的数据作为返回值
}


/*
 *LCD反白显示函数
 *
*/
void Display_16_point_fb(uchar ch, uchar arrange, uchar page)
{
    uchar i;                         //定义循环变量
    uchar xdata dat_fb[32];          //定义反白显示数组
    CHOOSE_12864_SCREEN(ch);         //选择屏幕
    for(i = 0; i < 16; i++)          //循环进行反白
    {
        dat_fb = ~(DAT_READ_12864((page << 1), ((arrange << 3) + i)));
        dat_fb[i+16]=~(DAT_READ_12864((page << 1) + 1, ((arrange << 3) + i)));
    }
    Display_16_point(ch, arrange, page, dat_fb);      //调用16x16显示函数,将反白的数据显示
}

/*
 *LCD划线函数
 *
*/              
//y1比y2小,这里给出画竖线的函数而不用画点的方法  
//是为了减少单片机的处理负担
void DRAW_TRANSVERSE_Line(uchar y1, uchar y2, uchar x)//y1表示起点,y2表示终点,x表示列地址
{
    uchar i;
    uchar sum = 0;
    if(x > 63)                             //如果x比63大,则行地址在右半屏
    {
        CHOOSE_12864_SCREEN(1);            //选择右半屏
        x = x - 64;                        //确定右半屏行地址
    }
    else
    {
        CHOOSE_12864_SCREEN(0);            //选择了左半屏显示
    }

    if((y1 / 8) != (y2 / 8))               
    {
        for(i = 0; i < (8 - y1 % 8); i++)         
        {
            sum = sum | ((2 << ((y1 % 8) + i)));
        }
        LCD_12864_CMD(x + 0x40);
        LCD_12864_CMD(y1 / 8 + 0xb8);
        LCD_12864_DAT(sum);
        sum = 0;
        for(i = 0; i < (y2 / 8 - y1 / 8 - 1); i++)
        {
            LCD_12864_CMD(x + 0x40);
            LCD_12864_CMD((y1 / 8) + 0xb9 + i);
            LCD_12864_DAT(0xff);
        }
        for(i = 0; i <= (y2 % 8); i++)
        {
            sum = sum | (2 << i);
        }
        LCD_12864_CMD(x + 0x40);
        LCD_12864_CMD(y2 / 8 + 0xb8);
        LCD_12864_DAT(sum | 1);
        sum = 0;        
    }
    else
    {
        for(i = 0; i <= y2 - y1; i++)
        {
            sum = sum | (2 << (i + (y1 % 8)));
        }
        LCD_12864_CMD(0x40 | x);
        LCD_12864_CMD(0xb8 | (y1 / 8));
        LCD_12864_DAT(sum);
    } 
}

/*
 *LCD划点函数
 *x横坐标,y纵坐标左上角为0,0
*/  
void DRAW_DOT_HS(uchar x, uchar y)
{
    uchar dat;
    if(x > 63)
    {
        CHOOSE_12864_SCREEN(1);        //选右屏
        x = x - 64;
    }
    else
    {
        CHOOSE_12864_SCREEN(0);        //选左屏
    }

    dat = DAT_READ_12864(y / 8, x);    //读取lcd的行地址为y/8,列地址为x处的点的内容
    LCD_12864_CMD(0x40 | x);           //向指令寄存器写入列指令
    LCD_12864_CMD(0xb8 | y / 8);       //此处内部地址标号是0-7     向指令寄存器写入行指令
    LCD_12864_DAT((1 << (y % 8)) | dat);
}

你可能感兴趣的:(51单片机驱动)