拿到一块带驱动板的LCD屏,很多人的第一反应是:这比裸屏好搞定多了吧?实际上,驱动板这东西既是福音也是坑。我帮朋友调试过不少显示类小项目,最常见的情况就是——不知道手里的屏到底用的什么驱动方案,把I2C接口的屏按并行接口一顿乱接,折腾一晚上屏幕只亮背光不出字符。这篇内容就是把"Arduino驱动LCD屏+超声波测距(有驱动板)"这件事彻底讲透:从辨别驱动板类型开始,到接线、写代码、调试避坑,一步步带你做出一台能实时显示距离的超声波测距显示器。用到的东西都不贵,逻辑也不复杂,适合刚入门的朋友复现,也可以给准备做避障小车、倒车雷达、距离提示器的项目当显示模块参考。
1. 先把"有驱动板"这件事搞清楚:它决定了你后面怎么接线
很多人以为"有驱动板"就是到手即用,其实不是。驱动板只是把屏幕的控制逻辑封装好了,但屏幕对外暴露的接口可能完全不同。这一步判断错了,后面全是白干。
1.1 三种常见的"带驱动板"形态
市面上带驱动板的LCD,最常见的是下面三类。你可以把手上的屏翻过来对照一下。
并行接口驱动板(最常见):这种屏通常是16个引脚,引脚间距2.54mm,板上直接焊着HD44780或其兼容芯片。引脚顺序一般是VSS、VDD、VO、RS、RW、E、D0-D7、A、K。这种"驱动板"其实只是把控制芯片坐上去了,接线还是要用一堆杜邦线,至少接6根信号线才能跑起来。
I2C转接板(最省事):在屏背面焊了一块小转接板,核心芯片是PCF8574T之类的IO扩展芯片,对外只引出VCC、GND、SDA、SCL四个引脚。你只需要接4根线,代码里用LiquidCrystal_I2C库就能驱动。这是现在我给别人做小项目时最喜欢的方案,省引脚、省时间、不容易接错。
SPI/串口转接板(较少见):用74HC595移位寄存器或者专用串口芯片做转接,引脚数量和I2C差不多,但协议是SPI或定制串口。Arduino里能用,但库的选择少一些,新手建议绕开。
1.2 快速识别方法:数引脚、看芯片、看丝印
怎么判断你手里是哪一种?三个动作:
- 数引脚:16个引脚大概率是并行HD44780方案;4个引脚且有标着SDA/SCL的,就是I2C转接板。
- 看芯片丝印:翻到背面,看到PCF8574T这类芯片,就是I2C;看到HD44780或兼容型号(比如很多国产屏用的还是类似封装),就是并行裸驱。
- 看库名:卖家提供的示例代码里如果出现
LiquidCrystal_I2C,那就是I2C板;出现LiquidCrystal(不带I2C),就是并行板。
下表做个汇总,方便你对号入座:
| 驱动板类型 | 对外引脚数 | 接线复杂度 | 需要用的库 | 适用场景 |
|---|---|---|---|---|
| 并行HD44780 | 16 | 高,6-11根杜邦线 | LiquidCrystal | 教学、引脚充足时使用 |
| I2C转接板 | 4 | 极低,4根线 | LiquidCrystal_I2C | 推荐,省引脚省调试 |
| SPI/串口转接 | 5-6 | 中,协议驱动 | LiquidCrystal_SPI等 | 特定场景,新手不推荐 |
一条比较实用的经验:拿到屏先不要急着翻代码,把屏背面的芯片丝印拍下来,查一下型号,远比你盲试省时间。我之前遇到过一次,屏上芯片丝印型号比较模糊,结果其实是兼容HD44780的国产芯片,用标准LiquidCrystal库反而跑不起来,害我排查了半天时序问题。
2. 超声波测距模块HC-SR04:原理、引脚与接线
说回测距部分。超声波模块里最普及的就是HC-SR04,几十块钱四个包邮,精度对于做小项目来说完全够用。它测距的原理一句话就能说清:发出超声波,测出回声回来的时间,用声速乘以时间的一半,就是距离。
2.1 原理拆解:声音打了个来回
整个测距过程可以拆成五个阶段:
- 你给Trig引脚一个至少10微秒的高电平脉冲。
- 模块内部自动发出8个40kHz的超声波脉冲。
- 超声波遇到前方障碍物后被反射回来。
- 模块的Echo引脚输出一个高电平,高电平持续的时间就是"声波发出去到收回来"的总时间。
- 用这个时间乘以声速再除以2,得到障碍物到模块的距离。
其中关键的计算公式是:
距离(cm) = Echo高电平时间(微秒) × 0.034 / 2
因为声波在空气中的传播速度大约是340米/秒,也就是0.034厘米/微秒。你自己拿笔算一下就明白了:如果Echo高电平持续了1000微秒,那距离就是1000 × 0.034 / 2 = 17厘米。
实际代码里大家更习惯这么写:
float distance = duration * 0.034 / 2;也有人直接除以58,接近但不够精确,我更推荐上面这个写法。
2.2 引脚说明与接线
HC-SR04正面看过去有四个引脚:
| 引脚名 | 功能 | 接Arduino |
|---|---|---|
| VCC | 供电正极,接5V | 5V |
| Trig | 触发脚,高电平脉冲启动测距 | 任选数字口,示例用D7 |
| Echo | 回声脚,输出高电平时间 | 任选数字口,示例用D6 |
| GND | 地线,必须和Arduino共地 | GND |
接线本身不复杂,但有两个细节值得注意。
第一是共地问题。超声波模块的GND和Arduino的GND必须连在一起,否则电平参考点不一致,Echo返回的信号可能乱掉。这算是所有传感器接线里的基本功,但真会有三成新手栽在这上面。
第二是Echo引脚的电压。HC-SR04的Echo回传信号是5V高电平,如果你用的是Arduino Uno这类5V单片机,直接连没问题。但如果你用ESP32、STM32这类3.3V的单片机,5V高电平可能会损伤引脚,需要用电阻分压或电平转换模块。本文默认用Arduino Uno,所以可以直连。
HC-SR04的标称测量范围是2厘米到400厘米,精度大约3毫米。但要注意它有个"盲区"——距离小于2厘米时,模块往往测不准,直接输出0或者乱跳。做倒车雷达、测距显示器时,不要在太近的场景里奢求精确值。
3. 完整接线方案:以I2C驱动板为例的推荐组合
既然标题里明确说了"有驱动板",又考虑到I2C驱动板是现在市面上占有量最高的方案,我整篇的接线方案就按I2C来组织,同时把并行方案作为备选放在小标题里讲一下。
3.1 推荐配置:I2C转接板 + 4根线搞定显示
如果你屏上焊的是PCF8574T这类I2C转接板,恭喜你,接线是最省心的一套。全局总共只需要6根信号线:
- 显示屏:VCC接5V,GND接GND,SDA接A4,SCL接A5
- 超声波模块:VCC接5V,Trig接D7,Echo接D6,GND接GND
接到Arduino Uno上时,A4和A5默认是I2C总线的SDA和SCL,这一点要记住。换到其他板子时(比如Arduino Mega的SDA是D20、SCL是D21),这个引脚映射会变,不要照搬。
下面是完整的接线对照表:
| 设备 | 引脚 | 接Arduino Uno引脚 |
|---|---|---|
| LCD I2C转接板 | VCC | 5V |
| LCD I2C转接板 | GND | GND |
| LCD I2C转接板 | SDA | A4 |
| LCD I2C转接板 | SCL | A5 |
| HC-SR04 | VCC | 5V |
| HC-SR04 | Trig | D7 |
| HC-SR04 | Echo | D6 |
| HC-SR04 | GND | GND |
3.2 并行LCD屏的接线备选方案
如果你的驱动板还是老式16脚并行方案,也不用担心,照样能实现一样的功能,只是多占几个IO口。推荐这样接:
RS接D12,E接D11,D4-D7分别接D5、D4、D3、D2。RW直接接地,省得在代码里切读写方向。同时记得在VO引脚(第三个脚)上接一个10K电位器到GND或者5V,用来调屏幕对比度——这一步很多人都漏掉,结果屏幕亮着但什么都显示不出来。
接好并行屏之后,配合HC-SR04,代码里的LCD初始化方式也变了。并行方案用的是LiquidCrystal lcd(12, 11, 5, 4, 3, 2);,而I2C方案用的是LiquidCrystal_I2C lcd(0x27, 16, 2);。两种方案代码其他部分几乎一样,这就是LCD库成熟的好处。
4. 代码实战:从点亮屏幕到稳定显示实时距离
下面这一段是整篇文章的核心。我会给你一套能直接烧录的完整代码,并逐行解释关键逻辑。建议你分三个阶段来走:先点亮屏幕显示静态文字,再接超声波模块测距,最后把两者合到一起。
4.1 环境准备:装库、确认地址
我用的是Arduino IDE 1.8.x或者2.x版本,安装两个库:
- LiquidCrystal_I2C:用来驱动I2C接口的LCD。IDE的库管理器里直接搜就可以安装,注意下载量最高的那个通常没问题。
- 超声波模块不需要额外库,直接读
pulseIn()就行。
装完库以后,第一件事不是写业务逻辑,而是用一个小程序扫描一下I2C地址,确认你的屏挂在哪个地址上。I2C转接板默认地址通常是0x27,但有些板子是0x3F。地址不对,后面所有代码都白写。
扫描代码很简单:
#include <Wire.h> void setup() { Serial.begin(9600); Wire.begin(); Serial.println("I2C Scanner"); } void loop() { byte error, address; int nDevices = 0; for (address = 1; address < 127; address++) { Wire.beginTransmission(address); error = Wire.endTransmission(); if (error == 0) { Serial.print("I2C device found at address 0x"); if (address < 16) { Serial.print("0"); } Serial.println(address, HEX); nDevices++; } } if (nDevices == 0) { Serial.println("No I2C devices found"); } delay(3000); }如果你接好线后,串口监视器里打印出来的地址是0x27,那初始化LCD时就用LiquidCrystal_I2C lcd(0x27, 16, 2);,如果扫描出来是0x3F就用0x3F。这个问题我在帮助朋友调试时遇到过好多次,乱码和蓝屏百分之九十不是库问题,而是地址写错了。
4.2 第一步:先点亮屏幕,显示静态文字
在接超声波之前,务必先把屏幕点亮。单独跑下面这段,看到屏幕上有字,再继续下一步:
#include <Wire.h> #include <LiquidCrystal_I2C.h> LiquidCrystal_I2C lcd(0x27, 16, 2); void setup() { lcd.init(); lcd.backlight(); lcd.setCursor(0, 0); lcd.print("Hello LCD"); lcd.setCursor(0, 1); lcd.print("Distance Demo"); } void loop() { // 先空着,用于后续扩展 }代码里lcd.init()初始化屏幕,lcd.backlight()打开背光,setCursor(列, 行)设置光标位置,然后print()输出字符串。注意坐标是从0开始的,所以第一行第一个字符的位置是(0, 0),第二行起始位置是(0, 1)。如果你的屏烧进去后没有任何显示,先查I2C地址、查接线、查对比度(并行屏)这三件事。
4.3 第二步:单独测试超声波模块
接着把超声波模块接上,先用串口输出距离,确认模块本身工作正常:
const int trigPin = 7; const int echoPin = 6; void setup() { Serial.begin(9600); pinMode(trigPin, OUTPUT); pinMode(echoPin, INPUT); } void loop() { long duration; float distance; digitalWrite(trigPin, LOW); delayMicroseconds(2); digitalWrite(trigPin, HIGH); delayMicroseconds(10); digitalWrite(trigPin, LOW); duration = pulseIn(echoPin, HIGH); distance = duration * 0.034 / 2; Serial.print("Distance: "); Serial.print(distance); Serial.println(" cm"); delay(200); }这段代码里有个细节值得展开说:在给Trig高电平之前,先把它拉低2微秒,是为了确保引脚状态干净,避免上一个测距周期的电平残留影响本次触发。pulseIn(echoPin, HIGH)会阻塞等待Echo引脚从低电平变为高电平,然后统计高电平持续的时间,单位是微秒,这个值就是前面公式里的duration。
延时200毫秒是为了让测距周期不至于太频繁——超声波测距本质是"打一声、听一声"的过程,如果上一声还没完全收回来就发下一声,容易串信号,数值会跳。
4.4 第三步:合体——LCD实时显示测距结果
把前面两段逻辑合并起来,就得到了完整程序:
#include <Wire.h> #include <LiquidCrystal_I2C.h> LiquidCrystal_I2C lcd(0x27, 16, 2); const int trigPin = 7; const int echoPin = 6; void setup() { Serial.begin(9600); pinMode(trigPin, OUTPUT); pinMode(echoPin, INPUT); lcd.init(); lcd.backlight(); lcd.setCursor(0, 0); lcd.print("Distance:"); } void loop() { long duration; float distance; digitalWrite(trigPin, LOW); delayMicroseconds(2); digitalWrite(trigPin, HIGH); delayMicroseconds(10); digitalWrite(trigPin, LOW); duration = pulseIn(echoPin, HIGH); distance = duration * 0.034 / 2; lcd.setCursor(0, 1); lcd.print(" "); // 清空第二行显示 lcd.setCursor(0, 1); lcd.print(distance); lcd.print(" cm"); Serial.print("Distance: "); Serial.println(distance); delay(200); }之所以在重新输出前用一整行空格"清屏",是因为LCD字符屏不像电脑屏幕那样支持局部像素级刷新,写进去的字符会一直留在那里。如果上一次显示是"123.45 cm",这次测出来是"6.2",不清掉旧字符就会出现"6.2 5 cm"这样的残留。直接用空格覆盖整行,虽然简单粗笨,但非常可靠。
4.5 关于刷新频率和闪烁的处理
用liquidCrystal_I2C库时,屏幕内容刷新其实有两种思路:一种是每次把整行内容重新打印,另一种是只更新变化的部分。上面的代码简单粗暴每次都做整行覆盖,好处是逻辑简单,坏处是肉眼能察觉到刷新感。200毫秒刷新一次,人眼基本不敏感;如果提高到50毫秒,闪烁感就出来了。
如果不想看到刷新闪烁,可以把第二行拆成两个区域:距离值区域和单位区域分开放,只在数值变化超过0.5厘米时才刷新。这个小优化很容易实现,也可以留个思考题给读者自己折腾。我个人做过一个类似项目,最终采用的是"距离变化绝对值超过1厘米才刷新",屏幕几乎零闪烁。
5. 实测中的坑:乱码、跳数、闪屏的完整排查链路
写到这我手痒,把这块板子重新搭了一遍实测。结果毫不意外,又踩了三个经典的坑。下面按真实排查顺序讲一遍,你可以直接对照复现。
5.1 屏幕全亮但没有字符
场景:背光正常亮起,屏幕上却只有一块块小方块,或者干脆空白。
第一步,检查I2C地址扫描——用前面那段扫描程序跑一遍,看有没有设备响应。我实测时发现,有一块转接板默认地址是0x3F,而代码里写的是0x27,结果屏幕只亮背光,一个字符都不显示。改成0x3F就好了。
第二步,检查SDA和SCL是不是接反了。I2C总线的SDA和SCL接反,设备不响应,扫描程序也找不到设备。
第三步,如果是并行屏,检查VO脚的对比度电位器。这个脚电压决定了LCD显示对比度,调到中间位置通常能看到字符;如果直接悬空,基本上就是背光亮、无字符。
5.2 显示乱码:汉字变成方块
场景:显示"Distance"这类英文字符没问题,但想显示中文或者某些特殊字符时变成一坨方块。
这不一定是接线问题,而是HD44780这类字符型LCD根本不支持中文。它内置的字库只有128个ASCII和日文假名字符,显示中文需要自定义字模或者用图形点阵屏。所以代码里尽量避免显示中文,显示"cm"、"Distance"这些英文都没问题。如果你是做中文提示,建议老老实实换一块带字库的中文液晶屏或者OLED。
5.3 超声波数值乱跳
场景:测量静止墙壁,屏幕上却在10厘米到40厘米之间随机跳动。我实测中遇到过一次,最初怀疑是模块坏了,后来发现是USB供电不稳。
排查链路是这样:先用串口监视器观察数值——如果串口输出的数值和LCD显示的数值一样乱,问题大概率在超声波模块或电源;如果串口输出稳定但LCD显示乱,问题在显示刷新逻辑。
我那次实测,最终用的是独立5V电源配合一个100微法的电解电容并联在模块的VCC和GND之间,数值立刻稳定下来。原因很简单:HC-SR04发出超声波脉冲的瞬间电流尖峰很大,USB口供电扛不住这种瞬态波动,加上电容等于给它配了个"小型蓄水池"。
另外,测量对象也有影响。绒布、海绵这类软材料会吸收超声波,导致回声信号弱,数值偏大或者跳变。实测时拿硬纸板、木板的稳定性明显好很多。所以做测距显示时,前面尽量不要放抱枕这类软东西。
5.4 一接超声波,LCD屏幕就开始闪
场景:LCD单独跑很稳,超声波也单独跑很稳,两个代码合在一起屏幕就闪。
这个问题的原因比较隐蔽:Arduino Uno只有一个定时器资源用于pulseIn()延时的内部机制,而LCD的I2C刷新也依赖微控制器逐字节发送数据。当你在loop()里做了一次完整的超声波测量和一次LCD刷新,如果两个操作抢时间,I2C时序就会被拉长,屏幕刷新就出问题。
我的实测解法是:提高延时到300毫秒,把测距周期和刷新周期错开。实测300毫秒刷新下,肉眼基本看不出闪烁,距离刷新频率也不算低。更极端的做法是用定时器中断去测距,把测距过程和LCD刷新解耦,但这对于新手来说没必要,而且容易引入中断优先级问题。先跑通,再优化,比一上来就上中断靠谱。
对于排查过程,直接看下面这张表,能省很多事:
| 现象 | 排查方向 | 常见原因 | 解决方式 |
|---|---|---|---|
| 背光亮但无字符 | 地址扫描、接线 | I2C地址错误、SDA/SCL接反 | 用扫描程序找地址,改正接线 |
| 显示乱码方块 | 字库支持范围 | LCD不支持中文 | 英文输出或换点阵屏 |
| 距离数值跳变 | 电源、测量对象 | 供电瞬间电流不足、软质障碍物 | 加电容、独立供电、用硬质测试物 |
| 屏幕闪烁 | 刷新周期与测距抢时间 | 循环里高频刷新I2C | 提高延时、值变化才刷新 |
最后再分享一点使用心得
这套东西我前前后后做过好几版,从一开始的纯并行屏加超声波,到后来改I2C屏加I2C超声波模块,再到现在最稳定的Uno加I2C屏加HC-SR04。如果只留一条经验给后来的朋友,那就是:先点亮屏,再接传感器。很多人图省事把所有接线一次搞定,一上来就烧完整代码,出了问题根本不知道是哪一环坏了。把链路拆开一步步验证,每次改动只引入一个变量,排查效率翻倍。
另外,如果你想把这个小项目继续往外延伸,几个我实验过的方向供参考:加一个蜂鸣器做倒车提示,距离小于30厘米就报警;把距离数据通过串口发出去,配合上位机画距离曲线;换成OLED显示,体积更小、刷新更快;或者用中值滤波代替简单的单次采样,数值稳定性会再上一个台阶。整体成本都不高,玩法却不少,完全可以当做一个入门的综合项目来练手。