ESP8266 与 ILI9341 TFT 屏幕实战:解决花屏、镜像、显示不全等问题
引言
ESP8266 以其低成本和 Wi-Fi 功能成为物联网项目的热门选择。ILI9341 TFT 液晶屏则以其适中的价格和良好的显示效果,成为 ESP8266 的理想搭档。然而,将两者结合使用时,经常会出现花屏、镜像、显示不全等问题。本文将深入剖析这些问题的原因,并提供详细的解决方案,助您轻松驾驭 ESP8266 + ILI9341 的组合。
硬件连接
正确连接 ESP8266 和 ILI9341 是首要任务。下表列出了典型连接方式:
ILI9341 引脚 | ESP8266 引脚 | 说明 |
---|---|---|
VCC | 3.3V | 电源 (3.3V) |
GND | GND | 地 |
CS | D8 | 片选 (Chip Select) |
RST/RESET | D2 | 复位 (Reset) |
DC/RS | D1 | 数据/命令 (Data/Command) |
SDI/MOSI | D7 | SPI 数据输入 (MOSI) |
SCK | D5 | SPI 时钟 (SCLK) |
LED | 3.3V | 背光 (或通过电阻,不连接屏幕不亮,见下文) |
SDO/MISO | D6 | (本例中未使用) |
重要提示:
- 电源: ESP8266 和 ILI9341 必须 使用 3.3V 电源。
- LED 背光: 部分 ILI9341 模块的 LED 引脚可直接接 3.3V,部分则需串联限流电阻(如 220Ω 或 330Ω)。请务必查阅您购买的屏幕模块的规格书!
- ESP8266 SPI 引脚: ESP8266 默认的 SPI 引脚为:
D5 (GPIO14)
-SCK
D7 (GPIO13)
-MOSI
D6 (GPIO12)
-MISO
(本例中未使用)
软件设置 (Arduino IDE)
-
安装库:
- 打开 Arduino IDE。
- "工具" -> "管理库..."。
- 搜索并安装 "Adafruit GFX Library" 和 "Adafruit ILI9341"。
-
代码框架(竖屏示例):
#include <SPI.h> #include <Adafruit_GFX.h> #include <Adafruit_ILI9341.h> // 引脚定义 (根据您的实际接线修改) #define TFT_CS D8 #define TFT_DC D1 #define TFT_RST D2 Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST); void setup() { Serial.begin(115200); // 用于调试 Serial.println("ILI9341 初始化..."); delay(500); // **重要:** 上电延时,确保屏幕稳定 tft.begin(); // 初始化 ILI9341 SPI.setFrequency(1000000); // **重要:** 设置 SPI 速度 (1 MHz, 稳定) tft.setRotation(0); // 设置屏幕方向:竖屏 // 设置显示区域 (竖屏 240x320) uint8_t caset_data[] = {0x00, 0x00, 0x00, 0xEF}; // 列地址 0-239 tft.sendCommand(ILI9341_CASET, caset_data, 4); uint8_t raset_data[] = {0x00, 0x00, 0x01, 0x3F}; // 行地址 0-319 tft.sendCommand(ILI9341_PASET, raset_data, 4); tft.writeCommand(ILI9341_RAMWR); // 准备写入像素数据 // 设置 MADCTL (Memory Access Control) uint8_t madctl_data = 0xE8; // 竖屏常用值,可能需要调整 (见下文) tft.sendCommand(ILI9341_MADCTL, &madctl_data, 1); tft.fillScreen(ILI9341_BLACK); // 清屏 (黑色) // 显示一些文字 tft.setTextColor(ILI9341_WHITE); tft.setTextSize(2); tft.setCursor(20, 50); tft.println("Hello, ESP8266!"); tft.setTextSize(3); tft.setTextColor(ILI9341_YELLOW); tft.setCursor(40, 120); tft.print("ILI9341"); } void loop() { // (此处可以添加其他代码,例如读取传感器数据并显示) delay(1000); }
常见问题及解决方案
以下是 ESP8266 + ILI9341 组合的常见显示问题及其解决方案:
-
花屏 (Garbage Data):
-
原因:
- SPI 速度过快: ESP8266 的 SPI 速度可能超过 ILI9341 的承受范围。
- 接线问题: 松动、过长或受干扰的连接。
- 电源问题: 不稳定的电源或电压不足。
- 初始化序列问题: 库通常会自动处理,但某些特殊屏幕可能需要手动调整。
-
解决方案:
- 降低 SPI 速度(首要): 使用
SPI.setFrequency(1000000);
将 SPI 速度设置为 1 MHz(或更低)。 - 检查接线: 确保所有连接牢固、紧凑,远离干扰源。
- 检查电源: 用万用表确认 ILI9341 的 VCC 引脚电压稳定在 3.3V。
- 增加上电延时: 在
tft.begin();
前加入delay(500);
。
- 降低 SPI 速度(首要): 使用
-
-
镜像 (Mirroring) 和倒置 (Inversion):
-
原因: MADCTL (Memory Access Control) 寄存器设置不当。
-
解决方案:
- 理解 MADCTL: MADCTL 是一个 8 位寄存器,控制屏幕的扫描方向、行列顺序和颜色:
- Bit 7 (MY): 行地址顺序 (0: 从上到下, 1: 从下到上)
- Bit 6 (MX): 列地址顺序 (0: 从左到右, 1: 从右到左)
- Bit 5 (MV): 行/列交换 (0: 不交换, 1: 交换) <-- 竖屏/横屏的关键
- Bit 4 (ML): 垂直刷新 (0: 从上到下, 1: 从下到上)
- Bit 3 (RGB): 颜色顺序 (0: RGB, 1: BGR) <-- ILI9341 通常是 BGR
- Bits 2-0: 未使用
- 设置 MADCTL:
uint8_t madctl_data = 0x...; // 根据需要设置值 tft.sendCommand(ILI9341_MADCTL, &madctl_data, 1);
- 常用 MADCTL 值:
setRotation(0)
(竖屏):0x68
或0xE8
setRotation(1)
(横屏):0x48
或0xC8
- 重要:更改 MADCTL 值或旋转方向后, 务必完全断电重启!
- 理解 MADCTL: MADCTL 是一个 8 位寄存器,控制屏幕的扫描方向、行列顺序和颜色:
-
-
分辨率错误/显示不全:
-
原因:
- Adafruit_ILI9341 库未能自动识别屏幕的正确分辨率。
- MADCTL 设置错误。
-
解决方案:
- 强制设置显示区域 (CASET 和 PASET):
// 横屏 (320x240) uint8_t caset_data[] = {0x00, 0x00, 0x01, 0x3F}; // 列: 0-319 uint8_t raset_data[] = {0x00, 0x00, 0x00, 0xEF}; // 行: 0-239 // 竖屏 (240x320) // uint8_t caset_data[] = {0x00, 0x00, 0x00, 0xEF}; // 列: 0-239 // uint8_t raset_data[] = {0x00, 0x00, 0x01, 0x3F}; // 行: 0-319 tft.sendCommand(ILI9341_CASET, caset_data, 4); // 设置列地址 tft.sendCommand(ILI9341_PASET, raset_data, 4); // 设置行地址 tft.writeCommand(ILI9341_RAMWR); // 准备写入像素数据
- 结合 MADCTL 调整: 确保 MADCTL 设置与屏幕方向和 CASET/PASET 设置一致。
- 强制设置显示区域 (CASET 和 PASET):
-
总结
通过掌握以上方法,您应该能够解决 ESP8266 与 ILI9341 TFT 屏幕组合的绝大多数显示问题。记住,细心检查硬件、理解 MADCTL、正确设置显示区域是成功的关键。祝您的项目顺利!
横屏模式 (setRotation(1)) 代码,并加入了详细注释和可能需要的调整:
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
// ILI9341 引脚定义 (根据您的实际接线)
#define TFT_CS D8 // 片选
#define TFT_DC D1 // 数据/命令
#define TFT_RST D2 // 复位
// 创建 Adafruit_ILI9341 对象
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);
void setup() {
Serial.begin(115200); // 初始化串口,用于调试
Serial.println("ILI9341 Test!");
delay(500); // **重要:** 上电延时,确保 ILI9341 稳定
tft.begin(); // 初始化 ILI9341 屏幕
SPI.setFrequency(1000000); // **重要:** 设置 SPI 时钟频率为 1 MHz (保守值)
tft.setRotation(1); // 设置屏幕为横屏模式 (1:横屏)
// ********** 设置显示区域 (横屏 320x240) **********
// CASET (Column Address Set) - 设置列地址范围
uint8_t caset_data[] = {0x00, 0x00, 0x01, 0x3F}; // 0x0000 到 0x013F (0-319)
tft.sendCommand(ILI9341_CASET, caset_data, 4);
// PASET (Page Address Set) - 设置行地址范围
uint8_t raset_data[] = {0x00, 0x00, 0x00, 0xEF}; // 0x0000 到 0x00EF (0-239)
tft.sendCommand(ILI9341_PASET, raset_data, 4);
tft.writeCommand(ILI9341_RAMWR); // **重要:** 发送 RAM Write 命令,准备写入像素数据
// ********** 设置 MADCTL (Memory Access Control) **********
uint8_t madctl_data;
// 对于 setRotation(1) (横屏),通常 0x48 或 0xC8 是正确的值
// 0x48: 正常
// 0xC8: 垂直翻转 (如果 0x48 显示上下颠倒,就用 0xC8)
madctl_data = 0x48; // 先尝试 0x48
tft.sendCommand(ILI9341_MADCTL, &madctl_data, 1);
delay(1000); // 更改 MADCTL 后短暂延时
// 清屏 (黑色)
tft.fillScreen(ILI9341_BLACK);
// 设置文本颜色 (白色)
tft.setTextColor(ILI9341_WHITE);
// 设置文本大小 (2)
tft.setTextSize(2);
// 设置光标位置 (x=20, y=50)
tft.setCursor(20, 50);
// 显示文字
tft.println("Hello, ESP8266!");
// 设置文本大小 (3)
tft.setTextSize(3);
// 设置文本颜色 (黄色)
tft.setTextColor(ILI9341_YELLOW);
// 设置光标位置 (x=40, y=120)
tft.setCursor(40, 120);
// 显示文字
tft.print("ILI9341");
delay(1000); // 观察显示效果
}
void loop() {
delay(1000); // 简单延时
}
代码的关键点和解释:
-
#define TFT_CS D8
,#define TFT_DC D1
,#define TFT_RST D2
:- 这些是引脚定义,将 ESP8266 的 D8、D1 和 D2 引脚分别定义为 ILI9341 屏幕的 CS、DC 和 RST 引脚。
- 请根据您的实际接线修改这些定义!
-
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);
:- 创建一个
Adafruit_ILI9341
对象,用于控制 ILI9341 屏幕。 - 将之前定义的引脚传递给构造函数。
- 创建一个
-
Serial.begin(115200);
:- 初始化串口通信,波特率为 115200。这用于通过串口监视器进行调试。
-
delay(500);
:- 非常重要: 上电延时。在 ESP8266 和 ILI9341 上电后,需要一个短暂的延时(至少几百毫秒),以确保 ILI9341 控制器完全初始化并准备好接收命令。
-
tft.begin();
:- 初始化 ILI9341 屏幕。这会发送一系列初始化命令给 ILI9341 控制器。
-
SPI.setFrequency(1000000);
:- 非常重要: 设置 SPI 时钟频率为 1 MHz (1000000 Hz)。这是一个相对保守(慢)的值,可以最大程度地兼容不同的 ILI9341 屏幕和 ESP8266 板。
- 如果 1 MHz 工作正常,您可以尝试逐渐增加这个值(例如,2 MHz、4 MHz、8 MHz 等),以找到您的硬件配置下的最高稳定速度。但请记住,过高的 SPI 速度会导致数据错误(花屏)。
-
tft.setRotation(1);
:- 设置屏幕的旋转方向。
0
: 竖屏(肖像模式),240x3201
: 横屏(风景模式),320x240,您的代码中使用的就是这个2
: 竖屏(肖像模式,180 度旋转)3
: 横屏(风景模式,180 度旋转)
- 设置屏幕的旋转方向。
-
CASET
和PASET
(设置显示区域):uint8_t caset_data[] = {0x00, 0x00, 0x01, 0x3F}; // 0-319 (列) tft.sendCommand(ILI9341_CASET, caset_data, 4); uint8_t raset_data[] = {0x00, 0x00, 0x00, 0xEF}; // 0-239 (行) tft.sendCommand(ILI9341_PASET, raset_data, 4);
ILI9341_CASET
(Column Address Set):设置要显示的列的起始和结束地址。ILI9341_PASET
(Page Address Set):设置要显示的行的起始和结束地址。- 对于横屏模式 (
setRotation(1)
),我们设置:- 列:0-319 (0x0000 - 0x013F)
- 行:0-239 (0x0000 - 0x00EF)
-
tft.writeCommand(ILI9341_RAMWR);
:- 非常重要: 在设置完显示区域(
CASET
和PASET
)后,必须发送ILI9341_RAMWR
(Memory Write) 命令。这告诉 ILI9341 控制器,接下来我们要开始写入像素数据到显存了。
- 非常重要: 在设置完显示区域(
-
MADCTL
(Memory Access Control):uint8_t madctl_data = 0x48; // 尝试 0x48,如果不行,尝试 0xC8 tft.sendCommand(ILI9341_MADCTL, &madctl_data, 1);
MADCTL
是一个非常重要的寄存器,它控制着屏幕的扫描方向、行/列地址顺序以及颜色顺序(RGB 或 BGR)。- 对于
setRotation(1)
(横屏),通常0x48
或0xC8
是正确的值。0x48
: 正常(没有垂直翻转)0xC8
: 垂直翻转(如果0x48
显示上下颠倒,就用0xC8
)
- 如果您的屏幕显示仍然有问题(例如,上下镜像),请尝试将
0x48
改为0xC8
。
-
文本显示:
tft.fillScreen(ILI9341_BLACK);
: 清屏为黑色。tft.setTextColor(ILI9341_WHITE);
: 设置文本颜色为白色。tft.setTextSize(2);
: 设置文本大小为 2。tft.setCursor(20, 50);
: 设置光标位置(x=20, y=50)。tft.println("Hello, ESP8266!");
: 显示一行文字。tft.setTextSize(3);
,tft.setTextColor(ILI9341_YELLOW);
,tft.setCursor(40, 120);
,tft.print("ILI9341");
: 显示另一行文字。
总结:
这段代码提供了一个坚实的基础,用于在横屏模式下使用 ESP8266 和 ILI9341 屏幕。如果您的屏幕仍然显示不正常,请务必:
- 仔细检查接线。
- 尝试不同的
madctl_data
值(0x48
或0xC8
)。 - 确保在更改
madctl_data
或setRotation()
后,完全断电重启 ESP8266 和屏幕。 - 如果可以, 提供您的屏幕型号和购买链接, 这能帮助提供更具体的建议
加入wifi功能
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <map>
#include <vector>
const char* ssid = "yang1234"; // 请替换为你的 Wi-Fi SSID
const char* password = "y123456789"; // 请替换为你的 Wi-Fi 密码
#define TFT_CS D8
#define TFT_DC D1
#define TFT_RST D2
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);
ESP8266WebServer server(80);
struct LineData {
String text;
uint16_t color;
uint8_t size;
int16_t y;
};
std::map<int, LineData> lines;
// --- 图片数据结构 ---
struct ImageData {
std::vector<uint16_t> pixels;
int16_t width;
int16_t height;
int16_t y_offset; // 图片的 Y 轴偏移量
};
ImageData image;
// 全局变量,控制是否使用 GBR 格式
bool useGBR = true; // GBR 格式
void handleSet();
void handleImage(); // 新增图片处理函数
void handleNotFound();
void updateDisplay();
void connectToWiFi();
uint16_t parseColor(String colorString);
void setup() {
Serial.begin(115200);
tft.begin();
SPI.setFrequency(1000000);
tft.setRotation(0);
uint8_t caset_data[] = {0x00, 0x00, 0x00, 0xEF};
tft.sendCommand(ILI9341_CASET, caset_data, 4);
uint8_t raset_data[] = {0x00, 0x00, 0x01, 0x3F};
tft.sendCommand(ILI9341_PASET, raset_data, 4);
tft.writeCommand(ILI9341_RAMWR);
uint8_t madctl_data = 0xE8;
tft.sendCommand(ILI9341_MADCTL, &madctl_data, 1);
tft.fillScreen(ILI9341_BLACK);
connectToWiFi();
server.on("/set", HTTP_POST, handleSet);
server.on("/image", HTTP_POST, handleImage); // 处理图片请求
server.onNotFound(handleNotFound);
server.begin();
Serial.println("HTTP server started");
updateDisplay();
}
void loop() {
server.handleClient();
}
void connectToWiFi() {
WiFi.begin(ssid, password);
Serial.print("Connecting to Wi-Fi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nConnected! IP address:");
Serial.println(WiFi.localIP());
Serial.print("请在Python脚本中将 esp8266_ip 替换为: ");
Serial.println(WiFi.localIP());
}
void handleSet() {
lines.clear();
for (int i = 0; i < 100; i++) {
String lineKey = "line" + String(i);
if (server.hasArg(lineKey)) {
LineData line;
line.text = server.arg(lineKey);
String colorKey = "color" + String(i);
if (server.hasArg(colorKey)) {
line.color = parseColor(server.arg(colorKey));
} else {
line.color = ILI9341_WHITE;
}
String sizeKey = "size" + String(i);
if (server.hasArg(sizeKey)) {
int size = server.arg(sizeKey).toInt();
line.size = (size >= 1 && size <= 7) ? size : 2;
} else {
line.size = 2;
}
lines[i] = line;
}
}
updateDisplay();
server.send(200, "text/plain", "OK");
}
// --- 处理图片上传 ---
void handleImage() {
if (server.hasArg("width") && server.hasArg("height") && server.hasArg("data")) {
int width = server.arg("width").toInt();
int height = server.arg("height").toInt();
String data = server.arg("data");
if (width > 0 && height > 0 && data.length() > 0) {
image.width = width;
image.height = height;
image.pixels.clear();
image.pixels.reserve(width * height); // 预留空间
// 将接收到的字符串数据转换为像素数据
for (int i = 0; i < data.length(); i += 4) { // 假设每像素 4 字符 (16进制)
if (i + 4 <= data.length()) {
String hexColor = data.substring(i, i + 4);
unsigned long colorValue = strtoul(hexColor.c_str(), NULL, 16);
image.pixels.push_back(colorValue);
}
}
image.y_offset = 0; // 默认图片 Y 轴偏移量为 0
if (server.hasArg("y_offset")) {
image.y_offset = server.arg("y_offset").toInt();
}
updateDisplay(); // 更新显示
server.send(200, "text/plain", "Image OK");
} else {
server.send(400, "text/plain", "Invalid image parameters");
}
} else {
server.send(400, "text/plain", "Image parameters missing");
}
}
void handleNotFound() {
server.send(404, "text/plain", "404 Not Found");
}
void updateDisplay() {
tft.fillScreen(ILI9341_BLACK);
int16_t yPos = 20;
for (auto& [lineNum, line] : lines) {
line.y = yPos;
// --- 左对齐 ---
int16_t xPos = 20; // 左边距为 20 像素
tft.setCursor(xPos, line.y);
tft.setTextColor(line.color);
tft.setTextSize(line.size);
tft.println(line.text);
yPos += line.size * 6 * (tft.getRotation() % 2 == 0 ? 1 : 2) + 15; // 15 像素行间距
}
// --- 显示图片 ---
if (!image.pixels.empty()) {
int16_t startY = yPos + image.y_offset; // 图片的起始 Y 坐标,在文字下方
for (int y = 0; y < image.height; y++) {
for (int x = 0; x < image.width; x++) {
if (x < tft.width() && (startY + y) < tft.height()) { // 边界检查
tft.drawPixel(x, startY + y, image.pixels[y * image.width + x]);
}
}
}
}
}
uint16_t parseColor(String colorString) {
if (colorString == "red") return ILI9341_RED;
if (colorString == "green") return ILI9341_GREEN;
if (colorString == "blue") return ILI9341_BLUE;
if (colorString == "yellow") return ILI9341_YELLOW;
if (colorString == "cyan") return ILI9341_CYAN;
if (colorString == "magenta") return ILI9341_MAGENTA;
if (colorString == "white") return ILI9341_WHITE;
if (colorString == "black") return ILI9341_BLACK;
if (colorString == "orange") return ILI9341_ORANGE;
if (colorString.startsWith("#") && colorString.length() == 7) {
long number = strtol(colorString.substring(1).c_str(), NULL, 16);
uint8_t r = (number >> 16) & 0xFF;
uint8_t g = (number >> 8) & 0xFF;
uint8_t b = number & 0xFF;
uint16_t color565;
if (useGBR) {
// GBR 转换: G(5) B(6) R(5)
color565 = ((g & 0xF8) << 8) | ((b & 0xFC) << 3) | (r >> 3);
Serial.println("Using GBR conversion");
} else {
// RGB565 转换 (默认)
color565 = tft.color565(r, g, b);
Serial.println("Using RGB conversion");
}
// --- 串口调试输出 ---
Serial.print("Color String: ");
Serial.println(colorString);
Serial.print("R: "); Serial.println(r);
Serial.print("G: "); Serial.println(g);
Serial.print("B: "); Serial.println(b);
Serial.print("565 Color: "); Serial.println(color565);
return color565;
}
return ILI9341_WHITE;
}
本文作者: 永生
本文链接: http://yys.zone:8080/detail/?id=383
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!
评论列表 (0 条评论)