1、先确定屏幕的点阵结构点。
2、再确定汉字和acsII字符的数据
显示汉字和符号时候,地址累加是按照1号框大小计算和显示;
画图的时候,地址累加是按照2号框大小计算和显示;
X 坐标 |
||||||||
Line1 |
80H |
81H |
82H |
83H |
84H |
85H |
86H |
87H |
Line2 |
90H |
91H |
92H |
93H |
94H |
95H |
96H |
97H |
Line3 |
88H |
89H |
8AH |
8BH |
8CH |
8DH |
8EH |
8FH |
Line4 |
98H |
99H |
9AH |
9BH |
9CH |
9DH |
9EH |
9FH |
从表格可以看出屏幕的连续地址line1-->line3-->line2-->line4
画图时的地址是:写入行地址后写入列地址,每八个横点为一个列地址,最小单位是每8个横点,并且是字节控制8个点。此时有64行每行128个横点即每行16个列
地址定位: 比如i < 64先写命令0x80--0x80+i定位行再写列0x80+列。然后写数据一个字节。(记得是这样写的,不太确定,因为没用到画图做项目,但实践过一次)
首先汉字是两个字节一个汉字,ACSII字符是一个字节一个字符。并且编译器中的ACSII符号和中文的数据都是一样的。
比如字符‘1’的和显示屏的字库显示字符1的数据都是整数49。
汉字的数据也是和显示屏的数据一样。都是占用两个字节,而且两个字节数据一样。
使用原理:使用一个字符串,使用char类型指针一个写进屏幕地址。可以看汉字显示坐标。写两个字符显示一个汉字。0--127是ACSII,0xA0开头的是中文数据。程序下面会体现。
验证中文数据:
首先:看出爬字的两个字节为0xc5c0
其次:看C中的爬字的也是0xc5c0,运行vs2019观察得知是一样的数据
#include
int main()
{
char p1;
const char* p = { "爬狗" };
printf_s("%s",p);
p1 = (char)p;
printf_s("\r\n ");
for (;*p != '\0' ; p++)
{
printf_s("\r\n %x ",*p);
}
return 0;
}
得出结论:直接使用字符串进行按照字节打印输出。
引脚号 |
引脚名称 |
方向 |
功能说明 |
1 |
GND |
- |
模块的电源地 |
2 |
VCC |
- |
模块的电源正端 |
3 |
V0 |
- |
LCD 驱动电压输入端 |
4 |
RS(CS) |
H/L |
并行的指令/数据选择信号;串行的片选信号 |
5 |
R/W(SID) |
H/L |
并行的读写选择信号;串行的数据口 |
6 |
E(CLK) |
H/L |
并行的使能信号;串行的同步时钟 |
7 |
DB0 |
H/L |
数据 0 |
8 |
DB1 |
H/L |
数据 1 |
9 |
DB2 |
H/L |
数据 2 |
10 |
DB3 |
H/L |
数据 3 |
11 |
DB4 |
H/L |
数据 4 |
12 |
DB5 |
H/L |
数据 5 |
13 |
DB6 |
H/L |
数据 6 |
14 |
DB7 |
H/L |
数据 7 |
15 |
PSB |
H/L |
并/串行接口选择:H-并行;L-串行 |
16 |
NC |
空脚 |
|
17 |
/RST |
H/L |
复位 低电平有效 |
18 |
VOUT |
倍压输出脚 (VDD=+3.3V 有效) |
|
19 |
LED_A |
- |
背光源正极(LED+5V) |
20 |
LED_K |
- |
背光源负极(LED-OV) |
并行写
并行读:用来读取繁忙与否和当前游标地址
一般屏幕显示,需要进行刷新时间,需要等待不繁忙后才能继续写
串行写:但是没有读
串口需要等待屏幕操作时间,然后再写,接下来的命令表可以体现。每个命令大概需要72us
串行数据传送共分三个字节完成: 第一字节:串口控制—格式 11111ABC
A 为数据传送方向控制:H 表示数据从 LCD 到 MCU,L 表示数据从 MCU 到 LCD B 为数据类型选择:H 表示数据是显示数据,L 表示数据是控制指令
C 固定为 0
第二字节:(并行)8 位数据的高 4 位—格式 DDDD0000 第三字节:(并行)8 位数据的低 4 位—格式 0000DDDD 串行接口时序参数:(测试条件:T=25℃ VDD=4.5V)
可以从最后一列表格看出每个命令需要的操作时间。
1、指令表 1:(RE=0:基本指令集):写入命令0x30就是这个表
指令 |
指令码 |
说明 |
执行时 间 ( 540 KHZ) |
|||||||||
R S |
R W |
DB 7 |
DB 6 |
DB 5 |
DB 4 |
DB 3 |
DB 2 |
DB 1 |
DB 0 |
|||
清除显 示 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
将 DDRAM 填满“20H”,并且 设定 DDRAM 的地址计数器 (AC)到“00H” |
4.6ms |
地址归 位 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
X |
设定 DDRAM 的地址计数器 (AC)到“00H”,并且将游 标移到开头原点位置;这个指 令并不改变 DDRAM 的内容 |
4.6ms |
进入点 设定 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
I/D |
S |
指定在资料的读取与写入时, 设定游标移动方向及指定显示 的移位 |
72us |
显示状 态 开/关 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
D |
C |
B |
D=1:整体显示 ON C=1:游标 ON B=1:游标位置 ON |
72us |
游标或 显示移 位控制 |
0 |
0 |
0 |
0 |
0 |
1 |
S/ C |
R/ L |
X |
X |
设定游标的移动与显示的移位 控制位元;这个指令并不改变 DDRAM 的内容 |
72us |
功能设 定 |
0 |
0 |
0 |
0 |
1 |
DL |
X |
0 RE |
X |
X |
DL=1 (必须设为 1) RE=1: 扩充指令集动作 RE=0: 基本指令集动作 |
72us |
设 定 CGRA M 地 址 |
0 |
0 |
0 |
1 |
AC 5 |
AC 4 |
AC 3 |
AC 2 |
AC 1 |
AC 0 |
设定 CGRAM 地址到地址计数 器(AC) |
72us |
设 定 DDRA M 地址 |
0 |
0 |
1 |
AC 6 |
AC 5 |
AC 4 |
AC 3 |
AC 2 |
AC 1 |
AC 0 |
设定 DDRAM 地址到地址计数 器(AC) |
72us |
读取忙 碌标志 (BF) 和地址 |
0 |
1 |
BF |
AC 6 |
AC 5 |
AC 4 |
AC 3 |
AC 2 |
AC 1 |
AC 0 |
读取忙碌标志(BF)可以确认 内部动作是否完成,同时可以 读出地址计数器(AC)的值 |
0us |
写资料 到 RAM |
1 |
0 |
D7 |
D6 |
D5 |
D4 |
D3 |
D2 |
D1 |
D0 |
写 入 资 料 到 内 部 的 RAM ( DDRAM/CGRAM/IRAM/G DRAM) |
72us |
读 出 RAM |
1 |
1 |
D7 |
D6 |
D5 |
D4 |
D3 |
D2 |
D1 |
D0 |
从 内 部 RAM 读 取 资 料 ( DDRAM/CGRAM/IRAM/G |
72us |
的值 |
DRAM) |
指令表—2:(RE=1:扩充指令集)写入命令0x34就是这个表
指令 |
指令码 |
说明 |
执 行 时 间 (540KHZ) |
|||||||||
RS |
R W |
DB 7 |
DB 6 |
DB 5 |
DB 4 |
DB 3 |
DB 2 |
DB 1 |
DB 0 |
|||
待 命 模 式 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
将 DDRAM 填 满 “ 20H ”, 并 且 设 定 DDRAM 的地址计数 器(AC)到“00H” |
72us |
卷 动 地 址 或 IRAM 地 址选择 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
SR |
SR=1:允许输入垂直 卷动地址 SR=0:允许输入 IRAM 地址 |
72us |
反 白 选 择 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
R1 |
R0 |
选择 4 行中的任一行 作反白显示,并可决定 反白与否 |
72us |
睡 眠 模 式 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
SL |
X |
X |
SL=1:脱离睡眠模式 SL=0:进入睡眠模式 |
72us |
扩 充 功 能设定 |
0 |
0 |
0 |
0 |
1 |
1 |
X |
1 RE |
G |
0 |
RE=1: 扩充指令集动 作 RE=0: 基本指令集动 作 G=1 :绘图显示 ON G=0 :绘图显示 OFF |
72us |
设 定 IRAM 地 址 或 卷 动地址 |
0 |
0 |
0 |
1 |
AC 5 |
AC 4 |
AC 3 |
AC 2 |
AC 1 |
AC0 |
SR=1:AC5—AC0 为 垂直卷动地址 SR=0:AC3—AC0 为 ICON IRAM 地址 |
72us |
设 定 绘 图 RAM 地址 |
0 |
0 |
1 |
AC 6 |
AC 5 |
AC 4 |
AC 3 |
AC 2 |
AC 1 |
AC0 |
设定 CGRAM 地址到 地址计数器(AC) |
72us |
这个表的命令不够详细有些命令没有具体说明,官方手册也是如此。详细使用需要自己验证。一般几个命令够用了如下:
驱动程序如下:
/****************************** 以下为12864 管脚的定义 ***************************************/
/******************************* ***************************************/
#define SERIAL 1
#define PARALLEL 2
#define MODE PARALLEL
#define PSB(n) GPIO_WriteBit(GPIOA, GPIO_Pin_8,(BitAction) n);//H并行 L串行
#define RS(n) GPIO_WriteBit(GPIOA, GPIO_Pin_10,(BitAction) n); //并行H数据 L命令
#define CS(n) GPIO_WriteBit(GPIOA, GPIO_Pin_10, (BitAction) n) //串行片选1有效
#define LED_K(n) GPIO_WriteBit(GPIOA, GPIO_Pin_11,(BitAction) n);// 背光负,0 接通地
#define LCD_RST(n) GPIO_WriteBit(GPIOA, GPIO_Pin_12,(BitAction) n); //LCD复位 0有效
#define RW(n) GPIO_WriteBit(GPIOF, GPIO_Pin_0,(BitAction) n);//并行 0写 1读
#define EN(n) GPIO_WriteBit(GPIOF, GPIO_Pin_1,(BitAction) n);//并行 0 1 0 边沿信号
#define SID(n) GPIO_WriteBit(GPIOF, GPIO_Pin_0, (BitAction) n) //串行 信号线
#define CLK(n) GPIO_WriteBit(GPIOF, GPIO_Pin_1, (BitAction) n) //串行 时钟线
#define LD0(n) GPIO_WriteBit(GPIOC, GPIO_Pin_0,(BitAction) n);//并行数据位
#define LD1(n) GPIO_WriteBit(GPIOC, GPIO_Pin_1,(BitAction) n);
#define LD2(n) GPIO_WriteBit(GPIOC, GPIO_Pin_2,(BitAction) n);
#define LD3(n) GPIO_WriteBit(GPIOC, GPIO_Pin_3,(BitAction) n);
#define LD4(n) GPIO_WriteBit(GPIOC, GPIO_Pin_4,(BitAction) n);
#define LD5(n) GPIO_WriteBit(GPIOC, GPIO_Pin_5,(BitAction) n);
#define LD6(n) GPIO_WriteBit(GPIOC, GPIO_Pin_6,(BitAction) n);
#define LD7(n) GPIO_WriteBit(GPIOC, GPIO_Pin_7,(BitAction) n);
#include "main.h"
#if MODE==PARALLEL
void bus_check() //芯片繁忙检测
{
GPIO_InitTypeDef GPIO_InitStruct;
RS(0);
RW(1);
LD0(1); //并行数据位7位拉高
LD1(1);
LD2(1);
LD3(1);
LD4(1);
LD5(1);
LD6(1);
LD7(1);
EN(1);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 |GPIO_Pin_2 |GPIO_Pin_3 | GPIO_Pin_4 |GPIO_Pin_5 |GPIO_Pin_6 |GPIO_Pin_7;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN; //并行数据引脚改为输入
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_DOWN;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_Level_3;
GPIO_Init(GPIOC, &GPIO_InitStruct);
// delay_ms(2);
while(GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_7)==1);//读取最高位,为0就是不繁忙
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT; //改为输出
GPIO_Init(GPIOC, &GPIO_InitStruct);
EN(0);
}
void led12864_init()
{
GPIO_InitTypeDef GPIO_InitStruct;
RCC_AHBPeriphClockCmd( RCC_AHBPeriph_GPIOC, ENABLE);
RCC_AHBPeriphClockCmd( RCC_AHBPeriph_GPIOA, ENABLE);
RCC_AHBPeriphClockCmd( RCC_AHBPeriph_GPIOF, ENABLE);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 |GPIO_Pin_2 |GPIO_Pin_3 | GPIO_Pin_4 |GPIO_Pin_5 |GPIO_Pin_6 |GPIO_Pin_7;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT; //并行数据脚初始化
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_Level_3;
GPIO_Init(GPIOC, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_10 |GPIO_Pin_11 | GPIO_Pin_12 ;//PSB RS LED_K LCD_RST
GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1; // RW EN
GPIO_Init(GPIOF, &GPIO_InitStruct);
PSB(1);//并行选择
LED_K(0);
LCD_RST(0);//复位
LCD_RST(1);//
delay_ms(5);
write_commoned(0x30);//启动基本命令
write_commoned(0x04);//游标移动方向
write_commoned(0x0e);//游标显示或者反显
write_commoned(0x01);//清屏
write_commoned(0x02);//地址和游标回归首行首列
write_commoned(0x80);//显示地址回归首行首列
// write_data(0xbb);
// write_data(0xb6);
}
void write_commoned(unsigned char data)
{
bus_check();
RS(0);//命令
RW(0);//写
EN(0);
LD0(((data>>0)&0x01));
LD1(((data>>1)&0x01));
LD2(((data>>2)&0x01));
LD3(((data>>3)&0x01));
LD4(((data>>4)&0x01));
LD5(((data>>5)&0x01));
LD6(((data>>6)&0x01));
LD7(((data>>7)&0x01));
EN(1);
EN(0);
}
void write_data(unsigned char data)
{
bus_check();
RS(1);//s数据
RW(0);//写
EN(0);
LD0(((data>>0)&0x01));
LD1(((data>>1)&0x01));
LD2(((data>>2)&0x01));
LD3(((data>>3)&0x01));
LD4(((data>>4)&0x01));
LD5(((data>>5)&0x01));
LD6(((data>>6)&0x01));
LD7(((data>>7)&0x01));
EN(1);
EN(0);
}
void test(unsigned char data)
{
unsigned char i,j;
write_commoned(0x01);
write_commoned(0x34);
for(i=0;i<32;i++)
{
write_commoned(0x80+i);
write_commoned(0x80);
for(j=0;j<32;j++)
{
write_data(data);
}
}
write_commoned(0x36);
write_commoned(0x30);
}
#endif
/*
hang 显示在屏幕第几行 value should 1-4
lie 第几个格子,一个格子可容纳两个字符 value should 0-7
*a 指向的字符指针,可以是字符串
chang 显示字符长度
*/
void display(unsigned char hang , unsigned char lie ,char *a , unsigned char chang)
{
static unsigned char temp_data;
switch( hang)//每一行的起始坐标是不一样的
{
case 1:temp_data = 0x80;break; //第一行
case 2:temp_data = 0x90;break; //第二行
case 3:temp_data = 0x88;break; //第三行
case 4:temp_data = 0x98;break; //第四行
default :return;
}
if(lie > 8){ return; }//一个列可以容纳两个acsII字符;
write_commoned( temp_data + lie); //显示定位行列
for(;*a != '\0'&&chang>0; a++,chang--)
{
write_data( (unsigned char)*a);
}
}
串行程序代码:跟上面的并行共用宏定义
#if MODE==SERIAL
void led12864_init( void )
{
GPIO_InitTypeDef GPIO_InitStruct;
RCC_AHBPeriphClockCmd( RCC_AHBPeriph_GPIOA, ENABLE);
RCC_AHBPeriphClockCmd( RCC_AHBPeriph_GPIOF, ENABLE);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_Level_3;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_10 |GPIO_Pin_11 | GPIO_Pin_12 ;//PSB RS LED_K LCD_RST
GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1; // RW EN
GPIO_Init(GPIOF, &GPIO_InitStruct);
CLK(0);
SID(0);
CS(1) ;
PSB(0);
LCD_RST(1);
delay_us( 1000);//物理复位,需要等待点时间
write_commoned( 0x30); /*功能设置:一次送8位数据,基本指令集*/
write_commoned( 0x04); /*点设定:显示字符/光标从左到右移位,DDRAM地址加1*/
write_commoned( 0x0e); /*显示设定:开显示,显示光标,当前显示位反白闪动*/
write_commoned( 0x01); /*清DDRAM*/
delay_ms( 5); /* 等待4.6ms清屏完毕*/
write_commoned( 0x02); /*DDRAM地址归位*/
write_commoned( 0x80); /*把显示地址设为0X80,即为第一行的首位*/
// write_data( 0x7f); //显示一个字符
// write_commoned( 0x90); //第二行
// write_data( 0xbb); //显示一个汉字
// write_data( 0xb6);
}
void write_byte( unsigned char data)
{
static unsigned char i = 0;
CS(1) ;
CLK(0);
delay_us(72); //串行每个写都基本需要72us反应,代替了并行的繁忙等待
for(i = 0; i < 8; i++)
{
if((data&0x80) == 0x80){ SID(1);}
else { SID(0);}
data = data<<1;
CLK(1);
CLK(0);
}
CS(0) ;
}
void write_commoned( unsigned char data)
{
write_byte( 0xf8); //写命令固定格式
write_byte( 0xf0&data); //命令的高四
write_byte( ((0x0f&data)<<4) ); //命令的低四
}
void write_data( unsigned char data)
{
write_byte( 0xfa);//写数据的固定格式
write_byte( 0xf0&data); //写数据的高四
write_byte( ((0x0f&data)<<4) );//写数据的低四
}
void test( unsigned char data)
{
static unsigned char i = 0,j = 0;
write_commoned( 0x01);//清屏
delay_us( 46000);//清屏固定等待4.6ms
write_commoned( 0x34);
for(i = 0;i < 64;i++)
{
write_commoned( 0x80+i);
write_commoned( 0x80);
for(j = 0; j < 32; j++)
{
write_data( data);
}
}
write_commoned( 0x36);
write_commoned( 0x30);
}
#endif
/*画图函数*/
void write_picture(unsigned char *p)
{
static unsigned char i = 0,j = 0;
// write_commoned( 0x01);
write_commoned( 0x34);//关闭绘画
for(i = 0;i < 32; i++) //打印第一第三行
{
write_commoned( 0x80+i); //点的行定位
write_commoned( 0x80); //点的列定位 行的每八个点为下一个列
for(j = 0; j < 16; j++)
{
write_data( *p);p++;
// write_commoned( 0x36);
}
}
for(i = 0;i < 32; i++)//打印第二第四行
{
write_commoned( 0x80+i);
write_commoned( 0x88);
for(j = 0; j < 16; j++)
{
write_data( *p);p++;
// write_commoned( 0x36);
}
}
write_commoned( 0x36);//开启绘画
write_commoned( 0x30);//开启基本命令
}
在使用屏幕的时候的总结:无法单独定位到某个字节的位置,比如:0x80首行首列,0x81是首行第二列。但是首行首列和首行第二列之间存在两个字节的空间。所以地址不能定位到空间中的第二个字节的位置 。
或者这样说:地址定位只能按照两个字节大小来定位。