具有多个表盘、心率传感器、指南针和游戏的 DIY 智能手表

news/2024/12/2 13:46:20/

在此,我们将使用所学到的知识,结合使用硬件和软件组件从头开始创建自己的智能手表。在项目的这一部分,您将被指导完成组装硬件组件、设置软件以及配置智能手表的设置和功能的过程。到本项目结束时,您将拥有一款功能齐全的智能手表,您可以每天佩戴和使用。因此,我们在这里开始构建您自己的智能手表

这个完整的项目是由 PCBWAY 提供的紧凑型 PCB 板实现的。在这个项目中,我们还将向您展示如何向他们下订单,以将您的 PCB 板送到您家门口。

ESP32 智能手表功能

  • 1.69 英寸 IPS TFT 显示屏,分辨率为 280x240 像素
  • 单按钮控制
  • 深度睡眠省电模式
  • 使用加速度计自动唤醒
  • 使用环境光传感器自动调节亮度
  • 用于导航的数字罗盘
  • 心率监测器
  • 多个看起来很酷的表盘
  • 直观的菜单系统
  • 内置娱乐游戏
  • Micro SD 卡
  • 振动电机
  • 具有深度放电保护的电池充电能力

构建 ESP32 智能手表所需的组件

下面列出了构建智能手表所需的所有部件。每个元件的确切值可以在原理图或 BOM 中找到。

  • TTGO Micro-32 V2.0 x1
  • IPS6404L PSRAM IC x1
  • 1.69 英寸 TFT 显示屏,带 ST7789V 控制器 x1
  • MAX809T 3V 电源监控器复位控制器 x1
  • CP2102 USB UART 控制器 x1
  • MCP7383 1S 电池充电器 x1
  • DW01 电池保护IC x1
  • FS8205A MOSFET x1
  • NCP167AMX330TBG 3.3V LDO x1
  • XC6202P182MR 1.8V LDO x1
  • MPU6050 加速度计 IC x1
  • HMC5883L 磁力计传感器 IC x1
  • LSM303DLHC 加速度计磁力计 IC(可选,替代 MPU6050 和 HMC5883L)x1
  • MAX30102 心率传感器 x1
  • BH1750FVI 环境光传感器 x1
  • S8050 SOT-23 NPN 晶体管 x3
  • Micro SD 插槽 x1
  • 10mm 震动马达 x1
  • 1N5819 贴片二极管 x2
  • LED5D5 TVS ESD 保护二极管(可选) x3
  • 微型 USB 端口 x1
  • 0.5mm 间距 10 针 FPC 连接器 x2
  • SMD 电阻器
  • SMD 电容器
  • 印刷电路板
  • 其他工具和杂项

ESP32 智能手表完整电路图

ESP32 智能手表的完整电路图如下所示。它也可以从最后给出的链接以 PDF 格式下载。

6b79630165d94bfbac295ec218505d71.png

让我们逐节讨论 Schematics 以便更好地理解。micro-USB 端口用于充电和编程目的。micro-USB 端口的电源和数据连接连接到 TVS ESD 保护二极管。这些二极管将保护整个电路免受 USB 输入上的任何 ESD 尖峰的影响。然后将来自 USB 端口的 5V 连接到MCP7383 1S 锂离子电池充电器的输入端。然后,从充电 IC 输出到围绕 DW01 IC 和 FS8205 MOSFET 构建的保护电路。这种保护电路组合将保护电池免受过流放电和深度放电的影响。

 

377901d6b230da365d1b4a287e5a761a.jpeg

然后,电源通过两个 LDO。电路中使用的主要稳压器是 ON Semi 的 NCP167AMX330TBG。它可以提供 700mA 的最大电流。使用这种芯片的主要优点是尺寸。NCP167AMX采用 1mmx1mm 4-XDFN 封装。这节省了大量空间。电路中的第二个低电压稳压器是 XC620P182MR-G 1.8V LDO。该 LDO 用于 MAX30102 心率传感器芯片。

 

ca9880af97d949d969d59a5b5f510286.jpeg

USB UART 控制器的下一部分。本部分围绕 Silicon Labs 的CP2102N设计。它支持最高 12Mbps 的速度。最少数量的外部组件以及小型 QFN-24 封装使其成为同类别其他控制器芯片的更好选择。ESP32 的自动复位电路围绕两个 S8050 NPN 晶体管构建。晶体管连接到 CP2102 的 DTR 和 RTS 引脚以及 ESP32 的 EN 和 RST 引脚。这使我们能够对 ESP32 进行编程,而无需重置按钮。

 

01624ef5250f3893ba4d3fa730b51530.png

MPU6050 加速度计芯片用于检测运动。此功能使我们能够通过简单的手部动作唤醒智能手表MPU6050 的中断引脚连接到 ESP14 控制器的 GPIO32。当检测到超过设定阈值的运动时,MPU6050 将向 ESP32 发送中断信号,将其从深度睡眠中唤醒。

 

630633138275c17cd30d55585569a68b.jpeg

下一个传感器是 HMC5883 磁力计传感器。此传感器用于实现数字罗盘功能。使用此传感器时,请确保附近没有磁干扰或任何金属,这可能会产生错误的读数。

 

c34708a20403573230b5c42f034625b3.jpeg

在 PCB 中我们还为 LSM303 芯片预留了空间,它结合了加速度计和磁力计传感器。这个传感器包含在内,以防万一我们不想使用 MPU6050 和 HMC5883L。它是一个保留组件。如果您使用的是 MPU6050-HMC5883 组合,则不必填充它。

 

7003d92766545bc12c95a700b1b6d872.jpeg

接下来,我们有 BH1750 环境光传感器。该传感器用于实现自动亮度控制。该传感器位于 TFT 显示屏下方的正面。外壳上设有一个小孔,用于测量环境光。如果开启自动亮度调节,MCU 将从 BH1750 读取环境光数据,并相应地调整显示背光。

 

b320d5125052b0dc88cb7ecbef0d8c56.jpeg

为了测量心率,我们使用了 Maxim Integrated 的 MAX30102。该传感器在 1.8v 电源电压下工作,并且能够使用光传感器检测心率。该代码的调整方式是,当手表放置在手腕或手指以外的表面时,芯片不会误触发。

 

45e2b97da92965f6794e85ce8abec662.jpeg

我们还在 PCB 中包括一个 micro-SD 插槽和一个振动电机,以用于未来的发展。目前,这些未在代码中配置或使用。micro-SD 与 TFT 显示器共享相同的 SPI ba。它可用于存储固件文件、监控日志甚至表盘数据或图像等数据。振动电机使用 S8050 NPN 晶体管进行控制。电机两端还连接了一个续流二极管,以保护电路免受任何电压尖峰的影响。

 

11107634f45b91afc1716b84fe11cbb6.jpeg

对于显示器,我们使用了圆角的 1.69 英寸显示器。这些 IPS 显示屏提供了非常好的显示对比度和色彩饱和度。此显示器使用 ST7789 显示驱动程序。ST7789 可支持高达 100MHz 的 SPI 总线频率。这将使我们能够更快地驱动显示器,提供更好的 FPS。背光使用 N 沟道 MOSFET 进行控制。PWM 用于控制亮度。

 

aa1f4dca594abf6e397ee4f0f8b2d15b.jpeg

该项目的核心是 LILYGO 的 TTGO Micro-32 V2.0 模块。它基于 ESP32-PICO D4 SIP,集成了 ESP32 SoC、晶体振荡器、滤波电容器、射频匹配链路和 4MB 闪存,采用 7mm × 7mm QFN 封装。我们还将 IPS6404L PSRAM 与模块一起使用。MAX809T MPU 管理芯片用于确保 ESP32-PICO-D4 在冷启动期间重启。该芯片将使 ESP32 保持处于复位状态,直到达到阈值电压。一旦达到阈值电压,MAX809T(3V 重置阈值)将重置 ESP32 并将使能引脚钳位到 VCC。

 

89e70c0d7fbd8b310a90626eba164ef5.jpeg

ESP32 智能手表 PCB

对于 PCB,我们选择了两板设计。顶板包含 MCU 以及显示器、UART 控制器、电源电路、光传感器和 MPU6050 芯片。底部凹槽包含 HMC5883LSM303MAX30102、microSD 插槽和振动电机。这两块板使用间距为 10.0mm 的 5 针 FPC 电缆连接。

 

10c2135601a4bfd93626e51eff76663e.jpeg

这是两个板的 3D 视图

 

0f53625fe23bab66ed5a7be721e079f1.jpeg

这是主板上标记的所有组件。

 

d0206e8322d1c1cbbe1fe25e4d973848.jpeg

这是标记了 components 的子板。

 

d037ae6ba421ad8c699c1b0eb4ce1f3d.jpeg

这是完全组装的电路板以及 TFT 显示器。

 

e3ad79e8b3cedcc463cd1c81ec905a5f.jpeg

 

从 PCBWay 订购基于 ESP32 的智能手表 PCB

现在,在完成设计后,您可以继续订购 PCB:

第 1 步:进入 pcbway.com,如果这是您第一次注册。然后,在 PCB 原型选项卡中,输入 PCB 的尺寸、层数和所需的 PCB 数量。

 

a054317c4ee815aa7f7803c7738a9a50.png

第 2 步:单击“立即报价”按钮继续。您将被带到一个页面以设置一些附加参数,例如 板类型、层、PCB 材料、厚度等。默认情况下,它们中的大多数都是选中的,如果您选择任何特定参数,则可以在此处选择它。

 

245cbc3453daf725ba109f7be8565874.png

第 3 步:最后一步是上传 Gerber 文件并继续付款。为确保过程顺利,PCBWAY 会在继续付款之前验证您的 Gerber 文件是否有效。这样,您可以确保您的 PCB 对制造友好,并且会按承诺到达您手中。

 

8fefc1a9cd3b0259d68421ea9c4628f2.png

上传 Gerber 文件并付款后,您的工作就完成了,您将收到一封确认电子邮件,其中包含您的电子邮件地址中的所有详细信息。

3D 打印部件

 

c400f67607374b32a1df64613e9f4858.jpeg

我们为智能手表设计了一个看起来很酷的 3D 打印外壳。所有 3D 打印部件的文件都可以从本文末尾提供的 GitHub 链接以及 Arduino 草图和位图文件下载。建议打印填充度更高的部件,以获得更好的质量和坚固性。点击链接了解有关 3D 打印以及如何开始使用它的更多信息。

ESP32 智能手表 GUI 导航

整个 GUI 的设计方式是,我们可以使用一个按钮浏览每个选项。我们可以使用短按和长按来浏览它们。您可以在下图中对整个 GUI 流程进行 finify。蓝线表示单击/短按 ,而绿线表示长按。在 Time Settings 和 Settings 菜单中,您可以浏览每个选项或使用短时钟进行归档。选择选项并使用长按更改值。

 

9464fb7459096cf0835b224fb15b8fb1.jpeg

ESP32 智能手表的 Arduino 代码

现在让我们看看代码。像往常一样,我们使用 include 函数将所有必要的库包含在代码中,包括 TFT_eSPI、ESP32Time、EEPROM、OneButton、QMC5883L、BH1750 和 MAX30105 库。我们还将位图图像数据与字体文件一起包括在内。之后,我们定义了所有必要的全局变量。稍后,我们为每个单独的组件创建了实例。我们将使用这些实例来访问相应的函数。

#include <SPI.h>
#include <TFT_eSPI.h>  // Hardware-specific library
#include <ESP32Time.h>
#include "driver/gpio.h"
#include "esp_sleep.h"
#include <EEPROM.h>
#include "OneButton.h"
#include <QMC5883L.h>
#include <BH1750.h>  //BH1750 Library
#include "Free_Fonts.h"
#include "MAX30105.h"   // SparkFun librarry for MAX30102 sensor
#include "heartRate.h"  // Heartrate measurement algorithm
#include "dial240.h"    //Image data
#include "fonts.h"
#include "images.h"
#define PIN_INPUT 0
#define EEPROM_SIZE 25
#define FONT_SMALL NotoSansBold15
#define FONT_LARGE NotoSansBold36
#define TFT_GREY 0x5AEB
#define TFT_SKYBLUE 0x067D
#define color1 TFT_WHITE
#define color2 0x8410  //0x8410
#define color3 0x5ACB
#define color4 0x15B3
#define color5 0x00A3
#define colour6 0x0926
#define colour7 TFT_BLACK
#define Light_Green 0x07E8
#define background 0xB635
#define LCD_BACKLIGHT 4
#define TFTW 240          // screen width
#define TFTH 280          // screen height
#define TFTW2 (TFTW / 2)  // half screen width
#define TFTH2 (TFTH / 2)  // half screen height
#define SPEED 1
#define GRAVITY 9.8
#define JUMP_FORCE 2.15
#define SKIP_TICKS 20.0  // 1000 / 50fps
#define MAX_FRAMESKIP 5
#define BIRDW 16      // bird width
#define BIRDH 16      // bird height
#define BIRDW2 8      // half width
#define BIRDH2 8      // half height
#define PIPEW 24      // pipe width
#define GAPHEIGHT 42  // pipe gap height
#define FLOORH 30     // floor height (from bottom of the screen)
#define GRASSH 4      // grass height (inside floor, starts at floor y)
#define COLOR565(r, g, b) ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3)
#define BCKGRDCOL COLOR565(138, 235, 244)    // background
#define BIRDCOL COLOR565(255, 254, 174)      // bird
#define PIPECOL COLOR565(99, 255, 78)        // pipe
#define PIPEHIGHCOL COLOR565(250, 255, 250)  // pipe highlight
#define PIPESEAMCOL COLOR565(0, 0, 0)        // pipe seam
#define FLOORCOL COLOR565(246, 240, 163)     // floor
#define GRASSCOL COLOR565(141, 225, 87)      // grass (col2 is the stripe color)
#define GRASSCOL2 COLOR565(156, 239, 88)     // grass (col2 is the stripe color)
#define C0 BCKGRDCOL                         // bird sprite,bird sprite colors (Cx name for values to keep the array readable)
#define C1 COLOR565(195, 165, 75)
#define C2 BIRDCOL
#define C3 TFT_WHITE
#define C4 TFT_RED
#define C5 COLOR565(251, 216, 114)
ESP32Time rtc(0);   // RTC instance with offset in seconds
BH1750 lightMeter;  //BH1750 Instance
QMC5883L compass;
MAX30105 particleSensor;  //MAX30102 instance
OneButton button(PIN_INPUT, true);
TFT_eSPI tft = TFT_eSPI();  // Invoke custom library
TFT_eSprite img = TFT_eSprite(&tft);
static const unsigned int birdcol[] = {C0, C0, C1, C1, C1, C1, C1, C0, C0, C0, C1, C1, C1, C1, C1, C0,C0, C1, C2, C2, C2, C1, C3, C1, C0, C1, C2, C2, C2, C1, C3, C1,C0, C2, C2, C2, C2, C1, C3, C1, C0, C2, C2, C2, C2, C1, C3, C1,C1, C1, C1, C2, C2, C3, C1, C1, C1, C1, C1, C2, C2, C3, C1, C1,C1, C2, C2, C2, C2, C2, C4, C4, C1, C2, C2, C2, C2, C2, C4, C4,C1, C2, C2, C2, C1, C5, C4, C0, C1, C2, C2, C2, C1, C5, C4, C0,C0, C1, C2, C1, C5, C5, C5, C0, C0, C1, C2, C1, C5, C5, C5, C0,C0, C0, C1, C5, C5, C5, C0, C0, C0, C0, C1, C5, C5, C5, C0, C0
};
// bird structure
static struct BIRD {long x, y, old_y;long col;float vel_y;
} bird;
// pipe structure
static struct PIPES {long x, gap_y;long col;
} pipes;
// score
int score;
// temporary x and y var
static short tmpx, tmpy;
// ---------------
// draw pixel
// ---------------
// faster drawPixel method by inlining calls and using setAddrWindow and pushColor using macro to force inlining
#define _drawPixel(a, b, c) \tft.setAddrWindow(a, b, a, b); \tft.pushColor(c)
uint maxScore = 0;
float sx = 0, sy = 1, mx = 1, my = 0, hx = -1, hy = 0;  // Saved H, M, S x & y multipliers
float sdeg = 0, mdeg = 0, hdeg = 0;
uint16_t osx = 120, osy = 140, omx = 120, omy = 140, ohx = 120, ohy = 140;  // Saved H, M, S x & y coords
uint16_t x0 = 0, x1 = 0, yy0 = 0, yy1 = 0;
uint32_t targetTime = 0;                       // for next 1 second timeout
static uint8_t conv2d(const char* p);          // Forward declaration needed for IDE 1.6.x
uint8_t hh = 0, t_mm = 0, t_dd = 0, t_mn = 0;  //
uint32_t t_yr = 0;
uint8_t t_hh = 0, mm = 0, ss = 0;
unsigned long lastfacechange = 0;
unsigned long lastwake = 0;
unsigned long lastpressed = 0;
unsigned long lastvaluechange = 0;
bool initial = 1;
volatile int counter = 0;
float VALUE;
float lastValue = 0;
int lastsec = 0;
int pressstate = 0;
unsigned long lastDisplayUpdate = 0;
const byte RATE_SIZE = 4;  //Increase this for more averaging. 4 is good.
byte rates[RATE_SIZE];     //Array of heart rates
byte rateSpot = 0;
long lastBeat = 0;  //Time at which the last beat occurred
float beatsPerMinute;
int beatAvg;
bool beat = false;
double rad = 0.01745;
float x[360];
float y[360];
bool facechange = false;
bool Screenchange = false;
float px[360];
float py[360];
float lx[360];
float ly[360];
int r = 104;
int ssx = 120;
int ssy = 140;
String cc[12] = { "45", "40", "35", "30", "25", "20", "15", "10", "05", "0", "55", "50" };
String days[] = { "SUNDAY", "MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY" };
String days1[] = { "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" };
int start[12];
int startP[60];
const int pwmFreq = 5000;
const int pwmResolution = 8;
const int pwmLedChannelTFT = 0;
int angle = 0;
bool onOff = 0;
bool debounce = 0;
int watchface = 0, Screen = 0, SubScreen = 0, Autoscreen, AutoBright, AutoscreenTime, Brigtnesslevel;
String h, m, s, d1, d2, m1, m2;
unsigned long pressStartTime;

IRAM_ATTR 函数是通过 GPIO0 附加到硬件中断的中断函数。一旦检测到引脚变化,此函数将被调用。然后,此函数将调用 button_tick 函数,该函数负责检测按键操作。

// This function is called from the interrupt when the signal on the PIN_INPUT has changed.
// do not use Serial in here.
void IRAM_ATTR checkTicks() {// include all buttons here to be checkedbutton.tick();  // just call tick() to check the state.
}

如果 OneButton 库检测到短按键,将调用 Shortclick 函数。调用后,我们将向它传递 next 条件。此函数将检查我们当前所在的任务,并相应地更改变量。

// this function will be called for short click.
void ShortClick() {Serial.println("singleClick() detected.");lastwake = millis();if (Screen == 0) {SubScreen++;if (SubScreen > 2) {SubScreen = 0;facechange = true;}if (SubScreen == 1) {particleSensor.wakeUp();} else {particleSensor.shutDown();}} else if (Screen == 1) {SubScreen++;if (SubScreen > 4) {SubScreen = 0;}Screenchange = true;} else if (Screen == 2) {watchface++;if (watchface > 5) {watchface = 0;}EEPROM.write(0, watchface);EEPROM.commit();facechange = true;Screenchange = true;} else if (Screen == 3) {SubScreen++;if (SubScreen > 5) {SubScreen = 0;}Screenchange = true;} else if (Screen == 4) {SubScreen++;if (SubScreen > 2) {SubScreen = 0;}Screenchange = true;} else if (Screen == 5) {Screen = 6;game_init();game_loop();} else if (Screen == 6) {Screen = 7;} else if (Screen == 7) {Screen = 5;Screenchange = true;}Serial.print("Sub ");Serial.println(SubScreen);tft.fillScreen(colour7);pressstate = 1;facechange = true;lastDisplayUpdate = millis();lastpressed = millis();
}
// ShortClick

当检测到长按按键时,将调用 LongPress 函数,最短持续时间为 1 秒。调用后,它将检测当前正在运行的任务并相应地操作变量。

// long press
void LongPress() {Serial.println("pressStart()");pressStartTime = millis() - 1000;  // as set in setPressTicks()lastwake = millis();lastDisplayUpdate = millis();particleSensor.shutDown();if (Screen == 0) {Screen = 1;SubScreen = 0;} else if (Screen == 1) {if (SubScreen == 0) {Screen = 2;SubScreen = 0;} else if (SubScreen == 1) {Screen = 3;t_hh = rtc.getHour();t_mm = rtc.getMinute();t_dd = rtc.getDay();t_mn = rtc.getMonth();t_yr = rtc.getYear();Serial.println(rtc.getYear());Serial.println(t_yr);SubScreen = 0;} else if (SubScreen == 2) {Screen = 4;SubScreen = 0;facechange = true;Screenchange = true;} else if (SubScreen == 3) {Screen = 5;SubScreen = 0;} else if (SubScreen == 4) {Screen = 0;SubScreen = 0;}} else if (Screen == 2) {Screen = 1;SubScreen = 0;} else if (Screen == 3) {if (SubScreen == 0) {t_hh++;if (t_hh > 23) {t_hh = 0;}} else if (SubScreen == 1) {t_mm++;if (t_mm > 59) {t_mm = 0;}} else if (SubScreen == 2) {t_dd++;if (t_dd > 31) {t_dd = 0;}} else if (SubScreen == 3) {t_mn++;if (t_mn > 12) {t_mn = 0;}} else if (SubScreen == 4) {t_yr++;if (t_yr > 2041) {t_yr = 0;}} else {rtc.setTime(0, t_mm, t_hh, t_dd, t_mn, t_yr);Screen = 1;SubScreen = 1;}} else if (Screen == 4) {if (SubScreen == 0) {AutoBright++;if (AutoBright > 5) {AutoBright = 0;}if (AutoBright > 0) {analogWrite(LCD_BACKLIGHT, AutoBright * 50);}EEPROM.write(2, AutoBright);EEPROM.commit();} else if (SubScreen == 1) {Autoscreen++;if (Autoscreen > 5) {Autoscreen = 0;}EEPROM.write(1, Autoscreen);EEPROM.commit();} else if (SubScreen == 2) {Screen = 1;SubScreen = 2;}} else if (Screen == 5) {Screen = 1;SubScreen = 0;} else if (Screen == 7) {Screen = 1;SubScreen = 0;}facechange = true;Screenchange = true;pressstate = 1;lastpressed = millis();
}

在 setup 函数中,我们已经初始化了所有需要的库和引脚。设置功能还将检查是否使用了 EEPROM 保存区域。如果这些位置具有默认值或空白值,它会将出厂默认值加载到该位置,并将手表初始化为该值。中断附件也在 setup 函数中完成,包括 deep sleep wakeup interrupt 和 button tick interrupt。

void setup(void) {Serial.begin(115200);Serial.println("ESP32 Watch OS.");gpio_hold_dis((gpio_num_t)LCD_BACKLIGHT);pinMode(LCD_BACKLIGHT, OUTPUT);digitalWrite(LCD_BACKLIGHT, LOW);EEPROM.begin(EEPROM_SIZE);EEPROM.writeInt(10, 0);EEPROM.commit();if (EEPROM.read(0) > 3) {EEPROM.write(0, 4);EEPROM.commit();}watchface = EEPROM.read(0);if (EEPROM.read(1) > 5) {EEPROM.write(1, 5);EEPROM.commit();}Autoscreen = EEPROM.read(1);if (EEPROM.read(2) > 5) {EEPROM.write(2, 5);EEPROM.commit();}AutoBright = EEPROM.read(2);//rtc.setTime(ss, mm, hh, 0, 0, 0);  // 26th Jjuly 2022 compile dateparticleSensor.begin(Wire, I2C_SPEED_FAST);particleSensor.setup();                     //Configure sensor with default settingsparticleSensor.setPulseAmplitudeRed(0x0A);  //Turn Red LED to low to indicate sensor is runningparticleSensor.setPulseAmplitudeIR(0xFF);   //Turn Red LED to low to indicate sensor is runningparticleSensor.setPulseAmplitudeGreen(0);   //Turn off Green LEDparticleSensor.shutDown();compass.init();compass.setSamplingRate(50);tft.init();tft.setRotation(0);//tft.setColorDepth(16);tft.setSwapBytes(true);tft.fillScreen(colour7);int xw = tft.width() / 2;  // xw, yh is middle of screenint yh = tft.height() / 2;tft.setPivot(xw, yh);  // Set pivot to middle of TFT screenimg.createSprite(240, 280);img.setTextDatum(4);img1.createSprite(240, 70);img1.setSwapBytes(true);img2.createSprite(240, 70);img2.setSwapBytes(true);targetTime = millis() + 1000;facechange = true;esp_sleep_enable_ext0_wakeup(GPIO_NUM_0, 0);  //1 = High, 0 = Low// setup interrupt routine// when not registering to the interrupt the sketch also works when the tick is called frequently.attachInterrupt(digitalPinToInterrupt(PIN_INPUT), checkTicks, CHANGE);// link the xxxclick functions to be called on xxxclick event.button.attachClick(ShortClick);button.setPressTicks(1000);  // that is the time when LongPressStart is calledbutton.attachLongPressStart(LongPress);lightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE);  //Init BH1750 libraryif (AutoBright == 0) {unsigned int lv = constrain(lightMeter.readLightLevel(), 50, 500);analogWrite(LCD_BACKLIGHT, lv / 2);} else {analogWrite(LCD_BACKLIGHT, AutoBright * 50);}lastwake = millis();
}
int lastAngle = 0;
float circle = 100;
bool dir = 0;
int rAngle = 359;

loop 函数将定期调用所有 mains 函数,包括 button_tick、watchtask 和 game 函数。button_tick功能负责按键检测,区分射压和长按。watchtask 功能负责所有主要功能,包括表盘显示、心率监测、数字罗盘、菜单处理以及所有设置和导航。同时,游戏功能将处理 watch OS 中包含的 flappy bird 游戏。

void loop() {button.tick();if (Screen < 5) {watchtask();} else {game();}
}

如前所述,watchtask 将处理与智能手表相关的大部分任务。此功能将检查亮度是否设置并相应地调整背光 PWM。它还将检查屏幕超时设置,并相应地使 ESP32 进入深度睡眠。所有 sub 函数都将根据当前活动任务进行相应调用。我们使用了 gpio_deep_sleep_hold_en 函数和 gpio_hold_en 函数,以在深度睡眠期间保持背光引脚有效。如果没有这些功能,GPIO 将从任何设置的状态中释放出来,并且会影响背光控制。

void watchtask() {if (pressstate == 1 && digitalRead(0) == 1) {pressstate = 0;}if (AutoBright == 0) {unsigned int lv = constrain(lightMeter.readLightLevel(), 50, 500);analogWrite(LCD_BACKLIGHT, lv / 2);Serial.print("Light");Serial.println(lv / 2);}if (Autoscreen != 0 && millis() - lastwake > Autoscreen * 60000) {analogWrite(LCD_BACKLIGHT, 0);delay(1000);tft.fillScreen(colour7);gpio_deep_sleep_hold_en();gpio_hold_en((gpio_num_t)LCD_BACKLIGHT);esp_deep_sleep_start();}if (Screen == 0) {if (SubScreen == 0) {watchfacedsp();} else if (SubScreen == 1) {HRApp();} else {CompassApp();}} else if (Screen == 1 && Screenchange == true) {if (SubScreen == 0) {tft.pushImage(0, 0, 240, 280, facechangeicon);} else if (SubScreen == 1) {tft.pushImage(0, 0, 240, 280, timeseticon);} else if (SubScreen == 2) {tft.pushImage(0, 0, 240, 280, settingsicon);} else if (SubScreen == 3) {tft.pushImage(0, 0, 240, 280, gamesicon);} else if (SubScreen == 4) {tft.pushImage(0, 0, 240, 280, exiticon);}} else if (Screen == 3 && Screenchange == true) {timesetiings();Screenchange = false;} else if (Screen == 2 && Screenchange == true) {watchfacedsp();Screenchange = false;} else if (Screen == 4 && Screenchange == true) {settings();Screenchange = false;} else if (Screen == 3 && millis() - lastpressed > 2000 && millis() - lastvaluechange > 500 && pressstate == 1) {if (SubScreen == 0) {t_hh++;if (t_hh > 23) {t_hh = 0;}facechange = true;Screenchange = true;} else if (SubScreen == 1) {t_mm++;if (t_mm > 59) {t_mm = 0;}facechange = true;Screenchange = true;} else if (SubScreen == 2) {facechange = true;Screenchange = true;t_dd++;if (t_dd > 31) {t_dd = 0;}} else if (SubScreen == 3) {t_mn++;if (t_mn > 12) {t_mn = 0;}facechange = true;Screenchange = true;} else if (SubScreen == 4) {t_yr++;if (t_yr > 2041) {t_yr = 0;}facechange = true;Screenchange = true;}lastvaluechange = millis();}
}

timesettings 函数处理时间设置菜单。使用此功能,我们可以设置正确的日期和时间。短按更改字段,长按更改值。

void timesetiings() {tft.pushImage(0, 0, 240, 280, TimeSettings);tft.setTextColor(TFT_BLACK, TFT_WHITE);tft.setFreeFont(FF18);int tt_hh = t_hh;if (tt_hh > 12) {tt_hh = tt_hh - 12;}if (tt_hh < 10) {tft.drawString("0" + String(tt_hh), 25, 96);} else {tft.drawString(String(tt_hh), 25, 96);}if (t_mm < 10) {tft.drawString("0" + String(t_mm), 93, 96);} else {tft.drawString(String(t_mm), 93, 96);}if (t_hh < 13) {tft.drawString("AM", 164, 96);} else {tft.drawString("PM", 164, 96);}if (t_dd < 10) {tft.drawString("0" + String(t_dd), 25, 183);} else {tft.drawString(String(t_dd), 25, 183);}if (t_mn < 10) {tft.drawString("0" + String(t_mn), 93, 183);} else {tft.drawString(String(t_mn), 93, 183);}if (t_yr < 2022) {t_yr = 2022;}tft.drawString(String(t_yr), 161, 183);if (SubScreen == 0) {tft.drawRoundRect(9, 81, 58, 48, 6, Light_Green);} else if (SubScreen == 1) {tft.drawRoundRect(77, 81, 58, 48, 6, Light_Green);} else if (SubScreen == 2) {tft.drawRoundRect(9, 168, 58, 48, 6, Light_Green);} else if (SubScreen == 3) {tft.drawRoundRect(77, 168, 58, 48, 6, Light_Green);} else if (SubScreen == 4) {tft.drawRoundRect(144, 168, 88, 48, 6, Light_Green);} else {tft.drawRoundRect(79, 226, 88, 48, 6, Light_Green);}
}

Using the settings menu, we can manage the brightness and screen time out settings. We can either set the watch to adjust the screen brightness according to the BH1750 ambient light sensor reading or we can set it manually from 20-100% in 20% steps. For screen time out we can choose either to keep the screen on all the time or we can set the screen time out from 1 minute to 5 minutes in 1-minute steps.

void settings() {tft.pushImage(0, 0, 240, 280, Settingspage);tft.setTextColor(TFT_BLACK, TFT_WHITE);tft.setFreeFont(FF18);switch (AutoBright) {case 0: tft.drawString("  Auto  ", 80, 100); break;case 1: tft.drawString("  20%   ", 85, 100); break;case 2: tft.drawString("  40%   ", 85, 100); break;case 3: tft.drawString("  60%   ", 85, 100); break;case 4: tft.drawString("  80%   ", 85, 100); break;case 5: tft.drawString(" 100%   ", 80, 100); break;}switch (Autoscreen) {case 0: tft.drawString("Always On", 65, 185); break;case 1: tft.drawString("1 Minute ", 75, 185); break;case 2: tft.drawString("2 Minute ", 75, 185); break;case 3: tft.drawString("3 Minute ", 75, 185); break;case 4: tft.drawString("4 Minute ", 75, 185); break;case 5: tft.drawString("5 Minute ", 75, 185); break;}if (SubScreen == 0) {tft.drawRoundRect(16, 85, 208, 48, 6, Light_Green);} else if (SubScreen == 1) {tft.drawRoundRect(16, 169, 208, 48, 6, Light_Green);} else {tft.drawRoundRect(66, 225, 108, 48, 6, Light_Green);}
}

HRApp 函数处理心率传感器。一旦调用此函数,我们将激活 MAX30102 传感器并开始读取。如果未检测到手腕或手指,手表将显示错误消息。检测到后,手表将检测跳动,并以 bps 为单位计算心率。一旦我们退出此功能,手表会将 MAX30102 关机到低功耗模式以节省电量。

void HRApp() {long irValue = particleSensor.getIR();  //Reading the IR value it will permit us to know if there's a finger on the sensor or not//Also detecting a heartbeatif (checkForBeat(irValue) == true)  //If a heart beat is detected{long delta = millis() - lastBeat;  //Measure duration between two beatslastBeat = millis();beatsPerMinute = 60 / (delta / 1000.0);  //Calculating the BPMif (beatsPerMinute < 255 && beatsPerMinute > 20)  //To calculate the average we strore some values (4) then do some math to calculate the average {rates[rateSpot++] = (byte)beatsPerMinute;  //Store this reading in the arrayrateSpot %= RATE_SIZE;                     //Wrap variable//Take average of readingsbeatAvg = 0;for (byte x = 0; x < RATE_SIZE; x++)beatAvg += rates[x];beatAvg /= RATE_SIZE;}}if (millis() - lastDisplayUpdate > 500) {if (irValue < 60000) {  //If no finger is detected it inform the user and put the average BPM to 0 or it will be stored for the next measurebeatAvg = 0;img.loadFont(FONT_SMALL);img.setCursor(80, 120);img.setTextColor(TFT_CYAN, colour7);img.fillSprite(colour7);img.println("Please Place ");img.setCursor(80, 140);img.println("your finger ");img.pushSprite(0, 0);} else {img.fillSprite(colour7);  //Clear the displayif (beat == true) {img.pushImage(0, 0, 240, 280, hr1);} else {img.pushImage(0, 0, 240, 280, hr2);}beat = !beat;img.setTextColor(TFT_CYAN, colour7);img.loadFont(FONT_LARGE);img.setCursor(100, 130);img.print(beatAvg);img.setCursor(100, 175);img.loadFont(FONT_SMALL);img.print("BPM ");img.pushRotated(0);}lastDisplayUpdate = millis();}Serial.print("IR=");Serial.print(irValue);Serial.print(", BPM=");Serial.print(beatsPerMinute);Serial.print(", Avg BPM=");Serial.print(beatAvg);if (irValue < 60000)Serial.print(" No finger?");if (millis() - lastBeat > 5000) {beatsPerMinute = 0;beatAvg = 0;}Serial.println();
}

同样,CompassApp 函数将与 HMC5883L 传感器通信,并相应地计算航向。计算出角度后,该功能将以适当的方向显示罗盘刻度盘,指示方向。

void CompassApp() {for (int i = 0; i < 10; i++) {angle = angle + compass.readHeading();}angle = angle / 10;img.fillSprite(colour7);img.pushImage(0, 20, 240, 240, dial240);img.pushRotated(angle);  // create rotated image as per the angle from the compass sensorangle = 0;
}

For displaying the selected watch face we will use the watchfacedp function. This function will check for the current set watch face, and it will display the current time using that specific watch face. Current time is read from the internal RTC registries. The internal RTC will keep running even if the ESP32 goes to deep sleep, keeping the exact time.

void watchfacedsp() {if (facechange) {tft.fillScreen(colour7);if (watchface == 1) {tft.setTextSize(0);tft.pushImage(0, 0, 240, 280, Casio2);tft.setTextColor(0x0081, background);tft.fillRoundRect(48, 127, 128, 48, 5, background);} else if (watchface == 2) {tft.setTextSize(0);tft.pushImage(0, 0, 240, 280, Casio1);tft.setTextColor(0x0081, background);tft.fillRoundRect(48, 127, 128, 48, 5, background);} else if (watchface == 4) {tft.pushImage(0, 0, 240, 280, cdface1);img2.pushImage(0, 0, 240, 100, cdface11);tft.setTextColor(TFT_WHITE);tft.setFreeFont(FF18);tft.setTextSize(2);} else if (watchface == 5) {tft.pushImage(0, 0, 240, 280, cdface2);img2.pushImage(0, 0, 240, 100, cdface12);tft.setTextColor(TFT_WHITE);tft.setFreeFont(FF18);tft.setTextSize(2);}facechange = false;lastfacechange = millis();}if (watchface == 0) {int b = 0;int b2 = 0;for (int i = 0; i < 360; i++) {x[i] = (r * cos(rad * i)) + ssx;y[i] = (r * sin(rad * i)) + ssy;px[i] = ((r - 16) * cos(rad * i)) + ssx;py[i] = ((r - 16) * sin(rad * i)) + ssy;lx[i] = ((r - 26) * cos(rad * i)) + ssx;ly[i] = ((r - 26) * sin(rad * i)) + ssy;if (i % 30 == 0) {start[b] = i;b++;}if (i % 6 == 0) {startP[b2] = i;b2++;}}rAngle = rAngle - 2;angle = rtc.getSecond() * 6;s = String(rtc.getSecond());m = String(rtc.getMinute());h = String(rtc.getHour());if (m.toInt() < 10)m = "0" + m;if (h.toInt() < 10)h = "0" + h;if (s.toInt() < 10)s = "0" + s;if (rtc.getDay() > 10) {d1 = rtc.getDay() / 10;d2 = rtc.getDay() % 10;} else {d1 = "0";d2 = String(rtc.getDay());}if (rtc.getMonth() > 10) {m1 = rtc.getMonth() / 10;m2 = rtc.getMonth() % 10;} else {m1 = "0";m2 = String(rtc.getMonth());}if (angle >= 360)angle = 0;if (rAngle <= 0)rAngle = 359;if (dir == 0)circle = circle + 0.5;elsecircle = circle - 0.5;if (circle > 140)dir = !dir;if (circle < 100)dir = !dir;if (angle > -1) {lastAngle = angle;VALUE = ((angle - 270) / 3.60) * -1;if (VALUE < 0)VALUE = VALUE + 100;img.fillSprite(colour7);img.fillCircle(ssx, ssy, 124, colour7);img.setTextColor(TFT_WHITE, colour7);img.drawString(days[rtc.getDayofWeek()], circle, 140, 2);for (int i = 0; i < 12; i++)if (start[i] + angle < 360) {img.drawString(cc[i], x[start[i] + angle], y[start[i] + angle], 2);img.drawLine(px[start[i] + angle], py[start[i] + angle], lx[start[i] + angle], ly[start[i] + angle], color1);} else {img.drawString(cc[i], x[(start[i] + angle) - 360], y[(start[i] + angle) - 360], 2);img.drawLine(px[(start[i] + angle) - 360], py[(start[i] + angle) - 360], lx[(start[i] + angle) - 360], ly[(start[i] + angle) - 360], color1);}img.setFreeFont(&DSEG7_Modern_Bold_20);img.drawString(s, ssx, ssy - 36);img.setFreeFont(&DSEG7_Classic_Regular_28);img.drawString(h + ":" + m, ssx, ssy + 28);img.setTextFont(0);img.fillRect(70, 86, 12, 20, color3);img.fillRect(84, 86, 12, 20, color3);img.fillRect(150, 86, 12, 20, color3);img.fillRect(164, 86, 12, 20, color3);img.setTextColor(0x35D7, colour7);img.drawString("MONTH", 84, 78);img.drawString("DAY", 162, 78);img.setTextColor(TFT_SKYBLUE, colour7);img.drawString("Circuit Digest", 120, 194);img.drawString("***", 120, 124);img.setTextColor(TFT_WHITE, color3);img.drawString(m1, 77, 96, 2);img.drawString(m2, 91, 96, 2);img.drawString(d1, 157, 96, 2);img.drawString(d2, 171, 96, 2);for (int i = 0; i < 60; i++)if (startP[i] + angle < 360)img.fillCircle(px[startP[i] + angle], py[startP[i] + angle], 1, color1);elseimg.fillCircle(px[(startP[i] + angle) - 360], py[(startP[i] + angle) - 360], 1, color1);img.fillTriangle(ssx - 1, ssy - 70, ssx - 5, ssy - 56, ssx + 4, ssy - 56, TFT_ORANGE);img.fillCircle(px[rAngle], py[rAngle], 6, TFT_RED);img.pushSprite(0, 0);}} else if (rtc.getSecond() != lastsec || Screen == 3) {if (watchface == 1 || watchface == 2) {/*String med;if (rtc.getSecond() % 2) {med = ":";} else {med = " ";}*/tft.setFreeFont(&DSEG7_Classic_Bold_30);if (rtc.getHour() > 9 && rtc.getMinute() > 9) {tft.drawString(String(rtc.getHour()) + ":" + String(rtc.getMinute()), 46, 135);} else if (rtc.getHour() < 10 && rtc.getMinute() > 9) {tft.drawString("0" + String(rtc.getHour()) + ":" + String(rtc.getMinute()), 46, 135);} else if (rtc.getHour() > 9 && rtc.getMinute() < 10) {tft.drawString(String(rtc.getHour()) + ":0" + String(rtc.getMinute()), 46, 135);} else {tft.drawString("0" + String(rtc.getHour()) + ":0" + String(rtc.getMinute()), 46, 135);}tft.setFreeFont(&DSEG7_Classic_Bold_20);if (rtc.getSecond() < 10) {tft.drawString("0" + String(rtc.getSecond()), 154, 145);} else {tft.drawString(String(rtc.getSecond()), 154, 145);}tft.setFreeFont(&DSEG14_Classic_Bold_18);tft.drawString(days1[rtc.getDayofWeek()], 94, 106);tft.drawString(String(rtc.getDay()), 156, 106);} else if (watchface == 3) {img.setTextColor(TFT_WHITE, colour7);  // Adding a background colour erases previous text automatically// Draw clock faceimg.fillCircle(120, 140, 118, TFT_GREEN);img.fillCircle(120, 140, 110, colour7);// Draw 12 linesfor (int i = 0; i < 360; i += 30) {sx = cos((i - 90) * 0.0174532925);sy = sin((i - 90) * 0.0174532925);x0 = sx * 114 + 120;yy0 = sy * 114 + 140;x1 = sx * 100 + 120;yy1 = sy * 100 + 140;img.drawLine(x0, yy0, x1, yy1, TFT_GREEN);}// Draw 60 dotsfor (int i = 0; i < 360; i += 6) {sx = cos((i - 90) * 0.0174532925);sy = sin((i - 90) * 0.0174532925);x0 = sx * 102 + 120;yy0 = sy * 102 + 140;// Draw minute markersimg.drawPixel(x0, yy0, TFT_WHITE);// Draw main quadrant dotsif (i == 0 || i == 180) img.fillCircle(x0, yy0, 2, TFT_WHITE);if (i == 90 || i == 270) img.fillCircle(x0, yy0, 2, TFT_WHITE);}img.fillCircle(120, 141, 3, TFT_WHITE);// Pre-compute hand degrees, x & y coords for a fast screen updatesdeg = rtc.getSecond() * 6;                      // 0-59 -> 0-354mdeg = rtc.getMinute() * 6 + sdeg * 0.01666667;  // 0-59 -> 0-360 - includes secondshdeg = rtc.getHour() * 30 + mdeg * 0.0833333;    // 0-11 -> 0-360 - includes minutes and secondshx = cos((hdeg - 90) * 0.0174532925);hy = sin((hdeg - 90) * 0.0174532925);mx = cos((mdeg - 90) * 0.0174532925);my = sin((mdeg - 90) * 0.0174532925);sx = cos((sdeg - 90) * 0.0174532925);sy = sin((sdeg - 90) * 0.0174532925);if (rtc.getSecond() == 0 || initial) {initial = 0;// Erase hour and minute hand positions every minuteimg.drawLine(ohx, ohy, 120, 141, colour7);ohx = hx * 62 + 121;ohy = hy * 62 + 141;img.drawLine(omx, omy, 120, 141, colour7);omx = mx * 84 + 120;omy = my * 84 + 141;}// Redraw new hand positions, hour and minute hands not erased here to avoid flickerimg.drawLine(osx, osy, 120, 141, colour7);osx = sx * 90 + 121;osy = sy * 90 + 141;img.drawLine(osx, osy, 120, 141, TFT_RED);img.drawLine(ohx, ohy, 120, 141, TFT_WHITE);img.drawLine(omx, omy, 120, 141, TFT_WHITE);img.drawLine(osx, osy, 120, 141, TFT_RED);img.fillCircle(120, 141, 3, TFT_RED);img.pushSprite(0, 0);} else if (watchface == 4 || watchface == 5) {img1.setTextColor(TFT_WHITE, TFT_BLACK);img1.setFreeFont(FF24);img1.fillSprite(TFT_BLACK);//img1.setTextSize(2);if (rtc.getHour() > 9 && rtc.getMinute() > 9) {img1.drawString(String(rtc.getHour()) + ":" + String(rtc.getMinute()), 66, 30);} else if (rtc.getHour() < 10 && rtc.getMinute() > 9) {img1.drawString("0" + String(rtc.getHour()) + ":" + String(rtc.getMinute()), 66, 30);} else if (rtc.getHour() > 9 && rtc.getMinute() < 10) {img1.drawString(String(rtc.getHour()) + ":0" + String(rtc.getMinute()), 66, 30);} else {img1.drawString("0" + String(rtc.getHour()) + +":0" + String(rtc.getMinute()), 66, 30);}img1.setFreeFont(FF22);if (rtc.getSecond() < 10) {img1.drawString("0" + String(rtc.getSecond()), 190, 40);} else {img1.drawString(String(rtc.getSecond()), 190, 40);}img1.drawString(days1[rtc.getDayofWeek()] + " " + String(rtc.getDay()) + " " + String(rtc.getYear()), 54, 0);//img1.drawString(String(rtc.getDay()), 156, 0);img2.pushSprite(0, 180);img1.pushSprite(0, 180, TFT_BLACK);}lastsec = rtc.getSecond();}
}
static uint8_t conv2d(const char* p) {uint8_t v = 0;if ('0' <= *p && *p <= '9')v = *p - '0';return 10 * v + *++p - '0';
}
void game() {if (Screen == 5 && Screenchange == 1) {Screenchange = 0;tft.fillScreen(TFT_BLACK);tft.fillRect(10, TFTH2 - 20, TFTW - 20, 1, TFT_WHITE);tft.fillRect(10, TFTH2 + 32, TFTW - 20, 1, TFT_WHITE);tft.setTextColor(TFT_WHITE);tft.setFreeFont(0);tft.setTextSize(2);// half width - num char * char width in pixelstft.setCursor(TFTW2 - (6 * 9), TFTH2 - 16);tft.println("FLAPPY");tft.setTextSize(2);tft.setCursor(TFTW2 - (6 * 9), TFTH2 + 8);tft.println("-BIRD-");} else if (Screen == 7 && Screenchange == 1) {Screenchange = 0;tft.fillScreen(TFT_BLACK);maxScore = EEPROM.readInt(10);if (score > maxScore) {EEPROM.writeInt(10, score);EEPROM.commit();maxScore = score;tft.setTextColor(TFT_RED);tft.setTextSize(2);tft.setCursor(TFTW2 - (13 * 6), TFTH2 - 26);tft.println("NEW HIGHSCORE");}tft.setTextColor(TFT_WHITE);tft.setTextSize(3);// half width - num char * char width in pixelstft.setCursor(TFTW2 - (9 * 9), TFTH2 - 6);tft.println("GAME OVER");tft.setTextSize(2);tft.setCursor(10, 10);tft.print("score: ");tft.print(score);tft.setCursor(TFTW2 - (12 * 6), TFTH2 + 18);tft.println("press button");tft.setCursor(10, 28);tft.print("Max Score:");tft.print(maxScore);tft.setTextSize(0);facechange = 1;}
}
static uint8_t conv2d(const char* p) {uint8_t v = 0;if ('0' <= *p && *p <= '9')v = *p - '0';return 10 * v + *++p - '0';
}

游戏函数处理内置的 flappy 游戏。此功能负责开始和结束屏幕。它还将调用游戏初始化函数,并在触发后调用 game_loop 函数。

void game() {if (Screen == 5 && Screenchange == 1) {Screenchange = 0;tft.fillScreen(TFT_BLACK);tft.fillRect(10, TFTH2 - 20, TFTW - 20, 1, TFT_WHITE);tft.fillRect(10, TFTH2 + 32, TFTW - 20, 1, TFT_WHITE);tft.setTextColor(TFT_WHITE);tft.setFreeFont(0);tft.setTextSize(2);// half width - num char * char width in pixelstft.setCursor(TFTW2 - (6 * 9), TFTH2 - 16);tft.println("FLAPPY");tft.setTextSize(2);tft.setCursor(TFTW2 - (6 * 9), TFTH2 + 8);tft.println("-BIRD-");} else if (Screen == 7 && Screenchange == 1) {Screenchange = 0;tft.fillScreen(TFT_BLACK);maxScore = EEPROM.readInt(10);if (score > maxScore) {EEPROM.writeInt(10, score);EEPROM.commit();maxScore = score;tft.setTextColor(TFT_RED);tft.setTextSize(2);tft.setCursor(TFTW2 - (13 * 6), TFTH2 - 26);tft.println("NEW HIGHSCORE");}tft.setTextColor(TFT_WHITE);tft.setTextSize(3);// half width - num char * char width in pixelstft.setCursor(TFTW2 - (9 * 9), TFTH2 - 6);tft.println("GAME OVER");tft.setTextSize(2);tft.setCursor(10, 10);tft.print("score: ");tft.print(score);tft.setCursor(TFTW2 - (12 * 6), TFTH2 + 18);tft.println("press button");tft.setCursor(10, 28);tft.print("Max Score:");tft.print(maxScore);tft.setTextSize(0);facechange = 1;}
}

game_init 函数负责在启动游戏之前清除显示并设置初始游戏变量值。

void game_init() {// clear screentft.fillScreen(BCKGRDCOL);// reset scorescore = 0;// init birdbird.x = 144;bird.y = bird.old_y = TFTH2 - BIRDH;bird.vel_y = -JUMP_FORCE;tmpx = tmpy = 0;// generate new random seed for the pipe gaperandomSeed(analogRead(12));// init pipepipes.x = 0;pipes.gap_y = random(20, TFTH - 60);
}

游戏函数处理内置的 flappy 游戏。此功能负责开始和结束屏幕。它还将在触发后调用游戏初始化函数和 game_loop 函数。

void game() {if (Screen == 5 && Screenchange == 1) {Screenchange = 0;tft.fillScreen(TFT_BLACK);tft.fillRect(10, TFTH2 - 20, TFTW - 20, 1, TFT_WHITE);tft.fillRect(10, TFTH2 + 32, TFTW - 20, 1, TFT_WHITE);tft.setTextColor(TFT_WHITE);tft.setFreeFont(0);tft.setTextSize(2);// half width - num char * char width in pixelstft.setCursor(TFTW2 - (6 * 9), TFTH2 - 16);tft.println("FLAPPY");tft.setTextSize(2);tft.setCursor(TFTW2 - (6 * 9), TFTH2 + 8);tft.println("-BIRD-");} else if (Screen == 7 && Screenchange == 1) {Screenchange = 0;tft.fillScreen(TFT_BLACK);maxScore = EEPROM.readInt(10);if (score > maxScore) {EEPROM.writeInt(10, score);EEPROM.commit();maxScore = score;tft.setTextColor(TFT_RED);tft.setTextSize(2);tft.setCursor(TFTW2 - (13 * 6), TFTH2 - 26);tft.println("NEW HIGHSCORE");}tft.setTextColor(TFT_WHITE);tft.setTextSize(3);// half width - num char * char width in pixelstft.setCursor(TFTW2 - (9 * 9), TFTH2 - 6);tft.println("GAME OVER");tft.setTextSize(2);tft.setCursor(10, 10);tft.print("score: ");tft.print(score);tft.setCursor(TFTW2 - (12 * 6), TFTH2 + 18);tft.println("press button");tft.setCursor(10, 28);tft.print("Max Score:");tft.print(maxScore);tft.setTextSize(0);facechange = 1;}
}

The game_init function is responsible for clearing the display prior to starting the games along with setting the initial game variable values.

void game_init() {// clear screentft.fillScreen(BCKGRDCOL);// reset scorescore = 0;// init birdbird.x = 144;bird.y = bird.old_y = TFTH2 - BIRDH;bird.vel_y = -JUMP_FORCE;tmpx = tmpy = 0;// generate new random seed for the pipe gaperandomSeed(analogRead(12));// init pipepipes.x = 0;pipes.gap_y = random(20, TFTH - 60);
}

处理整个游戏的主要函数是 game_loop 函数。它负责所有游戏图形以及游戏动态。它还将监控按键操作。游戏的按键检测是直接在此功能中完成的,无需 OneButton 库。Sprite 和其他快速渲染技术用于实现流畅的游戏性能。

void game_loop() {// ===============// prepare game variables// draw floor// ===============// instead of calculating the distance of the floor from the screen height each time store it in a variableconst unsigned char GAMEH = TFTH - FLOORH;// draw the floor once, we will not overwrite on this area in-game// black linetft.drawFastHLine(0, GAMEH, TFTW, TFT_BLACK);// grass and stripetft.fillRect(0, GAMEH + 1, TFTW2, GRASSH, GRASSCOL);tft.fillRect(TFTW2, GAMEH + 1, TFTW2, GRASSH, GRASSCOL2);// black linetft.drawFastHLine(0, GAMEH + GRASSH, TFTW, TFT_BLACK);// mudtft.fillRect(0, GAMEH + GRASSH + 1, TFTW, FLOORH - GRASSH, FLOORCOL);// grass x position (for stripe animation)long grassx = TFTW;// game loop time variablesdouble delta, old_time, next_game_tick, current_time;next_game_tick = current_time = millis();// passed pipe flag to count scorebool passed_pipe = false;// temp var for setAddrWindowunsigned char px;while (true) {yield();int loops = 0;while (millis() > next_game_tick && loops < MAX_FRAMESKIP) {// ===============// input// ===============if (digitalRead(0) == LOW) {// if the bird is not too close to the top of the screen apply jump forceif (bird.y > BIRDH2 * 0.5)bird.vel_y = -JUMP_FORCE;// else zero velocityelsebird.vel_y = 0;}// ===============// update// ===============// calculate delta time// ---------------old_time = current_time;current_time = millis();delta = (current_time - old_time) / 1000;// bird// ---------------bird.vel_y += GRAVITY * delta;bird.y += bird.vel_y;// pipe// ---------------pipes.x -= SPEED;// if pipe reached edge of the screen reset its position and gapif (pipes.x < -PIPEW) {pipes.x = TFTW;pipes.gap_y = random(10, GAMEH - (10 + GAPHEIGHT));}// ---------------next_game_tick += SKIP_TICKS;loops++;}// ===============// draw// ===============// pipe// ---------------// we save cycles if we avoid drawing the pipe when outside the screenif (pipes.x >= 0 && pipes.x < TFTW) {// pipe colortft.drawFastVLine(pipes.x + 3, 0, pipes.gap_y, PIPECOL);tft.drawFastVLine(pipes.x + 3, pipes.gap_y + GAPHEIGHT + 1, GAMEH - (pipes.gap_y + GAPHEIGHT + 1), PIPECOL);// highlighttft.drawFastVLine(pipes.x, 0, pipes.gap_y, PIPEHIGHCOL);tft.drawFastVLine(pipes.x, pipes.gap_y + GAPHEIGHT + 1, GAMEH - (pipes.gap_y + GAPHEIGHT + 1), PIPEHIGHCOL);// bottom and top border of pipe_drawPixel(pipes.x, pipes.gap_y, PIPESEAMCOL);_drawPixel(pipes.x, pipes.gap_y + GAPHEIGHT, PIPESEAMCOL);// pipe seam_drawPixel(pipes.x, pipes.gap_y - 6, PIPESEAMCOL);_drawPixel(pipes.x, pipes.gap_y + GAPHEIGHT + 6, PIPESEAMCOL);_drawPixel(pipes.x + 3, pipes.gap_y - 6, PIPESEAMCOL);_drawPixel(pipes.x + 3, pipes.gap_y + GAPHEIGHT + 6, PIPESEAMCOL);}// erase behind pipeif (pipes.x <= TFTW)tft.drawFastVLine(pipes.x + PIPEW, 0, GAMEH, BCKGRDCOL);// bird// ---------------tmpx = BIRDW - 1;do {px = bird.x + tmpx + BIRDW;// clear bird at previous position stored in old_y// we can't just erase the pixels before and after current position// because of the non-linear bird movement (it would leave 'dirty' pixels)tmpy = BIRDH - 1;do {_drawPixel(px, bird.old_y + tmpy, BCKGRDCOL);} while (tmpy--);// draw bird sprite at new positiontmpy = BIRDH - 1;do {_drawPixel(px, bird.y + tmpy, birdcol[tmpx + (tmpy * BIRDW)]);} while (tmpy--);} while (tmpx--);// save position to erase bird on next drawbird.old_y = bird.y;// grass stripes// ---------------grassx -= SPEED;if (grassx < 0)grassx = TFTW;tft.drawFastVLine(grassx % TFTW, GAMEH + 1, GRASSH - 1, GRASSCOL);tft.drawFastVLine((grassx + 64) % TFTW, GAMEH + 1, GRASSH - 1, GRASSCOL2);// ===============// collision// ===============// if the bird hit the ground game overif (bird.y > GAMEH - BIRDH)break;// checking for bird collision with pipeif (bird.x + BIRDW >= pipes.x - BIRDW2 && bird.x <= pipes.x + PIPEW - BIRDW) {// bird entered a pipe, check for collisionif (bird.y < pipes.gap_y || bird.y + BIRDH > pipes.gap_y + GAPHEIGHT)break;elsepassed_pipe = true;}// if bird has passed the pipe increase scoreelse if (bird.x > pipes.x + PIPEW - BIRDW && passed_pipe) {passed_pipe = false;// erase score with background colortft.setTextColor(BCKGRDCOL);tft.setCursor(TFTW2, 4);tft.print(score);// set text color back to white for new scoretft.setTextColor(TFT_WHITE);// increase score since we successfully passed a pipescore++;}// update score// ---------------tft.setCursor(TFTW2, 4);tft.print(score);}// add a small delay to show how the player lostScreen = 7;Screenchange = 1;delay(1200);
}代码
#include <SPI.h>
#include <TFT_eSPI.h>  // Hardware-specific library
#include <ESP32Time.h>
#include "driver/gpio.h"
#include "esp_sleep.h"
#include <EEPROM.h>
#include "OneButton.h"
#include <QMC5883L.h>
#include <BH1750.h>  //BH1750 Library
#include "Free_Fonts.h"
#include "MAX30105.h"   // SparkFun librarry for MAX30102 sensor
#include "heartRate.h"  // Heartrate measurement algorithm
#include "dial240.h"    //Image data
#include "fonts.h"
#include "images.h"
#define PIN_INPUT 0
#define EEPROM_SIZE 25
#define FONT_SMALL NotoSansBold15
#define FONT_LARGE NotoSansBold36
#define TFT_GREY 0x5AEB
#define TFT_SKYBLUE 0x067D
#define color1 TFT_WHITE
#define color2 0x8410  //0x8410
#define color3 0x5ACB
#define color4 0x15B3
#define color5 0x00A3
#define colour6 0x0926
#define colour7 TFT_BLACK
#define Light_Green 0x07E8
#define background 0xB635
#define LCD_BACKLIGHT 4
#define TFTW 240          // screen width
#define TFTH 280          // screen height
#define TFTW2 (TFTW / 2)  // half screen width
#define TFTH2 (TFTH / 2)  // half screen height
#define SPEED 1
#define GRAVITY 9.8
#define JUMP_FORCE 2.15
#define SKIP_TICKS 20.0  // 1000 / 50fps
#define MAX_FRAMESKIP 5
#define BIRDW 16      // bird width
#define BIRDH 16      // bird height
#define BIRDW2 8      // half width
#define BIRDH2 8      // half height
#define PIPEW 24      // pipe width
#define GAPHEIGHT 42  // pipe gap height
#define FLOORH 30     // floor height (from bottom of the screen)
#define GRASSH 4      // grass height (inside floor, starts at floor y)
#define COLOR565(r, g, b) ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3)
#define BCKGRDCOL COLOR565(138, 235, 244)    // background
#define BIRDCOL COLOR565(255, 254, 174)      // bird
#define PIPECOL COLOR565(99, 255, 78)        // pipe
#define PIPEHIGHCOL COLOR565(250, 255, 250)  // pipe highlight
#define PIPESEAMCOL COLOR565(0, 0, 0)        // pipe seam
#define FLOORCOL COLOR565(246, 240, 163)     // floor
#define GRASSCOL COLOR565(141, 225, 87)      // grass (col2 is the stripe color)
#define GRASSCOL2 COLOR565(156, 239, 88)     // grass (col2 is the stripe color)
#define C0 BCKGRDCOL                         // bird sprite,bird sprite colors (Cx name for values to keep the array readable)
#define C1 COLOR565(195, 165, 75)
#define C2 BIRDCOL
#define C3 TFT_WHITE
#define C4 TFT_RED
#define C5 COLOR565(251, 216, 114)ESP32Time rtc(0);   // RTC instance with offset in seconds
BH1750 lightMeter;  //BH1750 Instance
QMC5883L compass;
MAX30105 particleSensor;  //MAX30102 instance
OneButton button(PIN_INPUT, true);
TFT_eSPI tft = TFT_eSPI();  // Invoke custom library
TFT_eSprite img = TFT_eSprite(&tft);
TFT_eSprite img1 = TFT_eSprite(&tft);
TFT_eSprite img2 = TFT_eSprite(&tft);static const unsigned int birdcol[] = {C0, C0, C1, C1, C1, C1, C1, C0, C0, C0, C1, C1, C1, C1, C1, C0,C0, C1, C2, C2, C2, C1, C3, C1, C0, C1, C2, C2, C2, C1, C3, C1,C0, C2, C2, C2, C2, C1, C3, C1, C0, C2, C2, C2, C2, C1, C3, C1,C1, C1, C1, C2, C2, C3, C1, C1, C1, C1, C1, C2, C2, C3, C1, C1,C1, C2, C2, C2, C2, C2, C4, C4, C1, C2, C2, C2, C2, C2, C4, C4,C1, C2, C2, C2, C1, C5, C4, C0, C1, C2, C2, C2, C1, C5, C4, C0,C0, C1, C2, C1, C5, C5, C5, C0, C0, C1, C2, C1, C5, C5, C5, C0,C0, C0, C1, C5, C5, C5, C0, C0, C0, C0, C1, C5, C5, C5, C0, C0
};
// bird structure
static struct BIRD {long x, y, old_y;long col;float vel_y;
} bird;
// pipe structure
static struct PIPES {long x, gap_y;long col;
} pipes;
// score
int score;
// temporary x and y var
static short tmpx, tmpy;
// ---------------
// draw pixel
// ---------------
// faster drawPixel method by inlining calls and using setAddrWindow and pushColor using macro to force inlining
#define _drawPixel(a, b, c) \tft.setAddrWindow(a, b, a, b); \tft.pushColor(c)uint maxScore = 0;
float sx = 0, sy = 1, mx = 1, my = 0, hx = -1, hy = 0;  // Saved H, M, S x & y multipliers
float sdeg = 0, mdeg = 0, hdeg = 0;
uint16_t osx = 120, osy = 140, omx = 120, omy = 140, ohx = 120, ohy = 140;  // Saved H, M, S x & y coords
uint16_t x0 = 0, x1 = 0, yy0 = 0, yy1 = 0;
uint32_t targetTime = 0;                       // for next 1 second timeout
static uint8_t conv2d(const char* p);          // Forward declaration needed for IDE 1.6.x
uint8_t hh = 0, t_mm = 0, t_dd = 0, t_mn = 0;  //
uint32_t t_yr = 0;
uint8_t t_hh = 0, mm = 0, ss = 0;
unsigned long lastfacechange = 0;
unsigned long lastwake = 0;
unsigned long lastpressed = 0;
unsigned long lastvaluechange = 0;
bool initial = 1;
volatile int counter = 0;
float VALUE;
float lastValue = 0;
int lastsec = 0;
int pressstate = 0;
unsigned long lastDisplayUpdate = 0;
const byte RATE_SIZE = 4;  //Increase this for more averaging. 4 is good.
byte rates[RATE_SIZE];     //Array of heart rates
byte rateSpot = 0;
long lastBeat = 0;  //Time at which the last beat occurred
float beatsPerMinute;
int beatAvg;
bool beat = false;
double rad = 0.01745;
float x[360];
float y[360];
bool facechange = false;
bool Screenchange = false;
float px[360];
float py[360];
float lx[360];
float ly[360];
int r = 104;
int ssx = 120;
int ssy = 140;
String cc[12] = { "45", "40", "35", "30", "25", "20", "15", "10", "05", "0", "55", "50" };
String days[] = { "SUNDAY", "MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY" };
String days1[] = { "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" };
int start[12];
int startP[60];
const int pwmFreq = 5000;
const int pwmResolution = 8;
const int pwmLedChannelTFT = 0;
int angle = 0;
bool onOff = 0;
bool debounce = 0;
int watchface = 0, Screen = 0, SubScreen = 0, Autoscreen, AutoBright, AutoscreenTime, Brigtnesslevel;
String h, m, s, d1, d2, m1, m2;
unsigned long pressStartTime;// This function is called from the interrupt when the signal on the PIN_INPUT has changed.
// do not use Serial in here.
void IRAM_ATTR checkTicks() {// include all buttons here to be checkedbutton.tick();  // just call tick() to check the state.
}// this function will be called for short click.
void ShortClick() {Serial.println("singleClick() detected.");lastwake = millis();if (Screen == 0) {SubScreen++;if (SubScreen > 2) {SubScreen = 0;facechange = true;}if (SubScreen == 1) {particleSensor.wakeUp();} else {particleSensor.shutDown();}} else if (Screen == 1) {SubScreen++;if (SubScreen > 4) {SubScreen = 0;}Screenchange = true;} else if (Screen == 2) {watchface++;if (watchface > 5) {watchface = 0;}EEPROM.write(0, watchface);EEPROM.commit();facechange = true;Screenchange = true;} else if (Screen == 3) {SubScreen++;if (SubScreen > 5) {SubScreen = 0;}Screenchange = true;} else if (Screen == 4) {SubScreen++;if (SubScreen > 2) {SubScreen = 0;}Screenchange = true;} else if (Screen == 5) {Screen = 6;game_init();game_loop();} else if (Screen == 6) {Screen = 7;} else if (Screen == 7) {Screen = 5;Screenchange = true;}Serial.print("Sub ");Serial.println(SubScreen);tft.fillScreen(colour7);pressstate = 1;facechange = true;lastDisplayUpdate = millis();lastpressed = millis();
}  // ShortClick// long press
void LongPress() {Serial.println("pressStart()");pressStartTime = millis() - 1000;  // as set in setPressTicks()lastwake = millis();lastDisplayUpdate = millis();particleSensor.shutDown();if (Screen == 0) {Screen = 1;SubScreen = 0;} else if (Screen == 1) {if (SubScreen == 0) {Screen = 2;SubScreen = 0;} else if (SubScreen == 1) {Screen = 3;t_hh = rtc.getHour();t_mm = rtc.getMinute();t_dd = rtc.getDay();t_mn = rtc.getMonth();t_yr = rtc.getYear();Serial.println(rtc.getYear());Serial.println(t_yr);SubScreen = 0;} else if (SubScreen == 2) {Screen = 4;SubScreen = 0;facechange = true;Screenchange = true;} else if (SubScreen == 3) {Screen = 5;SubScreen = 0;} else if (SubScreen == 4) {Screen = 0;SubScreen = 0;}} else if (Screen == 2) {Screen = 1;SubScreen = 0;} else if (Screen == 3) {if (SubScreen == 0) {t_hh++;if (t_hh > 23) {t_hh = 0;}} else if (SubScreen == 1) {t_mm++;if (t_mm > 59) {t_mm = 0;}} else if (SubScreen == 2) {t_dd++;if (t_dd > 31) {t_dd = 0;}} else if (SubScreen == 3) {t_mn++;if (t_mn > 12) {t_mn = 0;}} else if (SubScreen == 4) {t_yr++;if (t_yr > 2041) {t_yr = 0;}} else {rtc.setTime(0, t_mm, t_hh, t_dd, t_mn, t_yr);Screen = 1;SubScreen = 1;}} else if (Screen == 4) {if (SubScreen == 0) {AutoBright++;if (AutoBright > 5) {AutoBright = 0;}if (AutoBright > 0) {analogWrite(LCD_BACKLIGHT, AutoBright * 50);}EEPROM.write(2, AutoBright);EEPROM.commit();} else if (SubScreen == 1) {Autoscreen++;if (Autoscreen > 5) {Autoscreen = 0;}EEPROM.write(1, Autoscreen);EEPROM.commit();} else if (SubScreen == 2) {Screen = 1;SubScreen = 2;}} else if (Screen == 5) {Screen = 1;SubScreen = 0;} else if (Screen == 7) {Screen = 1;SubScreen = 0;}facechange = true;Screenchange = true;pressstate = 1;lastpressed = millis();
}void setup(void) {Serial.begin(115200);Serial.println("ESP32 Watch OS.");gpio_hold_dis((gpio_num_t)LCD_BACKLIGHT);pinMode(LCD_BACKLIGHT, OUTPUT);digitalWrite(LCD_BACKLIGHT, LOW);EEPROM.begin(EEPROM_SIZE);EEPROM.writeInt(10, 0);EEPROM.commit();if (EEPROM.read(0) > 3) {EEPROM.write(0, 4);EEPROM.commit();}watchface = EEPROM.read(0);if (EEPROM.read(1) > 5) {EEPROM.write(1, 5);EEPROM.commit();}Autoscreen = EEPROM.read(1);if (EEPROM.read(2) > 5) {EEPROM.write(2, 5);EEPROM.commit();}AutoBright = EEPROM.read(2);//rtc.setTime(ss, mm, hh, 0, 0, 0);  // 26th Jjuly 2022 compile dateparticleSensor.begin(Wire, I2C_SPEED_FAST);particleSensor.setup();                     //Configure sensor with default settingsparticleSensor.setPulseAmplitudeRed(0x0A);  //Turn Red LED to low to indicate sensor is runningparticleSensor.setPulseAmplitudeIR(0xFF);   //Turn Red LED to low to indicate sensor is runningparticleSensor.setPulseAmplitudeGreen(0);   //Turn off Green LEDparticleSensor.shutDown();compass.init();compass.setSamplingRate(50);tft.init();tft.setRotation(0);//tft.setColorDepth(16);tft.setSwapBytes(true);tft.fillScreen(colour7);int xw = tft.width() / 2;  // xw, yh is middle of screenint yh = tft.height() / 2;tft.setPivot(xw, yh);  // Set pivot to middle of TFT screenimg.createSprite(240, 280);img.setTextDatum(4);img1.createSprite(240, 70);img1.setSwapBytes(true);img2.createSprite(240, 70);img2.setSwapBytes(true);targetTime = millis() + 1000;facechange = true;esp_sleep_enable_ext0_wakeup(GPIO_NUM_0, 0);  //1 = High, 0 = Low// setup interrupt routine// when not registering to the interrupt the sketch also works when the tick is called frequently.attachInterrupt(digitalPinToInterrupt(PIN_INPUT), checkTicks, CHANGE);// link the xxxclick functions to be called on xxxclick event.button.attachClick(ShortClick);button.setPressTicks(1000);  // that is the time when LongPressStart is calledbutton.attachLongPressStart(LongPress);lightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE);  //Init BH1750 libraryif (AutoBright == 0) {unsigned int lv = constrain(lightMeter.readLightLevel(), 50, 500);analogWrite(LCD_BACKLIGHT, lv / 2);} else {analogWrite(LCD_BACKLIGHT, AutoBright * 50);}lastwake = millis();
}
int lastAngle = 0;
float circle = 100;
bool dir = 0;
int rAngle = 359;void loop() {button.tick();if (Screen < 5) {watchtask();} else {game();}
}void watchtask() {if (pressstate == 1 && digitalRead(0) == 1) {pressstate = 0;}if (AutoBright == 0) {unsigned int lv = constrain(lightMeter.readLightLevel(), 50, 500);analogWrite(LCD_BACKLIGHT, lv / 2);Serial.print("Light");Serial.println(lv / 2);}if (Autoscreen != 0 && millis() - lastwake > Autoscreen * 60000) {analogWrite(LCD_BACKLIGHT, 0);delay(1000);tft.fillScreen(colour7);gpio_deep_sleep_hold_en();gpio_hold_en((gpio_num_t)LCD_BACKLIGHT);esp_deep_sleep_start();}if (Screen == 0) {if (SubScreen == 0) {watchfacedsp();} else if (SubScreen == 1) {HRApp();} else {CompassApp();}} else if (Screen == 1 && Screenchange == true) {if (SubScreen == 0) {tft.pushImage(0, 0, 240, 280, facechangeicon);} else if (SubScreen == 1) {tft.pushImage(0, 0, 240, 280, timeseticon);} else if (SubScreen == 2) {tft.pushImage(0, 0, 240, 280, settingsicon);} else if (SubScreen == 3) {tft.pushImage(0, 0, 240, 280, gamesicon);} else if (SubScreen == 4) {tft.pushImage(0, 0, 240, 280, exiticon);}} else if (Screen == 3 && Screenchange == true) {timesetiings();Screenchange = false;} else if (Screen == 2 && Screenchange == true) {watchfacedsp();Screenchange = false;} else if (Screen == 4 && Screenchange == true) {settings();Screenchange = false;} else if (Screen == 3 && millis() - lastpressed > 2000 && millis() - lastvaluechange > 500 && pressstate == 1) {if (SubScreen == 0) {t_hh++;if (t_hh > 23) {t_hh = 0;}facechange = true;Screenchange = true;} else if (SubScreen == 1) {t_mm++;if (t_mm > 59) {t_mm = 0;}facechange = true;Screenchange = true;} else if (SubScreen == 2) {facechange = true;Screenchange = true;t_dd++;if (t_dd > 31) {t_dd = 0;}} else if (SubScreen == 3) {t_mn++;if (t_mn > 12) {t_mn = 0;}facechange = true;Screenchange = true;} else if (SubScreen == 4) {t_yr++;if (t_yr > 2041) {t_yr = 0;}facechange = true;Screenchange = true;}lastvaluechange = millis();}
}void timesetiings() {tft.pushImage(0, 0, 240, 280, TimeSettings);tft.setTextColor(TFT_BLACK, TFT_WHITE);tft.setFreeFont(FF18);tft.setTextSize(0);int tt_hh = t_hh;if (tt_hh > 12) {tt_hh = tt_hh - 12;}if (tt_hh < 10) {tft.drawString("0" + String(tt_hh), 25, 96);} else {tft.drawString(String(tt_hh), 25, 96);}if (t_mm < 10) {tft.drawString("0" + String(t_mm), 93, 96);} else {tft.drawString(String(t_mm), 93, 96);}if (t_hh < 13) {tft.drawString("AM", 164, 96);} else {tft.drawString("PM", 164, 96);}if (t_dd < 10) {tft.drawString("0" + String(t_dd), 25, 183);} else {tft.drawString(String(t_dd), 25, 183);}if (t_mn < 10) {tft.drawString("0" + String(t_mn), 93, 183);} else {tft.drawString(String(t_mn), 93, 183);}if (t_yr < 2022) {t_yr = 2022;}tft.drawString(String(t_yr), 161, 183);if (SubScreen == 0) {tft.drawRoundRect(9, 81, 58, 48, 6, Light_Green);} else if (SubScreen == 1) {tft.drawRoundRect(77, 81, 58, 48, 6, Light_Green);} else if (SubScreen == 2) {tft.drawRoundRect(9, 168, 58, 48, 6, Light_Green);} else if (SubScreen == 3) {tft.drawRoundRect(77, 168, 58, 48, 6, Light_Green);} else if (SubScreen == 4) {tft.drawRoundRect(144, 168, 88, 48, 6, Light_Green);} else {tft.drawRoundRect(79, 226, 88, 48, 6, Light_Green);}
}void settings() {tft.pushImage(0, 0, 240, 280, Settingspage);tft.setTextColor(TFT_BLACK, TFT_WHITE);tft.setFreeFont(FF18);tft.setTextSize(0);switch (AutoBright) {case 0: tft.drawString("  Auto  ", 80, 100); break;case 1: tft.drawString("  20%   ", 85, 100); break;case 2: tft.drawString("  40%   ", 85, 100); break;case 3: tft.drawString("  60%   ", 85, 100); break;case 4: tft.drawString("  80%   ", 85, 100); break;case 5: tft.drawString(" 100%   ", 80, 100); break;}switch (Autoscreen) {case 0: tft.drawString("Always On", 65, 185); break;case 1: tft.drawString("1 Minute ", 75, 185); break;case 2: tft.drawString("2 Minute ", 75, 185); break;case 3: tft.drawString("3 Minute ", 75, 185); break;case 4: tft.drawString("4 Minute ", 75, 185); break;case 5: tft.drawString("5 Minute ", 75, 185); break;}if (SubScreen == 0) {tft.drawRoundRect(16, 85, 208, 48, 6, Light_Green);} else if (SubScreen == 1) {tft.drawRoundRect(16, 169, 208, 48, 6, Light_Green);} else {tft.drawRoundRect(66, 225, 108, 48, 6, Light_Green);}
}void HRApp() {long irValue = particleSensor.getIR();  //Reading the IR value it will permit us to know if there's a finger on the sensor or not//Also detecting a heartbeatif (checkForBeat(irValue) == true)  //If a heart beat is detected{long delta = millis() - lastBeat;  //Measure duration between two beatslastBeat = millis();beatsPerMinute = 60 / (delta / 1000.0);  //Calculating the BPMif (beatsPerMinute < 255 && beatsPerMinute > 20)  //To calculate the average we strore some values (4) then do some math to calculate the average{rates[rateSpot++] = (byte)beatsPerMinute;  //Store this reading in the arrayrateSpot %= RATE_SIZE;                     //Wrap variable//Take average of readingsbeatAvg = 0;for (byte x = 0; x < RATE_SIZE; x++)beatAvg += rates[x];beatAvg /= RATE_SIZE;}}if (millis() - lastDisplayUpdate > 500) {if (irValue < 60000) {  //If no finger is detected it inform the user and put the average BPM to 0 or it will be stored for the next measurebeatAvg = 0;img.loadFont(FONT_SMALL);img.setCursor(80, 120);img.setTextColor(TFT_CYAN, colour7);img.fillSprite(colour7);img.println("Please Place ");img.setCursor(80, 140);img.println("your finger ");img.pushSprite(0, 0);} else {img.fillSprite(colour7);  //Clear the displayif (beat == true) {img.pushImage(0, 0, 240, 280, hr1);} else {img.pushImage(0, 0, 240, 280, hr2);}beat = !beat;img.setTextColor(TFT_CYAN, colour7);img.loadFont(FONT_LARGE);img.setCursor(100, 130);img.print(beatAvg);img.setCursor(100, 175);img.loadFont(FONT_SMALL);img.print("BPM ");img.pushRotated(0);}lastDisplayUpdate = millis();}Serial.print("IR=");Serial.print(irValue);Serial.print(", BPM=");Serial.print(beatsPerMinute);Serial.print(", Avg BPM=");Serial.print(beatAvg);if (irValue < 60000)Serial.print(" No finger?");if (millis() - lastBeat > 5000) {beatsPerMinute = 0;beatAvg = 0;}Serial.println();
}void CompassApp() {for (int i = 0; i < 10; i++) {angle = angle + compass.readHeading();}angle = random(355, 360);angle = angle / 10;img.fillSprite(colour7);img.pushImage(0, 20, 240, 240, dial240);img.pushRotated(angle);  // create rotated image as per the angle from the compass sensorangle = 0;
}
void watchfacedsp() {if (facechange) {tft.fillScreen(colour7);if (watchface == 1) {tft.setTextSize(0);tft.pushImage(0, 0, 240, 280, Casio2);tft.setTextColor(0x0081, background);tft.fillRoundRect(48, 127, 128, 48, 5, background);} else if (watchface == 2) {tft.setTextSize(0);tft.pushImage(0, 0, 240, 280, Casio1);tft.setTextColor(0x0081, background);tft.fillRoundRect(48, 127, 128, 48, 5, background);} else if (watchface == 4) {tft.pushImage(0, 0, 240, 280, cdface1);img2.pushImage(0, 0, 240, 100, cdface11);tft.setTextColor(TFT_WHITE);tft.setFreeFont(FF18);tft.setTextSize(2);} else if (watchface == 5) {tft.pushImage(0, 0, 240, 280, cdface2);img2.pushImage(0, 0, 240, 100, cdface12);tft.setTextColor(TFT_WHITE);tft.setFreeFont(FF18);tft.setTextSize(2);}facechange = false;lastfacechange = millis();}if (watchface == 0) {int b = 0;int b2 = 0;for (int i = 0; i < 360; i++) {x[i] = (r * cos(rad * i)) + ssx;y[i] = (r * sin(rad * i)) + ssy;px[i] = ((r - 16) * cos(rad * i)) + ssx;py[i] = ((r - 16) * sin(rad * i)) + ssy;lx[i] = ((r - 26) * cos(rad * i)) + ssx;ly[i] = ((r - 26) * sin(rad * i)) + ssy;if (i % 30 == 0) {start[b] = i;b++;}if (i % 6 == 0) {startP[b2] = i;b2++;}}rAngle = rAngle - 2;angle = rtc.getSecond() * 6;s = String(rtc.getSecond());m = String(rtc.getMinute());h = String(rtc.getHour());if (m.toInt() < 10)m = "0" + m;if (h.toInt() < 10)h = "0" + h;if (s.toInt() < 10)s = "0" + s;if (rtc.getDay() > 10) {d1 = rtc.getDay() / 10;d2 = rtc.getDay() % 10;} else {d1 = "0";d2 = String(rtc.getDay());}if (rtc.getMonth() > 10) {m1 = rtc.getMonth() / 10;m2 = rtc.getMonth() % 10;} else {m1 = "0";m2 = String(rtc.getMonth());}if (angle >= 360)angle = 0;if (rAngle <= 0)rAngle = 359;if (dir == 0)circle = circle + 0.5;elsecircle = circle - 0.5;if (circle > 140)dir = !dir;if (circle < 100)dir = !dir;if (angle > -1) {lastAngle = angle;VALUE = ((angle - 270) / 3.60) * -1;if (VALUE < 0)VALUE = VALUE + 100;img.fillSprite(colour7);img.fillCircle(ssx, ssy, 124, colour7);img.setTextColor(TFT_WHITE, colour7);img.drawString(days[rtc.getDayofWeek()], circle, 140, 2);for (int i = 0; i < 12; i++)if (start[i] + angle < 360) {img.drawString(cc[i], x[start[i] + angle], y[start[i] + angle], 2);img.drawLine(px[start[i] + angle], py[start[i] + angle], lx[start[i] + angle], ly[start[i] + angle], color1);} else {img.drawString(cc[i], x[(start[i] + angle) - 360], y[(start[i] + angle) - 360], 2);img.drawLine(px[(start[i] + angle) - 360], py[(start[i] + angle) - 360], lx[(start[i] + angle) - 360], ly[(start[i] + angle) - 360], color1);}img.setFreeFont(&DSEG7_Modern_Bold_20);img.drawString(s, ssx, ssy - 36);img.setFreeFont(&DSEG7_Classic_Regular_28);img.drawString(h + ":" + m, ssx, ssy + 28);img.setTextFont(0);img.fillRect(70, 86, 12, 20, color3);img.fillRect(84, 86, 12, 20, color3);img.fillRect(150, 86, 12, 20, color3);img.fillRect(164, 86, 12, 20, color3);img.setTextColor(0x35D7, colour7);img.drawString("MONTH", 84, 78);img.drawString("DAY", 162, 78);img.setTextColor(TFT_SKYBLUE, colour7);img.drawString("Circuit Digest", 120, 194);img.drawString("***", 120, 124);img.setTextColor(TFT_WHITE, color3);img.drawString(m1, 77, 96, 2);img.drawString(m2, 91, 96, 2);img.drawString(d1, 157, 96, 2);img.drawString(d2, 171, 96, 2);for (int i = 0; i < 60; i++)if (startP[i] + angle < 360)img.fillCircle(px[startP[i] + angle], py[startP[i] + angle], 1, color1);elseimg.fillCircle(px[(startP[i] + angle) - 360], py[(startP[i] + angle) - 360], 1, color1);img.fillTriangle(ssx - 1, ssy - 70, ssx - 5, ssy - 56, ssx + 4, ssy - 56, TFT_ORANGE);img.fillCircle(px[rAngle], py[rAngle], 6, TFT_RED);img.pushSprite(0, 0);}} else if (rtc.getSecond() != lastsec || Screen == 3) {if (watchface == 1 || watchface == 2) {/*String med;if (rtc.getSecond() % 2) {med = ":";} else {med = " ";}*/tft.setFreeFont(&DSEG7_Classic_Bold_30);if (rtc.getHour() > 9 && rtc.getMinute() > 9) {tft.drawString(String(rtc.getHour()) + ":" + String(rtc.getMinute()), 46, 135);} else if (rtc.getHour() < 10 && rtc.getMinute() > 9) {tft.drawString("0" + String(rtc.getHour()) + ":" + String(rtc.getMinute()), 46, 135);} else if (rtc.getHour() > 9 && rtc.getMinute() < 10) {tft.drawString(String(rtc.getHour()) + ":0" + String(rtc.getMinute()), 46, 135);} else {tft.drawString("0" + String(rtc.getHour()) + ":0" + String(rtc.getMinute()), 46, 135);}tft.setFreeFont(&DSEG7_Classic_Bold_20);if (rtc.getSecond() < 10) {tft.drawString("0" + String(rtc.getSecond()), 154, 145);} else {tft.drawString(String(rtc.getSecond()), 154, 145);}tft.setFreeFont(&DSEG14_Classic_Bold_18);tft.drawString(days1[rtc.getDayofWeek()], 94, 106);tft.drawString(String(rtc.getDay()), 156, 106);} else if (watchface == 3) {img.setTextColor(TFT_WHITE, colour7);  // Adding a background colour erases previous text automatically// Draw clock faceimg.fillCircle(120, 140, 118, TFT_GREEN);img.fillCircle(120, 140, 110, colour7);// Draw 12 linesfor (int i = 0; i < 360; i += 30) {sx = cos((i - 90) * 0.0174532925);sy = sin((i - 90) * 0.0174532925);x0 = sx * 114 + 120;yy0 = sy * 114 + 140;x1 = sx * 100 + 120;yy1 = sy * 100 + 140;img.drawLine(x0, yy0, x1, yy1, TFT_GREEN);}// Draw 60 dotsfor (int i = 0; i < 360; i += 6) {sx = cos((i - 90) * 0.0174532925);sy = sin((i - 90) * 0.0174532925);x0 = sx * 102 + 120;yy0 = sy * 102 + 140;// Draw minute markersimg.drawPixel(x0, yy0, TFT_WHITE);// Draw main quadrant dotsif (i == 0 || i == 180) img.fillCircle(x0, yy0, 2, TFT_WHITE);if (i == 90 || i == 270) img.fillCircle(x0, yy0, 2, TFT_WHITE);}img.fillCircle(120, 141, 3, TFT_WHITE);// Pre-compute hand degrees, x & y coords for a fast screen updatesdeg = rtc.getSecond() * 6;                      // 0-59 -> 0-354mdeg = rtc.getMinute() * 6 + sdeg * 0.01666667;  // 0-59 -> 0-360 - includes secondshdeg = rtc.getHour() * 30 + mdeg * 0.0833333;    // 0-11 -> 0-360 - includes minutes and secondshx = cos((hdeg - 90) * 0.0174532925);hy = sin((hdeg - 90) * 0.0174532925);mx = cos((mdeg - 90) * 0.0174532925);my = sin((mdeg - 90) * 0.0174532925);sx = cos((sdeg - 90) * 0.0174532925);sy = sin((sdeg - 90) * 0.0174532925);if (rtc.getSecond() == 0 || initial) {initial = 0;// Erase hour and minute hand positions every minuteimg.drawLine(ohx, ohy, 120, 141, colour7);ohx = hx * 62 + 121;ohy = hy * 62 + 141;img.drawLine(omx, omy, 120, 141, colour7);omx = mx * 84 + 120;omy = my * 84 + 141;}// Redraw new hand positions, hour and minute hands not erased here to avoid flickerimg.drawLine(osx, osy, 120, 141, colour7);osx = sx * 90 + 121;osy = sy * 90 + 141;img.drawLine(osx, osy, 120, 141, TFT_RED);img.drawLine(ohx, ohy, 120, 141, TFT_WHITE);img.drawLine(omx, omy, 120, 141, TFT_WHITE);img.drawLine(osx, osy, 120, 141, TFT_RED);img.fillCircle(120, 141, 3, TFT_RED);img.pushSprite(0, 0);} else if (watchface == 4 || watchface == 5) {img1.setTextColor(TFT_WHITE, TFT_BLACK);img1.setFreeFont(FF24);img1.fillSprite(TFT_BLACK);//img1.setTextSize(2);if (rtc.getHour() > 9 && rtc.getMinute() > 9) {img1.drawString(String(rtc.getHour()) + ":" + String(rtc.getMinute()), 66, 30);} else if (rtc.getHour() < 10 && rtc.getMinute() > 9) {img1.drawString("0" + String(rtc.getHour()) + ":" + String(rtc.getMinute()), 66, 30);} else if (rtc.getHour() > 9 && rtc.getMinute() < 10) {img1.drawString(String(rtc.getHour()) + ":0" + String(rtc.getMinute()), 66, 30);} else {img1.drawString("0" + String(rtc.getHour()) + +":0" + String(rtc.getMinute()), 66, 30);}img1.setFreeFont(FF22);if (rtc.getSecond() < 10) {img1.drawString("0" + String(rtc.getSecond()), 190, 40);} else {img1.drawString(String(rtc.getSecond()), 190, 40);}img1.drawString(days1[rtc.getDayofWeek()] + " " + String(rtc.getDay()) + " " + String(rtc.getYear()), 54, 0);//img1.drawString(String(rtc.getDay()), 156, 0);img2.pushSprite(0, 180);img1.pushSprite(0, 180, TFT_BLACK);}lastsec = rtc.getSecond();}
}static uint8_t conv2d(const char* p) {uint8_t v = 0;if ('0' <= *p && *p <= '9')v = *p - '0';return 10 * v + *++p - '0';
}void game() {if (Screen == 5 && Screenchange == 1) {Screenchange = 0;tft.fillScreen(TFT_BLACK);tft.fillRect(10, TFTH2 - 20, TFTW - 20, 1, TFT_WHITE);tft.fillRect(10, TFTH2 + 32, TFTW - 20, 1, TFT_WHITE);tft.setTextColor(TFT_WHITE);tft.setFreeFont(0);tft.setTextSize(2);// half width - num char * char width in pixelstft.setCursor(TFTW2 - (6 * 9), TFTH2 - 16);tft.println("FLAPPY");tft.setTextSize(2);tft.setCursor(TFTW2 - (6 * 9), TFTH2 + 8);tft.println("-BIRD-");} else if (Screen == 7 && Screenchange == 1) {Screenchange = 0;tft.fillScreen(TFT_BLACK);maxScore = EEPROM.readInt(10);if (score > maxScore) {EEPROM.writeInt(10, score);EEPROM.commit();maxScore = score;tft.setTextColor(TFT_RED);tft.setTextSize(2);tft.setCursor(TFTW2 - (13 * 6), TFTH2 - 26);tft.println("NEW HIGHSCORE");}tft.setTextColor(TFT_WHITE);tft.setTextSize(3);// half width - num char * char width in pixelstft.setCursor(TFTW2 - (9 * 9), TFTH2 - 6);tft.println("GAME OVER");tft.setTextSize(2);tft.setCursor(10, 10);tft.print("score: ");tft.print(score);tft.setCursor(TFTW2 - (12 * 6), TFTH2 + 18);tft.println("press button");tft.setCursor(10, 28);tft.print("Max Score:");tft.print(maxScore);tft.setTextSize(0);facechange = 1;}
}void game_init() {// clear screentft.fillScreen(BCKGRDCOL);// reset scorescore = 0;// init birdbird.x = 144;bird.y = bird.old_y = TFTH2 - BIRDH;bird.vel_y = -JUMP_FORCE;tmpx = tmpy = 0;// generate new random seed for the pipe gaperandomSeed(analogRead(12));// init pipepipes.x = 0;pipes.gap_y = random(20, TFTH - 60);
}void game_loop() {// ===============// prepare game variables// draw floor// ===============// instead of calculating the distance of the floor from the screen height each time store it in a variableconst unsigned char GAMEH = TFTH - FLOORH;// draw the floor once, we will not overwrite on this area in-game// black linetft.drawFastHLine(0, GAMEH, TFTW, TFT_BLACK);// grass and stripetft.fillRect(0, GAMEH + 1, TFTW2, GRASSH, GRASSCOL);tft.fillRect(TFTW2, GAMEH + 1, TFTW2, GRASSH, GRASSCOL2);// black linetft.drawFastHLine(0, GAMEH + GRASSH, TFTW, TFT_BLACK);// mudtft.fillRect(0, GAMEH + GRASSH + 1, TFTW, FLOORH - GRASSH, FLOORCOL);// grass x position (for stripe animation)long grassx = TFTW;// game loop time variablesdouble delta, old_time, next_game_tick, current_time;next_game_tick = current_time = millis();// passed pipe flag to count scorebool passed_pipe = false;// temp var for setAddrWindowunsigned char px;while (true) {yield();int loops = 0;while (millis() > next_game_tick && loops < MAX_FRAMESKIP) {// ===============// input// ===============if (digitalRead(0) == LOW) {// if the bird is not too close to the top of the screen apply jump forceif (bird.y > BIRDH2 * 0.5)bird.vel_y = -JUMP_FORCE;// else zero velocityelsebird.vel_y = 0;}// ===============// update// ===============// calculate delta time// ---------------old_time = current_time;current_time = millis();delta = (current_time - old_time) / 1000;// bird// ---------------bird.vel_y += GRAVITY * delta;bird.y += bird.vel_y;// pipe// ---------------pipes.x -= SPEED;// if pipe reached edge of the screen reset its position and gapif (pipes.x < -PIPEW) {pipes.x = TFTW;pipes.gap_y = random(10, GAMEH - (10 + GAPHEIGHT));}// ---------------next_game_tick += SKIP_TICKS;loops++;}// ===============// draw// ===============// pipe// ---------------// we save cycles if we avoid drawing the pipe when outside the screenif (pipes.x >= 0 && pipes.x < TFTW) {// pipe colortft.drawFastVLine(pipes.x + 3, 0, pipes.gap_y, PIPECOL);tft.drawFastVLine(pipes.x + 3, pipes.gap_y + GAPHEIGHT + 1, GAMEH - (pipes.gap_y + GAPHEIGHT + 1), PIPECOL);// highlighttft.drawFastVLine(pipes.x, 0, pipes.gap_y, PIPEHIGHCOL);tft.drawFastVLine(pipes.x, pipes.gap_y + GAPHEIGHT + 1, GAMEH - (pipes.gap_y + GAPHEIGHT + 1), PIPEHIGHCOL);// bottom and top border of pipe_drawPixel(pipes.x, pipes.gap_y, PIPESEAMCOL);_drawPixel(pipes.x, pipes.gap_y + GAPHEIGHT, PIPESEAMCOL);// pipe seam_drawPixel(pipes.x, pipes.gap_y - 6, PIPESEAMCOL);_drawPixel(pipes.x, pipes.gap_y + GAPHEIGHT + 6, PIPESEAMCOL);_drawPixel(pipes.x + 3, pipes.gap_y - 6, PIPESEAMCOL);_drawPixel(pipes.x + 3, pipes.gap_y + GAPHEIGHT + 6, PIPESEAMCOL);}// erase behind pipeif (pipes.x <= TFTW)tft.drawFastVLine(pipes.x + PIPEW, 0, GAMEH, BCKGRDCOL);// bird// ---------------tmpx = BIRDW - 1;do {px = bird.x + tmpx + BIRDW;// clear bird at previous position stored in old_y// we can't just erase the pixels before and after current position// because of the non-linear bird movement (it would leave 'dirty' pixels)tmpy = BIRDH - 1;do {_drawPixel(px, bird.old_y + tmpy, BCKGRDCOL);} while (tmpy--);// draw bird sprite at new positiontmpy = BIRDH - 1;do {_drawPixel(px, bird.y + tmpy, birdcol[tmpx + (tmpy * BIRDW)]);} while (tmpy--);} while (tmpx--);// save position to erase bird on next drawbird.old_y = bird.y;// grass stripes// ---------------grassx -= SPEED;if (grassx < 0)grassx = TFTW;tft.drawFastVLine(grassx % TFTW, GAMEH + 1, GRASSH - 1, GRASSCOL);tft.drawFastVLine((grassx + 64) % TFTW, GAMEH + 1, GRASSH - 1, GRASSCOL2);// ===============// collision// ===============// if the bird hit the ground game overif (bird.y > GAMEH - BIRDH)break;// checking for bird collision with pipeif (bird.x + BIRDW >= pipes.x - BIRDW2 && bird.x <= pipes.x + PIPEW - BIRDW) {// bird entered a pipe, check for collisionif (bird.y < pipes.gap_y || bird.y + BIRDH > pipes.gap_y + GAPHEIGHT)break;elsepassed_pipe = true;}// if bird has passed the pipe increase scoreelse if (bird.x > pipes.x + PIPEW - BIRDW && passed_pipe) {passed_pipe = false;// erase score with background colortft.setTextColor(BCKGRDCOL);tft.setCursor(TFTW2, 4);tft.print(score);// set text color back to white for new scoretft.setTextColor(TFT_WHITE);// increase score since we successfully passed a pipescore++;}// update score// ---------------tft.setCursor(TFTW2, 4);tft.print(score);}// add a small delay to show how the player lostScreen = 7;Screenchange = 1;delay(1200);
}

 

 


http://www.ppmy.cn/news/1551770.html

相关文章

win32com python 操作wps 解决修改 表格触发关闭 其他excel的功能

win32com python 操作wps 是很方便的一个东西 之前唯一的缺点就是会关闭wps的表格。 解决思路 新开wps 进程来处理 import os import subprocess import win32com.client as win32 import timedata [] def get_col_value(sheet):# 参数设置row_index 2 # 第二行start_colum…

记一次搞校园网的经历

接教室的校园网&#xff0c;到另一个屋子玩电脑&#xff0c;隔墙想放大一下AP的信号&#xff0c;发现死活不行 这是现状 由于校园网认证的存在&#xff0c;无法用桥接&#xff0c;桥接需要路由器有IP&#xff0c;而这个IP无法用未刷机的路由器来打开校园网页面认证 解决 将一…

Windows 服务器虚拟化技术详解

一、什么是 Windows 服务器虚拟化&#xff1f; Windows 服务器虚拟化是指通过虚拟化技术在一台物理服务器上运行多个虚拟机&#xff08;VMs&#xff09;。这些虚拟机&#xff08;VMs&#xff09;能够独立运行不同的操作系统、应用程序和服务。虚拟化技术允许多台虚拟机共享物理…

嵌入式Linux中常用的文件系统类型

嵌入式Linux系统中使用的文件系统类型多种多样&#xff0c;每种都有其特点和适用场景。以下是几种常见的嵌入式Linux文件系统类型及其特性&#xff1a; 只读压缩文件系统 SquashFS&#xff1a;一种高度压缩的只读文件系统&#xff0c;适合用于固件映像&#xff0c;它能够提供高…

C# 13 中的新增功能

C# 12 中的新增功能C# 11 中的新增功能C# 10 中的新增功能C# 9.0 中的新增功能C# 8.0 中的新增功能C&#xff03;7.0中有哪些新特性&#xff1f;C#6.0中10大新特性的应用和总结C# 5.0五大新特性 将C#语言版本升级为预览版 C# 13 包括一些新增功能。 可以使用最新的 Visual Stu…

存储结构及关系(一)

学习目标 描述数据库的逻辑结构列出段类型及其用途列出控制块空间使用的关键字获取存储结构信息 段的类型 段是数据库中占用空间的对象。它们使用数据库数据文件中的空间。介绍不同类型的段。 表 表是在数据库中存储数据的最常用方法。表段用于存储既没有集群也没有分区的表…

Python 中的 Lxml 库与 XPath 用法

Python 中的 Lxml 库与 XPath 用法 Python 中的 Lxml 库与 XPath 用法Lxml安装 Lxml基础用法加载文档解析与查询创建新的 XML/HTML 高级特性1. 复杂的 XPath 查询2. DTD 和 Schema 验证3. XSLT 变换4. 自定义命名空间5. 异常处理6. 大文件流式处理7. 并发和线程安全性8. 性能优…

vscode的markdown扩展问题

使用vscode编辑markdown文本时&#xff0c;我是用的是Office Viewer(Markdown Editor)这个插件 今天突然发现不能用了&#xff0c;点击切换编辑视图按钮时会弹出报错信息&#xff1a; command office.markdown.switch not found 在网上找了很久发现没有有关这个插件的文章………