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

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

这个完整的项目是由 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.mzph.cn/diannao/62770.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

算法魅力之牛叉的前缀和

1.什么是前缀和 前缀和算法&#xff08;Prefix Sum Algorithm&#xff09; 是一种常用的算法技巧&#xff0c;用于快速计算数组的某些子数组的和。它通过提前计算出数组中元素的累加和&#xff0c;来加速后续的区间和查询&#xff0c;特别适用于需要频繁查询子数组和的场景。 …

Java JVM(内存结构,垃圾回收,类加载,内存模型)

一、JVM 主要功能 1. 什么是 jvm&#xff1f; JVM&#xff08;Java Virtual Machine)&#xff1a;负责运行 Java 程序的核心组件。它将 Java 字节码&#xff08;.class 文件&#xff09;解释或编译为机器代码&#xff0c;并提供内存管理、垃圾回收和线程管理等功能。 JRE (J…

机器学习基础之集成学习

集成学习&#xff08;Ensemble Learning&#xff09;是一种强大的机器学习方法&#xff0c;它通过结合多个模型的预测结果来提高整体的学习效果。集成学习方法在许多实际应用中表现出了优秀的性能&#xff0c;尤其在处理复杂问题时&#xff0c;它常常能够比单一模型取得更好的结…

33 基于单片机的智能窗帘控制系统

目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 基于51单片机&#xff0c;采用DHT11温湿度传感器检测温湿度&#xff0c;滑动变阻器连接ADC0832数模转换器转换模拟,光敏传感器&#xff0c;采用GP2D12红外传感器&#xff0c;通过LCD1602显示屏显示…

使用docker-compese部署SFTPGo详解

官网&#xff1a;SFTP & FTP as a Managed Service (SaaS) and On-premise 一、SFTPGo简介 SFTPGo 是一款功能强大的文件传输服务器软件。它支持多种协议&#xff08;SFTP、SCP、FTP/S、WebDAV、HTTP/S&#xff09;和多个存储后端。 借助 SFTPGo&#xff0c;您可以利用本地…

我与Linux的爱恋:消息队列

​ ​ &#x1f525;个人主页&#xff1a;guoguoqiang. &#x1f525;专栏&#xff1a;Linux的学习 文章目录 消息队列的引入以及基本概念**​消息队列的基本概念** 消息队列与命名管道和共享内存的不同消息队列的原理消息队列工作流程 System V 消息队列的主要函数msggetms…

LWIP和FATFS 实现 FTP 服务端

目录 一、前言 二、LWIP 和 FTP 简介 1.LWIP 2.FTP 三、实现 FTP 服务端的主要步骤 1.初始化 LWIP 2.创建 FTP 服务器任务 3.处理客户端连接 4.实现 FTP 命令处理 5.文件系统操作 6.错误处理和日志记录 四、示例代码 1.创建FTP任务 2. FTP任务代码 3.处理交互数据…

Java基础访问修饰符全解析

一、Java 访问修饰符概述 Java 中的访问修饰符用于控制类、方法、变量和构造函数的可见性和访问权限&#xff0c;主要有四种&#xff1a;public、protected、default&#xff08;无修饰符&#xff09;和 private。 Java 的访问修饰符在编程中起着至关重要的作用&#xff0c;它…

llvm源码编译

0x00 获取llvm源码 获取llvm项目源码&#xff1a;git clone https://github.com/llvm/llvm-project.git 但是&#xff0c;该项目较大&#xff0c;且直接从github下载源码可能会超时失败。可利用gitee的镜像项目进行clone&#xff1a;git clone --depth 1 https://gitee.com/m…

SpringBoot源码-Spring Boot启动时控制台为何会打印logo以及自定义banner.txt文件控制台打印

1.当我们启动一个SpringBoot项目的时候&#xff0c;入口程序就是main方法&#xff0c;而在main方法中就执行了一个run方法。 SpringBootApplication public class StartApp {public static void main(String[] args) {// testSpringApplication.run(StartApp.class);} }publi…

Uniad复现学习

在优云平台部署训练&#xff0c;加速训练。 关于UCloud(优刻得)旗下的compshare算力共享平台 UCloud(优刻得)是中国知名的中立云计算服务商&#xff0c;科创板上市&#xff0c;中国云计算第一股。 UCloud&#xff08;优刻得&#xff09;旗下的Compshare算力共享平台具有以下优点…

数学建模——Topsis法

数模评价类&#xff08;2&#xff09;——Topsis法 概述 Topsis:Technique for Order Preference by Similarity to Ideal Solution 也称优劣解距离法&#xff0c;该方法的基本思想是&#xff0c;通过计算每个备选方案与理想解和负理想解之间的距离&#xff0c;从而评估每个…

基于单片机的四位数码管检测有毒气体

目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 基于51单片机&#xff0c;通过滑动变阻器连接ADC0832数模转换器模拟有毒气体浓度检测&#xff0c;通过数码管实时显示&#xff0c;如果超过阈值&#xff0c;则蜂鸣器报警&#xff0c;灯光亮起。按…

小程序 - 比较数字大小

小程序交互练习 - 比较数字大小的小程序 目录 比较数字大小 功能描述 准备工作 页面内容 设置页面事件 页面绑定事件 比较大小 按钮绑定事件 比较事件 设置结果显示 页面样式 功能截图 总结 比较数字大小 本案例将实现“比较数字大小”微信小程序&#xff0c;它的…

windows下用mysqld启动免安装mysql

windows系统可以下载免安装版本&#xff0c;就是绿色版&#xff0c;里面包含mysql运行的所有必要条件。 ![[Pasted image 20241128231459.png]] 启动步骤&#xff1a; 解压&#xff0c;然后在解压目录创建my.ini。 [mysqld] # 设置13306端口 port13306# 设置mysql的安装目录…

windows安装itop

本文介绍 win10 安装 itop 安装WAMP集成环境前 先安装visual c 安装itop前需要安装WAMP集成环境(windowsApacheMysqlPHP) 所需文件百度云盘 通过网盘分享的文件&#xff1a;itop.zip 链接: https://pan.baidu.com/s/1D5HrKdbyEaYBZ8_IebDQxQ 提取码: m9fh 步骤一&#xff1…

Leetcode - 周赛425

目录 一&#xff0c;3364. 最小正和子数组 二&#xff0c; 3365. 重排子字符串以形成目标字符串 三&#xff0c;3366. 最小数组和 四&#xff0c;3367. 移除边之后的权重最大和 一&#xff0c;3364. 最小正和子数组 本题可以直接暴力枚举&#xff0c;代码如下&#xff1a; …

微服务即时通讯系统的实现(服务端)----(2)

目录 1. 语音识别子服务的实现1.1 功能设计1.2 模块划分1.3 模块功能示意图1.4 接口的实现 2. 文件存储子服务的实现2.1 功能设计2.2 模块划分2.3 模块功能示意图2.4 接口的实现 3. 用户管理子服务的实现3.1 功能设计3.2 模块划分3.3 功能模块示意图3.4 数据管理3.4.1 关系数据…

Matlab Simulink HDL Coder开发流程(一)— 创建HDL兼容的Simulink模型

创建HDL兼容的Simulink模型 一、使用Balnk DUT模板二、从HDL Coder库中选择模块三、为DUT开发算法/功能四、为设计创建Testbench五、仿真验证设计功能六、Simulink模型生成HDL代码 这个例子说明了如何创建一个用于生成HDL代码的Simulink模型。要创建兼容HDL代码生成的MATLAB算法…

mfc110u.dll是什么意思,mfc110u.dll丢失解决方法大全详解

mfc110u.dll是Microsoft Foundation Classes (MFC)库的一个特定版本&#xff08;版本11.0&#xff09;的Unicode动态链接库文件。MFC是Microsoft为C开发者设计的一个应用程序框架&#xff0c;主要用于简化Windows应用程序的开发工作。这个框架封装了很多Windows API函数&#x…