Arduino LCD自定义动画:从createChar()原理到多帧动画实战

ArduinoLCD自定义字符
于 2026-05-31 12:55:58 修改
·本内容遵循CC 4.0 BY-SA版权协议

1. 项目概述:在16x2 LCD上玩转自定义动画

如果你手头正好有一块Arduino开发板和一块经典的16x2字符型LCD显示屏,除了显示“Hello World”和传感器读数,有没有想过让它“动”起来?比如,让一只像素风的蝴蝶在屏幕上扇动翅膀,或者让一个简单的进度条来回滚动。这听起来像是需要复杂图形库才能完成的任务,但实际上,利用Arduino标准库中一个非常强大却常被忽视的函数——createChar(),你就能轻松实现自定义字符乃至流畅的动画效果。这项技术的核心价值在于,它打破了标准字符集(那些固定的字母、数字和符号)的束缚,让你能在仅由5x8像素点阵构成的微小画布上,创造出任何你想要的图形,并通过多帧切换实现动态视觉。无论是为你的智能家居项目添加一个生动的状态图标,还是制作一个极简的复古小游戏,亦或是仅仅为了在调试时让设备显得更“酷”一点,掌握这项技能都能为你的嵌入式项目增色不少。接下来,我将以一个完整的“蝴蝶蜕变动画”为例,带你从原理到实践,一步步拆解如何在16x2 LCD上实现自定义动画,过程中会穿插大量我实际调试中积累的细节和避坑经验。

2. 核心原理与硬件基础解析

2.1 LCD 1602显示屏的显示机制

要玩转自定义图形,首先得理解这块屏幕是如何工作的。我们常说的16x2 LCD,通常指的是兼容Hitachi HD44780控制器的字符型液晶模块。“16x2”意味着它有两行,每行可以显示16个字符。这里的“字符”是基本显示单元,每个字符占据一个固定的“字符位”。

关键在于每个字符的构成。在HD44780的标准下,每个字符实际上是在一个8行 x 5列的像素点阵中定义的。也就是说,一个字符的高度是8个像素点,宽度是5个像素点。控制器内部存储了一套字符发生器ROM(CGROM),里面固化了几百个标准的字母、数字、日文片假名等字符的点阵数据。当你让屏幕显示字母‘A’时,控制器就是从CGROM中调取‘A’对应的5x8点阵数据,点亮相应的像素。

然而,HD44780还预留了一个非常灵活的功能:字符发生器RAM(CGRAM)。这块RAM区域允许用户自定义8个字符(对于大多数控制器而言)的点阵数据。你可以把自己设计的5x8像素图案写入CGRAM,并给它分配一个索引号(0-7)。之后,你就可以像调用普通字符一样,通过这个索引号在屏幕上显示你的自定义图案了。LiquidCrystal库中的createChar(num, data)函数,正是封装了向CGRAM写入数据的过程。

注意:CGRAM是易失性存储器。这意味着一旦Arduino断电,你写入的自定义字符数据就会丢失。每次上电初始化时,都必须重新向CGRAM写入字符数据,然后才能正常显示。

2.2 createChar()函数与位图映射原理

createChar()函数是连接你的创意和屏幕像素的桥梁。它的函数原型通常如下:

CPP
void createChar(uint8_t num, uint8_t data[]);
  • num: 自定义字符的索引号,范围是0到7。强烈建议避开0号,因为在某些显示模式下,0号字符可能被用于其他用途(如光标),容易导致显示异常。通常使用1-7号更安全。
  • data[]: 一个包含8个字节(byte)的数组,每个字节定义了字符的一行像素。

那么,如何用一个字节来表示一行(5个像素)呢?这里就用到了位图映射。每个字节有8个二进制位(bit),我们只使用其中最低的5位(bit0到bit4)来对应一行的5个像素。通常,1代表像素点亮(黑色),0代表像素熄灭(透明/背景色)。

例如,你想定义一行像素,从左到右依次为:亮、灭、亮、灭、亮。对应的二进制位就是10101。在Arduino中,我们可以用二进制字面量0b10101(十进制21)来表示这个字节。但更直观的方法是使用十六进制,0b10101等于0x15

一个完整的自定义字符,就需要8个这样的字节,从上到下定义8行。例如,定义一个简单的“笑脸”字符,其数据数组可能如下:

CPP
byte smiley[8] = {
0b00000,
0b10001,
0b00000,
0b00000,
0b10001,
0b01110,
0b00000,
};

实操心得:在纸上或心里画一个5列8行的网格,从左下角开始编号列(0-4),从下往上编号行(0-7)有时会让人困惑,因为数组下标0对应的是屏幕最顶行。我建议直接在网格纸上画图,标亮想要的点,然后逐行翻译成二进制,这样最不容易出错。

2.3 硬件连接与库初始化

硬件连接是项目的基础。16x2 LCD通常有16个引脚(有些背光模块可能略有不同),其与Arduino的连接遵循标准方式:

  • VSS, VDD, V0: 分别接GND(地)、5V(电源)、以及一个用于调节对比度的电位器中间引脚。对比度调节至关重要,对比度不对可能什么都看不见。
  • RS, RW, E: 寄存器选择、读写选择、使能引脚。通常RW接地(始终写模式),RS和E接Arduino的数字引脚。
  • D0-D7: 8位数据总线。为了节省IO口,我们几乎总是使用4位数据模式,即只连接高4位(D4-D7)。D0-D3悬空。
  • A, K: 背光阳极和阴极。通常A通过一个限流电阻接5V,K接GND。

一个典型的连接示例如下(以Arduino Uno为例):

LCD引脚 连接至
VSS GND
VDD 5V
V0 10kΩ电位器中端
RS Digital Pin 12
RW GND
E Digital Pin 11
D4 Digital Pin 5
D5 Digital Pin 4
D6 Digital Pin 3
D7 Digital Pin 2
A (背光+) 通过220Ω电阻接5V
K (背光-) GND

在代码中,使用LiquidCrystal库初始化对象时,就需要按照这个引脚顺序来声明:

CPP
# include <LiquidCrystal.h>
// 初始化对象:参数顺序为(RS, E, D4, D5, D6, D7)
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

setup()函数中,还需要指定显示屏的尺寸:

CPP
void setup() {
lcd.begin(16, 2); // 初始化一个16列2行的LCD
// ... 后续创建自定义字符
}

注意事项:如果连接后屏幕只显示一排方块或者乱码,请首先检查对比度(调节V0的电位器),其次检查lcd.begin()的调用是否在createChar()之前,最后仔细核对引脚连接顺序是否与代码中初始化对象时一致。

3. 从静态字符到动态动画的实现路径

3.1 利用在线工具高效设计字符

手动计算每个字符的8字节数组虽然可行,但效率极低且容易出错,尤其是设计复杂图形时。原作者提到的工具 https://tusindfryd.github.io/screenduino/ 是一个基于Web的图形化编辑器,它极大地简化了这个过程。

工具界面通常是一个模拟的5x8像素网格。你可以用鼠标点击格子来“点亮”或“熄灭”像素。当你设计图形时,工具会实时在右侧生成对应的Arduino代码,即一个byte数组和lcd.createChar()调用语句。这个工具的核心优势在于所见即所得,你可以立即预览图形效果,而无需经历“编写数组 -> 上传 -> 查看 -> 修改”的繁琐循环。

使用技巧

  1. 规划字符空间:工具允许你设计最多8个自定义字符(对应CGRAM的8个槽位)。在开始动画设计前,最好先在纸上规划好每一帧动画需要占用哪几个字符槽。一个复杂的图形可能需要多个自定义字符拼接而成。
  2. 利用“仅生成函数”选项:这是制作动画的关键。当你设计好第一帧(Frame 0)后,不要勾选“just the function”,这样生成的代码会包含完整的setup()loop()框架,方便你首次上传测试。从第二帧开始,务必勾选此选项,这样工具就只生成一个用于创建字符的函数(例如void createChar_0()),你可以将其复制粘贴到已有代码的末尾,并重命名以避免冲突。
  3. 注意像素边界:由于每个自定义字符是独立的5x8单元,如果你设计的图形跨越了字符边界(比如一个10像素宽的图形),你需要将它拆分成两个(或更多)自定义字符,并确保在显示时将它们相邻放置。

3.2 单帧图像的创建与显示流程

让我们通过第一帧图像的创建,来理解完整的工作流。

  1. 设计:在工具中绘制你的第一帧图像。例如,一只蝴蝶的闭合翅膀状态。假设这个图形用一个自定义字符就能表示。
  2. 生成代码:不勾选“just the function”,复制生成的完整代码。生成的代码结构大致如下:
    CPP
    #include <LiquidCrystal.h>
    LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
     
    byte customChar[8] = {
    // ... 你的8字节数据
    };
     
    void setup() {
    lcd.begin(16, 2);
    lcd.createChar(1, customChar); // 将图案存入1号CGRAM槽
    lcd.setCursor(0, 0); // 将光标移动到第1行第1列
    lcd.write(byte(1)); // 显示1号自定义字符
    }
     
    void loop() {
    // 初始代码loop是空的
    }
  3. 上传与测试:将代码上传到Arduino。如果一切正常,你将在LCD的指定位置看到你设计的静态图案。 常见问题:如果屏幕空白,请按顺序排查:背光亮了吗?对比度调了吗?lcd.begin()调用了吗?createChar的索引号是1-7吗?lcd.write的参数是否正确(需要用byte()包裹索引号)?

3.3 多帧动画的合成与切换逻辑

动画的本质是多帧静态图像的快速连续切换。在LCD上实现动画,需要以下步骤:

  1. 设计后续帧:回到在线工具,清除画布(或基于上一帧修改),绘制动画的第二帧(例如,蝴蝶翅膀半开)。勾选“just the function”,复制生成的函数。这个函数只包含一个新的字节数组和createChar调用。
  2. 整合代码:将新函数粘贴到你的Arduino代码末尾。关键一步是重命名函数和数组,避免与第一帧冲突。例如,将第一帧的相关内容改名为frame0CharcreateFrame0(),第二帧的改名为frame1CharcreateFrame1()
    CPP
    byte frame0Char[8] = { ... };
    byte frame1Char[8] = { ... };
     
    void createFrame0() {
    lcd.createChar(1, frame0Char); // 注意:我们复用1号CGRAM槽
    }
    void createFrame1() {
    lcd.createChar(1, frame1Char); // 将第二帧数据写入同一个1号槽
    }
    这里有一个重要技巧:为了节省有限的CGRAM(只有8个槽),对于单字符动画,我们可以让所有帧复用同一个字符索引。在切换帧时,我们用新一帧的数据覆盖这个CGRAM槽。这样,屏幕上显示该索引字符的位置,内容就会立即改变。
  3. 修改setup()loop()
    • setup()中,我们只需要初始化LCD和创建第一帧字符(createFrame0()),并显示它。
    • loop()中,我们实现动画循环:
      CPP
      void loop() {
      createFrame0(); // 写入第一帧数据到CGRAM
      lcd.setCursor(0, 0);
      lcd.write(byte(1)); // 显示(此时已是第一帧)
      delay(250); // 保持第一帧250毫秒
       
      createFrame1(); // 用第二帧数据覆盖CGRAM的1号槽
      // 注意:光标位置没变,我们不需要重新setCursor
      lcd.write(byte(1)); // 再次“显示”1号字符,此时内容已变为第二帧
      delay(250); // 保持第二帧250毫秒
      }
    核心原理lcd.write(byte(1))只是命令屏幕“显示存储在CGRAM中1号位置的字符”。它并不关心这个位置当前存的是什么数据。当我们用createFrame1()覆盖了1号CGRAM的数据后,下一次write(byte(1))就会显示出新的图案。通过交替写入不同帧的数据并显示,配合delay()控制节奏,动画就产生了。

3.4 延时控制与动画流畅度优化

delay()函数控制着每一帧的持续时间,它直接决定了动画的播放速度(帧率)。250毫秒的延迟对应大约4 FPS(帧每秒),对于简单的状态指示动画来说已经足够。但delay()有一个众所周知的缺点:它会阻塞整个程序。在延时期间,Arduino无法执行其他任何任务(如读取传感器、响应按钮)。

对于需要同时处理其他任务的复杂项目,可以考虑以下优化方案:

  1. 使用millis()进行非阻塞定时:这是最推荐的方法。其原理是利用Arduino开机后不断递增的毫秒计时器,通过检查时间间隔来触发帧切换,而不使用阻塞的delay()

    CPP
    unsigned long previousFrameTime = 0;
    const long frameInterval = 250; // 帧间隔250ms
    int currentFrame = 0;
     
    void loop() {
    unsigned long currentTime = millis();
     
    if (currentTime - previousFrameTime >= frameInterval) {
    // 时间到了,切换下一帧
    previousFrameTime = currentTime;
     
    switch(currentFrame) {
    case 0:
    createFrame0();
    currentFrame = 1;
    break;
    case 1:
    createFrame1();
    currentFrame = 0; // 回到第0帧,形成循环
    break;
    }
    lcd.setCursor(0,0);
    lcd.write(byte(1));
    }
    // 在这里可以添加其他非阻塞代码,如读取传感器
    // int sensorValue = analogRead(A0);
    }

    这样,动画会以固定的间隔运行,同时loop()函数在帧间隔期间可以快速执行其他任务,整个系统响应性更好。

  2. 调整帧间隔:动画的流畅度取决于帧率和图形复杂度。对于简单的两帧动画(如闪烁的箭头),较慢的帧率(500ms)可能更合适。对于需要表现连续运动的动画(如滚动的球),可能需要更短的间隔(100-150ms)和更多的中间帧(3-4帧)。你需要根据实际视觉效果进行试验。

4. 高级技巧与复杂动画实现

4.1 多字符拼接与场景构建

一个5x8的字符空间非常有限。要显示更复杂的图形或场景,必须将多个自定义字符拼接起来。例如,显示一个10像素宽、8像素高的图标,就需要横向拼接2个自定义字符。

实现步骤

  1. 设计:在在线工具或图形软件中,设计你的完整图形,并将其精确地分割到多个5x8的网格中。假设我们需要一个宽10像素的图标,那就需要两个字符(Char A和Char B)。

  2. 生成代码:为Char A和Char B分别生成自定义字符数据,并存入不同的CGRAM槽,例如1号和2号。

    CPP
    byte charA[8] = { ... }; // 图标的左半部分
    byte charB[8] = { ... }; // 图标的右半部分
    lcd.createChar(1, charA);
    lcd.createChar(2, charB);
  3. 显示:在屏幕上相邻的位置依次显示这两个字符。

    CPP
    lcd.setCursor(0, 0); // 从第1行第1列开始
    lcd.write(byte(1)); // 显示左半部分
    lcd.write(byte(2)); // 显示右半部分,它会紧挨着左半部分显示

    重要细节:字符型LCD的显示位置是固定的网格。两个字符之间没有间隙,所以拼接是连续的。但你需要确保在设计时,两个字符的接缝处图案是连贯的。

    对于动画,你需要为每一帧的每一个组成部分(Char A和Char B)都设计好数据。在动画循环中,你需要更新所有相关CGRAM槽的数据,然后重新显示它们。

4.2 利用CGRAM槽位管理复杂序列

当动画帧数超过8帧,或者一帧需要超过8个自定义字符时,8个CGRAM槽位就不够用了。这时需要使用槽位复用与动态加载策略。

策略一:分批次加载 如果动画总帧数很多,但每一帧同时需要的自定义字符不超过8个,你可以在内存(Arduino的RAM)中存储所有帧的所有字符数据,但在运行时,只把当前帧需要用到的字符数据加载到CGRAM中。

CPP
// 在全局定义所有帧的数据
byte allFrames[总帧数][8] = { ... };
 
int currentFrameIndex = 0;
void loadFrame(int frameIndex) {
// 假设每帧只用1个自定义字符
lcd.createChar(1, allFrames[frameIndex]);
}
// 在loop中,根据时间切换currentFrameIndex,并调用loadFrame

策略二:字符复用 分析你的动画,看是否有在不同帧中重复出现的图形元素。如果有,可以将这个元素固定存放在某个CGRAM槽中,所有帧都引用它,而不是为每一帧都重新定义。这可以节省宝贵的CGRAM槽位。

4.3 结合传感器输入的交互式动画

让动画与物理世界交互,项目会立刻变得生动有趣。例如,用一个旋钮(电位器)控制动画播放速度,或用按钮切换动画序列。

示例:电位器控制动画速度

CPP
const int potPin = A0; // 电位器接在A0引脚
int frameDelay; // 帧延迟时间变量
 
void loop() {
// 读取电位器值,映射到50ms到500ms的延迟范围
int potValue = analogRead(potPin);
frameDelay = map(potValue, 0, 1023, 50, 500);
 
// 非阻塞动画逻辑(使用millis())
unsigned long currentTime = millis();
static unsigned long lastFrameTime = 0;
static bool showFrame0 = true;
 
if (currentTime - lastFrameTime >= frameDelay) {
lastFrameTime = currentTime;
if (showFrame0) {
createFrame0();
} else {
createFrame1();
}
lcd.setCursor(0,0);
lcd.write(byte(1));
showFrame0 = !showFrame0; // 切换帧标志
}
}

这样,旋转电位器就能实时改变蝴蝶翅膀扇动的快慢。

示例:按钮切换动画场景 假设你有两组动画(A组:蝴蝶, B组:跳动的心)。你可以用一个按钮来切换。

CPP
const int buttonPin = 7;
int animationMode = 0; // 0:蝴蝶, 1:爱心
int lastButtonState = HIGH;
int buttonState;
 
void loop() {
// 检测按钮(带简单消抖)
buttonState = digitalRead(buttonPin);
if (buttonState == LOW && lastButtonState == HIGH) {
delay(50); // 消抖延时
buttonState = digitalRead(buttonPin);
if (buttonState == LOW) {
animationMode = 1 - animationMode; // 在0和1之间切换
}
}
lastButtonState = buttonState;
 
// 根据模式播放不同动画
if (animationMode == 0) {
playButterflyAnimation();
} else {
playHeartAnimation();
}
}

5. 实战:蝴蝶蜕变动画全流程拆解

现在,让我们将以上所有知识整合,从头开始实现一个完整的、包含多帧的蝴蝶扇翅动画。假设我们的蝴蝶需要两帧,且宽度超过5像素,因此需要两个自定义字符横向拼接。

5.1 图形设计与数据准备

  1. 帧设计

    • 帧0(翅膀收拢):设计一个宽10像素的收拢翅膀的蝴蝶。在纸上或绘图软件中画好,并明确分割线:左5列像素为“字符L”,右5列像素为“字符R”。
    • 帧1(翅膀展开):设计同一个蝴蝶展开翅膀的状态。同样分割成“字符L”和“字符R”。
  2. 使用在线工具生成数据

    • 打开 https://tusindfryd.github.io/screenduino/
    • 绘制“帧0-字符L”的图案,不勾选“just the function”,生成第一版完整代码。我们从中提取出字节数组,命名为frame0_L
    • 清空画布,绘制“帧0-字符R”。勾选“just the function”,复制生成的函数体。我们提取字节数组,命名为frame0_R
    • 同理,分别绘制“帧1-字符L”和“帧1-字符R”,并提取数组frame1_Lframe1_R

5.2 代码编写与整合

最终的Arduino代码结构如下:

CPP
# include <LiquidCrystal.h>
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
 
// 定义所有字符数据
byte frame0_L[8] = { ... }; // 帧0左半部分
byte frame0_R[8] = { ... }; // 帧0右半部分
byte frame1_L[8] = { ... }; // 帧1左半部分
byte frame1_R[8] = { ... }; // 帧1右半部分
 
// 用于非阻塞定时的变量
unsigned long previousMillis = 0;
const long interval = 300; // 动画间隔300ms
int currentFrame = 0;
 
void setup() {
lcd.begin(16, 2);
// 初始化显示第一帧
showFrame(0);
}
 
void loop() {
unsigned long currentMillis = millis();
 
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
 
// 切换到下一帧
currentFrame = 1 - currentFrame; // 在0和1之间交替
showFrame(currentFrame);
}
// 此处可添加其他任务代码
}
 
// 显示指定帧的函数
void showFrame(int frameNum) {
if (frameNum == 0) {
lcd.createChar(1, frame0_L); // 将帧0左半部分存入1号槽
lcd.createChar(2, frame0_R); // 将帧0右半部分存入2号槽
} else if (frameNum == 1) {
lcd.createChar(1, frame1_L); // 将帧1左半部分存入1号槽
lcd.createChar(2, frame1_R); // 将帧1右半部分存入2号槽
}
// 在屏幕中央显示(第0行,从第3列开始,这样蝴蝶大概在中间)
lcd.setCursor(3, 0);
lcd.write(byte(1)); // 显示左半字符
lcd.write(byte(2)); // 显示右半字符
}

代码解析

  • 我们使用了4个字节数组来存储两帧动画的左右部分。
  • showFrame函数根据传入的帧号,将对应的两组数据分别加载到CGRAM的1号和2号槽。这里我们固定使用这两个槽位,通过覆盖来更新内容。
  • loop中,我们使用基于millis()的非阻塞定时器,每300毫秒切换一次帧号(0或1),并调用showFrame更新显示。
  • lcd.setCursor(3,0)将光标定位到第一行的第四列(从0开始计数),然后连续写入两个自定义字符,它们就会并排显示,形成完整的蝴蝶。

5.3 调试与效果优化

上传代码后,你可能会遇到一些问题,或者希望对效果进行微调:

  1. 画面闪烁或残影:这是最常见的问题。原因是createChar()写入CGRAM和write()显示字符之间可能存在极短的时间差,导致屏幕在更新两个字符时,一个已更新另一个还是旧图。解决方法:在更新完所有相关CGRAM槽位(本例中1和2)之后,再执行setCursorwrite。我们的showFrame函数正是这样做的。
  2. 动画不流畅:可能是帧间隔时间不合适。尝试调整interval常量的值。太慢会像幻灯片,太快则可能因LCD响应速度有限而产生模糊。250-500ms是简单双帧动画的常用范围。
  3. 图形错位:检查setCursor的位置是否正确。确保为拼接字符留出了足够的空间。例如,如果你在第二行显示,要确保该行有足够的空位,不会与其他文字重叠。
  4. 内存不足:如果你设计的帧数很多,字节数组会占用大量RAM(每个字符8字节)。Arduino Uno只有2KB RAM。如果编译时提示内存不足,可以考虑将字符数据存放在PROGMEM(程序存储器)中,使用的时候再读取到RAM。这需要用到pgm_read_byte等函数,稍微复杂,但可以节省宝贵的RAM。

6. 常见问题排查与进阶思路

6.1 典型问题速查表

问题现象 可能原因 排查步骤与解决方案
屏幕完全空白 1. 电源或背光未接通。
2. 对比度调节不当。
3. 初始化失败。
1. 检查VDD、VSS、背光引脚连接。
2. 缓慢旋转对比度电位器。
3. 确认lcd.begin(16,2)已执行。
只显示一排方块 对比度过高或初始化不正确。 1. 首要调节对比度电位器。
2. 检查lcd.begin()是否在setup()中最早调用之一。
自定义字符显示为乱码 1. CGRAM索引错误。
2. 数据数组定义错误。
3. createCharbegin之前调用。
1. 确认lcd.write(byte(X))中的X是0-7,且建议用1-7。
2. 用在线工具重新生成并核对数组数据。
3. 确保代码顺序:先begin(),后createChar()
动画不切换/一直显示第一帧 1. loop()中动画切换逻辑未执行。
2. 帧切换函数未被调用。
3. 使用了阻塞的delay()且逻辑有误。
1. 检查loop()中控制帧切换的条件(如millis()判断)是否成立。
2. 添加串口打印调试信息,查看帧号是否变化。
3. 检查delay()的位置是否阻止了后续代码执行。
动画闪烁严重 CGRAM更新与显示不同步。 确保在同一时刻更新一帧所需的所有CGRAM槽位,然后再统一执行setCursorwrite。将更新和显示封装在一个函数(如showFrame)中是良好实践。
编译错误:数组太大 自定义字符数据占用过多RAM。 1. 减少动画帧数或每帧字符数。
2. 将常量数组移至PROGMEM(程序存储区)。

6.2 超越基础:更复杂的项目构思

掌握了基本动画后,你可以尝试更有挑战性的项目:

  1. 文本滚动特效:让一段长文本在LCD上平滑滚动。这不需要自定义字符,但需要精细控制setCursorscrollDisplayLeft/Right()函数,并处理好字符串的截取与拼接。
  2. 简易游戏:例如“接金币”游戏。用自定义字符表示玩家、金币和障碍物。通过按钮控制玩家移动,利用loop()循环更新游戏状态(金币下落、碰撞检测、计分),并在LCD上刷新画面。这需要将动画逻辑与游戏状态机结合。
  3. 系统状态仪表盘:为你的环境监测项目(温湿度、空气质量)设计一套自定义图标(太阳、云朵、水滴、警告标志等)。根据传感器读数,动态切换或组合显示这些图标,使状态显示更加直观。
  4. 多屏动画与场景叙事:利用LCD的两行,构建简单的多场景叙事动画。例如,第一行显示天空和飞鸟(动画),第二行显示地面和行走的小人(动画),讲述一个简短的故事。

6.3 资源管理与性能考量

对于更复杂的项目,资源管理变得重要:

  • 内存管理:如前所述,大量图形数据应存放在PROGMEM中。可以使用const PROGMEM关键字定义数组,并通过pgm_read_byte()函数在需要时读取。
  • 执行效率:频繁调用createChar()setCursor()write()会有一定开销。如果动画要求极高帧率,可以考虑:
    • 预计算:将所有帧的数据预先以特定格式组织好。
    • 减少操作:如果动画只是局部变化,可以只更新变化的字符,而不是全屏刷新。
  • 扩展CGRAM:有些兼容HD44780的控制器支持扩展CGRAM(多于8个字符)。但这需要查阅具体LCD模块的数据手册,并使用更底层的指令进行控制,超出了标准LiquidCrystal库的范围。

从我个人的经验来看,在16x2 LCD上制作动画,最大的乐趣不在于技术的复杂性,而在于在极其有限的资源(像素、内存、速度)下发挥创意的过程。每一次成功的像素跳动,都是对硬件理解和编程逻辑的一次小小胜利。从静态字符到动态效果,这一步跨越为你打开了嵌入式UI设计的一扇小窗,让你能够以更低的成本和更高的个性化程度,为你手中的项目注入灵魂。

LCD-Custom-Characters:使用LCD显示屏和Arduino创建自定义图标的代码示例
```cpplcd.createChar(0, heart);```4. **显示自定义字符**最后,可以通过字符位置在LCD上显示自定义字符。
Jmoh
39
在16x2 LCD显示屏上创建自定义动画-项目开发
()`,以及如何规划和设计自定义动画
weixin_38631773
65
visual-lcd-char-generator:此工具允许您使用 Arduino LiquidCrystal 库为 HD44780 LCD 模块创建自定义字符
《使用JavaScript创建自定义LCD字符Visual LCD Char Generator与Arduino LiquidCrystal库的结合》在电子制作和嵌入式系统领域,LCD显示器是常见的显示设备
19
LiquidCrystal_lcd:Arduino控制LCD
扩展功能除了基本的 LCD 控制,`LiquidCrystal` 库还支持创建自定义字符。通过 `createChar()` 方法,用户可以创建最多8个自定义字符供 LCD 使用。
易烊千玺的小朋友
71
DisplayClock:带有自定义字符的4x20 LCDArduino时钟
代码可能会包含以下几个关键部分- 初始化设置LCD的通信协议,初始化显示参数。- 时间获取使用Arduino的`millis()`函数或RTC模块获取当前时间。
王奥雷
11
LCD_alpha_arduino_Alpha_
**更高级的功能** LiquidCrystal库还支持显示数字、自定义字符、滚动文本等功能。可以创建自定义字符通过`lcd.createChar()`,并在屏幕上显示它们。
弓弢
4
LiquidCrystal_I2C.zip
};lcd.createChar(0, custom_char); // 创建一个自定义字符// 使用自定义字符lcd.setCursor(0, 1);lcd.write(0); // 显示自定义字符`
BIGBOSSyifi
1339
LCD.rar_arabic
**编程实现** - 使用Arduino、AVR、PIC或其他微控制器,我们需要编写代码来初始化LCD、设置控制线、写入指令和数据。
御道御小黑
3
LiquidCrystal_I2C
以及`lcd.createChar()`用于创建自定义字符等。
chenxi788
1250
(源码)基于Arduino平台的智能婴儿摇篮系统.zip
# 基于Arduino平台的智能婴儿摇篮系统## 项目简介智能婴儿摇篮系统是一个基于Arduino平台的物联网项目,旨在通过使用LiquidCrystalI2C库来控制I2C接口的LCD显示屏,实现对
静默小音箱
3
Arduino玩转LCD16025分钟实现自定义字符显示(附库函数详解)
本文详解Arduino平台下LCD1602的4位模式硬件连接与LiquidCrystal库应用,重点讲解5×8像素自定义字符原理createChar()函数使用方法;涵盖CGRAM空间管理、多图标批量定义、动态字符动画实现,并提供清屏优化、Flash字符串存储、状态机界面设计等高性能编程技巧。
399
Arduino 1602 LCD自定义字符全攻略从硬件连接到动态图标设计
本文详解Arduino平台下1602 LCD自定义字符的完整实现流程,涵盖硬件四线制连接要点、LiquidCrystal库核心函数(createChar、write)、5×8点阵图标设计原理与二进制编码方法、动态动画实现机制,以及突破8个字符槽限制的分页管理策略。重点解析CGRAM存储机制、字符动态刷新、对比度调节与常见显示异常调试方法。
weixin_30340819
401
Arduino字符LCD进度条实现:自定义字符与I2C通信详解
本文详解如何在字符型LCD(如1602)上用Arduino实现进度条,核心是利用CGRAM自定义8个5×8像素字符模拟填充效果,并通过I2C通信简化硬件连接。重点包括自定义字符定义、进度百分比到像素列的映射算法、基础版与增强版代码对比、位置/宽度灵活调整方法,以及I2C地址识别、对比度调节、编译错误处理等实战要点。
weixin_33701564
363
Arduino UNO移植Chrome恐龙游戏:LCD键盘护板嵌入式开发实战
本文详细介绍了如何在Arduino UNO与LCD键盘护板上复现Chrome断网恐龙游戏。内容涵盖硬件连接(UNO与1602 LCD护板)、按键ADC阈值校准、基于状态机的游戏逻辑设计、自定义字符渲染、局部刷新优化及机械按键去抖动处理。重点突出嵌入式环境下资源受限时的实时控制、图形适配与性能调优方法。
weixin_30644369
440
ArduinoLCD1602做个‘表情包’手把手教你自定义5x7点阵字符(附完整代码)
本文详解如何利用Arduino和LiquidCrystal库,在LCD1602液晶屏上实现5×7点阵自定义字符,涵盖CGRAM原理、像素级图案设计、二进制数据映射、createChar()编程方法及PROGMEM优化技巧,并拓展至传感器联动、状态指示与简易动画等嵌入式应用。
阿潇咿呀呀
307
Arduino+DHT11+LCD1602从零构建温湿度监测系统
本文详细介绍了基于Arduino Uno、DHT11温湿度传感器和I2C接口LCD1602显示屏构建嵌入式环境监测系统的完整流程。涵盖硬件选型原理(单总线通信、I2C驱动)、电路连接(VCC/GND/SDA/SCL/DATA引脚配置)、Arduino IDE开发环境搭建、DHT与LiquidCrystal_I2C库安装、核心代码解析(含时序控制、数据校验、Flash字符串优化)、调试技巧(I2C地址扫描、对比度调节、分治法排查)及扩展方向(阈值告警、SD卡记录、传感器升级)。聚焦嵌入式感知-处理-显示闭环实现。
weixin_30875157
385
Arduino智能城市原型从传感器到执行器的物联网实践指南
本文详细介绍了基于Arduino的智能城市微缩原型,涵盖智能照明、交通灯、停车场和太阳能追踪四大模块。系统采用分布式感知与集中决策架构,使用Arduino Uno和MKR1000作为主控,集成红外传感器、光敏电阻、SG90舵机、LED及1602 LCD等硬件,实现环境感知、逻辑判断与物理执行的完整物联网闭环。重点解析了传感器选型原理、PWM调光、状态机调度、防抖处理、差分光追踪及多模块联合调试等关键技术。
weixin_33694620
423
基于Arduino的智能声级计从传感器到3D外壳的完整DIY指南
本文详细介绍了基于Arduino MEGA 2560的智能声级计完整实现采用SparkFun声音检测器获取包络电压信号,通过ADC采样与分贝换算公式实现噪声测量;集成SD卡与DS3231 RTC模块实现带时间戳的数据记录;设计多级人机交互(数码管数值显示、Neopixel状态灯、I2C LCD提示);采用双电源隔离供电抑制LED干扰;使用Fusion 360完成模块化3D外壳设计与PLA打印;并通过手机APP对比法完成相对校准。涵盖电路搭建、非阻塞代码逻辑、硬件抗干扰及实测数据分析。
weixin_30520015
400