驴友花雕 发表于 6 天前

【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来制作各种有趣和有用的项目,如机器人、智能家居、艺术装置等。





驴友花雕 发表于 6 天前

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

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

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






驴友花雕 发表于 6 天前

【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 购买其中的一些组件。














驴友花雕 发表于 6 天前

【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。我们计划在上传代码后移除它们,并使用能够直接接触充电站引脚的裸铜线为手表充电。不过,目前我们先保留它们。











驴友花雕 发表于 6 天前

【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下载






驴友花雕 发表于 6 天前

【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 的原因。但如果你仍然想使用它,这不是问题。这只是我的偏好。









驴友花雕 发表于 6 天前

【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视图


























驴友花雕 发表于 6 天前

【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

这是用户设置的代码

// ST7789 240 x 280 显示屏,无芯片选择线
#define USER_SETUP_ID 203

#define ST7789_DRIVER // 配置所有寄存器

#define TFT_WIDTH 240
#define TFT_HEIGHT 280

#define CGRAM_OFFSET // 库将添加所需的偏移量

//#define TFT_RGB_ORDER TFT_RGB // 颜色顺序为红-绿-蓝
//#define TFT_RGB_ORDER TFT_BGR // 颜色顺序为蓝-绿-红

//#define TFT_INVERSION_ON
//#define TFT_INVERSION_OFF

// DSTIKE 升压//#
define TFT_DC 23
//#define TFT_RST 32
//#define TFT_MOSI 26
//#define TFT_SCLK 27

// 通用 ESP32 设置
#define TFT_MISO 19
#define TFT_MOSI 23
#define TFT_SCLK 18
#define TFT_CS 5 // 未连接
#define TFT_DC 17
#define TFT_RST 16 // 连接复位以确保显示初始化

#define LOAD_GLCD // 字体 1. 原始 Adafruit 8 像素字体需要~1820 字节 FLASH
#define LOAD_FONT2 // 字体 2. 小 16 像素高字体,需要~3534 字节 FLASH,96 个字符
#define LOAD_FONT4 // 字体 4. 中 26 像素高字体,需要~5848 字节 FLASH,96 个字符
#define LOAD_FONT6 // 字体 6. 大 48 像素字体,需要~2666 字节 FLASH,仅字符 1234567890:-.apm
#define LOAD_FONT7 // 字体 7. 7 段 48 像素字体,需要FLASH 中大约需要 2438 个字节,只有字符 1234567890:.
#define LOAD_FONT8 // 字体 8. 大型 75 像素字体需要 FLASH 中大约需要 3256 个字节,只有字符 1234567890:-.
//#define LOAD_FONT8N // 字体 8. 上述字体 8 的替代品,略窄,因此 3 位数字适合 160 像素 TFT
#define LOAD_GFXFF // FreeFonts.包括访问 48 种 Adafruit_GFX 免费字体 FF1 到 FF48 和自定义字体

#define SMOOTH_FONT


// #define SPI_FREQUENCY 27000000
#define SPI_FREQUENCY 40000000

#define SPI_READ_FREQUENCY 20000000

#define SPI_TOUCH_FREQUENCY 2500000

// #define SUPPORT_TRANSACTIONS

驴友花雕 发表于 6 天前

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

以下是主要代码

#include <Arduino.h>
#include "TFT_eSPI.h"
#include "MINGO.h"
#include "Flower_240x280.h"

#define BL 4
#define inLed 2

#define v_R 34

#define b_1 35
#define b_2 33
#define b_3 25
#define b_4 32

// TFT 设置
TFT_eSPI tft = TFT_eSPI(); // 调用自定义库
TFT_eSprite spritte = TFT_eSprite(&tft); // 创建 Sprite 对象“spritte”


// 菜单设置
int screenW = 240;
int screenH= 280;
int textWidth;
int x;

bool mainMenu = true ;
bool subMenu = false;
bool wait = false;
//SubMenu
const int menuCount = 4;
bool menuChange = true;
int selectedOption = 0;

const char* menuText = {"亮度","时间","设置","关于"};
// 时间设置菜单

// 页面处理程序
int page IRAM_ATTR = 0;
bool pageChange = true; // 在上一页完成加载之前,停止更改页面的代码
bool pageRefresh = true; // 清理页面

//时间
volatile int sec IRAM_ATTR= 50;
volatile int minutes IRAM_ATTR= 59;
volatile int hrs IRAM_ATTR= 23;
volatile int days IRAM_ATTR= 7;
volatile int months IRAM_ATTR= 4;
volatile int yrs IRAM_ATTR= 2024;

volatile int days_Max IRAM_ATTR;
volatile int daysOfWeekCount IRAM_ATTR;

const char* daysOfWeek[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
const char* monthsOfYear[] = {"NULL","January", "February", "March", "April", "May", "June", "July", "August", "September", "Octobor", "November", "December"};

const unsigned long interval = 1000;
//
// 输入
int 电压;
bool inp1,inp2,inp3,inp4;


// 亮度控制
int mapRange(int input, int input_start, int input_end, int output_start,int output_end) {
    return (input - input_start) * (output_end - output_start) / (input_end - input_start) + output_start;
}
int brighten = 80;
int Level = 4;

//时间菜单控制
int timeMenu_Select = 0;
const int timeMenu = 3;
bool timePage = false;

int clockSelect = 0;
    int dateSelect = 0;
//////////////////////////////////////////////////////////////////////////////////////////////
void input(void *pvParameters);
void v_Read(void *pvParameters);
void tft_page(void *pvParameters);
void onTimer(TimerHandle_t xTimer);


void setup()
{

    Serial.begin(115200);

pinMode(b_1,输入);pinMode(b_2,输入);pinMode(b_3,输入);pinMode(b_4,输入);pinMode(v_R,输入); //设置模式
pinMode(BL,输出);pinMode(inLed,输出);



//设置TFT
    tft.init();
    tft.setRotation(0);
    tft.setSwapBytes(true);
    tft.fillScreen(TFT_ORANGE);

      analogWrite(BL, 0);
      // for (int i = 0; i < 3; i++) {
      // digitalWrite(inLed, HIGH); // 打开LED
      // delay(500); // 等待500毫秒(0.5秒)
      // digitalWrite(inLed, LOW); // 关闭LED
      // delay(500); // 再等待 500 毫秒
      // }
      digitalWrite(inLed, HIGH);
      analogWrite(BL, brighten);

xTaskCreatePinnedToCore(input,"button", 1024, NULL, 1, NULL, 1); // 核心 1
xTaskCreatePinnedToCore(v_Read,"Voltage Read",1024, NULL, 5, NULL, 0);
xTaskCreatePinnedToCore(tft_page,"Page of TFT",20000, NULL, 2, NULL, 1);

TimerHandle_t timerHandle = xTimerCreate("timer", pdMS_TO_TICKS(interval), pdTRUE, 0, onTimer); // 名称、周期、自动重新加载、计时器 ID、回调
if (timerHandle != NULL) {
    xTimerStart(timerHandle, 0);
}

}

void loop() {


    //Serial.printf("%02d:%02d:%02d\n", hrs, minutes, sec);

    //Serial.print("months : ");
    //Serial.println(monthsOfYear);
    //Serial.print("day : ");
    //Serial.println(daysOfWeek);

    //按钮读取//
   // if (inp1 == HIGH ){
    //Serial.print("35");
    //}
    //页面读取//
    //Serial.print("PG: ");
    //Serial.println(page);
    //Serial.print("change: ");
    //Serial.println(pageChange);
    //Serial.print("Refresh: ");
    //Serial.打印(pageRefresh);
      延迟(1000);

}

void input(void *pvParameters){

while(1){
inp1=digitalRead(b_1);
inp2=digitalRead(b_2);
inp3=digitalRead(b_3);
inp4=digitalRead(b_4);

if(inp1==HIGH && pageChange ==true)
{
    pageChange=false;
    pageRefresh=true;
    page++; //下一页
}

if(inp2==HIGH && pageChange ==true)
{
    pageChange=false;
    pageRefresh=true;
    page--; //上一页
}

if(page ==1 && mainMenu == true&& inp3==HIGH && pageChange ==true && wait==false)//菜单选择控制
{
    pageRefresh=true;
   selectedOption = (selectedOption + 1) % menuCount;
   Serial.println(selectedOption);
      Serial.println("main");
}

if(inp4==HIGH && pageChange ==true && mainMenu == true && page == 1 && wait== false) //菜单进入控制
{
    wait = true;
    pageChange = false;
    mainMenu = false;
    subMenu= true;
    pageRefresh=true;

    Serial.print("INP4");

}

if(selectedOption == 1 && inp3==HIGH && subMenu==true && mainMenu==false&& wait==false) //时间设置菜单控制
{
   
    timePage =false;
    pageRefresh=true;
    //Serial.print("按下时钟菜单");
    timeMenu_Select = (timeMenu_Select + 1) % timeMenu ;
   //Serial.println(timeMenu_Select);
}

//页码处理程序
page>3?page=0:page<0?page=3:page=page;
vTaskDelay(200);
}
}


void tft_page(void *pvParameters)
{
while(1)
{
    if(page==0 && mainMenu == true) // main
    {
      if(pageRefresh==true){
          tft.fillScreen(TFT_BLACK);
          pageRefresh= false;
          Serial.print("refresh");
      }
      if(pageRefresh==false){
          //Page
            tft.setTextColor(TFT_WHITE, TFT_BLACK);
            // years //
            tft.setTextSize(3);
            tft.setCursor(85, 73); //xy
            tft.printf("%04d\n", yrs);
            // date/month //
            tft.setTextSize(3);
            tft.setCursor(75,101); //xy
            tft.printf("%02d/%02d\n", days, months);
            // 时间
            tft.setTextSize(4);
            tft.setCursor(28, 130); //xy
            tft.printf("%02d:%02d:%02d\n", hrs, minutes, sec);
            tft.setTextSize(3);
            // 天
            textWidth = tft.textWidth(daysOfWeek);
            x = (screenW - textWidth) / 2;
            tft.setCursor(x, 165); //xy
            tft.print(daysOfWeek);
            textWidth = tft.textWidth(monthsOfYear);
            // 月
            x = (screenW - textWidth) / 2;
            tft.setCursor(x, 193); //xy
            tft.打印(monthsOfYear);
            vTaskDelay(100);
            pageChange=true;
      }   
    }   
    else if(page==1 && mainMenu == true && wait==false) //菜单
    { wait ==true;
      if(pageRefresh==true){
          tft.fillScreen(TFT_BLACK);
          pageRefresh= false;
      }
      if(pageRefresh==false){
          //isi
             tft.setTextSize(2);
                const int menuItemWidth = 200; //每个菜单项的宽度
                const int menuItemHeight = 50; //每个菜单项的高度
                const int menuItemSpacing = 10; //菜单项之间的间距

                int totalMenuHeight = menuCount * (menuItemHeight + menuItemSpacing) - menuItemSpacing;
                int menuStartY = (tft.height() - totalMenuHeight) / 2;

                for (int i = 0; i < menuCount; i++) {
                  
                  int x = (tft.width() - menuItemWidth) / 2;
                  int y = menuStartY + i * (menuItemHeight + menuItemSpacing);

                  int xText = (screenW - tft.textWidth(menuText))/ 2;
                  

                  if (i == selectedOption) {
                  tft.fillRoundRect(x, y, menuItemWidth, menuItemHeight, 10, TFT_YELLOW);
                  tft.setTextColor(TFT_BLACK);
                  } else {
                  tft.drawRoundRect(x, y, menuItemWidth, menuItemHeight, 10, TFT_WHITE);
                  tft.setTextColor(TFT_WHITE);
                  }

                  tft.drawString(String(menuText) , xText, y + 15);
                     // Serial.println("wifiSub");
                }
            
            pageChange=true;
         
      }   
      vTaskDelay(100);
      wait== false;
    }
    else if(page==2 && mainMenu == true) //花
    {
      if(pageRefresh==true){
          tft.fillScreen(TFT_BLACK);
          pageRefresh= false;
      }
      if(pageRefresh==false){

          // isi
            tft.pushImage(0,0,screenW,screenH,Flower_240x280);
          vTaskDelay(100);
          pageChange=true;
      }   

    }
    else if(page==3 && mainMenu == true) //颜色
    {
      if(pageRefresh==true){
          tft.fillScreen(TFT_BLACK);
          pageRefresh= false;
      }
      if(pageRefresh==false){

          // isi
            tft.fillScreen(TFT_PINK);
          vTaskDelay(100);
          pageChange=true;
      }   

    }

    //子菜单
    if(mainMenu == false && subMenu ==true &&selectedOption == 0 ){ //亮度控制
      String text = "Brightness";
      if(pageRefresh==true){
          tft.fillScreen(TFT_BLACK);
          pageRefresh= false;
      }
      if(pageRefresh==false){
          //isi
            tft.setTextColor(TFT_WHITE, TFT_BLACK);
            tft.setTextSize(3);
            x = (screenW - tft.textWidth(text)) / 2;
            tft.setCursor(x,100);
            tft.print(text);
            tft.setTextSize(3);
            x = screenW/2;
            tft.setCursor(x,180);
            tft.print(Level);
            if(inp1 == HIGH){ //增加亮度
                  if(Level < 6){
                  Level++;
                  亮度 = mapRange(Level, 1, 6, 20 ,255);
                  analogWrite(BL, 亮度);
                  vTaskDelay(100);}}
                  
                  
            if(inp2 == HIGH){//降低亮度
                if(Level > 1){
                Level--;
                亮度 = mapRange(Level, 1, 6, 20 ,255);
                analogWrite(BL, 亮度);
                vTaskDelay(100);}}
                vTaskDelay(100);

            if(inp4 == HIGH && wait==false){//返回主
                串口。打印(“哎哟”);
             mainMenu=true;
             subMenu=false;
             pageChange=true;
             page=1;
             pageRefresh=true;
             vTaskDelay(100);
            }
            wait=false;
         
      }   
      
         
    }
    if(selectedOption == 1 && mainMenu == false && subMenu ==true ){ // 时间控制

            if(pageRefresh==true){
                  tft.fillScreen(TFT_BLACK);
            
                  pageRefresh= false;
                }
                if(pageRefresh==false){
                  timePage ==false;
                  // isi
                  const int timeMenuWidth = 180; // 每个菜单项的宽度
                  const int timeMenuHeight = 60; // 每个菜单项的高度
                  const int timeMenuSpacing = 10; // 菜单项之间的间距
                  
                  
                  String TimeMenuList={"Clock","Date","Back"};

                  int totalMenuHeight = timeMenu * (timeMenuHeight + timeMenuSpacing) - timeMenuSpacing;
                        int menuStartY = (tft.高度( - 总菜单高度)/ 2;

                        对于(int i = 0; i < timeMenu; i ++){
                        int x =(tft.width( - timeMenuWidth)/ 2;
                        int y = menuStartY + i *(timeMenuHeight + timeMenuSpacing);

                        如果(i == timeMenu_Select){
                            tft.fillRoundRect(x,y,timeMenuWidth,timeMenuHeight,10,TFT_YELLOW);
                            tft.setTextColor(TFT_BLACK);
                        } else {
                            tft.drawRoundRect(x,y,timeMenuWidth,timeMenuHeight,10,TFT_WHITE);
                            tft.setTextColor(TFT_WHITE);
                        }

                        int xText =(screenW - tft.textWidth(TimeMenuList ))/ 2;
                        tft.drawString(String(TimeMenuList ),xText,y + 15);
                            // Serial.println("wifiSub");
                        }
                  vTaskDelay(100);
                  if(inp4==HIGH && wait==false && timeMenu_Select==0){ // 到时间
                      wait=true;
                      subMenu=false;
                      pageRefresh=true;
                      timePage=true;
                      Serial.println("到时间");
                  }
                  if(inp4==HIGH && wait==false && timeMenu_Select==1){ // 到日期
                      wait=true;
                      subMenu=false;
                      pageRefresh=true;
                      timePage=true;
                      Serial.println("到日期");
                  }
                  if(inp4 == HIGH && wait==false && timeMenu_Select==2 ){ //返回主
                        Serial.print("Ouch");
                  mainMenu=true;
                  subMenu=false;
                  pageChange=true;
                  page=1;
                  pageRefresh=true;
                  vTaskDelay(100);
                      }
                  wait=false;
                  
                }
            }

    if(selectedOption == 2 && mainMenu == false && subMenu ==true ){ //设置
      
      if(pageRefresh==true){
            tft.fillScreen(TFT_BLACK);
            pageRefresh= false;
            }
            if(pageRefresh==false){
            // isi
                tft.pushImage(0,0,screenW,screenH,MINGO);
                vTaskDelay(100);
                if(inp4 == HIGH && wait==false){//返回主
                串口。打印(“哎哟”);
             mainMenu=true;
             subMenu=false;
             pageChange=true;
             page=1;
             pageRefresh=true;
             vTaskDelay(100);
            }
            wait=false;
            
            }   
    }
    if(selectedOption == 3 && mainMenu == false && subMenu ==true){//关于 esp32

          if(pageRefresh==true){
                tft.fillScreen(TFT_BLACK);
                pageRefresh=false;
            }
            if(pageRefresh==false){
                // isi
                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"};
                menuChange=false;
                tft.setTextSize(2);
                tft.setTextColor(TFT_WHITE, TFT_BLACK);
                textWidth = tft.textWidth(message);
                x = (screenW - textWidth) / 2;
                tft.setCursor(x, 27); //xy
                tft.print(message);

                textWidth = tft.textWidth(message);
                x = (屏幕宽 - textWidth) / 2;
                tft.setCursor(x, 45); //xy
                tft.print(message);

                textWidth = tft.textWidth(message);
                x = (屏幕宽 - textWidth) / 2;
                tft.setCursor(x, 62); //xy
                tft.print(message);

                textWidth = tft.textWidth(message);
                x = (屏幕宽 - textWidth) / 2;
                tft.setCursor(x, 79); //xy
                tft.print(message);

                textWidth = tft.textWidth(message);
                x = (屏幕宽 - textWidth) / 2;
                tft.setCursor(x, 95); //xy
                tft.print(message);

                textWidth = tft.textWidth(message);
                x = (屏幕宽 - textWidth) / 2;
                tft.setCursor(x, 112); //xy
                tft.print(message);

                textWidth = tft.textWidth(message);
                x = (屏幕宽 - textWidth) / 2;
                tft.setCursor(x, 128); //xy
                tft.print(message);

                textWidth = tft.文本宽度(消息);
                x =(屏幕宽 - 文本宽度)/ 2;
                tft.setCursor(x, 145); //xy
                tft.print(message);

                textWidth = tft.textWidth(message);
                x = (屏幕宽 - textWidth) / 2;
                tft.setCursor(x, 163); //xy
                tft.print(message);

                textWidth = tft.textWidth(message);
                x = (屏幕宽 - textWidth) / 2;
                tft.setCursor(x, 180); //xy
                tft.print(message);
               
                textWidth = tft.textWidth(message);
                x = (屏幕宽 - textWidth) / 2;
                tft.setCursor(x, 198); //xy
                tft.print(message);

                  vTaskDelay(100);
                  if(inp4 == HIGH && wait==false){ //返回主
                串口。打印("Ouch");
             mainMenu=true;
             subMenu=false;
             pageChange=true;
             page=1;
             pageRefresh=true;
             vTaskDelay(100);
            }
            wait=false;
               
            }   
    }

          //时钟设置页面
          if(timeMenu_Select==0 && mainMenu == false && timePage ==true ){ //设置

            if(pageRefresh==true){
                  tft.fillScreen(TFT_BLACK);
                  pageRefresh= false;
                }
                if(pageRefresh==false){
               
                  // isi
                  int menuColumnCount = 3;
                  int menuRowCount = 1;

                  const int menuItemSize = 60; //菜单项的较小尺寸
                  const int menuItemSpacing = 10; //菜单项之间的空间
                  const int menuItemCornerRadius = 5; //圆角半径
                  const int highlightsBorderWidth = 3; // 高亮边框的宽度

                  int clockMenuCount = menuColumnCount*menuRowCount;
                  

                  int clockNOW[] = {hrs,minute,sec};
                  
                      int totalMenuWidth = menuColumnCount * (menuItemSize + menuItemSpacing) - menuItemSpacing;
                      int totalMenuHeight = menuRowCount * (menuItemSize + menuItemSpacing) - menuItemSpacing;

                      int menuStartX = screenW/2 - totalMenuWidth / 2;
                      int menuStartY = screenH/2 - totalMenuHeight / 2;

                      for (int i = 0; i < clockMenuCount; i++) {
                        int row = i / menuColumnCount;
                        int col = i % menuColumnCount;

                        int x = menuStartX + col * (menuItemSize + menuItemSpacing);
                        int y = menuStartY + row * (menuItemSize + menuItemSpacing);

                        bool isSelected = (i == clockSelect);
                              tft.setTextSize(2);
                        if (isSelected) {
                        // 在菜单项周围绘制一个高亮圆角矩形轮廓
                        tft.drawRoundRect(x - highlightsBorderWidth, y - highlightsBorderWidth, menuItemSize + 2 * highlightsBorderWidth, menuItemSize + 2 * highlightsBorderWidth, menuItemCornerRadius + highlightsBorderWidth, TFT_ORANGE);
                        tft.setTextColor(TFT_ORANGE,TFT_BLACK);
                        
                        } else {
                        tft.drawRoundRect(x, y, menuItemSize, menuItemSize, menuItemCornerRadius, TFT_WHITE);
                        tft.setTextColor(TFT_WHITE,TFT_BLACK);
                        }

                  // 绘制菜单项徽标和文本
                      tft.drawString(String(clockNOW), x + 15, y + menuItemSize / 2 - 5);
                        }
                  vTaskDelay(200);
                  
                  if(inp1==HIGH){
                        Serial.println("HIGH 1");
                        if(clockSelect == 0){
                            hrs++;
                            if(hrs>23){
                              hrs=0;
                            }
                        }
                        if(clockSelect == 1){
                        minutes++;
                        if(minute > 59){
                            minutes=0;
                        }
                        }
                        if(clockSelect == 2){
                        sec=59;
                        
                        }
                  }
                  if(inp2==HIGH){
                        Serial.println("HIGH 2");
                        if(clockSelect == 0){
                            hrs--;
                            if(hrs<0){
                              hrs=23;
                            }
                        }
                        if(clockSelect == 1){
                        分钟--;
                            if(分钟 < 0){
                              分钟=59;
                            }
                        }
                        if(clockSelect == 2){
                        sec = 0;
                        }
                  }
                  if(inp3==HIGH && wait==false){
                              pageRefresh=true;
                              Serial.print("按下时钟选择");
                              clockSelect = (clockSelect + 1) % 3;
                              Serial.println(clockSelect);
                              vTaskDelay(100);
                  }
                  if(inp4 == HIGH && wait==false ){ //返回主
                        Serial.print("哎哟");
                  mainMenu=true;
                  subMenu=false;
                  timePage=false;
                  pageChange=true;
                  page=1;
                  pageRefresh=true;
                  vTaskDelay(100);
                      }
            
                  wait=false;
                  
                }   
      }

      if(timeMenu_Select==1 && mainMenu == false && timePage ==true ){ //设置
         
            if(pageRefresh==true){
                  tft.fillScreen(TFT_BLACK);
                  pageRefresh= false;
                }
                if(pageRefresh==false){
                   tft.setTextSize(3);
                const int menuItemWidth = 200; // 每个菜单项的宽度
                const int menuItemHeight = 50; // 每个菜单项的高度
                const int menuItemSpacing = 10; // 菜单项之间的间距

            

                const int dateOption = 3;
                int date_num[]={days,months,yrs};

                int totalMenuHeight = dateOption * (menuItemHeight + menuItemSpacing) - menuItemSpacing;
                int menuStartY = (tft.height() - totalMenuHeight) / 2;

                for (int i = 0; i < dateOption; i++) {
                  
                  int x = (tft.width() - menuItemWidth) / 2;
                  int y = menuStartY + i * (menuItemHeight + menuItemSpacing);

                  
                  

                  如果(i == dateSelect){
                  tft.fillRoundRect(x,y,menuItemWidth,menuItemHeight,10,TFT_YELLOW);
                  tft.setTextColor(TFT_BLACK);
                  } else {
                  tft.drawRoundRect(x,y,menuItemWidth,menuItemHeight,10,TFT_WHITE);
                  tft.setTextColor(TFT_WHITE);
                  }

                  tft.drawString(String(date_num ),x + 10,y + 15);
                     // Serial.println("wifiSub");
                }
                  vTaskDelay(200);
                  
                  wait=false;
                  if(inp1 ==HIGH&&wait==false){
                  if(dateSelect==0){

                      days++;
                      if(days>days_Max){
                        days=1;
                      }
                  }
                  if(dateSelect==1){
                      months++;
                      if(months>12){
                        months=1;
                      }
                  }
                  if(dateSelect==2){
                      yrs++;
                     
                  }
                  }
                  if(inp2 ==HIGH&&wait==false) {
                  if(dateSelect==0){
                      days--;
                      if(days < 1){
                        days=days_Max;
                      }
                  }
                  if(dateSelect==1){
                      months--;
                      if(months<1){
                        months=12;
                      }
                  }
                  if(dateSelect==2){
                      yrs--;
               
                      if (yrs <0){

                        yrs=9999;
                      }
                  }
                  }
                  if(inp3 ==HIGH &&wait==false){
                     pageRefresh=true;
                              Serial.print("按下日期选择");
                              dateSelect = (dateSelect + 1) % 3;
                              Serial.println(dateSelect);
                              vTaskDelay(100);
                  }
                  if(inp4 ==HIGH && wait==false){
                  Serial.print("哎哟");
                  mainMenu=true;
                  subMenu=false;
                  timePage
                  =false; pageChange=true
                  ; page=1;
                  pageRefresh=true;
                  vTaskDelay(100);
                  }
                  
                }   
      }

} void v_Read(void *pvParameters) {
while (1) {   uint32_t Vbatt = 0;   for(int i = 0; i < 16; i++) {







    Vbatt = Vbatt + analogReadMilliVolts(v_R); // 带校正的 ADC   
}
float Vbattf = 2 * Vbatt / 16 / 1000.0; // 衰减率 1/2,mV --> V
Serial.println(Vbattf, 3);
延迟(1000);
}


}
void onTimer(TimerHandle_t xTimer) {
    sec++;
   
    if (sec > 59) {
      sec = 0;
      分钟++;
      if (minute > 59) {
            分钟 = 0;
            hrs++;
            if (hrs > 23) {
                hrs = 0;
                days++;
                pageRefresh = true;
                if (months == 2) {
                        if (yrs % 4 == 0) {
                            days_Max = 29;
                        } else {
                            days_Max = 28;
                        }
                  } else if (months == 4 || months == 6 || months == 9 || months == 11) {
                        days_Max = 30;
                      } else {
                        days_Max = 31;
                      }
                if (days > days_Max) {
                  days = 1;   months
                  ++;
                  if (months > 12) {
                        months = 1;
                        yrs++;
                  }
                  
                }
               
            }
      }   daysOfWeekCount = (days + (13 * (months + 1)) / 5 + yrs % 100 + (yrs % 100) / 4 + (yrs / 100) / 4 + 5 * (yrs / 100)) % 7;   daysOfWeekCount = (daysOfWeekCount + 6) % 7; }

驴友花雕 发表于 6 天前

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

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

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

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

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


















驴友花雕 发表于 6 天前

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

## 步骤6:组装

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

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









驴友花雕 发表于 6 天前

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

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

问题与未来发展规划

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

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



驴友花雕 发表于 6 天前

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

附录
项目链接:https://www.instructables.com/ESP-WATCH-Using-IPS-Display-ST7789V/
项目作者:M_F_T
项目参考:https://www.youtube.com/watch?v=1Pp5RGtFSrU
https://www.youtube.com/watch?v=LUGhLd94f8A&t=348s
TFT_eSPI库:https://github.com/Bodmer/TFT_eSPI
ST7789V数据表链接:https://www.alldatasheet.com/html-pdf/1132511/SITRONIX/ST7789V/3171/7/ST7789V.html
JLCBPCB:https://jlcpcb.com/
3D文件:https://content.instructables.com/FQB/POFM/LWDJT48B/FQBPOFMLWDJT48B.stl
GBP文件: https://content.instructables.com/F2Y/N6Z9/LWEZ7Z65/F2YN6Z9LWEZ7Z65.gbp








页: [1]
查看完整版本: 【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表