news 2026/8/1 16:04:30

ESP32-S3-Touch-LCD-3.5B开发板:一体化嵌入式GUI与物联网交互平台实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ESP32-S3-Touch-LCD-3.5B开发板:一体化嵌入式GUI与物联网交互平台实战指南

1. 项目概述:ESP32-S3-Touch-LCD-3.5B 是什么?

如果你正在寻找一款集成了高性能MCU、电容触摸屏和3.5英寸高清LCD的“一体化”开发板,那么ESP32-S3-Touch-LCD-3.5B很可能就是你正在寻找的答案。这不是一个简单的屏幕模块,而是一个功能完备的嵌入式系统核心板,它把ESP32-S3这颗双核240MHz的“大脑”、一块分辨率高达480x320的IPS液晶屏、以及一个灵敏的电容触摸控制器,全部集成在了一块比名片大不了多少的PCB上。这意味着,你拿到手的就是一个可以直接运行图形界面、响应用户触摸交互的完整硬件平台,省去了屏幕驱动、触摸芯片选型、电路设计、PCB布局和焊接调试等一系列繁琐步骤。

对于开发者而言,它的核心价值在于“开箱即用”和“快速原型验证”。无论是想做一个智能家居的中控面板、一个工业设备的HMI人机界面、一个便携式的数据采集终端,还是一个带交互功能的电子相框,你都可以基于这块板子快速搭建出硬件原型。它解决了嵌入式GUI开发中一个典型的“鸡生蛋蛋生鸡”问题:在没有稳定可靠的显示和交互硬件之前,软件调试往往无从下手。ESP32-S3-Touch-LCD-3.5B直接提供了这个稳定的硬件基础,让你可以立刻专注于上层应用逻辑和UI设计的开发。无论是资深的嵌入式工程师,还是对硬件感兴趣的创客、学生,甚至是希望将想法快速产品化的创业者,这块板子都能大幅降低入门门槛和开发周期。

2. 核心硬件架构与选型解析

2.1 主控芯片:为什么是ESP32-S3?

ESP32-S3是乐鑫在ESP32系列中推出的高性能升级版本,选择它作为核心,是这块开发板能胜任复杂图形交互任务的关键。首先,它搭载了Xtensa® 32位LX7双核处理器,主频高达240MHz,相比经典的ESP32,其计算性能有显著提升。这对于驱动LCD屏、处理触摸事件、运行图形库(如LVGL)以及同时处理Wi-Fi/蓝牙无线连接至关重要。一个流畅的UI界面需要主控在每帧刷新周期内完成大量的图形绘制和事件处理计算,双核架构允许将UI任务和网络任务分配到不同核心,避免卡顿。

其次,ESP32-S3拥有丰富的外设接口和更大的内存。它通常集成512KB的片上SRAM,并支持外接PSRAM。在ESP32-S3-Touch-LCD-3.5B上,通常会板载8MB的QSPI PSRAM。这是驱动高分辨率屏幕的“内存底气”。一个480x320的16位色深(RGB565)帧缓冲区,就需要 480 * 320 * 2 bytes = 307,200 字节,约300KB。如果UI元素复杂、使用双缓冲或者需要加载图片资源,内存消耗会轻松超过1MB。板载的PSRAM完美解决了这个问题,为复杂的图形应用提供了充足的“画布”。

最后,ESP32-S3的GPIO矩阵和丰富的串行外设接口(如SPI、I2C、I2S)为连接屏幕和触摸芯片提供了极大的灵活性。驱动LCD屏通常需要高速SPI或并口(如8080/6800系列),而电容触摸芯片通常使用I2C接口。ESP32-S3的引脚可以灵活复用于这些功能,使得硬件设计可以优化布线,实现稳定的高速数据传输。

2.2 显示与触摸子系统:3.5英寸IPS屏与电容触摸

这块板子的“脸面”是一块3.5英寸的IPS液晶屏,分辨率480x320。选择IPS技术而非TN,主要看重其广视角和出色的色彩表现。无论从哪个角度观看,色彩和亮度衰减都很小,这对于需要多角度观察的设备(如放在桌面的中控屏)非常友好。480x320的分辨率是一个在成本、功耗和显示效果之间取得平衡的甜点,既能显示足够多的信息,又不会对MCU的图形渲染和内存造成过大压力。

屏幕的驱动接口通常是SPI或RGB并口。考虑到ESP32-S3的性能和引脚数量,这款开发板极有可能采用高速SPI(如QSPI)来驱动。虽然SPI的绝对带宽不如RGB并口,但通过优化驱动、使用DMA传输以及利用ESP32-S3的硬件加速特性,驱动这个分辨率的屏幕实现流畅动画是完全可行的。在软件上,通常会使用LVGL这类高效嵌入式图形库,它经过高度优化,能最大限度发挥硬件性能。

触摸部分采用的是电容式触摸屏,通过一个专门的触摸控制器(可能是GT911、FT6236等常见型号)与主控连接。电容触摸相比电阻式,具有反应灵敏、支持多点触控、耐用性好的优点。触摸控制器通过I2C接口与ESP32-S3通信,上报触摸坐标、手势等信息。开发者无需关心底层的电容信号采集和滤波算法,只需要通过I2C读取处理好的坐标数据即可,大大简化了开发难度。

2.3 板载资源与扩展接口设计

除了核心的MCU和屏幕,一个实用的开发板还需要考虑供电、调试和扩展。ESP32-S3-Touch-LCD-3.5B通常会集成一个高效的LDO稳压芯片,将输入的5V电压转换为3.3V,为整个系统供电。板上会预留一个Type-C接口,既用于5V供电,也用于USB串口通信和程序烧录,这是目前最主流和方便的设计。

为了调试和下载程序,板载一个USB转串口芯片(如CH340、CP2102)是标准配置。通过Type-C线连接电脑,在IDE中就能识别出串口,进行日志打印和固件烧写。

在扩展性方面,开发板会将ESP32-S3未用于屏幕和触摸驱动的剩余GPIO引脚通过排针或邮票孔的形式引出。这些引脚可能包括通用的GPIO、ADC输入、DAC输出、额外的I2C、SPI、UART接口等。这为连接外部传感器(如温湿度传感器)、执行器(如继电器)、通信模块(如LoRa)提供了可能。有些设计还会预留SD卡槽,用于存储图片、字体等UI资源,或者进行数据记录。

注意:拿到板子第一件事,是查阅官方或卖家提供的引脚定义图。一定要明确哪些引脚已经被屏幕、触摸芯片、PSRAM占用,哪些是可以自由使用的。盲目使用引脚可能导致屏幕不显示或触摸失灵。

3. 软件开发环境搭建与核心配置

3.1 框架选择:ESP-IDF vs Arduino

为ESP32-S3-Touch-LCD-3.5B开发软件,首要抉择是开发框架。主流有两个选择:乐鑫官方的ESP-IDF和创客圈流行的Arduino。

ESP-IDF是乐鑫为ESP系列芯片提供的原生开发框架,基于FreeRTOS,提供最底层、最全面的硬件控制能力和最丰富的功能组件(如Wi-Fi、蓝牙、文件系统、OTA等)。它的性能最优,对ESP32-S3的新特性支持最快最全,适合进行严肃的产品开发和需要深度优化性能的项目。如果你计划使用LVGL,社区有非常成熟的lvgl_esp32_drivers组件,可以无缝集成到ESP-IDF中,自动配置好屏幕和触摸驱动。

Arduino核心则提供了更简单易用的API和庞大的库生态系统,上手速度极快。对于快速验证想法、教育或简单项目非常友好。现在Arduino对ESP32-S3的支持也已完善,并且也有社区移植的LVGL库可供使用。但Arduino框架在代码结构优化和部分高级特性(如双核任务绑定、低功耗管理)上不如ESP-IDF灵活。

我的建议是:如果你是嵌入式开发新手,或者项目复杂度不高,追求快速出效果,可以从Arduino入手。如果你目标是开发稳定、高效、功能复杂的商业产品,或者希望深入学习ESP32的体系结构,那么直接学习ESP-IDF是更长远的选择。ESP32-S3-Touch-LCD-3.5B的供应商通常会同时提供这两种框架的示例代码。

3.2 驱动与图形库集成:点亮屏幕的第一步

无论选择哪个框架,第一步都是让屏幕亮起来并正确响应触摸。这个过程通常涉及三个部分:屏幕驱动、触摸驱动和图形库。

1. 屏幕驱动初始化:屏幕驱动代码负责向LCD控制器发送初始化序列(一组特定的命令和数据),设置其工作模式、扫描方向、颜色格式等。然后需要实现一个flush_cb回调函数。这个函数是图形库(如LVGL)的核心:当LVGL在内存帧缓冲区中完成一帧画面的绘制后,会调用此函数,你需要在这个函数里将帧缓冲区的数据通过SPI或并口发送到屏幕。为了提高效率,务必使用DMA进行数据传输,这样CPU在数据搬运期间可以处理其他任务。

// 伪代码示例:flush_cb 函数框架 static void my_flush_cb(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p) { // area: 需要更新的屏幕区域 // color_p: 该区域对应的颜色数据起始地址 int32_t width = lv_area_get_width(area); int32_t height = lv_area_get_height(area); // 设置屏幕的显示窗口(告诉屏幕接下来要更新哪一块区域) set_window(area->x1, area->y1, area->x2, area->y2); // 使用SPI DMA将color_p中的数据发送到屏幕 spi_transmit_dma(color_p, width * height * sizeof(lv_color_t)); // 传输完成后,必须调用 lv_disp_flush_ready,通知LVGL可以开始下一帧绘制了 // 这个调用通常放在DMA传输完成的中断回调函数里 }

2. 触摸驱动初始化:触摸驱动通过I2C与触摸控制器通信。你需要初始化I2C总线,并配置触摸芯片(如设置中断模式、坐标校正等)。同样,需要实现一个read_cb回调函数。LVGL会定期(例如每10ms)调用这个函数,你需要在这里读取I2C数据,获取当前是否有触摸、触摸坐标是多少,然后填充到lv_indev_data_t结构体中。

// 伪代码示例:read_cb 函数框架 static void my_touch_read_cb(lv_indev_drv_t * indev_drv, lv_indev_data_t * data) { // 通过I2C读取触摸控制器状态 uint8_t touch_status = i2c_read_touch_status(); if (touch_status & TOUCH_PRESSED) { >#include “lvgl.h” lv_obj_t * label; // 按钮的事件回调函数 static void btn_event_cb(lv_event_t * e) { lv_event_code_t code = lv_event_get_code(e); if(code == LV_EVENT_CLICKED) { lv_label_set_text(label, “Hello, ESP32-S3!”); } } void create_ui(void) { // 1. 创建一个按钮对象 lv_obj_t * btn = lv_btn_create(lv_scr_act()); // 在默认屏幕上创建 lv_obj_set_size(btn, 120, 50); // 设置大小 lv_obj_align(btn, LV_ALIGN_CENTER, 0, -50); // 居中并向上偏移50像素 lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_ALL, NULL); // 添加事件回调 // 2. 为按钮添加一个标签(用于显示按钮文字) lv_obj_t * btn_label = lv_label_create(btn); lv_label_set_text(btn_label, “Click Me!”); lv_obj_center(btn_label); // 3. 创建一个独立的标签对象 label = lv_label_create(lv_scr_act()); lv_label_set_text(label, “Ready...”); lv_obj_align(label, LV_ALIGN_CENTER, 0, 50); // 居中并向下偏移50像素 }

main函数中初始化LVGL并调用create_ui()后,一个简单的交互界面就完成了。LVGL的强大之处在于其丰富的内置控件和灵活的样式系统,你可以通过样式来统一设置颜色、边框、阴影、字体等,实现现代化的UI效果。

4.2 内存管理与性能优化技巧

在资源受限的嵌入式设备上开发GUI,性能优化是永恒的主题。ESP32-S3-Touch-LCD-3.5B虽然有PSRAM,但不当的使用仍会导致卡顿。

1. 帧缓冲区策略:

  • 单缓冲区:LVGL在内部一块内存(帧缓冲区)中绘图,画完后一次性flush到屏幕。这是最省内存的方式,但在flush期间(尤其是SPI传输慢时),LVGL必须等待,不能绘制下一帧,可能导致动画不连贯。
  • 双缓冲区:分配两块帧缓冲区(A和B)。LVGL在A中绘图时,DMA正在将B的内容发送到屏幕;完成后交换角色。这能实现流畅的动画,但内存占用翻倍(对于480x320 RGB565,约600KB)。对于有8MB PSRAM的ESP32-S3,强烈推荐使用双缓冲区,将缓冲区分配在PSRAM中,能极大提升流畅度。

2. 图片与字体处理:

  • 图片:避免在代码中直接包含巨大的位图数组(C文件)。应将图片转换为.bin.lvgl格式的文件,存放在SPIFFS或SD卡文件系统中,使用LVGL的文件系统API动态加载。加载时使用LV_IMG_CF_TRUE_COLORLV_IMG_CF_RAW格式,避免运行时解码PNG/JPG(非常消耗CPU)。
  • 字体:仅包含UI中实际用到的字符集。LVGL支持从二进制字体文件(.bin)中按需读取字形,这比将整个字体数组编译进固件要节省大量Flash空间。可以使用LVGL官方提供的在线字体转换工具。

3. 渲染优化:

  • 局部刷新:确保你的flush_cb函数正确实现了局部刷新。LVGL只会传递发生变化的区域(area参数)给驱动,驱动应该只更新屏幕的这一小块区域,而不是全屏刷新,这能显著减少数据传输量。
  • 降低刷新率:如果UI变化不频繁,可以在lv_conf.h中增大LV_DISP_DEF_REFR_PERIOD,比如从30ms改为50ms(20 FPS),能降低系统负载。
  • 使用硬件加速:研究ESP32-S3的SPI DMA和可能存在的2D图形加速引擎(如果有),确保驱动中已启用它们。

4. 任务优先级与CPU绑定:在ESP-IDF的FreeRTOS环境下,合理设置任务优先级。可以将LVGL的定时器任务(lv_timer_handler)放在一个中等优先级的任务中,而将flush的DMA完成中断回调(通知LVGL刷新完成)设置为高优先级,确保及时响应。如果应用复杂,可以考虑将LVGL任务绑定到CPU0,将网络任务绑定到CPU1,充分利用双核。

踩坑记录:我曾遇到UI在连续快速滑动时卡顿的问题。排查后发现是SPI时钟频率设置过低(26MHz),导致flush时间过长。将SPI时钟提升到80MHz(在屏幕控制器允许范围内)后,卡顿消失。务必在屏幕规格书允许的范围内,尽可能提高SPI通信速率。同时,检查spi_bus_config_tspi_device_interface_config_t结构体中的duty_cycle_poscs_ena_pretrans等时序参数,不合理的设置也会限制实际速率。

5. 无线功能集成与综合项目示例

5.1 连接Wi-Fi与MQTT实现数据同步

ESP32-S3的核心优势之一是集成了Wi-Fi。让ESP32-S3-Touch-LCD-3.5B连接网络,就能使其从本地交互设备升级为物联网节点。

在ESP-IDF中,使用esp_wifi组件连接Wi-Fi是标准操作。通常步骤是:初始化NVS(存储Wi-Fi配置)-> 创建默认事件循环 -> 初始化Wi-Fi驱动 -> 配置为STA模式 -> 设置SSID和密码 -> 启动连接。连接成功后,会收到WIFI_EVENT_STA_CONNECTEDIP_EVENT_STA_GOT_IP事件。

连接网络后,一个典型的应用是通过MQTT协议与云平台或本地服务器通信。你可以使用ESP-IDF内置的mqtt_client组件。例如,创建一个智能家居温湿度显示面板:

  1. 屏幕上显示当前室内温湿度(从板载或外接的DHT11传感器读取)。
  2. 同时,通过MQTT订阅云端服务器下发的天气信息(如室外温度)并显示。
  3. 在屏幕上设计一个开关按钮,点击后通过MQTT发布一条消息,控制家里的智能灯。
// 伪代码流程 void app_main() { lvgl_init(); // 初始化LVGL和屏幕 create_ui(); // 创建显示温湿度、天气、开关按钮的UI wifi_init_sta(); // 连接Wi-Fi mqtt_app_start(); // 启动MQTT客户端,连接broker // MQTT事件回调中 if (event->event_id == MQTT_EVENT_DATA) { // 解析收到的主题和消息 if (strcmp(topic, “home/weather/outdoor_temp”) == 0) { // 将消息(温度值)更新到屏幕对应的标签上 lv_label_set_text_fmt(weather_label, “室外: %s°C”, data); } } // 按钮事件回调中 if (code == LV_EVENT_VALUE_CHANGED) { // 开关状态改变 bool state = lv_obj_has_state(btn, LV_STATE_CHECKED); char msg[10]; sprintf(msg, “%s”, state ? “ON” : “OFF”); esp_mqtt_client_publish(client, “home/light/switch”, msg, 0, 1, 0); } }

这种模式将本地交互与云端控制完美结合,展现了ESP32-S3-Touch-LCD-3.5B作为物联网交互终端的强大潜力。

5.2 低功耗设计与电源管理考量

尽管作为交互设备,屏幕常亮时功耗不会太低,但在一些电池供电或需要待机的场景下,低功耗设计仍有价值。

  1. 屏幕背光控制:3.5寸LCD的背光是主要的耗电源。通过一个GPIO控制背光电路的使能端,或者使用PWM精细调节背光亮度,可以在无人操作时调暗或关闭背光,大幅省电。
  2. ESP32-S3睡眠模式:当设备处于闲置状态时(例如深夜),可以让ESP32-S3进入轻睡眠(Light Sleep)或深度睡眠(Deep Sleep)模式。
    • 轻睡眠:CPU暂停,RAM数据保持,外设时钟可关闭。可以通过定时器、触摸中断(需配置触摸芯片支持唤醒)或外部GPIO中断唤醒。唤醒后程序从睡眠点继续执行。适合需要快速恢复、保持网络连接的场景。
    • 深度睡眠:仅RTC模块和极少量RAM(ULP)保持供电,其他全部关闭。功耗最低(约10μA)。只能通过RTC定时器、RTC GPIO或触摸传感器(如果连接到了RTC GPIO)唤醒。唤醒后相当于硬件复位,程序从头开始执行。适合长时间间隔工作的数据采集器。
  3. 动态频率调整:ESP-IDF支持动态调整CPU频率。在UI空闲或处理简单任务时,可以调用esp_pm_configure降低CPU主频,节省功耗。

实现一个完整的低功耗流程:用户操作时,背光全亮,CPU全速运行。检测到无触摸操作超时(例如2分钟)后,逐渐调暗背光直至关闭,然后让ESP32-S3进入轻睡眠。触摸任意位置产生中断,唤醒系统,恢复背光和全速运行。这需要在软件上妥善管理外设状态(如Wi-Fi连接)的保存与恢复。

6. 常见问题排查与调试心得

在开发过程中,你一定会遇到各种问题。下面是一些典型问题的排查思路:

问题1:屏幕白屏或花屏

  • 检查电源:首先用万用表测量板子供电电压是否稳定在5V/3.3V。背光是否点亮?可以尝试调节背光控制引脚。
  • 检查连接:如果是分体式屏幕(排线连接),检查排线是否插紧、有无损坏。
  • 检查初始化序列:屏幕驱动IC型号是否正确?初始化命令和数据(通常是一个lcd_init_cmd_t数组)是否与数据手册完全一致?特别是电源上电时序相关的延时命令。
  • 检查SPI配置:SPI模式(CPOL, CPHA)是否正确?时钟频率是否在屏幕控制器支持范围内(过高或过低都会失败)?DMA通道是否配置正确?
  • 检查帧缓冲区地址:确保flush_cb函数中发送的数据地址是正确的帧缓冲区地址。

问题2:触摸无反应或坐标不准

  • 检查I2C通信:使用逻辑分析仪或示波器抓取I2C总线(SCL, SDA)波形,看是否有正确的起始信号、设备地址和应答。确认I2C引脚配置、上拉电阻是否正常。
  • 确认触摸芯片型号与地址:常见GT911地址可能是0x5D或0x14,FT6236是0x38。通过I2C扫描工具确认。
  • 检查中断引脚:很多触摸芯片使用中断引脚通知主控有触摸事件。检查中断引脚配置是否正确(上拉/下拉、触发边沿)。
  • 坐标校准与转换:读取的原始坐标可能需要经过旋转、镜像或线性校正才能匹配屏幕坐标。建立一个简单的测试程序,打印原始坐标,用手点击屏幕四个角,观察坐标值是否线性变化。

问题3:LVGL运行卡顿、刷新慢

  • 测量帧率:lv_conf.h中启用LV_USE_PERF_MONITOR,可以在屏幕上显示实时帧率和渲染时间。
  • 检查内存分配:帧缓冲区是否分配在了速度较慢的PSRAM?对于需要频繁访问的数据(如当前帧缓冲区),应尽量使用内部SRAM。可以尝试将LVGL的内存池(LV_MEM_SIZE)也放在内部SRAM。
  • 优化绘制操作:是否在短时间内创建/删除了大量对象?LVGL的对象创建和删除开销较大。应尽量复用对象,或使用lv_obj_add_flag(obj, LV_OBJ_FLAG_HIDDEN)来隐藏而非删除。
  • 分析任务调度:使用FreeRTOS的vTaskList()或相关工具查看各任务运行时间和堆栈使用情况,看是否有其他高优先级任务长时间阻塞了LVGL任务。

问题4:Wi-Fi连接不稳定,影响UI响应

  • 任务优先级调整:确保网络相关任务(如MQTT接收、Socket处理)的优先级不高于LVGL的任务优先级。否则网络任务可能长时间占用CPU,导致UI无法及时更新。
  • 使用事件回调:将网络状态更新(如“连接中”、“已连接”、“断开”)通过事件回调的方式传递给UI任务,而不是在UI任务中轮询网络状态。LVGL是线程不安全的,所有更新UI的操作必须在同一个任务(通常是调用lv_timer_handler的任务)中执行。可以使用队列(Queue)或任务通知(Task Notification)在任务间传递消息。
  • 减少无线干扰:确保开发板远离大功率电源、电机等干扰源。尝试在代码中降低Wi-Fi发射功率(esp_wifi_set_max_tx_power),有时过强的信号在近距离反而会引起问题。

调试是一个系统工程,从电源、信号等硬件基础,到驱动、配置等软件底层,再到应用逻辑,需要逐层排查。养成使用串口日志(ESP_LOGx)分级别打印关键信息的习惯,它能帮你快速定位问题发生的阶段。对于ESP32-S3-Touch-LCD-3.5B这样高度集成的平台,充分利用供应商提供的示例和文档,往往是解决问题最快的方法。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/1 16:00:20

Vivado IP核Global与OOC综合模式详解:原理、对比与工程实践

1. 项目概述:从一次综合时长引发的思考 最近在做一个图像处理相关的FPGA项目,里面用到了好几个Xilinx的IP核,比如DDR控制器、AXI Interconnect,还有自己封装的几个图像算法模块。项目不算特别大,但每次点击“Run Synth…

作者头像 李华
网站建设 2026/8/1 15:59:54

Java异常处理机制与面试高频考点解析

1. Java异常机制的核心概念 Java异常处理是每个开发者必须掌握的基础知识,也是面试中的高频考点。异常机制本质上是一种程序错误处理机制,它允许我们在程序出现非预期情况时,以一种结构化的方式进行响应,而不是直接导致程序崩溃。…

作者头像 李华
网站建设 2026/8/1 15:48:51

基于springboot3+vue3的智能文库平台(AI智能搜索、AI智能汇总、实时在线状态展示、多格式文档预览与富文本编辑、Echarts图形化分析)

🎈系统亮点:AI智能搜索、AI智能汇总、实时在线状态展示、多格式文档预览与富文本编辑、Echarts图形化分析;一.系统开发工具与环境搭建1.系统设计开发工具后端使用Java编程语言的Spring boot框架 项目架构:B/S架构 运行环境&#x…

作者头像 李华
网站建设 2026/8/1 15:47:41

STM32定时器中断编程:GetFlagStatus与GetITStatus函数核心区别详解

1. 项目概述:从两个看似相同的函数说起 如果你正在学习STM32的定时器(TIM),并且已经开始接触中断编程,那么你大概率会遇到这两个名字长得像双胞胎一样的固件库函数: TIM_GetFlagStatus 和 TIM_GetITStat…

作者头像 李华