# lcd1602
**Repository Path**: sjzc-qwz/lcd1602
## Basic Information
- **Project Name**: lcd1602
- **Description**: LCD1602 功能函数封装。(51单片机)
- **Primary Language**: Unknown
- **License**: MPL-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 3
- **Forks**: 3
- **Created**: 2022-01-16
- **Last Updated**: 2024-10-20
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# lcd1602
----------
### 介绍
lcd1602 是一个易用且高效的控制库,用于驱动 1602A 液晶显示屏。
目前,本库在 51 单片机(STC89C52)上测试通过,1602A 是一个 2x16 字符型带背光液晶显示屏。
**支持 LCD1602 的 8 位总线通信模式。**
### 关于此库
*lcd1602* - Version 1.1.2
*by Nixsawe *
本库说明文档及代码均由项目成员辛苦完成,请在遵循开源协议的场景下使用本库。
### 为什么编写这个库
抽象与封装,是现代计算机能够高效设计、开发、运行的基石。
为简化程序设计逻辑,为未来学习做准备,特封装此库。
### 使用方法
1. 将 `lcd1602.c` 添加到工程中
2. 将库所在目录,添加到工程的包含目录列表 (`INCLUDE DIR`)
3. 在程序源文件中引用本库的头文件 `lcd1602.h`
(可参考 **dled_ctl** 库的使用方法,大同小异)
### 减小空间占用
可在 `LX51` 的连接器参数中添加 `REMOVEUNUSED` 参数,**删除未使用的函数**以减小空间占用。
可参考 **dled_ctl** 库对应部分。
### 功能原理
本小节以及接下来的一节将叙述 LCD1602 液晶屏设备以及本模块的功能原理等有关内容,如果仅想查阅库的方法,请点击[传送门](#调用逻辑 "快带我传送过去")。
#### 基本组成
标准的 LCD1602 液晶屏显示模块有 16 个引脚
| 引脚号 | 符号 | 引脚说明 | 引脚号 | 符号 | 引脚说明 |
| :----: | :--- | :------- | :----: | :--- | :------- |
| 1 | VSS | 电源地 | 9 | DB2 | Data IO |
| 2 | VDD | 电源正极 | 10 | DB3 | Data IO |
| 3 | VO | 偏压信号 | 11 | DB4 | Data IO |
| 4 | RS | 命令/数据| 12 | DB5 | Data IO |
| 5 | RW | 读/写 | 13 | DB6 | Data IO |
| 6 | E | 使能 | 14 | DB7 | Data IO |
| 7 | DB0 | Data IO | 15 | BLA | 背光正极 |
| 8 | DB1 | Data IO | 16 | BLK | 背光负极 |
各引脚说明如下
1. VSS 接电源地 GND
2. VDD 接电源正 +5V
3. VO 负责调整显示时前景、背景的对比度。一般接电位器以方便调整
4. RS 是命令/数据选择线。当这条线为低电平时,代表要执行的是一条命令或是访问运行状态;当这条线是高电平时,将进行数据操作
5. RW 是读/写选择线。这条线低电平代表向设备写命令或数据,设备是接收端;高电平代表从设备读状态或数据,单片机是接收端
6. E 是 LCD1602 的使能线。在将要发起通信时,单片机在这条线上建立上升沿,LCD1602 监测到上升沿后设备动作,根据 RS、RW 的设置选择对应的模式,在 DB0\~DB7 引脚上与单片机完成通信。操作执行完成后,E 应维持低电平
7. DB0\~DB7,并行数据输入/输出引脚。在 8 位总线模式下,这 8 个引脚都需要连接,一次传输 8-bit 的数据;在 4 位总线模式下,只需接 DB4\~DB7 这高 4 位,一次传输 4-bit 的数据,先高后低,每次操作都需要两次传输
8. BLA 背光正极,通过一个 10\~47Ω 的限流电阻与 VDD 相连,如果不需要背光,也可以将此引脚直接接地
9. BLK 背光负极,接 VSS
LCD1602 有一个 **DDRAM** 显示数据存储器,要显示的数据会先写到这个存储器里面,再由设备内部的控制电路自行显示出来。LCD1602 同样支持自定义字符,这靠内部的 **CGRAM** 自建字模存储器实现。
下面的部分,我们将详细介绍 **DDRAM**、**CGRAM** 以及字符显示、自建原理。
#### 字符显示原理
LCD1602 有一个显示控制位 **D**,可以通过功能设置 (*Function Set*) 命令来控制整个屏幕是否启用。当 **D=1** 时,屏幕启用,能够显示东西;反之,屏幕显示将被关闭,但仍能写入和读取数据。
LCD1602 的 **DDRAM** 显示数据存储器就是它的显存。要显示字符时,我们写对应的字符码到显存中,则该字符就会自动显示在屏幕对应位置。所谓的字符码,实际和字符的 **ASCII** 码是一致的(在 ANSI ASCII 标准范围内)。
字符的绘制不需要我们自己动手,是因为 LCD1602 内置有一个 **CGROM** 字库,内置有 192 个字模数据。当字符被写到 DDRAM 后,LCD1602 内部的控制电路将根据字符码从内部的字模库 CGROM 中找到对应的字模数据,然后将其显示出来。
DDRAM 能存储 80 个字符数据。如果显示行数被设为两行,则 DDRAM 中地址范围 **00H\~27H** 存储第一行内容,**40H\~67H** 存储第二行的内容(每行 40 个字符)。
若显示行数被设为一行,则 **DDRAM** 中可使用的地址范围将是 **00H\~4FH** (单行 80 个字符)。
然而液晶屏一次最多只能展示 16\*2=32 个字符,那么剩下的字符去哪儿了呢?
实际上 LCD1602 维护了一个**可显示区域**,当显示行数为 2 行时,这个区域的最大显示范围是 32 个字符,仅显示一行时,最大显示范围是 16 个字符。下面我们将以 2 行显示模式为例,说明可显示区域的概念。
可显示区域就像一个窗口,是可以相对屏幕而移动 (*shift*) 的,若没有经过屏幕移位,则初始的显示效果将会是下面这样:

整幅图是 DDRAM 的存储范围,第一行是 **00H\~27H**,第二行是 **40H\~67H**,其中,**可显示区域**(被黑框圈起来的区域)中的字符将会在屏幕上显示出来。
与可显示区域相对的是**隐藏区域**,隐藏区域的内容目前没有被显示,但是写入 DDRAM 的数据不会丢失。
当产生屏幕移位 (*shift*) 操作,屏幕发生移动,可显示区域位置不变,则可以认为**可显示区域向相反的方向移动**。注意屏幕移动方向与可显示区域移动方向是相反的,这在用户视觉感官上更好理解。
在默认显示状态的基础上,我们将屏幕向左移动两个字符位置,对应的,可以认为是可显示区域向右移动两个字符位置,如下图所示:

实际显示中,原来屏幕上最左边的两列字符被“移出”可显示区域,右边有两列字符“进入”可显示区域。
尽管显示如此,DDRAM 的内容却没有发生改变,因为对于 LCD1602 设备来说,只是**可显示区域**这个窗口发生移动。
可显示区域**永远是连续的**,屏幕向右移动 n 个字符位置(n<40),等效于屏幕向左移动 (40 - n) 个位置。显示的内容总是从连续的可显示区域最左端开始,一直到最右端结束。
在默认显示状态的基础上,我们将屏幕向右移动 11 个字符位置(或者说,将屏幕向左移动 29 个字符位置),对应的,可显示区域向左移动 11 个字符位置,如下图所示:

由于可显示区域是连续的,那么实际的可显示区域,应该是从第 30 列开始,到最后的 40 列,连接到最前面的第 1 列到第 5 列,按这样的顺序连接显示,则实际显示的内容,将会如上图下半部分那般“拼凑”起来。
例如,我们写入如下的数据到 DDRAM 中:
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmn
opqrstuvwxyz0123456789------------------
则在 2 行模式下,未经移动时,液晶屏上将显示
ABCDEFGHIJKLMNOP
opqrstuvwxyz0123
进行两次屏幕右移操作,即屏幕向右移动 2 个字符位置,液晶屏上将显示
mnABCDEFGHIJKLMN
--opqrstuvwxyz01
DDRAM 的内容在屏幕移动过程中是不会改变的。例如,在上述的基础上,我们将光标归位,则光标会回到字符 A 所在的位置。
LCD1602 的光标 (*Cursor*) 表示当前字符位置,我们写数据到 DDRAM 中,或者从 DDRAM 中读当前字符,就是从光标处进行的。同其他顺序性操作一样,在写入或读取后,光标的位置会自动移动 1 个单位,这样在连续写入多个字符(或是连续读出)时,我们只需要设置它的起始位置即可,就像是在计算机中输入一段连续文本一样简单。
在 LCD1602 中,光标的位置等同于 DDRAM 地址计数器 (*Address Counter, AC*) 的值,因此更标准的说法是,每当我们完成一次写入、读取操作时,是根据这个地址进行访问的,在访问之后,地址计数器 **AC** 的值就会自动自增或自减 1 个单位。
AC 的变化方向由 **I/D** 确定,当 **I/D=1** 时,每完成一次读写操作后 AC 自增 1;若 **I/D=0**,每完成一次操作后 AC 自减 1。屏幕上光标位置的变化与之保持一致。
由于 DDRAM 的地址范围是有限的,那么在跨越边界(当前行,或是整个 DDRAM 有效地址范围)时 AC 地址计数器会跳变。
表现为,当越过一行末尾时(DDRAM 地址超过当前行的地址范围),设备将自动换行。
若换行超过最大行数(即 DDRAM 地址超出最大范围),设备将从第一个字符位置处(**I/D=1** 时,这个位置是左上角)继续操作。
DDRAM 的地址范围如下表
| 显示行数 | N | DDRAM AC 范围 |
| :------: |:-:| :------------ |
| 一行 | 0 | 整行 00H\~4FH |
| 两行 | 0 | 第一行 00H\~27H
第二行 00H\~4FH |
#### 字库及用户自建字模原理
LCD1602 提供了字模库,在 **CGROM** 中存储有 192 个内置字符。这些字符以其字符码为索引,存储在 CGROM 的固定区域。
如字符 A,它属于 ANSI ASCII 规定的字符,其 ASCII 码为 65,二进制表示为 01000001b,LCD1602 内部通过这个字符码,即可找到所在的字模数据。
| 字符码索引 | 行索引 | 字模数据 | 显示效果 |
| :--------: | :----: | :------: | :------------------------------------------------: |
| 01000001b | 000b | 01110b |  |
| | 001b | 10001b |  |
| | 010b | 10001b |  |
| | 011b | 10001b |  |
| | 100b | 11111b |  |
| | 101b | 10001b |  |
| | 110b | 10001b |  |
| | 111b | 00000b |  |
那么字符 A 字模整体将如下图

上述操作不需要我们去完成,我们只需要将对应的字符码写到 DDRAM 对应位置即可。
**值得一提的是,LCD1602 使用的可能是日文编码集。** 如字符 '\\' 被写到 DDRAM 中,显示的将是日元符号,在字符码 >=128 的范围,也有相当一部分字符是日文字符。不多赘述。
当想要显示内置字模库没有的字符时,就要用到**用户自建字模**了,用户自建字模被存储在**自建字模存储区** **CGRAM** 内,其索引方式与上述内置字模的类似。
CGRAM 提供了 64 字节的在线存储空间,别看这个数值挺大的,实际存不了几个自定义字符。
以 5\*8 点阵字体为例,一个字符是 5\*8 的点阵,每行 5 个点,用 1 个字节存储,实际只有低 5 位的数据有效,高 3 位也用来存储数据,但没有被用上。总共 8 行,**故存储一个字符的字模数据需要 8 个字节,64 个字节只能存放 8 个字模。**
而 5\*11 点阵字体需要的空间是 5\*8 的两倍,使用 16 个字节,前 11 个字节,每个字节低 5 位存放对应的行数据,后 5 字节保留而不使用。**整个 CGRAM 只能存放 4 个字符的字模数据。**
| 点阵大小 | 单个字模大小 | 总共可存放字模数 |
|:--------:|--------------|------------------|
| 5\*8 | 8 字节 | 8 个 |
| 5\*11 *(\*)* | 16 字节
(后 5 字节不使用) | 4 个 |
**注:** 5\*11 点阵仅在单行显示模式下可用,在双行模式下,将强制使用 5\*8 点阵。
使用时,如果写到 **DDRAM** 中的字符码高 4 位不为零,则 LCD1602 将根据字符码从 **CGROM** 中取**内置字模数据**。
若字符码高 4 位为 0,则 LCD1602 将从 **CGRAM** 中取**用户自建字模数据**。例如,我们在位置 2 建立一个字符 A 的 5\*8 字模副本
| 字符码索引 | 行索引 | 字模数据 | 显示效果 |
| :--------- | :----: | :------: | :----------------------------------------------------: |
| 0000 x010b | 000b | 01110b |  |
| 高4位为0 | 001b | 10001b |  |
| x位不起作用 | 010b | 10001b |  |
| | 011b | 10001b |  |
| | 100b | 11111b |  |
| | 101b | 10001b |  |
| | 110b | 10001b |  |
| | 111b | 00000b |  |
由于 CGRAM 最多也只能存储 8 个字符,那么只需要 3 位二进制 `000b~111b` 就能够索引所有的自建字模。在 LCD1602 中,用 00000010b 与 00001010b 都可以表示在 CGRAM 位置 2 定义的字符。
每次运行时,我们需要向 CGRAM 写入上面定义的字模数据。LCD1602 根据 DDRAM 中的字符码可直接找到对应的字模,而我们手动写入时,要多加一步。
由于字模的每一行存储于单独的字节中,我们手动索引时,需要用字符码的低 3 位和代表行的 3 位二进制数共同索引特定的行,然后将每一行的点阵数据单独写入。
例如,对于上面定义的字模,其字符码是 010b,那么每一行的 CGRAM 地址如下表所示
| 字符码 | 行 | CGRAM 地址 | 代表含义 |
| :----: | :----: | :--------: | :------------------------------------: |
| 010b | 000b | 010000b | 字符码为 2 的自建字模,第 1 行点阵数据 |
| | 001b | 010001b | 字符码为 2 的自建字模,第 2 行点阵数据 |
| | 010b | 010010b | 字符码为 2 的自建字模,第 3 行点阵数据 |
| | 011b | 010011b | 字符码为 2 的自建字模,第 4 行点阵数据 |
| | 100b | 010100b | 字符码为 2 的自建字模,第 5 行点阵数据 |
| | 101b | 010101b | 字符码为 2 的自建字模,第 6 行点阵数据 |
| | 110b | 010110b | 字符码为 2 的自建字模,第 7 行点阵数据 |
| | 111b | 010111b | 字符码为 2 的自建字模,第 8 行点阵数据 |
写入字模数据时,先设置字模第 1 行数据的 CGRAM 地址,然后写代表第 1 行点阵数据的字节,再以同样的方式处理第 2 行,依次类推。
5\*8 点阵下 8 个自建字模的起始地址如下表
| 字符码 | CGRAM 起始地址 | 代表含义 |
| :----: | :------------: | :-----------------------: |
| 000b | 000000b | 字符码为 0 的自建字模地址 |
| 001b | 001000b | 字符码为 1 的自建字模地址 |
| 010b | 010000b | 字符码为 2 的自建字模地址 |
| 011b | 011000b | 字符码为 3 的自建字模地址 |
| 100b | 100000b | 字符码为 4 的自建字模地址 |
| 101b | 101000b | 字符码为 5 的自建字模地址 |
| 110b | 110000b | 字符码为 6 的自建字模地址 |
| 111b | 111000b | 字符码为 7 的自建字模地址 |
对于 5\*11 点阵字符,情况要更复杂一些,它使用 CGRAM 的连续 16 个字节存储一个字模。
还是以字母 A 为例,在之前的位置 2 (CGRAM 地址 010000b 处) 建立一个用户自建字模
| 字符码 | 行 | CGRAM 地址 | 显示效果 |
| :----- | :----: | :--------: | :--------------------------------------------------------: |
| 01xb | 0000b | 01 0000b |  |
| | 0001b | 01 0001b |  |
| | 0010b | 01 0010b |  |
| | 0011b | 01 0011b |  |
| | 0100b | 01 0100b |  |
| | 0101b | 01 0101b |  |
| | 0110b | 01 0110b |  |
| | 0111b | 01 0111b |  |
| | 1000b | 01 1000b |  |
| | 1001b | 01 1001b |  |
| | 1010b | 01 1010b |  |
(后 5 字节由于不被显示,则在表格中从略了。)
可以看到,行的范围从 1\~8 扩展到了 1\~11,则对应的行索引范围变为 `0000b~1010b`。同时,由于行索引所占位数增多,CGRAM 地址不再是 3 位字符码、3 位行索引的组合了,而是字符码的前 2 位、4 位行索引组合在一起。
举例来说,字符 '\\2' 和字符 '\\3' 字符码的二进制分别是 010b 和 011b,它们的前两位都是 01b,则实际写入 DDRAM 后,两个字符都会显示上面定义的字模,如下图所示。

#### 光标 & 闪烁
#### 驱动 & 控制
### LCD1602 指令集(命令字)
### 调用逻辑
### 类型定义
库的代码中定义了三个基础数据类型和一个结构体类型。
// 基础数据类型
typedef unsigned char LCD1602_CMD;
typedef unsigned char LCD1602_DATA;
typedef unsigned char LCD1602_STATE;
// LCD1602 初始化结构体类型
typedef struct {
// 显示行数、字体大小
LCD1602_CMD FNS;
// 屏幕显示与光标
LCD1602_CMD DSP;
// 输入模式
LCD1602_CMD ENT;
} LCD1602_INIT_DATA;
三个基础数据类型仅仅是封装了一下 `unsigned char`,增强代码可读性。`LCD1602_INIT_DATA` 定义了 LCD1602 设备初始化配置结构体类型,三个成员分别代表三个命令值,作用见上文代码。
实际使用时,用户代码中可使用 `LCD1602_INIT_DATA` 定义初始化结构体
LCD1602_INIT_DATA init_data = {
0, // FNS (使用默认配置)
LCD1602_XCMD_DSP(LCD1602_DISPLAY_ON | LCD1602_CURSOR_ON), // 启用显示和光标
0 // ENT (使用默认配置)
};
上面的代码定义了一个结构体 `init_data`,包含了设备初始化信息,其中值为 0 的成员代表使用默认配置(设备的默认初始化配置见后文),`DSP` 成员则被设置成**启用显示**、**启用光标**。
用户代码可将结构体指针作为参数传递给 `lcd1602_init` 函数,以初始化液晶屏设备。(`lcd1602_init` 函数说明见后文。)
### 库方法
本库提供了多个已封装好的方法,可以按如下分类:
1. 设备接口方法
* ***void*** *lcd1602_write_cmd*(***LCD1602_CMD*** *cmd*);
* ***void*** *lcd1602_write_data*(***LCD1602_DATA*** *dat*);
写命令或数据
* ***LCD1602_STATE*** *lcd1602_read_state*();
* ***LCD1602_DATA*** *lcd1602_read_data*();
读状态或数据
* ***LCD1602_STATE*** *lcd1602_busy*();
判断设备是否繁忙
* ***void*** *lcd1602_waitBusy*();
同步等待直到 LCD1602 不再繁忙
这几个函数封装了单片机与 LCD1602 进行通信的最基本方法。所有其他方法均是靠调用这些方法来实现的。
由于 LCD1602 是慢速设备,执行操作前需要判断设备是否繁忙,并在设备繁忙时进行等待。
几个函数中,`lcd1602_busy` 用于判断设备繁忙状态,`lcd1602_waitBusy` 用于等待设备脱离繁忙状态。其他的函数在操作前会调用 `lcd1602_waitBusy` 函数,等待设备完全退出繁忙状态,再执行对应的功能。
2. 设备初始化功能
* ***void*** *lcd1602_init*(***LCD1602_INIT_DATA \**** *p_init_data*);
本方法会根据 `p_init_data` 指针指向的初始化数据来对 LCD1602 设备进行初始化。
如果 `p_init_data` 指向的结构体中,某些成员的值是 0,则会使用默认值来代替。如果 `p_init_data` 这个指针为 ***NULL***,则所有配置均会使用默认值。
初始化过程中,若 `p_init_data` 非 ***NULL***,且指向的结构体成员非 0,则对应的配置会覆盖模块的默认配置,配置的默认值会被修改,**下次调用时的默认值会使用当前值**。
设备初始化的默认配置存储于 `lcd1602_config` 结构体变量中(见后文)。
3. 常用基本方法封装
* ***void*** *lcd1602_cld*();
清屏
* ***void*** *lcd1602_ret*();
光标回到初始位置
* ***void*** *lcd1602_setcursor*(***LCD1602_DATA*** *x*, ***LCD1602_DATA*** *y*);
设置光标位置
* ***void*** *lcd1602_cursor_left*();
光标左移
* ***void*** *lcd1602_cursor_right*();
光标右移
* ***void*** *lcd1602_screen_left*();
屏幕左移
* ***void*** *lcd1602_screen_right*();
屏幕右移
这些方法封装了对应的命令调用过程,如清屏函数的实现如下:
// 清屏
void lcd1602_cld()
{
lcd1602_write_cmd(LCD1602_CMD_CLD());
}
函数没有使用 ***inline*** 内联定义,所以实际使用中可能需要考虑其所带来的“时/空”代换问题。
4. 常用功能封装
* ***void*** *lcd1602_print*(***char \**** *s*);
打印字符串(使用 ***NUL*** 截断)
* ***void*** *lcd1602_nprint*(***char \**** *s*, ***int*** *n*);
打印字符串(使用固定长度,不会被 ***NUL*** 截断)
在屏幕上打印对应的字符串。二者的区别是,`lcd1602_print` 使用 *C-Style* 字符串,在遇到 ***NUL ('\0')*** 字符时截断,停止输出;而 `lcd1602_nprint` 使用长度参数 `n` 标记字符串结尾,会输出 `n` 个连续字符,然后停止输出,不会被 ***NUL*** 字符截断。
设备 **DDRAM** 一次能存储 80 个字符,而屏幕上一行一次仅能显示 16 个字符,显示出来的部分被称为**可显示区域**,剩下的部分被“隐藏”掉了,也称**隐藏区域**。
如果显示行数被设为两行,则 **DDRAM** 中地址范围 **00H\~27H** 存储第一行内容,**40H\~67H** 存储第二行的内容(每行 40 个字符)。
若显示行数被设为一行,则 **DDRAM** 中可使用的地址范围将是 **00H\~4FH** (单行 80 个字符)。
当越过一行末尾时(**DDRAM** 地址超过当前行的地址范围),设备将自动换行。
若换行超过最大行数(即 **DDRAM** 地址超出最大范围),设备将从第一个字符位置处(**I/D=1** 时,这个位置是左上角)继续写入。
这些换行和自动归位的操作是由设备自行完成的,这两个函数仅调用 `lcd1602_write_data` 将字符串逐字符写给设备。
**这两个方法不会定位 DDRAM 地址,请确保在调用本函数之前,进行过 DDRAM 地址的定位。**
比如,用户代码对 **CGRAM** 进行了操作,则后续的 `lcd1602_write_data` 操作将会在 **CGRAM** 上下文中进行。若要确保数据被写入 **DDRAM**,需要通过某些命令手动设置 **DDRAM** 地址,或者执行地址归位、清屏等操作。
5. 字符生成功能封装
* ***void*** *lcd1602_define*(***LCD1602_DATA*** *char_code*, ***LCD1602_DATA*** *bytes[8]*);
在字库中构造生成对应的字符
这个函数会按照字模数据 `bytes`,在用户自建字模存储器 **CGRAM** 的对应位置处(由 `char_code` 指定)生成对应字符。
`char_code` 仅能为 0\~7,字模是按行设置的,每行低 5 位设置对应点是否点亮,共 8 行。生成字模时无需倒序。
这个函数仅进行 **CGRAM** 地址定位,然后调用 `lcd1602_nprint` 写入 8 行连续数据,地址的自增交由设备自行处理。
因此,在调用本函数前,需确保 **I/D** 被设置为 **I/D=1**,即每次写入后地址自增。
### 宏定义
#### 使用宏检测当前库的配置情况
***LCD1602_DATALINE_4_BITS***、***LCD1602_DATALINE_8_BITS***
这两个宏在对应的情况下被定义,用于标识数据总线的长度。
当 LCD1602_DATALINE_8_BITS 被定义,代表当前库工作于 8 位数据总线模式下;
当 LCD1602_DATALINE_4_BITS 被定义,代表当前库工作于 8 位数据总线模式下。
工作模式在编译期间被确定,编译以后不可修改。
#### 工程中定义宏来配置当前库
***LCD1602_DATA_LENGTH*** 配置 LCD1602 的总线位数,可能的值有两个,分别是 8 和 4
当宏未被定义时,默认的值会是 8。
***LCD1602_DATALINE_USE_PORT*** 配置在 4 位总线模式下,使用整个端口直接输出数据,而非在每一位上独立操作
***LCD1602_EN*** 配置 LCD1602 的使能线
这条线上产生上升沿时,设备动作,进行数据传输。
与 LCD1602 的 **E** 引脚相连。
在普中 A2 开发实验板上,它被定义于 P2.7 上。
***LCD1602_RS*** LCD1602 数据/命令选择线 (H/L)
这个位上输出高电平时代表读写数据,低电平时代表读状态或写命令。
与 LCD1602 的 **RS** 引脚相连。
在普中 A2 开发实验板上,它被定义于 P2.6 上。
***LCD1602_RW*** LCD1602 读/写选择线 (H/L)
这个位上输出高电平时代表读内容,低电平时代表写内容。
与 LCD1602 的 **RW** 引脚相连。
在普中 A2 开发实验板上,它被定义于 P2.5 上。
LCD1602_RW 和 LCD1602_RS 共同决定工作模式。
***LCD1602_IO_PORT*** LCD1602 Data I/O 端口
配置 LCD1602 的数据输入输出端口。
与 LCD1602 的 **DB0\~DB7** 口相连。
在普中 A2 开发实验板上,它被定义于 P0 口。
***LCD1602_IO_D7*** LCD1602 Data I/O 端口 第 7 位 (从 0 起记)
用于独立控制 **DB7** 引脚,定义于 P0.7。
当工作于 4 位总线模式时,设备仅使用 **DB4\~DB7** 四个引脚(而非整个 **DB0\~DB7**)。这时需要先传输数据的高 4 位,再传输低 4 位。
***LCD1602_IO_D6***、***LCD1602_IO_D5***、***LCD1602_IO_D4*** 同上
#### 使用宏函数构造命令字
***LCD1602_CMD_CLD()*** 构造一个清屏 (*Clear Display*) 命令字
***LCD1602_CMD_RET()*** 构造一个光标归位 (*Return Home*) 命令字
***LCD1602_CMD_ENT(ID,SH)*** 构造一个输入模式设置 (*Entry Mode Set*) 命令字
如 `LCD1602_CMD_ENT(1, 0)` 将返回一个命令字,执行该命令会设置输入时光标向右移动、地址自增 (**I/D=1**),但屏幕不移动 (**SH=0**)。
以下宏函数类似,暂不累述。
***LCD1602_CMD_DSP(D,C,B)*** 构造一个显示开关控制 (*Display ON/OFF Control*) 命令字
***LCD1602_CMD_SFT(SC,RL)*** 构造一个移动光标或屏幕 (*Cursor or Display Shift*) 命令字
***LCD1602_CMD_FNS(N,F)*** 构造一个功能设定 (*Function Set*) 命令字
LCD1602 的功能设定命令还可设置数据总线的位数,但本库在设计时采用的方式是,编译前通过定义宏来定义数据总线位数,编译期间确定,编译后不可修改,故而这里直接删掉了 **DL** 的设置位。
***LCD1602_CMD_CGR(AC)*** 构造一个设置 CGRAM 地址 (*Set CGRAM Address*) 命令字
***LCD1602_CMD_DDR(AC)*** 构造一个设置 DDRAM 地址 (*Set DDRAM Address*) 命令字
#### 使用宏函数构造命令字 (Another Approach)
有一定开发经验的开发者可能更喜欢使用位或运算 (*bitwise-or*) 来组合多个标志位,所以我们还提供了另一类宏函数(名称中使用 XCMD 而非 CMD)。
部分宏没有修改的必要,但我们也创建了对应的别名。
***LCD1602_XCMD_CLD()*** 构造一个清屏 (*Clear Display*) 命令字 (***LCD1602_CMD_CLD()*** 的别名)
***LCD1602_XCMD_RET()*** 构造一个光标归位 (*Return Home*) 命令字 (***LCD1602_CMD_RET()*** 的别名)
***LCD1602_XCMD_ENT(mask)*** 构造一个输入模式设置 (*Entry Mode Set*) 命令字
| 标志 | 功能 | 值 |
| :----- | :--------------- | :-: |
| LCD1602_ENTRY_ADDR_INCREASE | 操作后地址自增 | 2 |
| LCD1602_ENTRY_ADDR_DECREASE | 操作后地址自减 | 0 |
| LCD1602_ENTRY_SCREEN_MOVE | 操作后屏幕移动 | 1 |
| LCD1602_ENTRY_SCREEN_HOLD | 操作后屏幕不移动 | 0 |
如 `LCD1602_XCMD_ENT(LCD1602_ENTRY_ADDR_INCREASE | LCD1602_ENTRY_SCREEN_HOLD)` 代表构造一个命令字,执行该命令后,将设置输入时地址自增,屏幕不移动。
值为 0 的宏可以不参与位运算,上述等价于 `LCD1602_XCMD_ENT(LCD1602_ENTRY_ADDR_INCREASE)`。
以下宏函数类似,暂不累述。
***LCD1602_XCMD_DSP(mask)*** 构造一个显示开关控制 (*Display ON/OFF Control*) 命令字
| 标志 | 功能 | 值 |
| :--- | :----------- | :-: |
| LCD1602_DISPLAY_ON | 启用屏幕显示 | 4 |
| LCD1602_CURSOR_ON | 启用光标 | 2 |
| LCD1602_CURSOR_SHOW | 启用光标 | 2 |
| LCD1602_CURSOR_BLINK | 启用光标闪烁 | 1 |
***LCD1602_XCMD_SFT(mask)*** 构造一个移动光标或屏幕 (*Cursor or Display Shift*) 命令字
| 标志 | 功能 | 值 |
| :--- | :----------- | :-: |
| LCD1602_SHIFT_SCREEN | 仅移动屏幕 | 8 |
| LCD1602_SHIFT_CURSOR | 仅移动光标 | 0 |
| LCD1602_SHIFT_RIGHT | 左移 | 4 |
| LCD1602_SHIFT_LEFT | 右移 | 0 |
***LCD1602_XCMD_FNS(mask)*** 构造一个功能设定 (*Function Set*) 命令字
| 标志 | 功能 | 值 |
| :--- | :-------------- | :-: |
| LCD1602_TWO_LINES | 显示两行 | 8 |
| LCD1602_SINGLE_LINE | 显示一行 | 0 |
| LCD1602_LARGE_FONT | 使用 5\*11 字体 | 4 |
| LCD1602_SMALL_FONT | 使用 5\*8 字体 | 0 |
| LCD1602_NORMAL_FONT | 使用 5\*8 字体 | 0 |
***LCD1602_XCMD_CGR(AC)*** 构造一个设置 CGRAM 地址 (*Set CGRAM Address*) 命令字 (***LCD1602_CMD_CGR()*** 的别名)
***LCD1602_XCMD_DDR(AC)*** 构造一个设置 DDRAM 地址 (*Set DDRAM Address*) 命令字 (***LCD1602_CMD_DDR()*** 的别名)
### 变量使用
***LCD1602_INIT_DATA*** *lcd1602_config*;
在模块中定义并导出了一个全局的变量 `lcd1602_config`,这个结构体一开始包含初始化的默认值,被设置为:
LCD1602_INIT_DATA lcd1602_config = {
// 显示两行,字体大小为 5*8
LCD1602_XCMD_FNS(LCD1602_TWO_LINES | LCD1602_NORMAL_FONT),
// 打开屏幕显示
LCD1602_XCMD_DSP(LCD1602_DISPLAY_ON),
// 输入时光标右移,屏幕固定
LCD1602_XCMD_ENT(LCD1602_ENTRY_ADDR_INCREASE | LCD1602_ENTRY_SCREEN_HOLD)
};