LCD12864结构

image-20251114034517357

image-20251114034419225

由于我们的目的是将LCD12864以无字库的方式使用,因此只需要着重考虑GDRAM即可

指令 RS RW DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0 说明 执行时间(540KHZ)
功能设定 0 0 0 0 1 DL X RE X X DL=1(必须设为 1)RE=1:扩充指令集动作RE=0:基本指令集动作 -
设定绘图 RAM 地址 0 0 1 AC6 AC5 AC4 AC3 AC2 AC1 AC0 设定 CGRAM 地址到地址计数器(AC) 72us
扩充功能设定 0 0 0 0 1 1 X RE G 0 RE=1:扩充指令集动作RE=0:基本指令集动作G=1:绘图显示 ONG=0:绘图显示 OFF 72us

上面则是主要用到的相关cmd

任务流程

绘图显示RAM提供64×32个位元组的记忆空间,最多可以控制256×64点的二维也纳绘图缓冲空间, 在更改绘图RAM时,先连续写入水平与垂直的坐标值,再写入两个8位元的资料到绘图RAM,而地址计 数器(AC)会自动加一;在写入绘图RAM的期间,绘图显示必须关闭,整个写入绘图RAM的步骤如下:

1、关闭绘图显示功能。

2、先将水平的位元组坐标(X)写入绘图RAM地址;

3、再将垂直的坐标(Y)写入绘图RAM地址;

4、将D15——D8写入到RAM中;

5、将D7——D0写入到RAM中;

6、打开绘图显示功能。

那么从上面的GDRAM可以到,此处的X其实只有0-15,也就是直接控制无法精确控制x的位置。同时y的实际范围是0-31,那么在y>31的情况就需要考虑x的额外控制了。上面的都是些麻烦活,目前已经完成了对x的不连续处理。

TODO y的不连续越界处理

注意事项

  • 速率问题,尤其是RE扩展指令集的切换,该命令明显需要更长的时间执行

  • 在进入绘图模式后,cmd:0x01无法完成清屏操作,需要手动全部写0x00

效果展示

IMG20251114043836

那么也可以看到,“G”在越界后无法正常显示

代码

警告,代码未对任何越界进行判断处理。请自行处理越界问题

LCD12864.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#ifndef _lcd12864_H
#define _lcd12864_H

typedef unsigned char uint8_t ;
typedef unsigned int uint16_t ;


void lcd_w_cmd(uint8_t wcmd);
void lcd_w_data(uint8_t wdata);
void lcd_init();
void lcd_clean();
void lcd_draw_char(uint16_t x, uint8_t y, uint8_t *p);

#endif

LCD12864.c

其实代码的主要篇幅用在了x的不连续处理上

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
#include <STC89C5xRC.H>

#include "lcd12864.h"

void _nop_(void);


#define lcd_databus P0 // LCD的8位数据总线
sbit RS = P2^6;
sbit RW = P2^5;
sbit EN = P2^7;
// sbit PSB =
// 扩充指令集中无BF读取,故该代码采用延时来确保指令间不冲突

#define LCD_SET_CMD RS = 0
#define LCD_SET_DATA RS = 1
#define LCD_SET_WRITE RW = 0
#define LCD_SET_READ RW = 1

#define LCD_RE_DISABLE 0x30
#define LCD_RE_ENABLE 0x34
#define LCD_DRAW_ENABLE 0x36
#define LCD_DRAW_DISABLE 0x34 // RE = ENABLE

uint8_t jx(uint8_t a);
void Delay100us(void);
void Delay100us(void) //@11.0592MHz
{
unsigned char data i;

_nop_();
i = 43;
while (--i);
}

void lcd_w_cmd(uint8_t wcmd){
LCD_SET_CMD;
LCD_SET_WRITE;

lcd_databus = wcmd;

EN = 1;
Delay100us();
EN = 0;
Delay100us();
}

void lcd_w_data(uint8_t wdata){
LCD_SET_DATA;
LCD_SET_WRITE;

lcd_databus = wdata;

EN = 1;
Delay100us();
EN = 0;
Delay100us();
}
void lcd_clean(){
uint8_t i=0, j=0;
for(i=0; i<32; i++){
lcd_w_cmd(0x80 + i);
lcd_w_cmd(0x80);
for(j=0; j<32; j++){
lcd_w_data(0x00);
}
}
}
void lcd_init(){
uint8_t i=0;
while(i<40){
Delay100us();
i++;
}
lcd_w_cmd(LCD_RE_DISABLE);
lcd_w_cmd(0x01);
i=0;
while(i<40){
Delay100us();
i++;
}
lcd_w_cmd(LCD_DRAW_DISABLE);
Delay100us();
//Clean
lcd_clean();
lcd_w_cmd(LCD_DRAW_ENABLE);
}

// size:16x16
uint8_t jx(uint8_t a){
// 步骤1:交换相邻1位(将8位分为4组,每组2位,交换组内两位)
a = ((a & 0x55) << 1) | ((a & 0xAA) >> 1);
// 步骤2:交换相邻2位(将8位分为2组,每组4位,交换组内前2位和后2位)
a = ((a & 0x33) << 2) | ((a & 0xCC) >> 2);
// 步骤3:交换相邻4位(将8位分为1组,交换前4位和后4位)
a = ((a & 0x0F) << 4) | ((a & 0xF0) >> 4);
return a;
}

void lcd_draw_char(uint16_t x, uint8_t y, uint8_t *p){
uint8_t i=0;
uint16_t ls = 0;
lcd_w_cmd(LCD_DRAW_DISABLE);

// TODO y越界
if(y>31){
y -= 32;
x += 128;
}
// 判断x
if(x%16){
if(x%16<8){
for(i=0; i<16; i++){
ls = jx(*(p+i));
ls = ls << 8;
ls |= jx(*(p+16+i));

lcd_w_cmd(0x80 | (y+i));
lcd_w_cmd(0x80 | x/16);

lcd_w_data(ls >> (8+(x%16)));
lcd_w_data(ls >> (x%16));


lcd_w_cmd(0x80 | (y+i));
lcd_w_cmd(0x80 | x/16+1);

lcd_w_data((ls<<(16-(x%16)))>>8);
lcd_w_data(0x00);
}
}else{
for(i=0; i<16; i++){
ls = jx(*(p+i));
ls = ls << 8;
ls |= jx(*(p+16+i));

lcd_w_cmd(0x80 | (y+i));
lcd_w_cmd(0x80 | x/16);

lcd_w_data(0x00);
lcd_w_data(ls >> (x%16));


lcd_w_cmd(0x80 | (y+i));
lcd_w_cmd(0x80 | x/16+1);

lcd_w_data((ls<<(16-(x%16)))>>8);
lcd_w_data((ls<<(16-(x%16)))>>0);
}
}
}else{ // 整字
for(i=0; i<16; i++){
lcd_w_cmd(0x80 | (y+i));
lcd_w_cmd(0x80 | x/16);

lcd_w_data(jx(*(p+i)));
lcd_w_data(jx(*(p+16+i)));
}
}

//lcd_w_cmd(LCD_RE_ENABLE);



lcd_w_cmd(LCD_DRAW_ENABLE);

}


main.c

注意,字模提取采用 行列式,且固定为16点全角

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <STC89C5xRC.H>
#include "lcd12864.h"

const uint8_t zh16x16[][32] = {
/* 0 C */ {0x00,0x00,0x00,0x7c,0x82,0x02,0x01,0x01,0x01,0x01,0x01,0x02,0x82,0x7c,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,},
/* 1 Y */ {0x00,0x00,0x00,0x41,0x41,0x22,0x22,0x14,0x14,0x08,0x08,0x08,0x08,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,},
/* 2 G */ {0x00,0x00,0x00,0xfc,0x02,0x02,0x01,0x01,0xe1,0x01,0x01,0x02,0x02,0xfc,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x01,0x01,0x01,0x01,0x01,0x00,0x00,0x00,}
};

void main()
{
int i=0, j=0, k=0;
lcd_init();
lcd_draw_char(13, 41, zh16x16[0]);
lcd_draw_char(0, 0, zh16x16[1]);
lcd_draw_char(71, 21, zh16x16[2]);
while(1){

}
}