IIC 驱动 LCD1602 液晶屏幕
上一课介绍了如何使用四位数码管模块来显示数字,本课将进一步介绍如何使用基于 IIC 接口驱动的 LCD1602 液晶屏。
在前面章节,我们已经学习过两种显示装置,数码管和 4 位数码管。使用它们可以直观显示一些字符数据,但是它们也有各种局限性,比如显示字符数据太少,硬件设计复杂、代码编写难度大等。这一章就来介绍一种非常简单且常用的显示装置--LCD1602 液晶显示器,使用它可以显示更多的字符数字。
实验原理
1. LCD1602 液晶屏
LCD1602 是很多单片机爱好者较早接触的字符型液晶显示器,所以,在这里花点时间是值得的。
1602 液晶屏的称呼来自于其显示的内容容量,其中的 16 代表每行的字符(数字或英文字符)数,02 代表屏幕一共两行,实际开发中根据需要显示信息的内容多少不但可以选用 1602 屏,还可以选用诸如 2004 屏等。如下图:

1602 液晶显示屏除了电源、地以外,有 3 个控制引脚 RS R/W E 和 8 个数据引脚 DB0-7。

2. 认识 IIC(I2C)接口
由于 1602 的管脚数过多,如果直接与 ESP32 开发板连接需要占用大量的 GPIO 管脚,不但容易造成资源浪费,连接也非常不方便。
因此实际使用时往往会给 1602 屏增加一块 IIC 驱动版,将 1602 的 16 个管脚连接到由 PCF8574T 作为主要芯片的驱动版上,将接口转换为 IIC 再连接开发板,具体情况如上图所示。
IIC 是一种硬件设备间常用的接口通讯协议,全称是 Inter-Integrated Circuit,也可以写为 I2C。他的设计时的理念是:信号线尽量少并且速率要尽量高。 信号线少,可以减少引脚占用,这对早期的芯片(引脚很少)的很重要。
使用 IIC 接口时一共需要连接四根线,包括:VCC、GND、SDA、SCL,其中 SDA 和 SCL 需要占用 GPIO 管脚,连接到开发板上任何一组 IIC 接口的对应管脚都可以。
标准的 I2C 需要两根信号线:
- SCL(Serial Clock):时钟线,时钟都是有 master 提供的
- SDA(Serial Data):双向数据线,发数据或者收数据(收发不能同时)
简单来说,只需要 2 根线,就可以对多台设备传输大量数据,减少单片机上 IO 口的占用。
硬件电路设计
物料清单(BOM 表):
| 材料名称 | 数量 |
|---|---|
| 带有 IIC 模块的 LCD1602 液晶屏 | 1 |
| 杜邦线(跳线) | 若干 |
将材料按照下图相连:

注意
注意需要使用开发板上的 5V 电压,而不是 3.3V。真实环境下使用 3.3V 会无法显示或者显示很暗。
这里连接的这两个引脚可不是随便连接的,而是对应了 ESP32 芯片原理图,ESP32 的 I2C 引脚的 SDA 对应 D21,SCL 对应 D22,如图:

软件程序设计
在 Arduino 中使用 I2C 控制 LCD1602 需要下载第三方代码 LiquidCrystal_I2C,其中包括 LiquidCrystal_I2C.h 和 LiquidCrystal_I2C.cpp 两个文件,我们只需要把这两个文件放在项目的文件夹中即可。
Arduino 项目的创建非常简单,就是打开 Arduino IDE 后保存,它会在你选择的位置新建一个文件夹,这就是项目的文件夹。
.h 与 .cpp 文件到底是什么?在 Arduino 开发中,.h 和 .cpp 文件同样是用于代码组织和模块化的文件类型,但在 Arduino 环境中有些特殊的用法和约定。
.h文件(头文件):在 Arduino 中,头文件通常包含库、类、函数和变量的声明。头文件的目的是为了让多个源代码文件可以共享相同的声明,以便在程序中使用这些声明而无需重复编写。头文件通常使用预处理器指令#include引入到源文件中。在 Arduino 库中,.h文件中通常包含类的声明、常量定义、函数原型等。.cpp文件(源文件):在 Arduino 中,.cpp文件是用于存放函数和类的实现代码的文件。.cpp文件中包含了类成员函数的具体实现和其他函数的定义。通常,Arduino 项目中的.cpp文件中会包含.h文件,以便使用其中的声明。
需要注意的是,Arduino 开发中的 .h 和 .cpp 文件的约定与传统的 C++ 开发并不完全相同。Arduino IDE 会在编译过程中将 .ino 文件转换为 .cpp 文件,并将其中的代码放置在全局范围。因此,在 Arduino 项目中,.ino 文件也可以包含全局变量和函数,而不仅限于 .cpp 文件。
总结起来,.h 文件是用于声明库、类、函数和变量的头文件,而 .cpp 文件是用于实现函数和类的源文件。这样的组织方式有助于提高代码的可读性、可维护性和重用性。
接着,我们就可以了解 LiquidCrystal_I2C 库的使用:
LiquidCrystal_I2C(uint8_t addr, uint8_t cols, uint8_t rows):构造函数,用于构造 LCD I2C 对象,参数:addr是地址,默认的是 0x27,cols是 LCD 显示的列数,rows是 LCD 显示的函数;void init():初始化显示屏;void clear():清除 LCD 屏幕上内容,并将光标置于左上角;void home():将光标在定位在屏幕左上角;void noBacklight()与void backlight():是否开启背光;print():显示内容;void leftToRight()与void rightToLeft():控制文字显示的方向,默认是从左向右;void noDisplay()与void display():关闭显示或恢复显示(内容不会丢失);void setCursor(uint8_t col, uint8_t row):设置光标的位置,列,行,基于 0;void noCursor()与void cursor:显示与不显示光标,默认不显示;void noBlink()与void blink():光标是否闪烁,默认不闪烁。
现在,我们就可以写代码了。
1. 在屏幕上显示 Hello,world
了解了第三方库之后,我们先写一个最简单的程序,比如,在屏幕上显示 Hello, world,代码如下:
#include "LiquidCrystal_I2C.h"
// 设置 LCD1602 的地址,列数,行数
LiquidCrystal_I2C lcd(0x27, 16, 2);
void setup()
{
// 初始化 LCD 对象
lcd.init();
// 打印内容
lcd.backlight();
lcd.print("Hello, world!");
}
void loop()
{
}
2. 读取串口输入内容
在这个程序中,我们需要用到串口的另外两个方法 Serial.available() 与 Serial.read():
Serial.available():返回串口缓冲区中当前剩余的字符个数。一般用这个函数来判断串口的缓冲区有无数据,当Serial.available()>0时,说明串口接收到了数据,可以读取;Serial.read()指从串口的缓冲区取出并读取一个 Byte 的数据,比如有设备通过串口向 Arduino 发送数据了,我们就可以用Serial.read()来读取发送的数据。
#include "LiquidCrystal_I2C.h"
// 设置 LCD1602 的地址,列数,行数
LiquidCrystal_I2C lcd(0x27,16,2);
void setup()
{
// 初始化 LCD 对象
lcd.init();
// 开启背光
lcd.backlight();
// 开启串口通信
Serial.begin(9600);
}
void loop()
{
// 检测是否有串口输入
if (Serial.available()) {
// 延时以等待所有数据传输完成
delay(100);
// 清屏
lcd.clear();
// 反复读取串口的数据并在 LCD1602 屏幕上显示,直到数据读完
while (Serial.available() > 0) {
lcd.write(Serial.read());
}
}
}
