100浏览
查看: 100|回复: 12

[项目] 【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表

[复制链接]
Arduino是一个开放源码的电子原型平台,它可以让你用简单的硬件和软件来创建各种互动的项目。Arduino的核心是一个微控制器板,它可以通过一系列的引脚来连接各种传感器、执行器、显示器等外部设备。Arduino的编程是基于C/C++语言的,你可以使用Arduino IDE(集成开发环境)来编写、编译和上传代码到Arduino板上。Arduino还有一个丰富的库和社区,你可以利用它们来扩展Arduino的功能和学习Arduino的知识。

Arduino的特点是:
1、开放源码:Arduino的硬件和软件都是开放源码的,你可以自由地修改、复制和分享它们。
2、易用:Arduino的硬件和软件都是为初学者和非专业人士设计的,你可以轻松地上手和使用它们。
3、便宜:Arduino的硬件和软件都是非常经济的,你可以用很低的成本来实现你的想法。
4、多样:Arduino有多种型号和版本,你可以根据你的需要和喜好来选择合适的Arduino板。
5、创新:Arduino可以让你用电子的方式来表达你的创意和想象,你可以用Arduino来制作各种有趣和有用的项目,如机器人、智能家居、艺术装置等。

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表图1

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表图2

驴友花雕  中级技神
 楼主|

发表于 昨天 08:57

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表

大家好!在这个项目中,我将向大家展示我的作品:一款使用 ESP32 和 ST7789V 驱动器的 1.69 英寸 TFT-LCD 显示屏的 ESP 手表。它尺寸小巧,方便携带。我做这个项目是因为我的手表丢了,与其买新的,我觉得自己做一个更有趣。这样,我可以自由地设计和编程,添加任何我想要的功能。当时,智能手表对我来说非常酷,所以就把它分享出来。

在我制作的这块手表上,您可以控制日期和时间,还可以控制亮度,甚至可以在里面显示图片。


【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表图1

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表图2

回复

使用道具 举报

驴友花雕  中级技神
 楼主|

发表于 昨天 08:58

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表

## 补给品

以下是我在项目中使用的项目列表

对于主电路
-ST7789V 240x280 Pxl 1.69 英寸全彩 TFT IPS 显示屏 (1x)
-ESP32-WROOM32-D 4MB 闪存 WiFi 模块(1x)(您也可以使用具有相同引脚排列的相同类型 ESP32)
-IRLML2502 MOSFET(1个)
-电感器 0620 或 0420 3,3uH-10uH (1x)
-STI3408B电压调节器
-锂聚合物电池 603040,750mAh-800mAh
-22毫米小米手表S1表带/表带

-电容器 0603:
100nF(2x)
22uf(1x)
10uf(1x)
2.2uf(1x)

-电阻器SMD 0603:
22 欧姆 (1x)
220 欧姆 (1x)
1K(1x)
10k(6倍)
22万(2倍)
440K(1倍)
- 引脚接头 2.54mm 1x4 (1x)
-微型 2 针触觉按钮 SMD 3x4x2 (2x)
侧键SMD触觉开关ROHS 3x6x 3x6x3.5

-一些杜邦线
-从 JLCPCB 订购的 PCB Watch
-烙铁、助焊剂和一些焊锡

充电
-MH-CD42充放电模块
-连接器-XH 2.54mm 1x2 针 (1x)
-Type-C 母头
-电阻器 SMD 0603 4.7K(2x)
-从JLCPCB订购的PCB充电站

注意:您也可以使用市场上任何可用的锂充电器模块,我个人使用它是因为我想制作充电站,它是现在可供我使用的充电站(嗯,这就是计划)

为了追逐
为了节省时间并获得详细的结果,在这种情况下,我使用树脂 3D 打印机,我使用的树脂是“来自 SUNLU 的 ABS 类树脂”。
您也可以使用任何普通的 FDM 3D 打印来完成这个项目,但是外壳设计会有点笨重,您需要设置公差。我稍后会进行设计然后在这里分享。
我使用的打印机是“Anycubic Photon Mono SE”
注意:我列出的所有部件可能在您当地的市场上买不到或者非常昂贵,我建议使用 Aliexpress 购买其中的一些组件。

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表图1

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表图2

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表图3

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表图5

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表图4

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表图6


回复

使用道具 举报

驴友花雕  中级技神
 楼主|

发表于 昨天 09:02

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表

## 步骤1:ESP Watch PCB设计

ESP手表PCB设计
ESP手表PCB设计
ESP手表PCB设计更多图片
这是我的手表的原理图和 PCB 设计,我使用 ESP32 WROOM 32D,您也可以使用其他类型,例如 WROOM 32U 或 32E。在制作 PCB 之前,我使用市场上的 ST7789V 和 GC9A01 模块和一些按钮制作了一个简单的原型。

设计
如果我们看一下 PCB 设计,就会发现 PCB 顶部有一个小切口,这是为了方便用户在发生错误或损坏时移除 ESP 模块。同时也是为了确保天线能够正常接收信号。

我使用的信号轨道宽度为0.254毫米。
我使用的 VCC 轨道宽度为 0.5 毫米。
铜区域为 GND(顶部和底部)

我从JLCBPCB订购了 PCB ,您只需 2 美元即可订购(不含运费)

这是接线
第23章
SCK-18
CS-5
DC-17
RST-16
背光 - 4
电池读数 - 34
按钮1 - 35
按钮2 - 32
按钮3 - 33
按钮4 - 25

ESP32
我使用的模块是 ESP32 WROOM 32D,它是一个具有蓝牙和 WiFi 功能的 SoC(片上系统),具有双核、低功耗、高速和成本效益(便宜)。就价格、小尺寸和高性能而言,乐鑫的这款芯片确实是该项目的完美解决方案。你可以看到我在示意图上放置的位置

ST7789V
我使用的 ST7789V 是一款全彩色 TFT-LCD,尺寸为 1.69 英寸,240x280 像素,逻辑门为 3.3V。它采用 12 针,可接收 8 位/16 位。

为了控制它,我使用来自 ESP 的 SPI,为了控制背光,我使用了一些 MOSFET。

IRLML2502 是一款 N 沟道 MOSFET(增强型),当栅极电压为 3.3V 时可完全打开,非常适合 ESP32,而且它采用小封装(SOT-23),可以驱动更大的电流。为了将 MOSFET 和 ESP32 引脚上的信号设置为低电平,我使用了 440KΩ 电阻。

为了保护背光 LED,我使用 22Ω 电阻

注意:为了保护您的 ESP32 免受来自 MOSFET 的电压反向电流的影响,请使用 1K 欧姆的 mosfet 到您的 ESP32 的信号引脚

有关更多详细信息,请参阅数据表链接:ST7789V

电压调节器
通常,在标准的 ESP32 DevKit 上,会使用 AMS1117 3.3V 稳压器,这在大多数情况下都适用。然而,由于 AMS1117 的压降较大,因此在使用单块锂聚合物电池时,它并不适用。当电池电压降至 3.5V 左右时,AMS1117 无法正常工作。因此,为了最大限度地利用电池,我使用了低压差 (LDO) 降压转换器,例如 STI3408B。

并且足够小,可以安装在 PCB 上。

STI3408B是一款 1.5MHz、1.2A 同步降压转换器,非常适合仅需单节锂电池供电的项目。它的输入范围为 1.2V 至 6V,并可降至 1.2V。关于噪声,到目前为止,我在各种项目中使用此转换器时尚未遇到任何问题。为了减轻潜在的噪声问题,我使用了比推荐值更大的电容和电感。

使用此 IC,即使输入从 4.2 变为 3.3,我们也可以将输出电压设置为始终 3.3V
要将输出设置为 3.3V,您需要在电感器之后的 IC 输出端创建一个分压器,并将其连接到 IC 的 FB 引脚。将一个1kΩ电阻从3.3V输出端(电感器之后)连接到反馈引脚,并将一个220Ω电阻从反馈引脚连接到地(GND)。这样,我们就可以为系统实现 3.3 V 输出。
更像 LX----->电感------>1K 欧姆 ---->FB<---- 220R 欧姆 <----GND
就像我给出的示意图一样
您可以查看典型应用或 IC 最低要求的数据表

电池读取
为了读取系统的电池电压,我使用 2 个220kΩ电阻创建了一个简单的分压器。分压器的输出连接到 ESP32 的 34 号引脚。使用分压器的原因是 ESP32 ADC 的输入电压最高只能达到 3.3V。通过使用分压器,电池电压会被降低到 ADC 可以承受的安全水平。然后将降低后的电压乘以系统代码中的相应系数,即可得到实际的电池电压。此设置用于监控电池,让您了解何时需要充电或 ESP32 何时应停止工作以防止深度放电。

我使用与“seeed studio”相同的系统来检查电池电压。
我认为它可能因其他 ESP32 ADC 而异

按钮
我的设计上有几个按钮,一些用于输入,一些用于对 ESP32 进行编程。

对于输入按钮,有四个按钮连接到 ESP GPIO 引脚 35、32、33 和 25。每个按钮都使用 10K 电阻下拉,因此逻辑为 0。按下按钮时,它会向相应的 ESP32 GPIO 引脚发送 HIGH 信号。
对于编程,ESP Devkit中有一个用于启用引脚的按钮和一个用于GPIO 0左右的BOOT按钮。功能与普通Devkit相同。
要使 ESP 进入上传程序模式,需要按下 Boot 按钮(GPIO 0),然后按下 EN 按钮,然后松开 EN 按钮,再松开 Boot 按钮(GPIO 0)

针头
如你所见,PCB 侧面有四个引脚。它们的作用是上传代码并为手表电池充电。最初,这被设计为一个临时解决方案,但现在似乎已经成为一个永久方案了(哈哈)。我现在通过这些引脚给手表充电。它们用于调试和上传代码,包括 RX、TX、5V+ 输入和 GND。我们计划在上传代码后移除它们,并使用能够直接接触充电站引脚的裸铜线为手表充电。不过,目前我们先保留它们。

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表图3

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表图2

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表图1

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表图4

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表图5

回复

使用道具 举报

驴友花雕  中级技神
 楼主|

发表于 昨天 09:05

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表

## 步骤2:PCB组装

设计并订购 PCB 后,您需要进行组装。您可以使用普通的手动烙铁,无需使用热风焊接工具。只需确保不要使 ESP32 模块过热即可。

首先,焊接稳压器 IC STI3408B(或降压转换器)以及稳压器的其他组件,例如电阻器、电感器和电容器。
然后,用万用表确认稳压器输出 3.3V。如果输出不是 3.3V,则可能是 IC 损坏,也可能是电阻位置错误,或者使用了错误的阻值(我遇到过几次这种情况),或者可能是由于焊接不干净导致短路。
确认输出 3.3V 后,放置并焊接 ESP32 模块。然后,焊接其余按钮。
接下来,焊接引脚接头,以便通过编写简单代码(例如 Serial.println("Hello world"))来检查 ESP32 是否正常工作。注意:请参阅步骤 5 了解如何编写代码。
一切确认无误后,焊接 MOSFET 和主 TFT-LCD 显示屏 ST7789V。这样,您的智能手表就可以进行编程了。
对于锂电池,您可以在完成编程和上传代码后再进行焊接。

如果您想直接焊接然后上传代码,那就没问题了,但我建议将 5V 输入与 ESP 手表断开,以防止电池过度充电。您只需将 RX、TX 和 GND 连接到 UART 即可。

附件
下载 {{ file.name }}Gerber_BottomPasteMaskLayer.GBP下载
下载 {{ file.name }}Gerber_底部丝网层.GBO下载
下载 {{ file.name }}Gerber_底部焊锡掩模层.GBS下载
下载 {{ file.name }}钻孔_NPTH_Through.DRL下载


【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表图2

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表图1

回复

使用道具 举报

驴友花雕  中级技神
 楼主|

发表于 昨天 09:07

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表

## 步骤3:充电站

注意:此充电站设计不是强制性的,您可以跳过此步骤,只需使用市场上任何可用的锂充电模块即可。

我使用 MH-CD42 作为充电站板。我设计了另一块 PCB 来与模块配对。为了方便使用,我添加了一个 Type-C 接口和一些引脚排列。

MH-CD42 是一款兼具充电和放电功能的电源管理板。它提供锂电池保护功能,包括过压、短路和过温保护。该板还配备 4 级 LED 指示灯。它持续提供 5V 输出,并可提供高达 2.1 安培的电流。此外,它还能以 2 安培的速率为电池充电。

在我为充电站设计的 PCB 上,我使用了 12 针的 Type-c,其中我将 CC1 和 CC2 连接到地面,因此我可以使用 Type-C 到 Type-C 电缆。

是的,这就是我开始使用它而不是传统的 TP4056 的原因。但如果你仍然想使用它,这不是问题。这只是我的偏好。

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表图3

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表图1

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表图2

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表图4

回复

使用道具 举报

驴友花雕  中级技神
 楼主|

发表于 昨天 10:25

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表

## 步骤4:案例设计和印刷

对于表壳设计,我首先使用 CorelDRAW 以二维形式绘制了 PCB、表带、表壳设计、布局和其他元素的整体尺寸。我用卡尺测量了所有部件,并估算出合适的尺寸,所有步骤均未添加公差。然后,我利用收集并记录(在本例中是输入)的数据,使用 SolidWorks 设计了 ​​3D 表壳,并添加了必要的公差。对于树脂 3D 打印,所需的公差约为 0.1 毫米至 0.15 毫米。设计完成后,我在 SolidWorks 中组装了各个部件,检查它们是否能够正确装配。我还设计了不带调试引脚的表壳。最后,我将设计转换为 STL 格式。

注意:对于使用FDM的3D打印,公差约为0.24毫米至0.34毫米,具体取决于设计和尺寸。我的3D打印机是Ender 3 V2,喷嘴直径为0.2毫米,因此公差足够。目前,我设计它用于树脂3D打印。

设计完所有必要的部件后,我使用荔枝切片树脂(Lychee Slicer Resin)进行打印准备。具体设置可以在上面上传的图片中看到。对于支撑,我将其设置为“中等”和“超高”密度。您无需遵循此配置;我之所以采用这个配置,是因为我不想在调整支撑上花费太多时间。然而,出现了一个问题,支撑太多了,当我尝试移除它们时,我的设计底座破裂了,如图所示。(我就是没那么有耐心,哈哈)。

你可以在 Youtube 上观看一些关于如何处理树脂印刷品的视频,不要像我一样。记住要小心谨慎地处理并保护它。

附件
下载 {{ file.name }}Assem1 - 主外壳调试-1.STL下载3D视图
下载 {{ file.name }}装配1-主外壳-1.STL下载3D视图
下载 {{ file.name }}Assem1-侧键-1.STL下载3D视图
下载 {{ file.name }}装配1-盖顶-1.STL下载3D视图

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表图1

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表图2

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表图3

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表图4

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表图5

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表图6

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表图7

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表图8

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表图10

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表图9

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表图12

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表图11


回复

使用道具 举报

驴友花雕  中级技神
 楼主|

发表于 昨天 10:28

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表

## 步骤5:编码

我使用 USB 到 UART CP2102 将程序上传到我的 ESP32 手表,您也可以使用我之前提到的其他 UART。

将 ESP Watch RX 连接到 USB UART TX,将 ESP Watch TX 连接到 USB RX。然后连接地面。

并确保已安装驱动程序。

我使用 VSCode

为了对 ESP 进行编码,我们使用Platform.io,我选择的主板是“uPesy ESP32 Wroom Devkit”,您也可以使用 Denky32,但我更喜欢使用它。

为了控制显示,我使用Bodmer的库TFT_eSPI,并在 user_setup 中复制并放置 ST7789 240x280,设置 ID 为 203

这是用户设置的代码

  1. // ST7789 240 x 280 显示屏,无芯片选择线
  2. #define USER_SETUP_ID 203
  3. #define ST7789_DRIVER // 配置所有寄存器
  4. #define TFT_WIDTH 240
  5. #define TFT_HEIGHT 280
  6. #define CGRAM_OFFSET // 库将添加所需的偏移量
  7. //#define TFT_RGB_ORDER TFT_RGB // 颜色顺序为红-绿-蓝
  8. //#define TFT_RGB_ORDER TFT_BGR // 颜色顺序为蓝-绿-红
  9. //#define TFT_INVERSION_ON
  10. //#define TFT_INVERSION_OFF
  11. // DSTIKE 升压//#
  12. define TFT_DC 23
  13. //#define TFT_RST 32
  14. //#define TFT_MOSI 26
  15. //#define TFT_SCLK 27
  16. // 通用 ESP32 设置
  17. #define TFT_MISO 19
  18. #define TFT_MOSI 23
  19. #define TFT_SCLK 18
  20. #define TFT_CS 5 // 未连接
  21. #define TFT_DC 17
  22. #define TFT_RST 16 // 连接复位以确保显示初始化
  23. #define LOAD_GLCD // 字体 1. 原始 Adafruit 8 像素字体需要~1820 字节 FLASH
  24. #define LOAD_FONT2 // 字体 2. 小 16 像素高字体,需要~3534 字节 FLASH,96 个字符
  25. #define LOAD_FONT4 // 字体 4. 中 26 像素高字体,需要~5848 字节 FLASH,96 个字符
  26. #define LOAD_FONT6 // 字体 6. 大 48 像素字体,需要~2666 字节 FLASH,仅字符 1234567890:-.apm
  27. #define LOAD_FONT7 // 字体 7. 7 段 48 像素字体,需要FLASH 中大约需要 2438 个字节,只有字符 1234567890:.
  28. #define LOAD_FONT8 // 字体 8. 大型 75 像素字体需要 FLASH 中大约需要 3256 个字节,只有字符 1234567890:-.
  29. //#define LOAD_FONT8N // 字体 8. 上述字体 8 的替代品,略窄,因此 3 位数字适合 160 像素 TFT
  30. #define LOAD_GFXFF // FreeFonts.包括访问 48 种 Adafruit_GFX 免费字体 FF1 到 FF48 和自定义字体
  31. #define SMOOTH_FONT
  32. // #define SPI_FREQUENCY 27000000
  33. #define SPI_FREQUENCY 40000000
  34. #define SPI_READ_FREQUENCY 20000000
  35. #define SPI_TOUCH_FREQUENCY 2500000
  36. // #define SUPPORT_TRANSACTIONS
复制代码


回复

使用道具 举报

驴友花雕  中级技神
 楼主|

发表于 昨天 10:29

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表

以下是主要代码

  1. #include <Arduino.h>
  2. #include "TFT_eSPI.h"
  3. #include "MINGO.h"
  4. #include "Flower_240x280.h"
  5. #define BL 4
  6. #define inLed 2
  7. #define v_R 34
  8. #define b_1 35
  9. #define b_2 33
  10. #define b_3 25
  11. #define b_4 32
  12. // TFT 设置
  13. TFT_eSPI tft = TFT_eSPI(); // 调用自定义库
  14. TFT_eSprite spritte = TFT_eSprite(&tft); // 创建 Sprite 对象“spritte”
  15. // 菜单设置
  16. int screenW = 240;
  17. int screenH= 280;
  18. int textWidth;
  19. int x;
  20. bool mainMenu = true ;
  21. bool subMenu = false;
  22. bool wait = false;
  23. //SubMenu
  24. const int menuCount = 4;
  25. bool menuChange = true;
  26. int selectedOption = 0;
  27. const char* menuText[menuCount] = {"亮度","时间","设置","关于"};
  28. // 时间设置菜单
  29. // 页面处理程序
  30. int page IRAM_ATTR = 0;
  31. bool pageChange = true; // 在上一页完成加载之前,停止更改页面的代码
  32. bool pageRefresh = true; // 清理页面
  33. //时间
  34. volatile int sec IRAM_ATTR= 50;
  35. volatile int minutes IRAM_ATTR= 59;
  36. volatile int hrs IRAM_ATTR= 23;
  37. volatile int days IRAM_ATTR= 7;
  38. volatile int months IRAM_ATTR= 4;
  39. volatile int yrs IRAM_ATTR= 2024;
  40. volatile int days_Max IRAM_ATTR;
  41. volatile int daysOfWeekCount IRAM_ATTR;
  42. const char* daysOfWeek[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
  43. const char* monthsOfYear[] = {"NULL","January", "February", "March", "April", "May", "June", "July", "August", "September", "Octobor", "November", "December"};
  44. const unsigned long interval = 1000;
  45. //
  46. // 输入
  47. int 电压;
  48. bool inp1,inp2,inp3,inp4;
  49. // 亮度控制
  50. int mapRange(int input, int input_start, int input_end, int output_start,int output_end) {
  51.     return (input - input_start) * (output_end - output_start) / (input_end - input_start) + output_start;
  52. }
  53. int brighten = 80;
  54. int Level = 4;
  55. //时间菜单控制
  56. int timeMenu_Select = 0;
  57. const int timeMenu = 3;
  58. bool timePage = false;
  59. int clockSelect = 0;
  60.     int dateSelect = 0;
  61. //////////////////////////////////////////////////////////////////////////////////////////////
  62. void input(void *pvParameters);
  63. void v_Read(void *pvParameters);
  64. void tft_page(void *pvParameters);
  65. void onTimer(TimerHandle_t xTimer);
  66. void setup()
  67. {
  68.     Serial.begin(115200);
  69.   pinMode(b_1,输入);pinMode(b_2,输入);pinMode(b_3,输入);pinMode(b_4,输入);pinMode(v_R,输入); //设置模式
  70.   pinMode(BL,输出);pinMode(inLed,输出);
  71.   
  72.   
  73.   //设置TFT
  74.     tft.init();
  75.     tft.setRotation(0);
  76.     tft.setSwapBytes(true);
  77.     tft.fillScreen(TFT_ORANGE);
  78.       analogWrite(BL, 0);
  79.         // for (int i = 0; i < 3; i++) {
  80.         // digitalWrite(inLed, HIGH); // 打开LED
  81.         // delay(500); // 等待500毫秒(0.5秒)
  82.         // digitalWrite(inLed, LOW); // 关闭LED
  83.         // delay(500); // 再等待 500 毫秒
  84.         // }
  85.       digitalWrite(inLed, HIGH);
  86.       analogWrite(BL, brighten);
  87.   xTaskCreatePinnedToCore(input,"button", 1024, NULL, 1, NULL, 1); // 核心 1
  88.   xTaskCreatePinnedToCore(v_Read,"Voltage Read",1024, NULL, 5, NULL, 0);
  89.   xTaskCreatePinnedToCore(tft_page,"Page of TFT",20000, NULL, 2, NULL, 1);
  90.   TimerHandle_t timerHandle = xTimerCreate("timer", pdMS_TO_TICKS(interval), pdTRUE, 0, onTimer); // 名称、周期、自动重新加载、计时器 ID、回调
  91.   if (timerHandle != NULL) {
  92.     xTimerStart(timerHandle, 0);
  93.   }
  94. }
  95. void loop() {
  96.   
  97.     //Serial.printf("%02d:%02d:%02d\n", hrs, minutes, sec);
  98.     //Serial.print("months : ");
  99.     //Serial.println(monthsOfYear[months]);
  100.     //Serial.print("day : ");
  101.     //Serial.println(daysOfWeek[days]);
  102.     //按钮读取//
  103.    // if (inp1 == HIGH ){
  104.     //Serial.print("35");
  105.     //}
  106.     //页面读取//
  107.     //Serial.print("PG: ");
  108.     //Serial.println(page);
  109.     //Serial.print("change: ");
  110.     //Serial.println(pageChange);
  111.     //Serial.print("Refresh: ");
  112.     //Serial.打印(pageRefresh);
  113.       延迟(1000);
  114. }
  115. void input(void *pvParameters){
  116.   
  117.   while(1){
  118.   inp1=digitalRead(b_1);
  119.   inp2=digitalRead(b_2);
  120.   inp3=digitalRead(b_3);
  121.   inp4=digitalRead(b_4);
  122.   if(inp1==HIGH && pageChange ==true)
  123.   {
  124.     pageChange=false;
  125.     pageRefresh=true;
  126.     page++; //下一页
  127.   }
  128.   
  129.   if(inp2==HIGH && pageChange ==true)
  130.   {
  131.     pageChange=false;
  132.     pageRefresh=true;
  133.     page--; //上一页
  134.   }
  135.   
  136.   if(page ==1 && mainMenu == true&& inp3==HIGH && pageChange ==true && wait==false)//菜单选择控制
  137.   {
  138.     pageRefresh=true;
  139.      selectedOption = (selectedOption + 1) % menuCount;
  140.      Serial.println(selectedOption);
  141.       Serial.println("main");
  142.   }
  143.   
  144.   if(inp4==HIGH && pageChange ==true && mainMenu == true && page == 1 && wait== false) //菜单进入控制
  145.   {
  146.     wait = true;
  147.     pageChange = false;
  148.     mainMenu = false;
  149.     subMenu= true;
  150.     pageRefresh=true;
  151.     Serial.print("INP4");
  152.   }
  153.   if(selectedOption == 1 && inp3==HIGH && subMenu==true && mainMenu==false&& wait==false) //时间设置菜单控制
  154.   {
  155.    
  156.     timePage =false;
  157.     pageRefresh=true;
  158.     //Serial.print("按下时钟菜单");
  159.     timeMenu_Select = (timeMenu_Select + 1) % timeMenu ;
  160.      //Serial.println(timeMenu_Select);
  161.   }
  162.   //页码处理程序
  163.   page>3?page=0:page<0?page=3:page=page;
  164.   vTaskDelay(200);
  165.   }
  166. }
  167. void tft_page(void *pvParameters)
  168. {
  169.   while(1)
  170.   {
  171.     if(page==0 && mainMenu == true) // main
  172.     {
  173.         if(pageRefresh==true){
  174.           tft.fillScreen(TFT_BLACK);
  175.           pageRefresh= false;
  176.           Serial.print("refresh");
  177.         }
  178.         if(pageRefresh==false){
  179.           //Page
  180.             tft.setTextColor(TFT_WHITE, TFT_BLACK);
  181.             // years //
  182.               tft.setTextSize(3);
  183.               tft.setCursor(85, 73); //xy
  184.               tft.printf("%04d\n", yrs);
  185.             // date/month //
  186.               tft.setTextSize(3);
  187.               tft.setCursor(75,101); //xy
  188.               tft.printf("%02d/%02d\n", days, months);
  189.             // 时间
  190.               tft.setTextSize(4);
  191.               tft.setCursor(28, 130); //xy
  192.               tft.printf("%02d:%02d:%02d\n", hrs, minutes, sec);
  193.               tft.setTextSize(3);
  194.             // 天
  195.               textWidth = tft.textWidth(daysOfWeek[daysOfWeekCount]);
  196.               x = (screenW - textWidth) / 2;
  197.               tft.setCursor(x, 165); //xy
  198.               tft.print(daysOfWeek[daysOfWeekCount]);
  199.               textWidth = tft.textWidth(monthsOfYear[months]);
  200.             // 月
  201.               x = (screenW - textWidth) / 2;
  202.               tft.setCursor(x, 193); //xy
  203.               tft.打印(monthsOfYear[months]);
  204.             vTaskDelay(100);
  205.             pageChange=true;
  206.         }   
  207.     }   
  208.     else if(page==1 && mainMenu == true && wait==false) //菜单
  209.     { wait ==true;
  210.         if(pageRefresh==true){
  211.           tft.fillScreen(TFT_BLACK);
  212.           pageRefresh= false;
  213.         }
  214.         if(pageRefresh==false){
  215.           //isi
  216.              tft.setTextSize(2);
  217.                 const int menuItemWidth = 200; //每个菜单项的宽度
  218.                 const int menuItemHeight = 50; //每个菜单项的高度
  219.                 const int menuItemSpacing = 10; //菜单项之间的间距
  220.                 int totalMenuHeight = menuCount * (menuItemHeight + menuItemSpacing) - menuItemSpacing;
  221.                 int menuStartY = (tft.height() - totalMenuHeight) / 2;
  222.                 for (int i = 0; i < menuCount; i++) {
  223.                   
  224.                   int x = (tft.width() - menuItemWidth) / 2;
  225.                   int y = menuStartY + i * (menuItemHeight + menuItemSpacing);
  226.                   int xText = (screenW - tft.textWidth(menuText[i]))/ 2;
  227.                   
  228.                   if (i == selectedOption) {
  229.                     tft.fillRoundRect(x, y, menuItemWidth, menuItemHeight, 10, TFT_YELLOW);
  230.                     tft.setTextColor(TFT_BLACK);
  231.                   } else {
  232.                     tft.drawRoundRect(x, y, menuItemWidth, menuItemHeight, 10, TFT_WHITE);
  233.                     tft.setTextColor(TFT_WHITE);
  234.                   }
  235.                   tft.drawString(String(menuText[i]) , xText, y + 15);
  236.                      // Serial.println("wifiSub");
  237.                 }
  238.             
  239.             pageChange=true;
  240.          
  241.         }   
  242.         vTaskDelay(100);
  243.       wait== false;
  244.     }
  245.     else if(page==2 && mainMenu == true) //花
  246.     {
  247.         if(pageRefresh==true){
  248.           tft.fillScreen(TFT_BLACK);
  249.           pageRefresh= false;
  250.         }
  251.         if(pageRefresh==false){
  252.           // isi
  253.             tft.pushImage(0,0,screenW,screenH,Flower_240x280);
  254.           vTaskDelay(100);
  255.           pageChange=true;
  256.         }   
  257.     }
  258.     else if(page==3 && mainMenu == true) //颜色
  259.     {
  260.         if(pageRefresh==true){
  261.           tft.fillScreen(TFT_BLACK);
  262.           pageRefresh= false;
  263.         }
  264.         if(pageRefresh==false){
  265.           // isi
  266.             tft.fillScreen(TFT_PINK);
  267.           vTaskDelay(100);
  268.           pageChange=true;
  269.         }   
  270.     }
  271.     //子菜单
  272.     if(mainMenu == false && subMenu ==true &&selectedOption == 0 ){ //亮度控制
  273.         String text = "Brightness";
  274.         if(pageRefresh==true){
  275.           tft.fillScreen(TFT_BLACK);
  276.           pageRefresh= false;
  277.         }
  278.         if(pageRefresh==false){
  279.           //isi
  280.             tft.setTextColor(TFT_WHITE, TFT_BLACK);
  281.             tft.setTextSize(3);
  282.             x = (screenW - tft.textWidth(text)) / 2;
  283.             tft.setCursor(x,100);
  284.             tft.print(text);
  285.             tft.setTextSize(3);
  286.             x = screenW/2;
  287.             tft.setCursor(x,180);
  288.             tft.print(Level);
  289.               if(inp1 == HIGH){ //增加亮度
  290.                   if(Level < 6){
  291.                   Level++;
  292.                   亮度 = mapRange(Level, 1, 6, 20 ,255);
  293.                   analogWrite(BL, 亮度);
  294.                   vTaskDelay(100);}}
  295.                   
  296.                   
  297.               if(inp2 == HIGH){//降低亮度
  298.                 if(Level > 1){
  299.                 Level--;
  300.                 亮度 = mapRange(Level, 1, 6, 20 ,255);
  301.                 analogWrite(BL, 亮度);
  302.                 vTaskDelay(100);}}
  303.                 vTaskDelay(100);
  304.               if(inp4 == HIGH && wait==false){//返回主
  305.                 串口。打印(“哎哟”);
  306.              mainMenu=true;
  307.              subMenu=false;
  308.              pageChange=true;
  309.              page=1;
  310.              pageRefresh=true;
  311.              vTaskDelay(100);
  312.               }
  313.               wait=false;
  314.            
  315.         }   
  316.         
  317.          
  318.     }
  319.     if(selectedOption == 1 && mainMenu == false && subMenu ==true ){ // 时间控制
  320.   
  321.             if(pageRefresh==true){
  322.                   tft.fillScreen(TFT_BLACK);
  323.               
  324.                   pageRefresh= false;
  325.                 }
  326.                 if(pageRefresh==false){
  327.                   timePage ==false;
  328.                   // isi
  329.                     const int timeMenuWidth = 180; // 每个菜单项的宽度
  330.                     const int timeMenuHeight = 60; // 每个菜单项的高度
  331.                     const int timeMenuSpacing = 10; // 菜单项之间的间距
  332.                     
  333.                     
  334.                     String TimeMenuList[timeMenu]={"Clock","Date","Back"};
  335.                     int totalMenuHeight = timeMenu * (timeMenuHeight + timeMenuSpacing) - timeMenuSpacing;
  336.                         int menuStartY = (tft.高度( - 总菜单高度)/ 2;
  337.                         对于(int i = 0; i < timeMenu; i ++){
  338.                           int x =(tft.width( - timeMenuWidth)/ 2;
  339.                           int y = menuStartY + i *(timeMenuHeight + timeMenuSpacing);
  340.                           如果(i == timeMenu_Select){
  341.                             tft.fillRoundRect(x,y,timeMenuWidth,timeMenuHeight,10,TFT_YELLOW);
  342.                             tft.setTextColor(TFT_BLACK);
  343.                           } else {
  344.                             tft.drawRoundRect(x,y,timeMenuWidth,timeMenuHeight,10,TFT_WHITE);
  345.                             tft.setTextColor(TFT_WHITE);
  346.                           }
  347.                           int xText =(screenW - tft.textWidth(TimeMenuList [i]))/ 2;
  348.                           tft.drawString(String(TimeMenuList [i]),xText,y + 15);
  349.                             // Serial.println("wifiSub");
  350.                         }
  351.                     vTaskDelay(100);
  352.                     if(inp4==HIGH && wait==false && timeMenu_Select==0){ // 到时间
  353.                       wait=true;
  354.                       subMenu=false;
  355.                       pageRefresh=true;
  356.                       timePage=true;
  357.                       Serial.println("到时间");
  358.                     }
  359.                     if(inp4==HIGH && wait==false && timeMenu_Select==1){ // 到日期
  360.                       wait=true;
  361.                       subMenu=false;
  362.                       pageRefresh=true;
  363.                       timePage=true;
  364.                       Serial.println("到日期");
  365.                     }
  366.                     if(inp4 == HIGH && wait==false && timeMenu_Select==2 ){ //返回主
  367.                         Serial.print("Ouch");
  368.                     mainMenu=true;
  369.                     subMenu=false;
  370.                     pageChange=true;
  371.                     page=1;
  372.                     pageRefresh=true;
  373.                     vTaskDelay(100);
  374.                       }
  375.                     wait=false;
  376.                     
  377.                 }
  378.             }
  379.     if(selectedOption == 2 && mainMenu == false && subMenu ==true ){ //设置
  380.       
  381.         if(pageRefresh==true){
  382.               tft.fillScreen(TFT_BLACK);
  383.               pageRefresh= false;
  384.             }
  385.             if(pageRefresh==false){
  386.               // isi
  387.                 tft.pushImage(0,0,screenW,screenH,MINGO);
  388.                 vTaskDelay(100);
  389.                 if(inp4 == HIGH && wait==false){//返回主
  390.                 串口。打印(“哎哟”);
  391.              mainMenu=true;
  392.              subMenu=false;
  393.              pageChange=true;
  394.              page=1;
  395.              pageRefresh=true;
  396.              vTaskDelay(100);
  397.               }
  398.               wait=false;
  399.               
  400.             }   
  401.     }
  402.     if(selectedOption == 3 && mainMenu == false && subMenu ==true){//关于 esp32
  403.           if(pageRefresh==true){
  404.                 tft.fillScreen(TFT_BLACK);
  405.                 pageRefresh=false;
  406.               }
  407.               if(pageRefresh==false){
  408.                 // isi
  409.                 const char *message[] = {"ESP32-WROOM","ESP32-D0WDQ6","Chip-v4.4.3","ID:DCC84E9EF0C8","Speed-240 Mhz","Flash:4.19 MB","F-Speed:80 Mhz","Flash Mode:0","F-Used:674.06 KB","Cores:2","RAM:256 KB"};
  410.                 menuChange=false;
  411.                 tft.setTextSize(2);
  412.                 tft.setTextColor(TFT_WHITE, TFT_BLACK);
  413.                 textWidth = tft.textWidth(message[0]);
  414.                 x = (screenW - textWidth) / 2;
  415.                 tft.setCursor(x, 27); //xy
  416.                 tft.print(message[0]);
  417.                 textWidth = tft.textWidth(message[1]);
  418.                 x = (屏幕宽 - textWidth) / 2;
  419.                 tft.setCursor(x, 45); //xy
  420.                 tft.print(message[1]);
  421.                 textWidth = tft.textWidth(message[2]);
  422.                 x = (屏幕宽 - textWidth) / 2;
  423.                 tft.setCursor(x, 62); //xy
  424.                 tft.print(message[2]);
  425.                 textWidth = tft.textWidth(message[3]);
  426.                 x = (屏幕宽 - textWidth) / 2;
  427.                 tft.setCursor(x, 79); //xy
  428.                 tft.print(message[3]);
  429.                 textWidth = tft.textWidth(message[4]);
  430.                 x = (屏幕宽 - textWidth) / 2;
  431.                 tft.setCursor(x, 95); //xy
  432.                 tft.print(message[4]);
  433.                 textWidth = tft.textWidth(message[5]);
  434.                 x = (屏幕宽 - textWidth) / 2;
  435.                 tft.setCursor(x, 112); //xy
  436.                 tft.print(message[5]);
  437.                 textWidth = tft.textWidth(message[6]);
  438.                 x = (屏幕宽 - textWidth) / 2;
  439.                 tft.setCursor(x, 128); //xy
  440.                 tft.print(message[6]);
  441.                 textWidth = tft.文本宽度(消息[7]);
  442.                 x =(屏幕宽 - 文本宽度)/ 2;
  443.                 tft.setCursor(x, 145); //xy
  444.                 tft.print(message[7]);
  445.                 textWidth = tft.textWidth(message[8]);
  446.                 x = (屏幕宽 - textWidth) / 2;
  447.                 tft.setCursor(x, 163); //xy
  448.                 tft.print(message[8]);
  449.                 textWidth = tft.textWidth(message[9]);
  450.                 x = (屏幕宽 - textWidth) / 2;
  451.                 tft.setCursor(x, 180); //xy
  452.                 tft.print(message[9]);
  453.                
  454.                 textWidth = tft.textWidth(message[10]);
  455.                 x = (屏幕宽 - textWidth) / 2;
  456.                 tft.setCursor(x, 198); //xy
  457.                 tft.print(message[10]);
  458.                   vTaskDelay(100);
  459.                   if(inp4 == HIGH && wait==false){ //返回主
  460.                 串口。打印("Ouch");
  461.              mainMenu=true;
  462.              subMenu=false;
  463.              pageChange=true;
  464.              page=1;
  465.              pageRefresh=true;
  466.              vTaskDelay(100);
  467.               }
  468.               wait=false;
  469.                
  470.               }   
  471.     }
  472.           //时钟设置页面
  473.           if(timeMenu_Select==0 && mainMenu == false && timePage ==true ){ //设置
  474.             if(pageRefresh==true){
  475.                   tft.fillScreen(TFT_BLACK);
  476.                   pageRefresh= false;
  477.                 }
  478.                 if(pageRefresh==false){
  479.                  
  480.                   // isi
  481.                   int menuColumnCount = 3;
  482.                   int menuRowCount = 1;
  483.                   const int menuItemSize = 60; //菜单项的较小尺寸
  484.                   const int menuItemSpacing = 10; //菜单项之间的空间
  485.                   const int menuItemCornerRadius = 5; //圆角半径
  486.                   const int highlightsBorderWidth = 3; // 高亮边框的宽度
  487.                   int clockMenuCount = menuColumnCount*menuRowCount;
  488.                   
  489.                     int clockNOW[] = {hrs,minute,sec};
  490.                   
  491.                       int totalMenuWidth = menuColumnCount * (menuItemSize + menuItemSpacing) - menuItemSpacing;
  492.                       int totalMenuHeight = menuRowCount * (menuItemSize + menuItemSpacing) - menuItemSpacing;
  493.                       int menuStartX = screenW/2 - totalMenuWidth / 2;
  494.                       int menuStartY = screenH/2 - totalMenuHeight / 2;
  495.                       for (int i = 0; i < clockMenuCount; i++) {
  496.                         int row = i / menuColumnCount;
  497.                         int col = i % menuColumnCount;
  498.                         int x = menuStartX + col * (menuItemSize + menuItemSpacing);
  499.                         int y = menuStartY + row * (menuItemSize + menuItemSpacing);
  500.                         bool isSelected = (i == clockSelect);
  501.                               tft.setTextSize(2);
  502.                         if (isSelected) {
  503.                           // 在菜单项周围绘制一个高亮圆角矩形轮廓
  504.                           tft.drawRoundRect(x - highlightsBorderWidth, y - highlightsBorderWidth, menuItemSize + 2 * highlightsBorderWidth, menuItemSize + 2 * highlightsBorderWidth, menuItemCornerRadius + highlightsBorderWidth, TFT_ORANGE);
  505.                           tft.setTextColor(TFT_ORANGE,TFT_BLACK);
  506.                           
  507.                         } else {
  508.                           tft.drawRoundRect(x, y, menuItemSize, menuItemSize, menuItemCornerRadius, TFT_WHITE);
  509.                           tft.setTextColor(TFT_WHITE,TFT_BLACK);
  510.                         }
  511.                     // 绘制菜单项徽标和文本
  512.                       tft.drawString(String(clockNOW[i]), x + 15, y + menuItemSize / 2 - 5);
  513.                           }
  514.                     vTaskDelay(200);
  515.                     
  516.                     if(inp1==HIGH){
  517.                         Serial.println("HIGH 1");
  518.                         if(clockSelect == 0){
  519.                             hrs++;
  520.                             if(hrs>23){
  521.                               hrs=0;
  522.                             }
  523.                         }
  524.                         if(clockSelect == 1){
  525.                           minutes++;
  526.                           if(minute > 59){
  527.                             minutes=0;
  528.                           }
  529.                         }
  530.                         if(clockSelect == 2){
  531.                           sec=59;
  532.                         
  533.                         }
  534.                     }
  535.                     if(inp2==HIGH){
  536.                         Serial.println("HIGH 2");
  537.                         if(clockSelect == 0){
  538.                             hrs--;
  539.                             if(hrs<0){
  540.                               hrs=23;
  541.                             }
  542.                         }
  543.                         if(clockSelect == 1){
  544.                           分钟--;
  545.                             if(分钟 < 0){
  546.                                 分钟=59;
  547.                             }
  548.                         }
  549.                         if(clockSelect == 2){
  550.                           sec = 0;
  551.                         }
  552.                     }
  553.                     if(inp3==HIGH && wait==false){
  554.                                 pageRefresh=true;
  555.                                 Serial.print("按下时钟选择");
  556.                                 clockSelect = (clockSelect + 1) % 3;
  557.                                 Serial.println(clockSelect);
  558.                                 vTaskDelay(100);
  559.                     }
  560.                     if(inp4 == HIGH && wait==false ){ //返回主
  561.                         Serial.print("哎哟");
  562.                     mainMenu=true;
  563.                     subMenu=false;
  564.                     timePage=false;
  565.                     pageChange=true;
  566.                     page=1;
  567.                     pageRefresh=true;
  568.                     vTaskDelay(100);
  569.                       }
  570.             
  571.                   wait=false;
  572.                   
  573.                 }   
  574.         }
  575.         if(timeMenu_Select==1 && mainMenu == false && timePage ==true ){ //设置
  576.          
  577.             if(pageRefresh==true){
  578.                   tft.fillScreen(TFT_BLACK);
  579.                   pageRefresh= false;
  580.                 }
  581.                 if(pageRefresh==false){
  582.                    tft.setTextSize(3);
  583.                 const int menuItemWidth = 200; // 每个菜单项的宽度
  584.                 const int menuItemHeight = 50; // 每个菜单项的高度
  585.                 const int menuItemSpacing = 10; // 菜单项之间的间距
  586.             
  587.                 const int dateOption = 3;
  588.                 int date_num[]={days,months,yrs};
  589.                 int totalMenuHeight = dateOption * (menuItemHeight + menuItemSpacing) - menuItemSpacing;
  590.                 int menuStartY = (tft.height() - totalMenuHeight) / 2;
  591.                 for (int i = 0; i < dateOption; i++) {
  592.                   
  593.                   int x = (tft.width() - menuItemWidth) / 2;
  594.                   int y = menuStartY + i * (menuItemHeight + menuItemSpacing);
  595.                   
  596.                   
  597.                   如果(i == dateSelect){
  598.                     tft.fillRoundRect(x,y,menuItemWidth,menuItemHeight,10,TFT_YELLOW);
  599.                     tft.setTextColor(TFT_BLACK);
  600.                   } else {
  601.                     tft.drawRoundRect(x,y,menuItemWidth,menuItemHeight,10,TFT_WHITE);
  602.                     tft.setTextColor(TFT_WHITE);
  603.                   }
  604.                   tft.drawString(String(date_num [i]),x + 10,y + 15);
  605.                      // Serial.println("wifiSub");
  606.                 }
  607.                     vTaskDelay(200);
  608.                     
  609.                   wait=false;
  610.                   if(inp1 ==HIGH&&wait==false){
  611.                     if(dateSelect==0){
  612.                       days++;
  613.                       if(days>days_Max){
  614.                         days=1;
  615.                       }
  616.                     }
  617.                     if(dateSelect==1){
  618.                       months++;
  619.                       if(months>12){
  620.                         months=1;
  621.                       }
  622.                     }
  623.                     if(dateSelect==2){
  624.                       yrs++;
  625.                      
  626.                     }
  627.                   }
  628.                   if(inp2 ==HIGH&&wait==false) {
  629.                     if(dateSelect==0){
  630.                       days--;
  631.                       if(days < 1){
  632.                         days=days_Max;
  633.                       }
  634.                     }
  635.                     if(dateSelect==1){
  636.                       months--;
  637.                       if(months<1){
  638.                         months=12;
  639.                       }
  640.                     }
  641.                     if(dateSelect==2){
  642.                       yrs--;
  643.                  
  644.                       if (yrs <0){
  645.                         yrs=9999;
  646.                       }
  647.                     }
  648.                   }
  649.                   if(inp3 ==HIGH &&wait==false){
  650.                        pageRefresh=true;
  651.                                 Serial.print("按下日期选择");
  652.                                 dateSelect = (dateSelect + 1) % 3;
  653.                                 Serial.println(dateSelect);
  654.                                 vTaskDelay(100);
  655.                   }
  656.                   if(inp4 ==HIGH && wait==false){
  657.                     Serial.print("哎哟");
  658.                     mainMenu=true;
  659.                     subMenu=false;
  660.                     timePage
  661.                     =false; pageChange=true
  662.                     ; page=1;
  663.                     pageRefresh=true;
  664.                     vTaskDelay(100);
  665.                   }
  666.                   
  667.                 }   
  668.         }
  669.   } void v_Read(void *pvParameters) {
  670. while (1) {   uint32_t Vbatt = 0;   for(int i = 0; i < 16; i++) {
  671.     Vbatt = Vbatt + analogReadMilliVolts(v_R); // 带校正的 ADC   
  672.   }
  673.   float Vbattf = 2 * Vbatt / 16 / 1000.0; // 衰减率 1/2,mV --> V
  674.   Serial.println(Vbattf, 3);
  675.   延迟(1000);
  676. }
  677. }
  678. void onTimer(TimerHandle_t xTimer) {
  679.     sec++;
  680.    
  681.     if (sec > 59) {
  682.         sec = 0;
  683.         分钟++;
  684.         if (minute > 59) {
  685.             分钟 = 0;
  686.             hrs++;
  687.             if (hrs > 23) {
  688.                 hrs = 0;
  689.                 days++;
  690.                 pageRefresh = true;
  691.                 if (months == 2) {
  692.                         if (yrs % 4 == 0) {
  693.                             days_Max = 29;
  694.                         } else {
  695.                             days_Max = 28;
  696.                         }
  697.                     } else if (months == 4 || months == 6 || months == 9 || months == 11) {
  698.                         days_Max = 30;
  699.                       } else {
  700.                         days_Max = 31;
  701.                       }
  702.                 if (days > days_Max) {
  703.                     days = 1;     months
  704.                     ++;
  705.                     if (months > 12) {
  706.                         months = 1;
  707.                         yrs++;
  708.                     }
  709.                     
  710.                 }
  711.                
  712.             }
  713.         }     daysOfWeekCount = (days + (13 * (months + 1)) / 5 + yrs % 100 + (yrs % 100) / 4 + (yrs / 100) / 4 + 5 * (yrs / 100)) % 7;     daysOfWeekCount = (daysOfWeekCount + 6) % 7; }
复制代码


回复

使用道具 举报

驴友花雕  中级技神
 楼主|

发表于 昨天 10:34

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表

我提供的代码包含一个来自 ESP32 核心的精密定时器和一个时钟。它可以控制亮度、显示图像,并允许您设置日期和时间。

我还没有时间将 NTP 同步添加到时钟中,我可能会稍后添加它

我测试了时钟时间两周,漂移没有达到 1 秒。

我会把代码发布到我的 github 上

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表图1

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表图2

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表图3

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表图4

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表图5

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表图6

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表图7

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表图8


回复

使用道具 举报

驴友花雕  中级技神
 楼主|

发表于 昨天 10:36

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表

## 步骤6:组装

之后你就可以组装 ESP 手表了,但记得先焊接电池,

确保电缆没有缠绕或被机箱内的任何东西阻挡
然后先把电池放进去
把侧面按钮
然后将 ESP Watch PCB 放在上面
然后用盒子的顶部将其关闭
我建议使用智能手机后门胶水,因为如果 ESP Watch 内部出现问题,您可以将其剥离。
然后戴上表带

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表图1

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表图2

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表图3

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表图4

回复

使用道具 举报

驴友花雕  中级技神
 楼主|

发表于 昨天 10:38

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表

## 第七步:问题与未来发展规划

问题与未来发展规划

以下是我在PCB编程和焊接过程中遇到的一些问题:

该代码效率不够高,因为它会不断刷新显示,这意味着它会消耗更多的电量来保持运行。
ESP 手表没有过放电保护。即使电池电压达到 3.3V,电池也会耗尽。ESP 手表会持续消耗电量,导致电池电压降至 2.9V 的危险水平。为了防止这种情况发生,我们需要在 PCB 上添加电池保护电路。
输入按钮上没有电容,按下时会产生很大的噪音。以后我打算加个电容,并用一些代码来消除抖动。
还有一些插针挂在表壳外面。以后我会把它们拆下来,给 ESP 手表加一个简单的充电接口。
在时钟选择菜单中更新显示时出现问题。TFT 无法正确更新数字,导致数字一直悬空,直到我们切换到另一个菜单。

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表图1

回复

使用道具 举报

驴友花雕  中级技神
 楼主|

发表于 昨天 10:52

【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4 备案 沪公网安备31011502402448

© 2013-2025 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail