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 <stdio.h>
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 |
这个表的命令不够详细有些命令没有具体说明,官方手册也是如此。详细使用需要自己验证。一般几个命令够用了如下:
- 清除显 示
- 地址归 位
- 显示状 态 开/关
- 游标或 显示移 位控制
- 设 定 DDRA M地址
- 读取忙 碌标志(BF) 和地址
- 扩 充 功 能设定
- 设 定 绘 图 RAM地址
驱动程序如下:
/****************************** 以下为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是首行第二列。但是首行首列和首行第二列之间存在两个字节的空间。所以地址不能定位到空间中的第二个字节的位置 。
或者这样说:地址定位只能按照两个字节大小来定位。