FEATURED · 精选文章

从零复刻ESP32开源智能手表:硬件选型、环境搭建与核心代码实现

发布时间 / 2026/9/2 3:00:11
来源 / 创域科博编辑部
栏目 / 资讯中心
从零复刻ESP32开源智能手表:硬件选型、环境搭建与核心代码实现 大家好我是专注于嵌入式开发的技术博主。你是否也曾对智能手表内部构造充满好奇想亲手打造一款属于自己的、功能可定制的智能穿戴设备市面上的成品手表功能固定而基于ESP32的开源手表项目则为开发者提供了一个绝佳的DIY平台。本文将围绕一个开源的ESP32多功能手表项目提供一份从零开始的完整复刻教程。无论你是嵌入式新手想入门物联网还是有经验的开发者想拓展项目经验都能通过本文掌握硬件选型、环境搭建、核心功能实现到最终烧录的全流程。我们将重点解析如何驱动屏幕、读取传感器、实现OTA升级等关键技术点并提供可直接复用的代码和清晰的避坑指南。1. 项目背景与核心概念1.1 什么是ESP32开源手表ESP32开源手表是指基于乐鑫Espressif公司的ESP32系列芯片为核心搭配显示屏、电池、传感器等外围硬件并运行开源固件的可穿戴设备项目。它并非一个成熟的商业产品而是一个由开发者社区驱动的、高度可定制和可学习的开发平台。与商业智能手表相比其核心价值在于完全开源硬件电路图、PCB设计、软件代码全部公开你可以透彻理解每一处设计。高度可定制你可以自由增删功能如GPS、NFC修改UI界面甚至更换传感器。绝佳的学习平台它集成了MCU编程、外设驱动I2C/SPI、低功耗管理、无线通信Wi-Fi/蓝牙、GUI开发等嵌入式核心技能。成本可控自行采购元件组装成本远低于品牌手表。1.2 为什么选择ESP32ESP32芯片成为此类DIY项目的首选主要归功于其强大的特性双核处理器主频高达240MHz性能足以流畅运行图形界面和复杂逻辑。丰富的无线连接集成Wi-Fi和蓝牙经典/低功耗便于连接网络、同步数据或与手机通信。低功耗管理支持多种睡眠模式对于电池供电的手表设备至关重要。外设接口齐全拥有多个I2C、SPI、UART、ADC、DAC接口可轻松连接各类传感器和屏幕。活跃的社区与生态Arduino-ESP32、ESP-IDF等开发框架成熟有海量的库和教程支持。1.3 项目核心功能预览一个典型的ESP32开源手表通常包含以下功能模块这也是我们复刻的重点时间显示基于RTC实时时钟或NTP网络时间协议的精准计时。传感器数据读取心率、血氧、计步、温湿度等取决于搭载的传感器。用户界面通过LCD或OLED屏幕显示信息支持触摸或按键交互。电源管理锂电池充电、电量监控、低功耗睡眠与唤醒。无线功能通过蓝牙接收手机通知或通过Wi-Fi同步天气、更新时间。固件更新利用OTA空中升级功能无需连接线缆即可更新手表程序。2. 硬件准备与物料清单复刻的第一步是准备硬件。你可以根据开源项目的BOM物料清单自行采购也可以购买现成的模块进行组装。以下是一个通用性较强的核心物料清单。2.1 核心控制器与主板ESP32开发板/模组推荐使用集成度高、尺寸较小的型号如ESP32-S3性能更强外设更多或ESP32-WROOM-32经典稳定。考虑到手表需要丰富的IO驱动屏幕和传感器ESP32-S3是更优选择。手表主板你可以选择开源PCB根据开源项目如Watchy、ESP32-Watch的Gerber文件打板并焊接。集成主板购买市面上已有的ESP32手表开发板通常已集成充电管理、振动马达等这能极大降低硬件门槛。2.2 显示与交互模块显示屏1.54英寸或1.28英寸的圆形或方形LCD屏常见接口为SPI。分辨率建议在240x240或更高。需确认是否带触摸功能。触摸屏控制器如使用通常为I2C接口的芯片如GT911、FT6236等。按键或表冠用于实现返回、菜单、确认等物理交互。2.3 传感器模块心率血氧传感器如MAX30102或更新的MAX30105通过I2C通信可检测心率和血氧饱和度。运动传感器如MPU6050加速度计陀螺仪或BMA423专为可穿戴设备优化的加速度计用于计步、识别手势、监测睡眠。环境传感器可选如BME280温湿度气压用于获取环境数据。2.4 电源与结构件锂电池容量在300mAh至500mAh之间的3.7V锂聚合物电池尺寸需与表壳匹配。充电管理模块如TP4056芯片模块负责安全地为锂电池充电。电池连接器与开关JST PH2.0接头以及拨动开关或轻触开关。振动马达小型扁平振动马达用于提供触觉反馈。表壳与表带可通过3D打印根据开源模型或购买通用表壳解决。2.5 工具与耗材焊接工具电烙铁、焊锡丝、吸锡器。万用表用于检查电路通断和电压。杜邦线用于前期模块化测试。USB转串口下载器如CH340、CP2102模块用于初次烧录程序。3. 软件开发环境搭建在动手焊接前我们先搭建好软件开发环境。这里以Arduino IDE为例因为它对初学者更友好库生态丰富。3.1 安装Arduino IDE与ESP32开发板支持下载Arduino IDE从 Arduino 官网下载并安装最新版本的IDE。添加ESP32开发板网址打开Arduino IDE进入文件-首选项。在“附加开发板管理器网址”中填入以下网址如果已有其他用逗号分隔https://espressif.github.io/arduino-esp32/package_esp32_index.json安装ESP32开发板进入工具-开发板-开发板管理器。搜索“esp32”找到由“Espressif Systems”提供的esp32包选择最新版本如3.3.10并安装。这个过程可能需要一些时间因为它会下载所有相关工具链。3.2 安装必要的库文件手表项目依赖于多个第三方库来驱动硬件。通过库管理器安装进入项目-加载库-管理库...。搜索并安装以下常用库具体库名可能因项目而异U8g2或TFT_eSPI用于驱动OLED或LCD屏幕。Adafruit_GFX与Adafruit_ST7789如果使用ST7789驱动的LCD屏。MAX3010x用于驱动MAX30102/MAX30105心率血氧传感器。Adafruit_MPU6050或BMA423_Sensor用于驱动运动传感器。WiFi和HTTPClientESP32核心已包含用于网络连接。NTPClient用于从网络获取时间。ArduinoJson用于解析从网络API获取的天气等数据。3.3 测试开发环境安装完成后选择一个简单的示例程序来测试环境是否正常。在工具-开发板中选择你的ESP32型号例如 “ESP32S3 Dev Module”。选择正确的端口插入USB线后会出现。打开文件-示例-01.Basics-Blink。点击上传按钮。如果一切正常代码将被编译并上传到ESP32板载LED开始闪烁。4. 核心功能模块代码实现现在我们进入核心环节分模块讲解如何用代码驱动手表的各个功能。我们将以模块化方式进行便于理解和集成。4.1 驱动LCD显示屏假设我们使用一款1.54英寸、240x240分辨率、ST7789驱动芯片的SPI接口屏幕。// 文件display.h #include TFT_eSPI.h // 引入TFT_eSPI库 #ifndef DISPLAY_H #define DISPLAY_H extern TFT_eSPI tft; // 声明全局显示对象 void setupDisplay(); void drawWatchFace(int hour, int minute, int second, int batteryPercent); #endif// 文件display.cpp #include “display.h” TFT_eSPI tft TFT_eSPI(); // 定义全局显示对象 void setupDisplay() { tft.init(); tft.setRotation(1); // 根据屏幕实际方向调整旋转角度 (0,1,2,3) tft.fillScreen(TFT_BLACK); // 清屏为黑色 tft.setTextColor(TFT_WHITE, TFT_BLACK); // 设置字体颜色前景色背景色 tft.setTextSize(2); // 设置字体大小 Serial.println(“LCD显示屏初始化完成”); } void drawWatchFace(int hour, int minute, int second, int batteryPercent) { tft.fillScreen(TFT_BLACK); // 每次刷新前清屏 // 1. 绘制时间 char timeStr[9]; sprintf(timeStr, “%02d:%02d:%02d”, hour, minute, second); tft.setTextSize(4); tft.setTextColor(TFT_CYAN, TFT_BLACK); tft.drawString(timeStr, 20, 80); // 2. 绘制电池图标 tft.setTextSize(1); tft.setTextColor(TFT_WHITE, TFT_BLACK); tft.drawString(“BAT:”, 180, 10); // 绘制电池外框 tft.drawRect(210, 10, 20, 10, TFT_WHITE); // 根据电量百分比填充电池内部 int fillWidth map(batteryPercent, 0, 100, 0, 18); tft.fillRect(211, 11, fillWidth, 8, TFT_GREEN); // 3. 可以在此添加更多元素如日期、步数等 }关键点TFT_eSPI库需要通过用户配置文件来适配不同屏幕。你需要在Arduino库文件夹中找到TFT_eSPI/User_Setup.h或User_Setup_Select.h文件根据屏幕驱动芯片和引脚连接进行配置。setRotation()函数用于调整屏幕显示方向。在手表应用中为了省电应避免全屏频繁刷新可以采用局部刷新或双缓冲技术。4.2 读取心率血氧传感器MAX30102MAX30102通过I2C接口通信。// 文件heartrate_sensor.h #include Wire.h #include “MAX30105.h” // 确保已安装MAX3010x库 #ifndef HEARTRATE_SENSOR_H #define HEARTRATE_SENSOR_H extern MAX30105 particleSensor; bool setupHeartRateSensor(); bool readHeartRateSensor(int heartRate, int oxygenSat); #endif// 文件heartrate_sensor.cpp #include “heartrate_sensor.h” MAX30105 particleSensor; bool setupHeartRateSensor() { if (!particleSensor.begin(Wire, I2C_SPEED_FAST)) { // 使用Wire端口快速模式 Serial.println(“MAX30102传感器未找到请检查连接”); return false; } // 配置传感器参数 particleSensor.setup(); particleSensor.setPulseAmplitudeRed(0x0A); // 设置红光LED亮度 particleSensor.setPulseAmplitudeIR(0x0A); // 设置红外LED亮度 // 启用传感器读数 particleSensor.enableDIETEMPRDY(); // 启用数据就绪中断如果使用 Serial.println(“MAX30102传感器初始化完成”); return true; } bool readHeartRateSensor(int heartRate, int oxygenSat) { // 注意MAX30102的原始数据需要复杂的算法如心率变异性分析来计算出心率和血氧。 // 此处为简化示例实际项目应使用库中提供的算法或自行实现。 // 这里仅演示读取原始缓冲区数据。 long irValue particleSensor.getIR(); long redValue particleSensor.getRed(); if (irValue 50000) { // 阈值判断表示手指未放置好 Serial.println(“未检测到手指”); heartRate 0; oxygenSat 0; return false; } // 伪代码在此处调用心率血氧计算算法 // heartRate calculateHeartRate(irBuffer, bufferLength); // oxygenSat calculateSpO2(irBuffer, redBuffer, bufferLength); // 示例返回模拟值实际开发必须替换为真实算法 heartRate 72; // 模拟心率值 oxygenSat 98; // 模拟血氧值 return true; }重要提示MAX30102的心率和血氧计算是难点需要持续采样并运行特定算法如“心率变异性HRV”和“比值法计算血氧”。建议直接使用成熟的第三方算法库如SparkFun_MAX3010x_Sensor库中的示例算法而不是自己从头实现。4.3 获取网络时间与天气手表通过Wi-Fi连接网络获取精确时间和天气信息。// 文件network_service.h #ifndef NETWORK_SERVICE_H #define NETWORK_SERVICE_H #include WiFi.h #include HTTPClient.h #include ArduinoJson.h void connectToWiFi(const char* ssid, const char* password); bool syncTimeFromNTP(int hour, int minute, int second); bool fetchWeatherData(float temperature, int humidity, String weatherDesc); #endif// 文件network_service.cpp #include “network_service.h” #include time.h // 用于NTP const char* ntpServer “pool.ntp.org”; // NTP服务器 const long gmtOffset_sec 8 * 3600; // 东八区 (UTC8) const int daylightOffset_sec 0; void connectToWiFi(const char* ssid, const char* password) { WiFi.begin(ssid, password); Serial.print(“正在连接到Wi-Fi”); while (WiFi.status() ! WL_CONNECTED) { delay(500); Serial.print(“.”); } Serial.println(“\nWi-Fi连接成功”); Serial.print(“IP地址: “); Serial.println(WiFi.localIP()); } bool syncTimeFromNTP(int hour, int minute, int second) { configTime(gmtOffset_sec, daylightOffset_sec, ntpServer); struct tm timeinfo; if (!getLocalTime(timeinfo)) { Serial.println(“从NTP获取时间失败”); return false; } hour timeinfo.tm_hour; minute timeinfo.tm_min; second timeinfo.tm_sec; Serial.printf(“时间同步成功: %02d:%02d:%02d\n”, hour, minute, second); return true; } bool fetchWeatherData(float temperature, int humidity, String weatherDesc) { if (WiFi.status() ! WL_CONNECTED) { return false; } HTTPClient http; // 示例使用和风天气API需要自行申请API Key String url “http://api.qweather.com/v7/weather/now?location101010100keyYOUR_API_KEY”; http.begin(url); int httpCode http.GET(); if (httpCode HTTP_CODE_OK) { String payload http.getString(); // 解析JSON DynamicJsonDocument doc(1024); deserializeJson(doc, payload); temperature doc[“now”][“temp”].asfloat(); humidity doc[“now”][“humidity”].asint(); weatherDesc doc[“now”][“text”].asString(); http.end(); return true; } else { Serial.printf(“HTTP请求失败错误码: %d\n”, httpCode); http.end(); return false; } }注意事项将YOUR_SSID、YOUR_PASSWORD和YOUR_API_KEY替换为你自己的信息。网络操作Wi-Fi连接、HTTP请求非常耗电在手表中应仅在需要时如每小时同步一次时间每几小时更新一次天气唤醒执行然后立即进入睡眠。天气API通常有调用频率限制请遵守服务商条款。4.4 主程序逻辑与低功耗管理主程序需要协调各个模块并管理电源。// 文件esp32_watch.ino (Arduino主程序) #include “display.h” #include “heartrate_sensor.h” #include “network_service.h” // 定义引脚 #define BUTTON_PIN 0 // 假设按键接在GPIO0 #define BATTERY_PIN 34 // 假设电池电压检测接在GPIO34 (ADC) // 全局变量 int lastSecond -1; int batteryPercent 100; unsigned long lastSyncTime 0; const unsigned long syncInterval 3600000; // 1小时同步一次 void setup() { Serial.begin(115200); pinMode(BUTTON_PIN, INPUT_PULLUP); // 配置按键引脚内部上拉 setupDisplay(); if (!setupHeartRateSensor()) { Serial.println(“心率传感器初始化失败部分功能不可用。”); } // 首次连接Wi-Fi并同步时间在实际应用中可能由用户触发 // connectToWiFi(“YOUR_SSID”, “YOUR_PASSWORD”); // syncTimeFromNTP(...); // 初始化RTC如果使用硬件RTC或软件RTC // 这里简化处理使用NTP同步后的系统时间 } void loop() { // 1. 读取当前时间从RTC或系统时间 struct tm timeinfo; if (!getLocalTime(timeinfo)) { // 如果从未同步过时间使用一个默认值或从硬件RTC读取 timeinfo.tm_hour 12; timeinfo.tm_min 0; timeinfo.tm_sec 0; } // 2. 仅在秒数变化时刷新时间显示避免屏幕闪烁 if (timeinfo.tm_sec ! lastSecond) { lastSecond timeinfo.tm_sec; // 读取电池电量简化ADC读数映射到百分比 int batAdc analogRead(BATTERY_PIN); // 注意需要根据实际电池电压-ADC值曲线进行校准 batteryPercent map(batAdc, 0, 4095, 0, 100); // ESP32 ADC为12位 (0-4095) batteryPercent constrain(batteryPercent, 0, 100); // 绘制表盘 drawWatchFace(timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec, batteryPercent); } // 3. 处理按键事件 if (digitalRead(BUTTON_PIN) LOW) { // 按键按下低电平有效 delay(50); // 简单消抖 if (digitalRead(BUTTON_PIN) LOW) { Serial.println(“按键被按下”); // 此处可以切换显示界面例如切换到心率页面 // showHeartRatePage(); while(digitalRead(BUTTON_PIN) LOW); // 等待按键释放 } } // 4. 定期同步网络时间例如每小时一次 unsigned long currentMillis millis(); if (currentMillis - lastSyncTime syncInterval) { if (WiFi.status() ! WL_CONNECTED) { connectToWiFi(“YOUR_SSID”, “YOUR_PASSWORD”); } int h,m,s; if (syncTimeFromNTP(h, m, s)) { // 同步成功可以更新硬件RTC lastSyncTime currentMillis; } // 同步后可以断开Wi-Fi以省电 // WiFi.disconnect(true); } // 5. 低功耗策略在loop循环末尾添加短暂延迟并考虑使用深度睡眠 // 对于手表更佳的策略是在无操作一段时间后关闭屏幕和大部分外设让ESP32进入Light Sleep或Deep Sleep模式。 // 当有按键中断或RTC定时中断时再唤醒。 // 示例delay(100); // 当前简单延迟100ms // 进阶esp_sleep_enable_ext0_wakeup(GPIO_NUM_0, 0); // 配置GPIO0为唤醒源 // esp_deep_sleep_start(); }5. 硬件组装、烧录与调试5.1 硬件连接与焊接根据你选择的屏幕、传感器型号参照其数据手册或开源项目的原理图进行连接。以下是一个典型的SPI屏幕和I2C传感器连接示例引脚号需根据你的主板调整外设模块接口ESP32引脚说明ST7789 LCDSPI_SCKGPIO 18时钟线SPI_MOSIGPIO 23数据线DC (数据/命令)GPIO 2RESETGPIO 4复位CS (片选)GPIO 5BL (背光)GPIO 15通过三极管控制MAX30102I2C_SDAGPIO 21数据线I2C_SCLGPIO 22时钟线INT (中断)GPIO 19可选按键GPIOGPIO 0带内部上拉电池电压ADCGPIO 34需分压电路焊接建议先使用杜邦线连接所有模块进行功能测试确认无误后再焊接。焊接时注意温度和时间避免烫坏芯片或焊盘。为电池和充电模块的连接做好绝缘保护。5.2 代码烧录与初次启动连接电脑使用USB线将ESP32主板或通过USB转串口模块连接与电脑连接。选择开发板与端口在Arduino IDE中正确选择你的ESP32型号和对应的串口。编译与上传点击上传按钮。首次上传可能需要按住主板上的“BOOT”或“IO0”按钮进入下载模式。查看日志打开串口监视器波特率通常为115200查看初始化日志确认各模块是否正常工作。5.3 基础功能测试在串口监视器中你应该能看到类似以下的输出并观察到屏幕点亮显示时间LCD显示屏初始化完成 MAX30102传感器初始化完成 Wi-Fi连接成功 IP地址: 192.168.1.100 时间同步成功: 14:30:05检查屏幕显示是否正常时间是否正确。将手指轻放在MAX30102传感器上观察串口是否输出心率血氧数据或模拟数据。按下按键观察串口是否有“按键被按下”的日志。6. 常见问题与排查思路在复刻过程中你几乎一定会遇到一些问题。下表列出了常见问题及解决方法问题现象可能原因排查思路与解决方案上传代码失败1. 端口选择错误。2. 开发板型号选择错误。3. ESP32未进入下载模式。4. 驱动未安装CH340/CP2102。1. 检查设备管理器确认串口端口号。2. 核对ESP32具体型号如ESP32-S3 DevKitC-1。3. 尝试按住BOOT键再点击上传待编译开始后松开。4. 安装对应的USB转串口芯片驱动。屏幕白屏或花屏1. 引脚连接错误。2. 屏幕驱动库配置错误。3. 电源电压不足。4. 屏幕初始化代码有误。1. 用万用表逐线检查SPI引脚连接。2. 仔细检查TFT_eSPI库的用户配置文件确认驱动芯片、分辨率、引脚定义。3. 确保屏幕供电电压通常是3.3V稳定。4. 检查setupDisplay()函数中的初始化顺序和参数。传感器读取失败1. I2C地址错误。2. I2C引脚接错或未上拉。3. 传感器供电问题。4. 库不兼容或版本不对。1. 使用I2C扫描程序Arduino示例中有Wire库的扫描示例确认传感器地址。2. I2C的SDA和SCL线需要接上拉电阻通常4.7kΩ到10kΩ。3. 确保传感器VCC接3.3VGND接地。4. 尝试更换或更新传感器库。Wi-Fi无法连接1. SSID或密码错误。2. 路由器设置了MAC过滤。3. ESP32的Wi-Fi天线问题某些模块需要外接天线。4. 代码中Wi-Fi模式设置错误。1. 再三检查SSID和密码注意大小写和特殊字符。2. 检查路由器后台将ESP32的MAC地址加入白名单。3. 确保天线连接牢固如果可拆卸。4. 尝试在代码开头添加WiFi.mode(WIFI_STA);。功耗过高电池耗电快1. 屏幕常亮且背光最亮。2. Wi-Fi/蓝牙一直开启。3. 未使用低功耗睡眠模式。4. 外部电路存在漏电。1. 启用屏幕休眠和背光调光。2. 仅在需要时开启无线功能完成后立即断开。3. 使用esp_deep_sleep_start()并配置RTC定时器或外部中断唤醒。4. 检查电路确保在睡眠模式下不必要的模块如传感器、屏幕已断电。OTA升级失败1. 初始固件未启用OTA功能。2. 网络不稳定。3. 升级包过大内存不足。4. 分区表不匹配。1. 首次必须通过串口烧录包含OTA功能的固件。2. 确保升级过程中Wi-Fi信号良好。3. 优化代码减少体积或使用压缩升级。4. 检查项目中的partitions.csv文件确保OTA分区设置正确。7. 进阶优化与最佳实践完成基本功能后你可以从以下几个方面优化你的手表使其更接近产品级体验。7.1 电源管理深度优化使用深度睡眠在无交互时让ESP32进入深度睡眠模式仅保留RTC内存功耗可降至10μA级别。通过RTC定时器如每小时唤醒一次同步时间或外部按键中断唤醒。// 进入深度睡眠由GPIO0下降沿唤醒 esp_sleep_enable_ext0_wakeup(GPIO_NUM_0, 0); // 0 LOW esp_deep_sleep_start();动态电源控制通过MOSFET或电源管理IC在睡眠时彻底切断屏幕、传感器等外围模块的供电。电池电量精确监测使用专门的电量计芯片如MAX17048或通过ADC读取分压电压并建立电压-电量百分比对应曲线进行软件校准。7.2 实现可靠的OTA升级基础OTA使用ArduinoOTA库可以让手表在同一个Wi-Fi网络下通过浏览器或OTA工具上传新固件。HTTP OTA从指定的HTTP服务器下载固件并更新便于远程升级。双分区OTAESP-IDF支持双OTA分区确保升级失败后能回滚到旧版本提高可靠性。在Arduino中需要自定义分区表。7.3 设计更友好的用户界面使用LVGL图形库相比直接使用TFT_eSPI绘图LVGL提供了更强大的控件按钮、滑块、列表、动画和主题系统能创建更复杂的UI。多页面管理设计状态机来管理不同的显示页面表盘、心率、天气、设置等。平滑动画对于指针式表盘或页面切换使用帧动画提升视觉体验。7.4 结构设计与外壳3D建模与打印使用Fusion 360、SolidWorks等软件为你的PCB和电池设计专属表壳。注意预留按钮孔、充电口和传感器开窗。防水考虑虽然DIY项目很难做到高等级防水但可以在屏幕和外壳间使用橡胶垫圈在接缝处涂抹防水胶来提升生活防水能力。佩戴舒适性选择柔软亲肤的表带合理分配内部元件重量避免头重脚轻。7.5 代码架构与维护模块化设计如本文示例将显示、传感器、网络等功能分离成独立的.h和.cpp文件提高代码可读性和可维护性。使用配置文件将Wi-Fi密码、API密钥等敏感信息放在单独的config.h文件中并加入.gitignore避免上传至公开仓库。版本控制使用Git管理你的代码清晰地记录每次功能添加和Bug修复。复刻一个ESP32多功能手表是一个综合性的嵌入式系统项目它挑战并融合了硬件设计、底层驱动、无线通信、电源管理和用户交互等多个领域的知识。从点亮第一块屏幕到成功读取第一个心率数据再到实现OTA升级每一步都充满成就感。希望这份详细的教程能为你扫清障碍成功打造出属于你自己的智能手表。过程中遇到的具体问题欢迎在社区交流分享。项目的精髓在于开源与共享当你完成自己的版本后不妨也将改进和心得回馈给社区让这个有趣的生态更加繁荣。
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻