# 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*) 的,若没有经过屏幕移位,则初始的显示效果将会是下面这样: ![图片1-默认显示效果](images/lcd1602-display-zone-01.png "默认的显示效果") 整幅图是 DDRAM 的存储范围,第一行是 **00H\~27H**,第二行是 **40H\~67H**,其中,**可显示区域**(被黑框圈起来的区域)中的字符将会在屏幕上显示出来。 与可显示区域相对的是**隐藏区域**,隐藏区域的内容目前没有被显示,但是写入 DDRAM 的数据不会丢失。 当产生屏幕移位 (*shift*) 操作,屏幕发生移动,可显示区域位置不变,则可以认为**可显示区域向相反的方向移动**。注意屏幕移动方向与可显示区域移动方向是相反的,这在用户视觉感官上更好理解。 在默认显示状态的基础上,我们将屏幕向左移动两个字符位置,对应的,可以认为是可显示区域向右移动两个字符位置,如下图所示: ![图片2-移位后显示效果](images/lcd1602-display-zone-02.png "移位后显示效果") 实际显示中,原来屏幕上最左边的两列字符被“移出”可显示区域,右边有两列字符“进入”可显示区域。 尽管显示如此,DDRAM 的内容却没有发生改变,因为对于 LCD1602 设备来说,只是**可显示区域**这个窗口发生移动。 可显示区域**永远是连续的**,屏幕向右移动 n 个字符位置(n<40),等效于屏幕向左移动 (40 - n) 个位置。显示的内容总是从连续的可显示区域最左端开始,一直到最右端结束。 在默认显示状态的基础上,我们将屏幕向右移动 11 个字符位置(或者说,将屏幕向左移动 29 个字符位置),对应的,可显示区域向左移动 11 个字符位置,如下图所示: ![图片3-“不连续的”可显示区域怎么解读](images/lcd1602-display-zone-03.png "“不连续的”可显示区域怎么解读") 由于可显示区域是连续的,那么实际的可显示区域,应该是从第 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 | ![A 字模第一行](images/char-A0.png "A 字模第一行") | | | 001b | 10001b | ![A 字模第二行](images/char-A1.png "A 字模第二行") | | | 010b | 10001b | ![A 字模第三行](images/char-A1.png "A 字模第三行") | | | 011b | 10001b | ![A 字模第四行](images/char-A1.png "A 字模第四行") | | | 100b | 11111b | ![A 字模第五行](images/char-A4.png "A 字模第五行") | | | 101b | 10001b | ![A 字模第六行](images/char-A1.png "A 字模第六行") | | | 110b | 10001b | ![A 字模第七行](images/char-A1.png "A 字模第七行") | | | 111b | 00000b | ![A 字模第八行](images/char-A7.png "A 字模第八行") | 那么字符 A 字模整体将如下图 ![字符 A 字模](images/char-A-5x8.png "字符 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 | ![自建字模第一行](images/char-A0.png "自建字模第一行") | | 高4位为0 | 001b | 10001b | ![自建字模第二行](images/char-A1.png "自建字模第二行") | | x位不起作用 | 010b | 10001b | ![自建字模第三行](images/char-A1.png "自建字模第三行") | | | 011b | 10001b | ![自建字模第四行](images/char-A1.png "自建字模第四行") | | | 100b | 11111b | ![自建字模第五行](images/char-A4.png "自建字模第五行") | | | 101b | 10001b | ![自建字模第六行](images/char-A1.png "自建字模第六行") | | | 110b | 10001b | ![自建字模第七行](images/char-A1.png "自建字模第七行") | | | 111b | 00000b | ![自建字模第八行](images/char-A7.png "自建字模第八行") | 由于 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 | ![自建字模第 1 行](images/char-A0.png "自建字模第 1 行") | | | 0001b | 01 0001b | ![自建字模第 2 行](images/char-A1.png "自建字模第 2 行") | | | 0010b | 01 0010b | ![自建字模第 3 行](images/char-A1.png "自建字模第 3 行") | | | 0011b | 01 0011b | ![自建字模第 4 行](images/char-A1.png "自建字模第 4 行") | | | 0100b | 01 0100b | ![自建字模第 5 行](images/char-A4.png "自建字模第 5 行") | | | 0101b | 01 0101b | ![自建字模第 6 行](images/char-A1.png "自建字模第 6 行") | | | 0110b | 01 0110b | ![自建字模第 7 行](images/char-A1.png "自建字模第 7 行") | | | 0111b | 01 0111b | ![自建字模第 8 行](images/char-A7.png "自建字模第 8 行") | | | 1000b | 01 1000b | ![自建字模第 9 行](images/char-A7.png "自建字模第 9 行") | | | 1001b | 01 1001b | ![自建字模第 10 行](images/char-A7.png "自建字模第 10 行") | | | 1010b | 01 1010b | ![自建字模第 11 行](images/char-A7.png "自建字模第 11 行") | (后 5 字节由于不被显示,则在表格中从略了。) 可以看到,行的范围从 1\~8 扩展到了 1\~11,则对应的行索引范围变为 `0000b~1010b`。同时,由于行索引所占位数增多,CGRAM 地址不再是 3 位字符码、3 位行索引的组合了,而是字符码的前 2 位、4 位行索引组合在一起。 举例来说,字符 '\\2' 和字符 '\\3' 字符码的二进制分别是 010b 和 011b,它们的前两位都是 01b,则实际写入 DDRAM 后,两个字符都会显示上面定义的字模,如下图所示。 ![自建 5\*11 点阵字模](images/char-A-5x11.png "自建 5*11 点阵字模") #### 光标 & 闪烁 #### 驱动 & 控制 ### 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) };