#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);
}
LCD12864经典驱动(详细注释)
最新推荐文章于 2024-02-24 22:52:53 发布