回到首页 返回首页
回到顶部 回到顶部
返回上一页 返回上一页

【花雕学编程】Arduino动手做(249)---ESP32 1.28寸 TFT GC9A01屏之3 个独立旋转的立方体倾斜 20°位置随机 简单

头像 驴友花雕 2025.04.11 6 0

00 (2).jpg
02.jpg
03.jpg
10 (1).jpg
12.jpg
15.jpg

  【Arduino】189种传感器模块系列实验(资料代码+仿真编程+图形编程)
 实验二百四十九:1.28寸圆形彩色TFT显示屏 高清IPS 模块 240*240 SPI接口GC9A01驱动
 项目之六十二:GC9A01园屏之3个独立旋转的立方体倾斜 20°位置随机

实验开源代码

 

代码
/*
  【Arduino】189种传感器模块系列实验(资料代码+仿真编程+图形编程)
  实验二百四十九:1.28寸圆形彩色TFT显示屏 高清IPS 模块 240*240 SPI接口GC9A01驱动
  项目之六十二:GC9A01园屏之3个独立旋转的立方体倾斜 20°位置随机
*/

//       GC9A01---------- ESP32
//       RST ------------ NC(复位引脚,此处未连接)
//       CS ------------- D4(片选引脚,连接到ESP32的D4引脚)
//       DC ------------- D2(数据/命令选择引脚,连接到ESP32的D2引脚)
//       SDA ------------ D23 (green)(主数据输出引脚,连接到ESP32的D23引脚,绿色线)
//       SCL ------------ D18 (yellow)(时钟信号引脚,连接到ESP32的D18引脚,黄色线)
//       GND ------------ GND(接地引脚,连接到ESP32的接地端)
//       VCC -------------3V3(电源引脚,连接到ESP32的3.3V电源)

#include "SPI.h"                      // **包含 SPI 库,用于 TFT 屏幕通信**
#include "Adafruit_GFX.h"             // **包含 Adafruit GFX 图形库,用于绘制图形**
#include "Adafruit_GC9A01A.h"         // **包含 GC9A01A 屏幕驱动库**

#define TFT_CS 4                      // **定义 TFT 屏幕片选引脚**
#define TFT_DC 2                      // **定义 TFT 屏幕数据/命令选择引脚**
#define TFT_RST -1                    // **屏幕复位引脚(-1 表示未使用)**

Adafruit_GC9A01A tft = Adafruit_GC9A01A(TFT_CS, TFT_DC, TFT_RST); // **创建 TFT 屏幕对象**

#define SCREEN_WIDTH 240               // **屏幕宽度**
#define SCREEN_HEIGHT 240              // **屏幕高度**
#define CUBE_SIZE 30                   // **立方体的大小**
#define ROTATION_SPEED 0.05            // **立方体旋转速度**
#define TILT_ANGLE 20 * M_PI / 180     // **定义倾斜角度(20°转换为弧度)**

float angles[3] = {0, 0, 0};           // **3 个立方体的旋转角度**
int cubeX[3];                          // **3 个立方体的 X 坐标**
int cubeY[3];                          // **3 个立方体的 Y 坐标**
uint16_t colors[3] = {
  tft.color565(255, 0, 0),  // **红色**
  tft.color565(0, 0, 255),  // **蓝色**
  tft.color565(0, 255, 0)   // **绿色**
};

// **定义立方体的 3D 顶点坐标**
float cubeVertices[8][3] = {
  { -CUBE_SIZE, -CUBE_SIZE, -CUBE_SIZE},
  { CUBE_SIZE, -CUBE_SIZE, -CUBE_SIZE},
  { CUBE_SIZE,  CUBE_SIZE, -CUBE_SIZE},
  { -CUBE_SIZE,  CUBE_SIZE, -CUBE_SIZE},
  { -CUBE_SIZE, -CUBE_SIZE,  CUBE_SIZE},
  { CUBE_SIZE, -CUBE_SIZE,  CUBE_SIZE},
  { CUBE_SIZE,  CUBE_SIZE,  CUBE_SIZE},
  { -CUBE_SIZE,  CUBE_SIZE,  CUBE_SIZE}
};

// **绘制立方体的边**
void drawEdge(int i, int j, float transformedVertices[8][2], uint16_t color) {
  tft.drawLine(transformedVertices[i][0], transformedVertices[i][1],
               transformedVertices[j][0], transformedVertices[j][1], color);
}

void setup() {
  Serial.begin(115200);               // **初始化串口,方便调试**
  tft.begin();                        // **初始化 TFT 屏幕**
  tft.setRotation(1);                 // **设置屏幕方向**
  tft.fillScreen(tft.color565(0, 0, 0));  // **设置黑色背景**

  // **随机生成 3 个立方体的位置**
  for (int i = 0; i < 3; i++) {
    cubeX[i] = random(40, SCREEN_WIDTH - 40);  // **X 位置随机**
    cubeY[i] = random(40, SCREEN_HEIGHT - 40); // **Y 位置随机**
  }
}

void loop() {
  tft.fillScreen(tft.color565(0, 0, 0)); // **清屏,避免重影**

  // **循环绘制 3 个立方体**
  for (int c = 0; c < 3; c++) {
    float transformedVertices[8][2]; // **存储立方体的 2D 投影坐标**

    for (int i = 0; i < 8; i++) {
      float x = cubeVertices[i][0];  // **获取 X 坐标**
      float y = cubeVertices[i][1];  // **获取 Y 坐标**
      float z = cubeVertices[i][2];  // **获取 Z 坐标**

      // **绕 X 轴倾斜 20°**
      float tiltedY = y * cos(TILT_ANGLE) - z * sin(TILT_ANGLE);
      float tiltedZ = y * sin(TILT_ANGLE) + z * cos(TILT_ANGLE);

      // **绕 Y 轴旋转**
      float rotatedX = x * cos(angles[c]) - tiltedZ * sin(angles[c]);
      float rotatedZ = x * sin(angles[c]) + tiltedZ * cos(angles[c]);

      // **投影到 2D 屏幕**
      transformedVertices[i][0] = cubeX[c] + rotatedX;
      transformedVertices[i][1] = cubeY[c] + tiltedY;
    }

    // **绘制立方体的 12 条边**
    int edges[12][2] = {
      {0, 1}, {1, 2}, {2, 3}, {3, 0},  // **前面四条边**
      {4, 5}, {5, 6}, {6, 7}, {7, 4},  // **背面四条边**
      {0, 4}, {1, 5}, {2, 6}, {3, 7}   // **连接前后面的四条边**
    };

    for (int i = 0; i < 12; i++) {
      drawEdge(edges[i][0], edges[i][1], transformedVertices, colors[c]); // **绘制边线**
    }

    angles[c] += ROTATION_SPEED;  // **更新旋转角度**
  }

  delay(50); // **延迟以控制动画速度**
}

代码解读:

1. 注释与硬件连接说明

/*
 【Arduino】189种传感器模块系列实验(资料代码+仿真编程+图形编程)
 实验二百四十九:1.28寸圆形彩色TFT显示屏 高清IPS 模块 240*240 SPI接口GC9A01驱动
 项目之六十二:GC9A01园屏之3个独立旋转的立方体倾斜 20°位置随机
*/

//       GC9A01---------- ESP32
//       RST ------------ NC(复位引脚,此处未连接)
//       CS ------------- D4(片选引脚,连接到ESP32的D4引脚)
//       DC ------------- D2(数据/命令选择引脚,连接到ESP32的D2引脚)
//       SDA ------------ D23 (green)(主数据输出引脚,连接到ESP32的D23引脚,绿色线)
//       SCL ------------ D18 (yellow)(时钟信号引脚,连接到ESP32的D18引脚,黄色线)
//       GND ------------ GND(接地引脚,连接到ESP32的接地端)
//       VCC -------------3V3(电源引脚,连接到ESP32的3.3V电源)

这部分内容主要是代码的注释和硬件连接说明,告知开发者该代码的实验背景、项目名称以及 GC9A01 显示屏与 ESP32 开发板之间的引脚连接关系。

2. 头文件包含

#include "SPI.h"                      // **包含 SPI 库,用于 TFT 屏幕通信**
#include "Adafruit_GFX.h"             // **包含 Adafruit GFX 图形库,用于绘制图形**
#include "Adafruit_GC9A01A.h"         // **包含 GC9A01A 屏幕驱动库**

SPI.h:引入 SPI(串行外设接口)库,用于与 TFT 屏幕进行通信,实现数据的传输。
Adafruit_GFX.h:Adafruit 的图形库,提供了一系列用于绘制图形的基本函数,如绘制线条、矩形、圆形等。
Adafruit_GC9A01A.h:专门针对 GC9A01A 屏幕的驱动库,用于控制该型号的显示屏。

3. 引脚定义与对象创建

#define TFT_CS 4                      // **定义 TFT 屏幕片选引脚**
#define TFT_DC 2                      // **定义 TFT 屏幕数据/命令选择引脚**
#define TFT_RST -1                    // **屏幕复位引脚(-1 表示未使用)**

Adafruit_GC9A01A tft = Adafruit_GC9A01A(TFT_CS, TFT_DC, TFT_RST); // **创建 TFT 屏幕对象**

TFT_CS、TFT_DC、TFT_RST:分别定义了 TFT 屏幕的片选引脚、数据 / 命令选择引脚和复位引脚。TFT_RST 为 -1 表示不使用复位引脚。
Adafruit_GC9A01A tft:创建了一个 Adafruit_GC9A01A 类的对象 tft,用于控制 TFT 屏幕。

4. 常量与变量定义

#define SCREEN_WIDTH 240               // **屏幕宽度**
#define SCREEN_HEIGHT 240              // **屏幕高度**
#define CUBE_SIZE 30                   // **立方体的大小**
#define ROTATION_SPEED 0.05            // **立方体旋转速度**
#define TILT_ANGLE 20 * M_PI / 180     // **定义倾斜角度(20°转换为弧度)**

float angles[3] = {0, 0, 0};           // **3 个立方体的旋转角度**
int cubeX[3];                          // **3 个立方体的 X 坐标**
int cubeY[3];                          // **3 个立方体的 Y 坐标**
uint16_t colors[3] = {
 tft.color565(255, 0, 0),  // **红色**
 tft.color565(0, 0, 255),  // **蓝色**
 tft.color565(0, 255, 0)   // **绿色**
};

SCREEN_WIDTH 和 SCREEN_HEIGHT:定义了屏幕的宽度和高度,均为 240 像素。
CUBE_SIZE:立方体的大小,即立方体每条边的长度。
ROTATION_SPEED:立方体的旋转速度,控制立方体旋转的快慢。
TILT_ANGLE:将 20° 转换为弧度,用于立方体的倾斜操作。
angles:一个包含 3 个元素的数组,用于存储 3 个立方体的旋转角度,初始值都为 0。
cubeX 和 cubeY:分别存储 3 个立方体的 X 坐标和 Y 坐标。
colors:一个包含 3 个元素的数组,存储 3 个立方体的颜色,分别为红色、蓝色和绿色。

5. 立方体顶点坐标定义

// **定义立方体的 3D 顶点坐标**
float cubeVertices[8][3] = {
 { -CUBE_SIZE, -CUBE_SIZE, -CUBE_SIZE},
 { CUBE_SIZE, -CUBE_SIZE, -CUBE_SIZE},
 { CUBE_SIZE,  CUBE_SIZE, -CUBE_SIZE},
 { -CUBE_SIZE,  CUBE_SIZE, -CUBE_SIZE},
 { -CUBE_SIZE, -CUBE_SIZE,  CUBE_SIZE},
 { CUBE_SIZE, -CUBE_SIZE,  CUBE_SIZE},
 { CUBE_SIZE,  CUBE_SIZE,  CUBE_SIZE},
 { -CUBE_SIZE,  CUBE_SIZE,  CUBE_SIZE}
};

这是一个二维数组,存储了立方体的 8 个顶点的三维坐标,用于后续的坐标变换和绘制。

6. 绘制立方体边的函数

// **绘制立方体的边**
void drawEdge(int i, int j, float transformedVertices[8][2], uint16_t color) {
 tft.drawLine(transformedVertices[i][0], transformedVertices[i][1],
              transformedVertices[j][0], transformedVertices[j][1], color);
}

drawEdge 函数用于绘制立方体的边,接收两个顶点的索引 i 和 j、存储变换后二维坐标的数组 transformedVertices 以及线条颜色 color 作为参数,使用 tft.drawLine 函数在屏幕上绘制一条直线。

7. setup 函数

void setup() {
 Serial.begin(115200);               // **初始化串口,方便调试**
 tft.begin();                        // **初始化 TFT 屏幕**
 tft.setRotation(1);                 // **设置屏幕方向**
 tft.fillScreen(tft.color565(0, 0, 0));  // **设置黑色背景**

 // **随机生成 3 个立方体的位置**
 for (int i = 0; i < 3; i++) {
   cubeX[i] = random(40, SCREEN_WIDTH - 40);  // **X 位置随机**
   cubeY[i] = random(40, SCREEN_HEIGHT - 40); // **Y 位置随机**
 }
}

Serial.begin(115200):初始化串口通信,波特率为 115200,方便开发者进行调试信息的输出。
tft.begin():初始化 TFT 屏幕,使其准备好接收和显示数据。
tft.setRotation(1):设置屏幕的旋转方向,这里设置为旋转 1 次(通常是顺时针旋转 90°)。
tft.fillScreen(tft.color565(0, 0, 0)):将屏幕填充为黑色,作为背景颜色。
通过 for 循环随机生成 3 个立方体的 X 坐标和 Y 坐标,范围在距离屏幕边缘 40 像素的内部区域。

8. loop 函数

void loop() {
 tft.fillScreen(tft.color565(0, 0, 0)); // **清屏,避免重影**

 // **循环绘制 3 个立方体**
 for (int c = 0; c < 3; c++) {
   float transformedVertices[8][2]; // **存储立方体的 2D 投影坐标**

   for (int i = 0; i < 8; i++) {
     float x = cubeVertices[i][0];  // **获取 X 坐标**
     float y = cubeVertices[i][1];  // **获取 Y 坐标**
     float z = cubeVertices[i][2];  // **获取 Z 坐标**

     // **绕 X 轴倾斜 20°**
     float tiltedY = y * cos(TILT_ANGLE) - z * sin(TILT_ANGLE);
     float tiltedZ = y * sin(TILT_ANGLE) + z * cos(TILT_ANGLE);

     // **绕 Y 轴旋转**
     float rotatedX = x * cos(angles[c]) - tiltedZ * sin(angles[c]);
     float rotatedZ = x * sin(angles[c]) + tiltedZ * cos(angles[c]);

     // **投影到 2D 屏幕**
     transformedVertices[i][0] = cubeX[c] + rotatedX;
     transformedVertices[i][1] = cubeY[c] + tiltedY;
   }

   // **绘制立方体的 12 条边**
   int edges[12][2] = {
     {0, 1}, {1, 2}, {2, 3}, {3, 0},  // **前面四条边**
     {4, 5}, {5, 6}, {6, 7}, {7, 4},  // **背面四条边**
     {0, 4}, {1, 5}, {2, 6}, {3, 7}   // **连接前后面的四条边**
   };

   for (int i = 0; i < 12; i++) {
     drawEdge(edges[i][0], edges[i][1], transformedVertices, colors[c]); // **绘制边线**
   }

   angles[c] += ROTATION_SPEED;  // **更新旋转角度**
 }

 delay(50); // **延迟以控制动画速度**
}

tft.fillScreen(tft.color565(0, 0, 0)):每次循环开始时,将屏幕清屏,避免出现重影现象。
通过外层 for 循环遍历 3 个立方体,对于每个立方体:
定义一个二维数组 transformedVertices 用于存储立方体顶点的二维投影坐标。
内层 for 循环遍历立方体的 8 个顶点,对每个顶点进行坐标变换:
首先绕 X 轴倾斜 20°,通过三角函数计算倾斜后的 Y 和 Z 坐标。
然后绕 Y 轴旋转,计算旋转后的 X 和 Z 坐标。
最后将旋转后的坐标投影到二维屏幕上,加上该立方体的 X 和 Y 坐标,得到最终的二维投影坐标。
定义一个二维数组 edges 存储立方体的 12 条边的顶点索引。
通过另一个 for 循环遍历这 12 条边,调用 drawEdge 函数绘制每条边。
更新该立方体的旋转角度,使其不断旋转。
delay(50):延迟 50 毫秒,控制动画的速度,避免动画过快。

综上所述,这段代码通过不断地对 3 个立方体进行坐标变换和绘制,实现了在 TFT 屏幕上显示 3 个独立旋转且倾斜 20° 的立方体的动画效果,并且立方体的位置是随机生成的。

实验场景图  动态图

 

71 (1).jpg
71 (2).jpg
71 (3).jpg
71 (4).jpg
71 (5).jpg
00102.gif

评论

user-avatar
icon 他的勋章
    展开更多