简介:本资源是一份面向嵌入式初学者与物联网开发者的ESP32图像显示实践方案,聚焦于在资源受限的单片机平台上实现摄像头数据采集与1.44英寸TFT屏幕实时显示这一典型嵌入式视觉应用。压缩包共含2个Python源文件(总计4KB),核心为MicroPython环境下SPI驱动协同设计:其中lcd_spi.py封装了TFT屏与OV2640等摄像头的底层SPI通信配置(含时钟极性、相位、波特率及引脚映射),test_lcd_cam.py则整合图像捕获、JPEG解码适配、RGB565格式转换与帧缓冲刷新逻辑,支持低延迟显示。已有1756人学习下载,代码结构清晰、注释完整,无需额外依赖库,可直接烧录运行,是理解ESP32硬件加速外设协同、嵌入式图像处理流程与微型GUI显示机制的优质入门范例。
1. 项目概述:让ESP32“看见”并“展示”图像
如果你手头有一块ESP32开发板和一块小巧的1.44寸TFT彩屏,是不是总想让它做点比显示文字和简单图形更酷的事情?比如,让它显示一张照片、一个图标,甚至是动态的传感器数据可视化图表。这个想法实现起来,远比想象中要直接和有趣。今天要聊的,就是如何驱动ESP32,从获取图像数据源开始,一步步让这些数据在1.44寸的TFT屏幕上完美呈现出来。
这不仅仅是一个简单的“点亮屏幕”实验。它涉及从数据源头(可能是内存数组、文件系统或者网络)获取原始的像素信息,理解TFT屏幕的驱动原理,再到通过SPI或并行接口高效地将这些数据“刷”到屏幕上。整个过程就像是为ESP32构建了一套微型的“视觉系统”。无论是想做一个迷你相框、一个传感器数据的图形化终端,还是一个简单游戏的显示界面,这个核心流程都是必经之路。接下来,我会结合自己多次调试的经验,把从硬件连接到软件实现的每一个环节掰开揉碎,特别是那些容易踩坑的地方,让你能快速复现并应用到自己的项目中。
2. 核心思路与方案选型:为什么是SPI与LVGL?
拿到ESP32和1.44寸TFT屏,第一反应可能是直接找最通用的库。但市面上库那么多,选哪个?怎么搭配?这里面的门道,直接决定了后续开发的效率和最终显示的效果。
2.1 硬件接口的抉择:SPI为何成为首选
绝大多数1.44寸TFT屏(如常见的ST7735S、ILI9341驱动芯片的变种)都支持SPI(串行外设接口)和8位并行接口。对于ESP32而言,我强烈推荐使用SPI模式,原因有三点。
第一是引脚资源占用少。一个标准的SPI接口通常只需要4-6根线(SCLK时钟、MOSI主出从入、DC数据/命令选择、CS片选、RST复位,有时再加一个背光控制)。而8位并行接口动辄需要十几根数据线和控制线,对于引脚资源并不算极度充裕的ESP32(尤其是某些只有较少GPIO的型号)来说,SPI能节省出大量引脚用于连接其他传感器或外设。
第二是软件生态成熟。Arduino社区和ESP-IDF框架下,针对SPI TFT屏的驱动库(如TFT_eSPI)经过多年优化,其稳定性和效率都非常高。这些库底层通常利用ESP32的硬件SPI控制器,配合DMA(直接内存访问)技术,能在极低的CPU占用率下完成屏幕刷新。
第三是布线简单,抗干扰能力相对较好。SPI线少,在面包板或洞洞板上连接更清晰,减少了飞线混乱导致的接触不良或信号串扰问题。虽然理论上并行接口的刷新率更高,但对于1.44寸这种分辨率(常见128x128或128x160)的小屏幕,SPI的刷新速度已经完全足够流畅显示图像和简单动画。
注意:在购买屏幕时,务必确认其驱动芯片型号(如ST7735, ILI9163, ILI9341等),并留意屏幕模块上是否有电阻配置来选择SPI或并行模式。很多模块默认就是SPI模式,需要通过焊接电阻来切换。
2.2 软件库的黄金组合:TFT_eSPI + LVGL
确定了硬件接口,软件栈的选择就清晰了。我的方案是:TFT_eSPI作为底层硬件驱动,LVGL作为上层图形库。
TFT_eSPI是一个专为ESP32、ESP8266等平台优化的TFT屏幕驱动库。它的强大之处在于高度可配置性。你需要在一个名为User_Setup.h的头文件里,根据你的屏幕型号、驱动芯片、引脚连接方式、SPI频率等进行精确配置。一旦配好,它就能提供一套非常高效的底层API,用于画点、线、矩形、填充、显示图片等基本操作。它直接操作ESP32的硬件SPI,效率是很多通用Adafruit库无法比拟的。
那么,为什么还要引入LVGL(Light and Versatile Graphics Library)呢?因为如果你需要显示复杂的用户界面,比如按钮、滑块、图表、动画,或者需要从文件系统加载多张图片,纯靠TFT_eSPI的基础API来手撸代码,工作量巨大且难以维护。LVGL是一个开源的高性能嵌入式图形库,它提供了完整的UI组件(Widgets)、样式系统、动画引擎和事件处理机制。你可以用类似“创建按钮->设置坐标->绑定回调函数”这样的高级抽象来构建界面,LVGL底层会调用TFT_eSPI(或其他驱动)来实际渲染。
简单来说,TFT_eSPI是“发动机”,负责把像素数据高速送到屏幕;LVGL是“车身和内饰设计”,让你能轻松构建出美观易用的界面。两者通过一个“显示驱动回调函数”接口连接,配合非常默契。
2.3 图像数据来源的规划
图像数据从哪里来?这决定了项目的应用方向。主要有三种来源:
- 程序内数组(C数组):最简单的方式,将小尺寸的图标、Logo转换成C语言数组直接编译进程序。适用于固定不变的静态小图。工具可以用
Image2Lcd或在线转换工具。 - SPIFFS/LittleFS文件系统:将图片文件(如BMP、PNG,需解码)存入ESP32的Flash中划分出的文件系统分区。运行时动态读取并显示。这种方式更灵活,可以更换图片而不需重新编译程序。需要用到LVGL的图片解码器或额外的库。
- 网络获取:ESP32通过Wi-Fi从服务器下载图片数据(如JPEG),在内存中解码后显示。这可以实现天气信息图、远程监控画面等高级功能。这对ESP32的内存(RAM)和解码能力是较大考验。
对于入门和大多数应用,我建议从程序内数组和SPIFFS文件系统开始,它们稳定且易于调试。
3. 硬件连接与驱动库配置详解
理论清楚了,现在开始动手。第一步是把硬件正确地连接起来,并配置好软件环境。
3.1 ESP32与1.44寸TFT屏的接线指南
我以一款常见的驱动芯片为ST7735S的1.44寸SPI屏幕为例。你需要准备杜邦线(母对母或公对母),并找到ESP32开发板的引脚定义图。
以下是经典的接线方式,我推荐使用ESP32的默认硬件SPI引脚(HSPI),以获得最佳性能:
| TFT屏引脚 | 功能 | ESP32引脚(推荐) | 备注 |
|---|---|---|---|
| VCC | 电源正极 | 3.3V | 绝对不要接5V!大部分1.44寸屏是3.3V逻辑。 |
| GND | 电源地 | GND | 共地。 |
| SCL(SCK) | SPI时钟 | GPIO 18(HSPI CLK) | 硬件SPI时钟线。 |
| SDA(MOSI) | SPI数据线 | GPIO 23(HSPI MOSI) | 硬件SPI主设备输出线。 |
| RES(RST) | 复位 | GPIO 4 | 可自定义,上电或软件复位用。 |
| DC(A0) | 数据/命令选择 | GPIO 2 | 可自定义,高电平写数据,低电平写命令。 |
| CS(SS) | 片选 | GPIO 15 | 可自定义,低电平选中该SPI从设备。 |
| BL | 背光控制 | GPIO 12(或接VCC) | 可自定义,PWM控制可实现调光。 |
实操心得:
GPIO 2在ESP32上电时会有个短暂的电平变化,有时会导致屏幕初始化乱码。如果遇到,可以换到GPIO 0等其他引脚,但注意GPIO 0涉及启动模式。最稳妥的方法是,在程序初始化时,先确保DC引脚为低电平,延迟片刻后再执行屏幕初始化序列。
接线时务必断电操作。接好后,检查一遍VCC和GND是否接反、接错,这是烧毁模块最常见的原因。
3.2 TFT_eSPI库的精准配置
在Arduino IDE中安装好TFT_eSPI库后,找到其配置文件。它位于你的Arduino库目录下,例如文档\Arduino\libraries\TFT_eSPI\User_Setup.h。用文本编辑器打开它,我们需要修改关键部分。
首先,注释掉其他所有驱动芯片的定义,只启用你屏幕对应的那一个。对于ST7735S,通常是:
// #define ILI9341_DRIVER // 常见2.4/2.8寸屏 #define ST7735_DRIVER // 常见1.44寸屏 // #define ILI9163_DRIVER // 另一种1.44寸屏然后,根据你的屏幕分辨率设置。常见的1.44寸屏是128x128,但也有128x160的。必须设对,否则显示会错位。
#define TFT_WIDTH 128 #define TFT_HEIGHT 128 // 或 160接下来是最关键的引脚定义部分,必须与你刚才的接线一一对应:
#define TFT_CS PIN_D8 // GPIO 15,对应你的CS引脚 #define TFT_DC PIN_D4 // GPIO 2, 对应你的DC引脚 #define TFT_RST PIN_D2 // GPIO 4, 对应你的RST引脚 // 如果你的背光控制接了GPIO,可以定义,否则注释掉或接VCC常亮 #define TFT_BL PIN_D1 // GPIO 12然后是SPI设置,我们使用ESP32的硬件SPI(HSPI):
#define USE_HSPI_PORT // 注释掉则使用默认VSPI,但HSPI更常用 #define TFT_SPI_FREQUENCY 27000000 // SPI时钟频率,27MHz对于ST7735通常很稳定,可尝试40000000(40MHz)追求速度,但可能不稳定。最后,有些屏幕初始化时需要特殊的偏移(Offset)或颜色顺序(RGB/BGR)。如果显示颜色不对(比如红色和蓝色反了),可以调整:
#define TFT_RGB_ORDER TFT_RGB // 或 TFT_BGR // 如果显示内容整体偏移,调整这些值 #define ST7735_OFFSETX 0 #define ST7735_OFFSETY 0保存文件。现在,TFT_eSPI库就针对你的硬件准备好了。
3.3 LVGL库的引入与基础集成
在Arduino库管理中搜索并安装lvgl。LVGL的集成稍复杂,因为它本身是一个框架,需要你写一些“胶水代码”将其与TFT_eSPI和ESP32的系统对接。
你需要完成三个核心任务:
- 显示驱动(Display Driver):告诉LVGL如何初始化屏幕、刷新指定区域。
- 输入设备驱动(如不需要触摸可跳过)。
- 任务调度(Task Handler):在
loop()函数中定期调用lv_timer_handler(),以处理LVGL的内部动画、事件等。
网上有很多现成的集成示例。一个最简化的流程是:在setup()中,初始化TFT_eSPI对象,然后调用一个自定义的lvgl_init()函数。在这个函数里,你需要分配一块绘图缓冲区(draw buffer)。对于ESP32,建议使用其PSRAM(如果板子有的话),因为LVGL的缓冲区消耗内存较大。
#include <lvgl.h> #include <TFT_eSPI.h> TFT_eSPI tft = TFT_eSPI(); static lv_disp_draw_buf_t draw_buf; // 声明绘图缓冲区 static lv_color_t buf[TFT_WIDTH * 10]; // 分配一个缓冲区,大小为例:屏幕宽度 * 10行像素 void my_disp_flush(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p) { // 这个函数由LVGL在需要刷新屏幕时调用 uint32_t w = (area->x2 - area->x1 + 1); uint32_t h = (area->y2 - area->y1 + 1); tft.startWrite(); tft.setAddrWindow(area->x1, area->y1, w, h); tft.pushColors((uint16_t *)&color_p->full, w * h, true); // 推送颜色数据到屏幕 tft.endWrite(); lv_disp_flush_ready(disp_drv); // 通知LVGL刷新完成 } void lvgl_init() { lv_init(); // 初始化LVGL核心 lv_disp_draw_buf_init(&draw_buf, buf, NULL, TFT_WIDTH * 10); // 初始化绘图缓冲区 static lv_disp_drv_t disp_drv; // 声明显示驱动 lv_disp_drv_init(&disp_drv); // 初始化显示驱动 disp_drv.hor_res = TFT_WIDTH; // 设置水平分辨率 disp_drv.ver_res = TFT_HEIGHT; // 设置垂直分辨率 disp_drv.flush_cb = my_disp_flush; // 设置刷新回调函数 disp_drv.draw_buf = &draw_buf; // 设置绘图缓冲区 lv_disp_drv_register(&disp_drv); // 注册显示驱动 } void setup() { Serial.begin(115200); tft.init(); // 初始化TFT_eSPI tft.setRotation(1); // 根据需要设置旋转方向(0-3) lvgl_init(); // 初始化LVGL // ... 创建你的LVGL界面对象(按钮、标签等) } void loop() { lv_timer_handler(); // 必须定期调用,延迟5ms左右 delay(5); }这段代码是集成的骨架。my_disp_flush函数是性能关键,它利用TFT_eSPI的pushColors函数进行区域刷新,效率很高。
4. 图像数据的获取与显示实战
硬件和底层驱动就绪后,我们进入核心环节:获取图像数据并显示。我将分两种最实用的场景来讲解。
4.1 场景一:显示编译到程序内部的图标(C数组)
这是最轻量级的方式,适合显示启动Logo、固定图标等。你需要先将图片转换成C语言数组。可以使用工具如Image2Lcd或在线转换网站(搜索“image to c array”)。
转换时关键参数设置:
- 输出数据类型:选择
C Array或Bin File。 - 扫描模式:选择水平扫描(通常对应TFT_eSPI的格式)。
- 输出灰度:16位真彩色(RGB565)。这是TFT屏幕最常用的格式,一个像素用2字节(16位)表示,R(5位)、G(6位)、B(5位)。
- 包含头文件:可选,会生成宽度和高度的定义。
转换后会得到一个.c和.h文件。将数组声明复制到你的Arduino项目中。使用TFT_eSPI显示非常简单:
// 假设你有一个名为 gImage_test 的数组,定义了宽度和高度 IMAGE_WIDTH, IMAGE_HEIGHT #include "image.h" // 你的图片头文件 void showEmbeddedImage() { // 方法1:使用pushImage函数,速度最快 tft.pushImage(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT, (uint16_t *)gImage_test); // 方法2:如果需要先绘制到Sprite(精灵图)进行一些处理 TFT_eSprite spr = TFT_eSprite(&tft); // 创建精灵图 spr.createSprite(IMAGE_WIDTH, IMAGE_HEIGHT); spr.pushImage(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT, (uint16_t *)gImage_test); // ... 可以在spr上进行绘图操作 spr.pushSprite(10, 10); // 将精灵图绘制到屏幕指定位置 spr.deleteSprite(); // 释放内存 }pushImage函数是阻塞式的,会一次性将整个数组发送到屏幕。对于大图,可能会暂时阻塞主循环。对于LVGL,你需要使用LVGL的图片对象(lv_img),并将数组设置为其数据源,这需要遵循LVGL特定的图片数据格式(通常是lv_img_dsc_t结构体)。
4.2 场景二:从SPIFFS文件系统读取并显示图片
这种方式更灵活。首先,你需要准备一张图片,并将其上传到ESP32的SPIFFS分区中。
步骤1:准备Arduino IDE的SPIFFS上传工具在Arduino IDE中,通常通过“项目文件夹”下的data目录来管理SPIFFS文件。你需要安装一个插件(如ESP32FS),它会将一个菜单项添加到“工具”中,用于将data目录下的所有文件上传到ESP32的Flash。
步骤2:转换并存放图片将你的图片(如test.jpg或test.bmp)放到项目目录下的data文件夹里。对于LVGL,它支持直接读取并解码BMP、PNG等格式(需要启用相应的解码器)。为了简单起见,我们可以先用TFT_eSPI显示BMP格式,因为TFT_eSPI内置了BMP解码函数。
步骤3:编写读取与显示代码
#include <SPIFFS.h> void showBMPFromSPIFFS(const char *filename, int x, int y) { fs::File bmpFS = SPIFFS.open(filename, "r"); // 以只读方式打开文件 if (!bmpFS) { Serial.println("File not found"); return; } // 使用TFT_eSPI的drawBmp函数直接绘制 // 注意:此函数会解析BMP文件头并绘制,但仅支持特定的RGB565格式BMP bool success = tft.drawBmpFile(SPIFFS, filename, x, y); if (!success) { Serial.println("BMP format not supported or draw failed."); } bmpFS.close(); } void setup() { Serial.begin(115200); if (!SPIFFS.begin(true)) { // 初始化SPIFFS,true表示如果失败则格式化 Serial.println("SPIFFS Mount Failed"); return; } tft.init(); showBMPFromSPIFFS("/test.bmp", 0, 0); // 显示根目录下的test.bmp }TFT_eSPI的drawBmpFile函数非常方便,但限制较多(主要是BMP格式和位深度)。对于更通用的图片显示,尤其是在LVGL中,更好的做法是:
- 使用LVGL的文件系统接口,将SPIFFS挂载到LVGL。
- 启用LVGL的PNG或JPG解码器(需要额外的库,如
lodepng或tjpgd)。 - 创建一个LVGL图片对象(
lv_img_set_src(img, “S:/path/to/image.png”)),LVGL会自动处理解码和显示。
这种方式更强大,但初始配置更复杂。它允许你在UI中动态加载和切换图片。
4.3 图像显示的性能优化技巧
在ESP32上显示图像,尤其是动态更新时,性能是关键。以下是几个实测有效的优化点:
使用双缓冲(Double Buffering):在LVGL中,可以分配两个绘图缓冲区。当LVGL在其中一个缓冲区(draw buffer)中渲染下一帧时,DMA可以同时将另一个缓冲区(render buffer)的数据发送到屏幕。这能极大提高刷新流畅度,避免撕裂感。前提是你的ESP32有足够的PSRAM来分配两个缓冲区。
// 在PSRAM中分配两个缓冲区 static lv_color_t *buf1 = (lv_color_t *)ps_malloc(TFT_WIDTH * 20 * sizeof(lv_color_t)); static lv_color_t *buf2 = (lv_color_t *)ps_malloc(TFT_WIDTH * 20 * sizeof(lv_color_t)); lv_disp_draw_buf_init(&draw_buf, buf1, buf2, TFT_WIDTH * 20);提高SPI时钟频率:在
User_Setup.h中逐步提高TFT_SPI_FREQUENCY,例如从27MHz到40MHz,观察屏幕是否稳定。频率越高,刷新越快。但过高的频率可能导致显示花屏或通信失败。减少单次刷新区域:无论是直接使用TFT_eSPI还是通过LVGL,都应避免全屏频繁刷新。只更新需要变化的区域。在LVGL中,这由库自动管理。如果自己写驱动,可以使用
setAddrWindow和pushColors来指定更新区域。图片预处理:确保图片尺寸不超过屏幕分辨率(128x128/128x160)。在电脑上提前将图片缩放并转换为正确的格式(RGB565的BMP),可以节省ESP32上宝贵的内存和解码时间。
5. 常见问题排查与实战心得
即使按照步骤操作,也难免会遇到问题。这里我整理了几个最常遇到的“坑”及其解决方案。
5.1 屏幕白屏、花屏或不亮
这是最常见的问题,排查思路如下:
- 电源检查:首先确认VCC接3.3V,GND共地。用万用表测量屏幕VCC和GND之间电压是否为稳定的3.3V。背光(BL)是否已使能(接高电平或PWM信号)。
- 接线检查:逐根检查SPI线是否接触不良,尤其是CLK和MOSI这两根数据线。可以尝试更换引脚(比如换到另一组硬件SPI:VSPI的引脚是GPIO 18, 23, 19, 5? 注意MISO不用接)。
- 复位时序:在
setup()的最开始,手动控制一下RST引脚复位屏幕。pinMode(TFT_RST, OUTPUT); digitalWrite(TFT_RST, LOW); delay(50); digitalWrite(TFT_RST, HIGH); delay(150); // 等待屏幕复位稳定 - 驱动配置:反复核对
User_Setup.h中的驱动芯片型号、分辨率、引脚定义。一个字符错误都会导致失败。可以尝试将SPI频率先调低,比如到10MHz,看是否正常。 - 库冲突:确保只包含了必要的库。有时多个图形库存在冲突。
5.2 显示颜色异常(红蓝反色、颜色失真)
这几乎百分百是颜色顺序(RGB/BGR)设置错误。
- 症状:红色显示为蓝色,绿色正常。
- 解决:在
User_Setup.h中修改TFT_RGB_ORDER定义。如果原来是TFT_RGB,就改为TFT_BGR,反之亦然。 - 更深层原因:有些屏幕驱动芯片的初始化命令需要单独设置颜色格式。除了宏定义,可能还需要在初始化代码中发送特定的命令。TFT_eSPI库的
init()函数里通常已经处理,但某些山寨屏幕可能需要你查找其数据手册,在setup()中额外用tft.sendCommand()来配置。
5.3 显示位置偏移或镜像
- 偏移:显示的内容整体向左/右/上/下偏移,屏幕边缘有黑边或内容被截断。调整
User_Setup.h中的ST7735_OFFSETX和ST7735_OFFSETY参数。也可以尝试tft.setRotation()函数,它会影响偏移的基准。 - 镜像或旋转:直接使用
tft.setRotation(0-3)来设置0、90、180、270度旋转。这个函数同时会影响绘图坐标的原点位置。
5.4 内存不足导致崩溃
当加载大图片或使用LVGL复杂界面时,ESP32的片上RAM(约520KB)可能很快耗尽,程序崩溃重启。
- 症状:在打开文件、解码图片或创建大量UI对象时,ESP32不断重启,串口提示“
Alloc failed!”或“Brownout detector was triggered”。 - 解决方案:
- 使用PSRAM:如果你的ESP32模块带有外部PSRAM(如ESP32-WROVER),务必启用它。在Arduino IDE的“工具”菜单下,选择“PSRAM”为“Enabled”。然后在代码中使用
ps_malloc来分配图片缓冲区或LVGL的绘图缓冲区。 - 优化内存使用:减少LVGL的缓冲区大小;避免同时加载多张大图片;使用更简单的UI样式;将字符串常量存储在Flash中(用
F()宏包裹)。 - 监控内存:定期使用
ESP.getFreeHeap()和ESP.getFreePsram()打印剩余内存,定位内存泄漏点。
- 使用PSRAM:如果你的ESP32模块带有外部PSRAM(如ESP32-WROVER),务必启用它。在Arduino IDE的“工具”菜单下,选择“PSRAM”为“Enabled”。然后在代码中使用
5.5 LVGL界面卡顿、刷新慢
- 原因1:
lv_timer_handler()调用间隔过长或不规律。确保它在loop()中尽可能频繁且稳定地被调用,延迟不要超过10ms。避免在loop()中执行长时间的阻塞操作(如delay(100))。如果需要延时,使用LVGL自带的定时器(lv_timer_create)。 - 原因2:绘图缓冲区太小。LVGL的缓冲区大小决定了其最大可更新的区域面积。如果缓冲区高度只有10行,那么刷新一个高度为20行的区域就需要分两次操作。增大缓冲区(尤其是高度)可以提升性能,但会消耗更多内存。这是一个权衡。
- 原因3:SPI频率太低。尝试在硬件允许范围内提高SPI时钟频率。
- 原因4:开启了LVGL的复杂特效:如阴影、渐变、混合模式等,会显著增加渲染负担。在性能紧张的场合,尽量使用扁平化设计。
5.6 SPIFFS文件上传失败
- 提示“SPIFFS Upload Failed”:首先确认你安装了正确的SPIFFS上传工具。然后,关闭所有可能占用串口的终端软件(如串口监视器)。如果还不行,尝试在ESP32上电启动的瞬间点击上传按钮,或者先按住BOOT键再按一下RST键进入下载模式,然后再上传SPIFFS。
最后,分享一个我个人的调试习惯:分步验证法。不要试图一次性集成所有功能。先写一个最简单的TFT_eSPI测试程序(比如画一个矩形),确保屏幕硬件和基础驱动是好的。然后,再单独测试SPIFFS读取文件。最后,再集成LVGL。每一步都通过串口打印关键信息(如“SPIFFS init OK”、“File opened”)。这样,当问题出现时,你能迅速定位到是哪一个环节出了错。嵌入式开发就是这样,耐心和细致的排查比写出华丽的代码更重要。当你看到第一张图片稳定地显示在那块小小的1.44寸屏幕上时,那种成就感会让你觉得所有的调试都是值得的。
本文还有配套的精品资源,点击获取