原文链接: http://bbs.elecfans.com/forum.php?mod=viewthread&tid=282698&extra=&highlight=12864&page=1
参考帖子:http://home.eeworld.com.cn/my/space-uid-159112-blogid-40752.html
http://v.youku.com/v_show/id_XNDYwOTM2Njc2.html
LCD12864是一种常用的图形液晶显示模块,顾名思义,就是可以在水平方向显示128个点,在竖直方向显示64个点。通过对控制芯片写入数据,可以控制点的亮灭,从而显示字符、数字、汉字或者自定义的图形。尽管LCD12864有各个不同厂家生产的产品,控制芯片和引脚定义也不尽相同,但是控制原理都大同小异。本文是对我个人使用LCD12864的经验做一个总结,希望能对入门者起到抛砖引玉的作用。
就以深圳市亚晶达电子有限公司生产的YJD12864C-1为例,我不想深究显示屏的内部结构,单讲讲各个引脚的作用以及数据读写时的时序。
上图是YJD12864C-1的实物图,从右往左,1脚到20脚的定义如下:
1:VSS,接地端
2:VDD,电源正,接+5V
3:VO,对比度调整,一般接+5V就行了
4:D/I(CS*),片选,也叫使能,接+5V
5:R/W(SID*),数据输入端
6:E(SCLK*),时钟输入端
7~14:DB0 ~ DB7,并行数据总线
15:PSB,串并模式选择,串行模式下接地,并行模式下接+5V
16:NC,空引脚,不需要连接
17:RSTB,复位端,低电平有效,一般接+5V就行了
18:VEE,空引脚,不需要连接
19:BLA,背光正极,接+5V
20: BLK,背光负极,接地
在实际编程时,有串行、并行两种模式可以选择。个人觉得,并行模式占用单片机引脚多(11个),优点是速度快(一次传8位数据,速度自然快),串行模式占用引脚少(2个),速度慢点。我喜欢使用串行模式,AVR单片机的时钟频率最快可达20MHZ(不用除以12),经过实测,从头到尾刷一次屏大约只需0.1s,这在很多场合已经够用了。由于并行模式用的少,不熟悉,下面只讲串行模式。
在串行模式下,硬件的连接为:1、15、20接地,2、3、4、17、19接+5V,5接单片机SPI输出(下图第6脚),6接单片机SPI时钟信号输出(下图第8脚)。
ATMEGA324引脚图
下面介绍程序:
1、底层数据通信程序:包括SPI设置,SPI发送单字节,LCD写数据,LCD初始化
//////////////////////////////////////////////////////////////////////////////////////////
#define F_CPU 8//时钟频率8MHZ
#include<ioavr.h>
#include"delay.h"
//////////////////////////////////////////////////////////////////////////////////////////
上面是开头部分,其中delay.h内容如下:
#ifndef __IAR_DELAY_H
#define __IAR_DELAY_H
#include <intrinsics.h>
#define delay_us(x) __delay_cycles((unsigned long)(x * F_CPU))
#define delay_ms(x) __delay_cycles((unsigned long)(x * F_CPU*1000UL))
#define delay_s(x) __delay_cycles((unsigned long)(x * F_CPU*1000000UL))
#endif
//////////////////////////////////////////////////////////////////////////////////////////
SPI设置:
void SPI_MasterInit(void)
{
DDRB=(1<<7)|(1<<5)|(1<<4);//(1<<5)|(1<<7);//设置MOSI和SCK 为输出
SPCR = (1<<6)|(1<<4);// 1<<SPE 1<<MSTR 使能SPI,主机模式
SPSR = 1<<0;// 1<<SPI2X倍速
}
//////////////////////////////////////////////////////////////////////////////////////////
SPI发送单字节:
void SPI_MasterTransmit(char cData)
{
SPDR = cData;
while(!(SPSR & (1<<7)));//1<<SPIF
}
LCD写数据:
void LCD_Write (char RS,char content)//RS=1发数据RS=0发命令
{ charStart,High4,Low4;
Start=0xf8|(RS<<1);
High4=content&0xf0;
Low4=(content<<4)&0xf0;
SPI_MasterTransmit(Start);//发送开始字节,前面5个1,倒数第二位RS
SPI_MasterTransmit(High4);//发送数据高4位
SPI_MasterTransmit(Low4);//发送数据低4位
delay_us(300);
}
//////////////////////////////////////////////////////////////////////////////////////////
LCD初始化
void LCD_INIT()
{
LCD_Write(0,0x30); /*30---基本指令动作*/
LCD_Write(0,0x01); /*清屏,地址指针指向00H*/
LCD_Write(0,0x06); /*光标的移动方向*/
LCD_Write(0,0x0c); /*开显示,关游标*/
}
//////////////////////////////////////////////////////////////////////////////////////////
2、应用层程序,包括汉字显示,字符显示,图形显示等等
汉字显示:
分4行,一行可显示8个汉字,每个汉字占16*16个格
void Show_Chinese(char x0,char y0,chark,char *chn)
//x0,y0为显示位置x0: 0~3, y0: 0~7, k为汉字个数, chn为汉字数组
{
charadr,i;
switch(x0)
{
case0: adr = 0x80 + y0;break; //在第1行y列显示
case1: adr = 0x90 + y0;break; //在第2行y列显示
case 2: adr = 0x88 + y0;break; //在第3行y列显示
case3: adr = 0x98 + y0;break; //在第4行y列显示
default:;
}
LCD_Write(0,adr);
for(i=0;i<2*k;i++)
LCD_Write(1,chn);
}
//////////////////////////////////////////////////////////////////////////////////////////
显示字符串:
分4行,一行可显示16个汉字,每个汉字占8*16个格
void Show_String(char x0,char y0,chark,char *chn)
//x0,y0为显示位置x0: 0~8, y0:0~3, k为字符串个数, chn为字符串
{
charadr,i;
switch(x0)
{
case0: adr = 0x80 + y0;break; //在第1行y列显示
case1: adr = 0x90 + y0;break; //在第2行y列显示
case2: adr = 0x88 + y0;break; //在第3行y列显示
case3: adr = 0x98 + y0;break; //在第4行y列显示
default:;
}
LCD_Write(0,0x30);
LCD_Write(0,adr);
for(i=0;i<k;i++)
LCD_Write(1,chn);
}
//////////////////////////////////////////////////////////////////////////////////////////
显示数字:
void Show_Number(char x0,char y0,charnum)//显示两位数字
//x0,y0为显示位置x0: 0~8, y0:0~3,显示数字位数可调
{
charadr;
switch(x0)
{
case0: adr = 0x80 + y0;break; //在第1行y列显示
case1: adr = 0x90 + y0;break; //在第2行y列显示
case2: adr = 0x88 + y0;break; //在第3行y列显示
case3: adr = 0x98 + y0;break; //在第4行y列显示
default:;
}
LCD_Write(0,0x30);
LCD_Write(0,adr);
//LCD_Write(1,num/100%10+‘0‘);
//上句不注释则显示3位数字,要显示更多位可以此类推,不过要注意num的字长
LCD_Write (1,num/10%10+‘0‘);
LCD_Write(1,num%10+‘0‘);
}
//////////////////////////////////////////////////////////////////////////////////////////
显示图片:
char lcd_x,lcd_y;
void Show_Image(char *p) //水平扫描
//P含128*64/8=1024字节
//可用字模软件获得任意图片的水平扫描码
{
chari,j,k;
lcd_x=0x9f;
lcd_y=0x80;
LCD_Write(0,0x34);
for(i=0;i<2;i++)//分为上下两屏
{
for(j=0;j<32;j++)
{
LCD_Write(0,lcd_y+j);
LCD_Write (0,lcd_x);
for(k=0;k<16;k++)//写入显示数据
{ LCD_Write (1,*p++); }
}
lcd_x=0x87;
}
LCD_Write(0,0x36);
LCD_Write(0,0x30);
}
图片模式下清屏:
void Clear_Gcrom()
{
chari,j,k;
lcd_x=0x80;
lcd_y=0x80;
LCD_Write (0,0x34);
for(i=0;i<2;i++)
{
for(j=0;j<32;j++)
{
LCD_Write(0,lcd_y+j);
LCD_Write (0,lcd_x);
for(k=0;k<16;k++){ LCD_Write (1,0x00); }
}
lcd_x=0x88;
}
LCD_Write(0,0x36);
LCD_Write(0,0x30);
}
显示两字节,LCD12864每次至少刷新16格:
void Show_2Byte(char x,char y,chardat1,char dat2)// x:0~7 y:0~63
{
if(y<32)//下屏
{
x+=8;
y=31-y;
}
else//上屏
{
y=63-y;
}
LCD_Write (0,0x34);
LCD_Write (0,0x80+y);//y:0~31
LCD_Write (0,0x80+x);//x:0~15
LCD_Write (1,dat1);
LCD_Write (1,dat2);
LCD_Write (0,0x36);
LCD_Write (0,0x30);
}
刷新1行:
void Show_Hang(char y,char p[64][16]) //显示一行 y:0~63
{
chark,y0=y;
lcd_y=0x80;
LCD_Write(0,0x34);
if(y<32)
{
lcd_x=0x80+8;//下屏
y=31-y;
}
else
{
lcd_x=0x80;//上屏
y=63-y;
}
//LCD_Write(0,0x80+0);//y1:0~31
//LCD_Write (0,0x80+0);//x1:0~15
LCD_Write (0,lcd_y+y);
LCD_Write (0,lcd_x);
for(k=0;k<16;k++)//写入显示数据
{ LCD_Write (1,p[y0][k]); }
LCD_Write(0,0x36);
LCD_Write(0,0x30);
}
有了以上函数就可以轻松玩转LCD12864了,注意在main函数中应先执行初始化程序:
void main()
{
SPI_MasterInit();
LCD_INTI ();
while(1)
{
}
}
最后是我用ATMEGA324PA单片机和LCD12864做的一个贪吃蛇游戏,见开头视频。
有兴趣的可以看下
全部程序如下:
/***********************************************************************
LCD12864贪吃蛇
贪吃蛇游戏(不死版)
单片机:ATMEGA324PA
编程软件:IAR
编程语言:C++
屏幕:LCD12864
按键:上下左右
***********************************************************************/
<main.cpp>:
#define F_CPU 8
#include"delay.h"
#include <stdlib.h>
#include <math.h>
#include<ioavr.h>
#include "function.h"
#define K0 PINA_Bit0
#define K1 PINA_Bit1
#define K2 PINA_Bit2
#define K3 PINA_Bit3
#define K4 PINA_Bit4
#define K5 PINA_Bit5
class Point{
public:
char x;//x:0~127
char y;//y:0~63
Point(){};
Point(char m,char n){x=m;y=n;}
bool operator==(Point &a)
{
if(x==a.x&&y==a.y)return true;
else return false;
}
//char Get_x(){return x;}
//char Get_y(){return y;}
};
class Snake{
public:
char length;
Point body[64];//头:body[0]尾:body[length-1] 其余:空
Point food;
Snake()
{
length=3;
for(char i=0;i<64;i++)body=Point(0,0);
body[0]=Point(64,33);
body[1]=Point(64,32);
body[2]=Point(64,31);
}
void Go_ahead();
//void Go_back();
void Turn_left();
void Turn_right();
void Turn_up();
void Turn_down();
void Generate_food();
void Eat();
void Restart();
bool Near_food();
char Get_direction();
void Refresh(Point p);
};
void Snake::Go_ahead()
{
Point temp=body[0],temp1=body[length-1];
char v=Get_direction();
switch(v)
{
case 0:
if(body[0].y==63)Restart();
else body[0].y++;break;
case 1:
if(body[0].y==0)Restart();
else body[0].y--;break;
case 2:
if(body[0].x==0)Restart();
else body[0].x--;break;
case 3:
if(body[0].x==127)Restart();
else body[0].x++;break;
default:break;
}
if(body[0].x==food.x&&body[0].y==food.y)
{
length++;
for(char i=length-1;i>1;i--)
{
body=body[i-1];
}
body[1]=temp;
Generate_food();
}
else
{
for(char i=length-1;i>1;i--)
{
body=body[i-1];
}
body[1]=temp;
}
Refresh(body[0]);
Refresh(temp1);
}
void Snake::Turn_left()
{
Point temp1=body[length-1];
for(char i=length-1;i>0;i--)
{
body=body[i-1];
}
if(body[0].x==0)Restart();
else body[0].x--;
Refresh(body[0]);
Refresh(temp1);
}
void Snake::Turn_right()
{
Point temp1=body[length-1];
for(char i=length-1;i>0;i--)
{
body=body[i-1];
}
if(body[0].x==127)Restart();
else body[0].x++;
Refresh(body[0]);
Refresh(temp1);
}
void Snake::Turn_up()
{
Point temp1=body[length-1];
for(char i=length-1;i>0;i--)
{
body=body[i-1];
}
if(body[0].y==63)Restart();
else body[0].y++;
Refresh(body[0]);
Refresh(temp1);
}
void Snake::Turn_down()
{
Point temp1=body[length-1];
for(char i=length-1;i>0;i--)
{
body=body[i-1];
}
if(body[0].y==0)Restart();
else body[0].y--;
Refresh(body[0]);
Refresh(temp1);
}
void Snake::Generate_food()
{
char x,y,i=0;
while(i!=length)
{ x=rand()%128;
y=rand()%64;
food=Point(x,y);
for(i=0;i<length;i++)
{
if(food==body)break;
}
}
Refresh(food);
}
bool Snake::Near_food()
{
bool k;
if(body[0].x==food.x&&body[0].y==food.y)
k=true;
else k=false;
return k;
}
void Snake::Eat()
{
for(char i=length;i>0;i--)
{
body=body[i-1];
}
body[0]=food;
length++;
Generate_food();
}
//enum direction{up,down,left,right}
char Snake::Get_direction()
{
char i;
if(body[0].x==body[1].x)
{
if(body[0].y>body[1].y){i=0;}
else i=1;
}
else
{
if(body[0].x>body[1].x){i=3;}
else i=2;
}
return i;
}
void Snake::Refresh(Point p)
{
char temp1=0,temp2=0,a,b,c;
for(char i=0;i<2;i++)
{
a=p.x-p.x%8;
if((p.x/8)%2==0)
{
b=a+8;
c=b+8;
}
else
{
b=a;
a-=8;
c=b+8;
}
for(char i=0;i<length;i++)
{
if(body.y==p.y)
{
if(body.x>=a&&body.x<b)temp1|=(1<<7-body.x%8);
if(body.x>=b&&body.x<c)temp2|=(1<<7-body.x%8);
}
if(food.y==p.y)
{
if(food.x>=a&&food.x<b){temp1|=(1<<7-food.x%8);}
if(food.x>=b&&food.x<c){temp2|=(1<<7-food.x%8);}
}
}
Show_2Byte(p.x/16,p.y,temp1,temp2);
}
}
void WDT_off(void)
{
__disable_interrupt();
__watchdog_reset();
/* Clear WDRF in MCUSR */
MCUSR &= ~(1<<WDRF);
/* Write logical one to WDCE and WDE */
/* Keep old prescaler setting to prevent unintentional time-out */
WDTCSR |= (1<<WDCE) | (1<<WDE);
/* Turn off WDT */
WDTCSR = 0x00;
__enable_interrupt();
}
void WDT_Prescaler_Change(void)
{
__disable_interrupt();
__watchdog_reset();
/* Start timed equence */
WDTCSR |= (1<<WDCE) | (1<<WDE);
/* Set new prescaler(time-out) value = 64K cycles (~16 ms) */
WDTCSR = (1<<WDE);
__enable_interrupt();
}
void Snake::Restart()
{
//WDT_Prescaler_Change();
}
void main()
{
WDT_off();
Snake Snake1;
DDRB=0xff;
PORTA=0x00;
DDRA=0x00;
SPI_MasterInit();
LCD_INIT();
Clear_Gcrom();
Snake1.Generate_food();
while(1)
{
{
delay_ms(50);
Snake1.Go_ahead();
if(!K2)
{
Snake1.Turn_left();
}
if(!K3)
{
Snake1.Turn_up();
}
if(!K4)
{
Snake1.Turn_right();
}
if(!K5)
{
Snake1.Turn_down();
}
//if(Snake1.body[0].x<0||Snake1.body[0].x>127||Snake1.body[0].y<0||
Snake1.body[0].y>63)
//Snake1.Die();
}
}
}
///////////////////////////////////////////////////////////////////////////
<LCD12864.c>:
#define F_CPU 8
#include<ioavr.h>
#include"delay.h"
void SPI_MasterInit(void)
{
/* Set MOSI and SCK output, all others input */
//DDRB =(1<<7)|(1<<5)|(1<<4);//(1<<5)|(1<<7);
/* Enable SPI, Master, set clock rate fck/16 */
SPCR = (1<<6)|(1<<4);//|(1<<1)|0x01;//1<<SPE 1<<MSTR 1<<SPR1//128分频
SPSR = 1<<0;//SPI2X倍速
}
void SPI_MasterTransmit(char cData)
{
/* Start transmission */
SPDR = cData;
/* Wait for transmission complete */
while(!(SPSR & (1<<7)));//1<<SPIF
}
void LCD_Write (char RS,char content)
{ char Start,High4,Low4;
Start=0xf8|(RS<<1);
High4=content&0xf0;
Low4=(content<<4)&0xf0;
//SS=1;
SPI_MasterTransmit(Start);
SPI_MasterTransmit(High4);
SPI_MasterTransmit(Low4);
//SS=0;
delay_us(300);
}
/*-----------------------------------*/
void LCD_INIT(void)
{
LCD_Write (0,0x30); /*30---基本指令动作*/
LCD_Write (0,0x01); /*清屏,地址指针指向00H*/
LCD_Write (0,0x06); /*光标的移动方向*/
LCD_Write (0,0x0c); /*开显示,关游标*/
}
/*--------------清DDRAM------------------*/
void ClearRam(void)
{
LCD_Write (0,0x30);
LCD_Write (0,0x01);
}
char lcd_x,lcd_y;
void Clear_Gcrom()
{
char i,j,k;
lcd_x=0x80;
lcd_y=0x80;
LCD_Write (0,0x34);
for(i=0;i<2;i++)
{
for(j=0;j<32;j++)
{
LCD_Write (0,lcd_y+j);
LCD_Write (0,lcd_x);
for(k=0;k<16;k++) { LCD_Write (1,0x00); }
}
lcd_x=0x88;
}
LCD_Write (0,0x36);
LCD_Write (0,0x30);
}
void Show_2Byte(char x,char y,char dat1,char dat2)// x:0~7 y:0~63
{
if(y<32)//下屏
{
x+=8;
y=31-y;
}
else//上屏
{
y=63-y;
}
LCD_Write (0,0x34);
LCD_Write (0,0x80+y);//y:0~31
LCD_Write (0,0x80+x);//x:0~15
LCD_Write (1,dat1);
LCD_Write (1,dat2);
LCD_Write (0,0x36);
LCD_Write (0,0x30);
}
void Show_Hang(char y,char p[64][16]) //显示一行 y:0~63
{
char k,y0=y;
lcd_y=0x80;
LCD_Write (0,0x34);
if(y<32)
{
lcd_x=0x80+8;//下屏
y=31-y;
}
else
{
lcd_x=0x80;//上屏
y=63-y;
}
//LCD_Write (0,0x80+0);//y1:0~31
//LCD_Write (0,0x80+0);//x1:0~15
LCD_Write (0,lcd_y+y);
LCD_Write (0,lcd_x);
for(k=0;k<16;k++) //写入显示数据
{ LCD_Write (1,p[y0][k]); }
LCD_Write (0,0x36);
LCD_Write (0,0x30);
}
//////////////////////////////////////////////////////////////////////////////
<delay.h>:
#ifndef __IAR_DELAY_H
#define __IAR_DELAY_H
#include <intrinsics.h>
#define delay_us(x) __delay_cycles ((unsigned long)(x * F_CPU))
#define delay_ms(x) __delay_cycles ((unsigned long)(x * F_CPU*1000UL))
#define delay_s(x) __delay_cycles ((unsigned long)(x * F_CPU*1000000UL))
#endif
//////////////////////////////////////////////////////////////////////////////
<function.h>:
#ifndef _FUNCTION_INCLUDED
#define _FUNCTION_INCLUDED
extern void Show_Hang(char y,char p[64][16]); //显示一行 y:0~63
extern void LCD_INIT(void);
extern void Clear_Gcrom();
extern void ClearRam(void);
extern void SPI_MasterInit(void);
extern void Show_2Byte(char x,char y,char dat1,char dat2);// x:0~7 y:0~63
#endif