【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 手表
大家好!在这个项目中,我将向大家展示我的作品:一款使用 ESP32 和 ST7789V 驱动器的 1.69 英寸 TFT-LCD 显示屏的 ESP 手表。它尺寸小巧,方便携带。我做这个项目是因为我的手表丢了,与其买新的,我觉得自己做一个更有趣。这样,我可以自由地设计和编程,添加任何我想要的功能。当时,智能手表对我来说非常酷,所以就把它分享出来。在我制作的这块手表上,您可以控制日期和时间,还可以控制亮度,甚至可以在里面显示图片。
【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: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 手表
## 步骤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 手表
## 步骤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 手表
## 步骤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 手表
## 步骤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
【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; }
【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表
我提供的代码包含一个来自 ESP32 核心的精密定时器和一个时钟。它可以控制亮度、显示图像,并允许您设置日期和时间。我还没有时间将 NTP 同步添加到时钟中,我可能会稍后添加它
我测试了时钟时间两周,漂移没有达到 1 秒。
我会把代码发布到我的 github 上
【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表
## 步骤6:组装之后你就可以组装 ESP 手表了,但记得先焊接电池,
确保电缆没有缠绕或被机箱内的任何东西阻挡
然后先把电池放进去
把侧面按钮
然后将 ESP Watch PCB 放在上面
然后用盒子的顶部将其关闭
我建议使用智能手机后门胶水,因为如果 ESP Watch 内部出现问题,您可以将其剥离。
然后戴上表带
【Arduino 动手做】采用 IPS 显示屏 ST7789V 的 ESP32 手表
## 第七步:问题与未来发展规划问题与未来发展规划
以下是我在PCB编程和焊接过程中遇到的一些问题:
该代码效率不够高,因为它会不断刷新显示,这意味着它会消耗更多的电量来保持运行。
ESP 手表没有过放电保护。即使电池电压达到 3.3V,电池也会耗尽。ESP 手表会持续消耗电量,导致电池电压降至 2.9V 的危险水平。为了防止这种情况发生,我们需要在 PCB 上添加电池保护电路。
输入按钮上没有电容,按下时会产生很大的噪音。以后我打算加个电容,并用一些代码来消除抖动。
还有一些插针挂在表壳外面。以后我会把它们拆下来,给 ESP 手表加一个简单的充电接口。
在时钟选择菜单中更新显示时出现问题。TFT 无法正确更新数字,导致数字一直悬空,直到我们切换到另一个菜单。
【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]