实验具体内容

本实验利用单片机并行口实现 LCD12864 的显示控制。实验原理图如下图 4.9.1 所示。

img

连线关系:

img

流程图

实验过程

  1. 关掉实验箱电源。将 MCU 板,LCD 板插接在母板上。将 LCD12864 插接在 LCD 板的J4M7 上,按照前面连接关系表将硬件连接好。
  2. 在仿真器断电情况下将仿真器的仿真头插在 MCU 板的 CPU 插座上。将仿真器与开发 PC 机的通信口连接好,打开实验箱及仿真器的电源。
  3. 运行 Keil uVision2 开发环境,建立工程 LCD12864_c.uV2,CPU 为 AT89S51,包含启动文件 STARTUP.A51。
  4. 按照实验功能要求创建源程序 LCD12864.c 并加入到工程 LCD12864_c.uV2,并设置工程 LCD12864_c.uV2 属性,将其晶振频率设置为 11.0592MHz,选择输出可执行文件,DEBUG 方式选择硬件 DEBUG,并选择其中的“WAVE V series MCS51 Driver”仿真器。
  5. 构造(Build)工程 LCD12864_c.uV2。如果编程有误进行修改,直至构造正确为止。
  6. 运行程序,观察结果否符合程序要求,若不符合,分析出错原因,继续重复第 4、5 步的步骤,直至结果正确。

实验结果:显示姓名学号成功。

实验源程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
/************************必要的变量定义****************************/ 
#include <reg51.h>
#include <intrins.h>
#define uint unsigned int
#define uchar unsigned char
#define DATA P1
sbit PSB=P0^0;
sbit EN=P0^1;
sbit RW=P0^2;
sbit RS=P0^3;  
uchar code name1[] = "姓名1";
uchar code name2[] = "姓名2";
uchar code number1[] = "学号1";
uchar code number2[] = "学号2";
/**********************************延时子程序**********************/
void delay(uint xms)
{
uint i,j;
for(i=xms;i>0;i--)
for(j=110;j>0;j--);
}
/**********************************LCD 忙检查子程序**********************/
void CheckState()
{
uchar dat;
RS = 0;
RW = 1;
do
{
DATA = 0x00;
EN = 1;
_nop_();
dat = DATA;
EN = 0;
dat = 0x80&dat; //检查忙信号
}while(!(dat==0x00)); //当忙信号为0时跳出循环
}
/**********************************写命令子程序**********************/
void SendCommandToLCD(uchar com)
{
CheckState();
RS=0; //写命令
RW=0;
delay(1);
DATA=com;
delay(1);
EN=1; //利用 EN 下降沿完成命令写操作
_nop_();
_nop_();
EN=0;
delay(2);
}
void Write_Data(uchar dat) //写数据
{
CheckState();
RS=1;
RW=0;
DATA=dat;
EN=1;
_nop_();
_nop_();
EN=0;
}
void SelectLine(uchar Line) //选择显示行数
{
uchar temp;
switch(Line)
{
case 1:    
       temp = 0x80;         //在第一行显示
       break;
case 2:
       temp = 0x90;        //在第二行显示
       break;
case 3:
       temp = 0x88;        //在第三行显示
       break;
case 4:
       temp =0x98;         //在第四行显示
       break;
}
SendCommandToLCD(temp);
}
/**********************************初始化子程序**********************/
void InitLCD()
{
   PSB = 1;
   RW = 0;
   EN = 0;
   SendCommandToLCD(0x30);        //8位数据总线,基本指令操作
   SendCommandToLCD(0x08);        //显示关,光标关闭,不闪烁
   SendCommandToLCD(0x01);        //清屏并重置地址计数器
   SendCommandToLCD(0x06);        //进入模式设置指令中 I/D=1,S=0,地址自动增加
   SendCommandToLCD(0x0c)         //显示开,光标关闭,不闪烁
   
}

void Display1(uchar x,uchar y){
uchar i;
SendCommandToLCD(pos[x]+y);
for(i=0;name1[i]!='\0';i++){
Write_Data(school[i]);
}
}

void Display2(uchar x,uchar y){
   uchar i;
   SendCommandToLCD(pos[x]+y);
for(i=0;name2[i]!='\0';i++)  
       Write_Data(name[i]);
}
void Display3(uchar x,uchar y){
   uchar i;
   SendCommandToLCD(pos[x]+y);
for(i=0;number1[i]!='\0';i++)  
       Write_Data(num[i]);
}

void Display4(uchar x,uchar y){
   uchar i;
   SendCommandToLCD(pos[x]+y);
for(i=0;number2[i]!='\0';i++)      
       Write_Data(num[i]);
}


/***************************主程序*****************************/
void main()
{  
   uchar i;
   delay(10);
   InitLCD();
   //第一行显示
   Display1(0,0);
   //第二行显示
      Display2(1,0);
   //第三行显示
   Display4(2,0);
   //第四行显示
   Display4(3,0);
while(1);  
}

12864总结与拓展

①最重要的是编码方式!编码方式!编码方式!

最开始仿真一直用的是keil5进行仿真,到了LCD12864硬件仿真也是用keil5,但是每次烧录后汉字部分总是会乱码。初步也是怀疑是编码方式不同造成的乱码,keil5默认的编码方式与LCD12864不同,但是不知道该调整keil5的编码方式为哪种,而且也只是初步怀疑而已。直到实验的过程中意外地用keil5打开了在keil2编写的项目,发现只要在keil5选中在keil2写的中文注释,中文就会乱码。于是我将在keil5中写的代码复制到keil2中,发现在keil5中编写的正常的中文字符串或中文注释全部都会乱码,而且定义的字符串在keil2中的乱码的情况刚好和LCD12864上的乱码情况一模一样,于是我肯定keil2默认的编码方式和LCD12864汉字库的编码方式是相同的,但和keil5默认的编码方式不同。

总结了一下编码方式的不同导致的后果:其实编码方式的不同会直接影响编辑的源代码的编码,从将keil5的源程序复制到keil2的项目上出现乱码就体现了,但是直观的体现应该是在编译后形成的目标文件上,因为编码方式的不同,在源程序文件编译成目标文件后,c语言或汇编语言会被编译为机器语言,如果编码方式不同,同一个汉字的编码不同,呈现的二进制数也不同。例如如果“山”在UTF-8中的编码为1,在ANSI中的编码为2;“大”在UTF-8中编码2,在ANSI的编码方式为1,那么在ANSI的编码方式下程序想要输出显示字符串“山大”,对应的编码就是“2”和“1”,如果连接了UTF-8编码方式的显示屏,就会显示“大山”。

img

查资料说keil2默认是UTF-8编码方式,那么如果LCD12864字库的编码方式也应为UTF-8。而keil5可以在configuration上看到是ANSI编码方式,那么这样其实我们也可以修改在keil5上的编码方式为UTF-8,再重新编译中文字符串,按理来说也是也是可以正常显示的。这一点是在写实验报告时想到的,因此没有具体去实践……

②LCD12864显示控制的实现方法:

首先需要初始化端口电平(PSB=1,RW,RS,E按需要设置高低电平)和初始化各种显示屏设置(显示开关、显示光标开关、光标闪烁开关等),然后是写数据到显示屏之前应该选择显示的位置(第1行为0x80,第2行为0x90,第3行为0x88,第4行为0x98),利用for循环(!=’\0’),输出显示字符串即可。

③关于LCD12864的总结(拓展):

Protues仿真LCD12864和实际用的LCD12864管脚有些不同,proteus仿真所用的LCD12864还有CS1和CS2引脚,主要是用于分屏显示,但实际用的LCD12864没有分屏显示,但多出一个PSB引脚。查资料说,带字库的LCD12864和不带字库的LCD12864用取模软件取模时的设置应不同,proteus的是不带字库的LCD12864,而我们硬件实验所用的是带字库的。

不带字库的12864取模:

img

带字库的12864取模:

img

于是出于好奇心,我用不带字库的取模方式去玩了一下proteus的不带字库LCD12864:

img

实验源程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
/************************必要的变量定义****************************/ 
#include <reg51.h>
#include <intrins.h>
#define uint unsigned int
#define uchar unsigned char
#define DATA P0
sbit RS=P2^2;
sbit RW=P2^1;
sbit EN=P2^0;
sbit cs1=P2^3;
sbit cs2=P2^4;
/****************************定义字库*********************************/
uchar code Hzk1[]=
{
0x00,0xE0,0x00,0xFF,0x10,0x20,0x08,0x08,0x08,0xFF,0x08,0x08,0xF8,0x00,0x00,0x00,
0x01,0x00,0x00,0xFF,0x00,0x81,0x41,0x31,0x0D,0x03,0x0D,0x31,0x41,0x81,0x81,0x00,/*"快",0*/

0x20,0x30,0xAC,0x63,0x10,0x20,0x50,0x48,0x44,0x43,0x44,0x48,0x50,0x20,0x20,0x00,
0x22,0x67,0x22,0x12,0x12,0x00,0xFE,0x42,0x42,0x42,0x42,0x42,0xFE,0x00,0x00,0x00,/*"给",1*/

0x00,0x40,0x42,0x44,0x58,0x40,0xC0,0xFF,0xC0,0x40,0x50,0x48,0x46,0x40,0x00,0x00,
0x20,0x20,0x10,0x08,0x04,0x03,0x00,0xFF,0x00,0x03,0x04,0x08,0x10,0x20,0x20,0x00,/*"米",2*/

0x10,0x10,0x10,0x10,0x10,0x10,0x10,0xFF,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x00,
0x00,0x00,0x00,0xFF,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0xFF,0x00,0x00,0x00,0x00,/*"古",3*/

0x00,0x00,0x00,0xFE,0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,0xFE,0x00,0x00,0x00,
0x80,0x40,0x30,0x0F,0x02,0x02,0x02,0x02,0x02,0x02,0x42,0x82,0x7F,0x00,0x00,0x00,/*"月",4*/

};

uchar code Hzk2[] =
{
0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,/*"一",0*/

0x40,0x30,0xEF,0x24,0x24,0x80,0xE4,0x9C,0x10,0x54,0x54,0xFF,0x54,0x7C,0x10,0x00,
0x01,0x01,0x7F,0x21,0x51,0x26,0x18,0x27,0x44,0x45,0x45,0x5F,0x45,0x45,0x44,0x00,/*"键",1*/

0x00,0x04,0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x04,0x00,0x00,
0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x00,/*"三",2*/

0x40,0x40,0x42,0xCC,0x00,0x04,0x44,0x64,0x5C,0x47,0xF4,0x44,0x44,0x44,0x04,0x00,
0x00,0x40,0x20,0x1F,0x20,0x44,0x44,0x44,0x44,0x44,0x7F,0x44,0x44,0x44,0x44,0x00,/*"连",3*/

0x00,0x00,0x00,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x33,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00/*"!",4*/
};

uchar code Hzk3[] =
{
0x00,0xE0,0xE0,0xE0,0x00,0xE0,0xF8,0xFC,0xFE,0xFC,0xE0,0xE0,0xE0,0xE0,0xC0,0x00,
0x00,0x3F,0x3F,0x3F,0x00,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x1F,0x07,0x00,0x00,/*赞2.bmp",0*/

0xF0,0xF8,0x7C,0x32,0xB3,0xB3,0xB3,0x03,0x03,0xB3,0xB3,0xB3,0x32,0x7C,0xF8,0xF0,
0x0F,0x1F,0x30,0x70,0xFF,0xFF,0xFF,0x00,0x00,0xFF,0xFF,0xFF,0x70,0x30,0x1F,0x0F,/*币.bmp",0*/

0xC0,0xE0,0xE0,0xE0,0xF0,0xF8,0xFC,0xFF,0xFF,0xFC,0xF8,0xF0,0xE0,0xE0,0xE0,0xC0,
0x00,0x00,0x01,0xFF,0xFF,0x7F,0x3F,0x1F,0x1F,0x3F,0x7F,0xFF,0xFF,0x01,0x00,0x00/*收藏.bmp",0*/
};

/**********************************延时子程序**********************/
void delay(uint xms)
{
uint i,j;
for(i=xms;i>0;i--)
for(j=110;j>0;j--);
}
/**********************************LCD 忙检查子程序**********************/
void CheckState()
{
uchar dat;
RS = 0;
RW = 1;
do
{
DATA = 0x00;
EN = 1;
_nop_();
dat = DATA;
EN = 0;
dat = 0x80&dat; //检查忙信号
}while(!(dat==0x00)); //当忙信号为0时跳出循环
}
/**********************************写命令子程序**********************/
void SendCommandToLCD(uchar com)
{
CheckState();
RS=0; //写命令
RW=0;
DATA=com;
EN=1; //利用 EN 下降沿完成命令写操作
_nop_();
_nop_();
EN=0;
}
void SetLine(uchar page) //设置页码,页码为 0~7
{
page=0xb8page;        //0xb8 = 10111000
SendCommandToLCD(page);
}
void SetStartLine(uchar startline) //设置起始行,行号为 0~63
{
startline=0xc0startline;          //0xc0 = 11000000
SendCommandToLCD(startline);
}
void SetColumn(uchar column) //设置列,列号为 0~63
{
column=column&0x3f;        //0x3f = 00111111
column=0x40column;        //0x40 = 01000000
SendCommandToLCD(column);
}
void SetOnOff(uchar onoff) //开关显示屏,onoff 只能为 0 或 1
{
onoff=0x3eonoff;
SendCommandToLCD(onoff);
}
void WriteByte(uchar dat) //写数据
{
CheckState();
RS=1;
RW=0;
DATA=dat;
EN=1;
_nop_();
_nop_();
EN=0;
}
void SelectScreen(uchar screen) //选屏,screen=0,1,2
{
switch(screen)
{
case 0: cs1=0; //全屏显示
_nop_();
_nop_();
_nop_();
cs2=0;
_nop_();
_nop_();
_nop_();
break;
case 1: cs1=0; //左屏显示
_nop_();
_nop_();
_nop_();
cs2=1;
_nop_();
_nop_();
_nop_();
break;
case 2: cs1=1; //右屏显示
_nop_();
_nop_();
_nop_();
cs2=0;
_nop_();
_nop_();
_nop_();
break;
}
}
void ClearScreen(uchar screen) //清屏,screen=0,1,2
{
uchar i,j;
SelectScreen(screen);
for (i=0;i<8;i++)      //将8页全部清屏
{
SetLine(i);
SetColumn(0);
for(j=0;j<64;j++)
{
WriteByte(0x00); //写数据列地址将自动加 1
}
}
}
/**********************************初始化子程序**********************/
void InitLCD()
{
   CheckState();
   SelectScreen(0);
   SetOnOff(0);            //关屏
   SelectScreen(0);    
   SetOnOff(1);            //开屏
   SelectScreen(0);
   ClearScreen(0);         //清屏
   SetStartLine(0);        //开始行为0
}
/************************显示全角汉字**********************/
void Display1(uchar ss,uchar page,uchar column,uchar number)
{
int i;
SelectScreen(ss); //ss 为屏号
SetLine(page); //page 为页号,显示第 number 个汉字的上半部分,
//page 可理解为要显示的汉字位于屏幕的第 page 行
column=column&0x3f; //column 为列号
SetColumn(column);
for(i=0;i<16;i++) //i 为一个字里面的各个列
{
WriteByte(Hzk1[i+32*number]); //number 为字号,
//取第 number 个汉字的第 i 列数据编码值
}
SetLine(page+1); //显示第 number 个汉字的下半部分
SetColumn(column);
for(i=0;i<16;i++)
{
WriteByte(Hzk1[i+32*number+16]);//取第 number 个汉字的下半部分
//第 i 列数据编码值
}
}

void Display2(uchar ss,uchar page,uchar column,uchar number)
{
int i;
SelectScreen(ss); //ss 为屏号
SetLine(page); //page 为页号,显示第 number 个汉字的上半部分,
//page 可理解为要显示的汉字位于屏幕的第 page 行
column=column&0x3f; //column 为列号
SetColumn(column);
for(i=0;i<16;i++) //i 为一个字里面的各个列
{
WriteByte(Hzk2[i+32*number]); //number 为字号,
//取第 number 个汉字的第 i 列数据编码值
}
SetLine(page+1); //显示第 number 个汉字的下半部分
SetColumn(column);
for(i=0;i<16;i++)
{
WriteByte(Hzk2[i+32*number+16]);//取第 number 个汉字的下半部分
//第 i 列数据编码值
}
}

void Display3(uchar ss,uchar page,uchar column,uchar number)
{
int i;
SelectScreen(ss); //ss 为屏号
SetLine(page); //page 为页号,显示第 number 个汉字的上半部分,
//page 可理解为要显示的汉字位于屏幕的第 page 行
column=column&0x3f; //column 为列号
SetColumn(column);
for(i=0;i<16;i++) //i 为一个字里面的各个列
{
WriteByte(Hzk3[i+32*number]); //number 为字号,
//取第 number 个汉字的第 i 列数据编码值
}
SetLine(page+1); //显示第 number 个汉字的下半部分
SetColumn(column);
for(i=0;i<16;i++)
{
WriteByte(Hzk3[i+32*number+16]);//取第 number 个汉字的下半部分
//第 i 列数据编码值
}
}

/***************************主程序*****************************/
void main()
{
delay(10);
   InitLCD();
   ClearScreen(0);
           SetStartLine(0);
           Display1(1,0,1*16,0);
           Display1(1,0,2*16,1);  
           Display1(1,0,3*16,2);  
           Display1(2,0,4*16,3);
           Display1(2,0,5*16,4);

           Display2(1,2,1*16,0);
           Display2(1,2,2*16,1);
           Display2(1,2,3*16,2);
           Display2(2,2,4*16,3);
           Display2(2,2,5*16,4);

           Display3(1,4,1*16,0);
           Display3(1,4,3*16,1);
           Display3(2,4,5*16,2);

           while(1);
}