欢迎光临
我们一直在努力

怀旧游戏机项目:STM32F407驱动TFT,贪吃蛇与俄罗斯方块

文章目录

    • 一、项目概述
      • 1.1 硬件准备
      • 1.2 软件环境
    • 二、硬件电路连接
      • 2.1 TFT LCD(ILI9341)与STM32F407引脚映射
      • 2.2 按键引脚映射
    • 三、STM32CubeMX初始化配置
      • 3.1 新建工程
      • 3.2 系统配置
      • 3.3 生成代码
    • 四、TFT LCD驱动实现
      • 4.1 创建驱动文件
        • 文件名:tft_ili9341.h
        • 文件名:tft_ili9341.c
      • 4.2 驱动代码说明
    • 五、按键驱动实现
      • 5.1 创建按键驱动文件
        • 文件名:key.h
        • 文件名:key.c
      • 5.2 按键驱动说明
    • 六、贪吃蛇游戏实现
      • 6.1 游戏逻辑流程图
      • 6.2 创建贪吃蛇游戏文件
        • 文件名:snake.h
        • 文件名:snake.c
      • 6.3 贪吃蛇代码说明
    • 七、俄罗斯方块游戏实现
      • 7.1 游戏逻辑流程图
      • 7.2 创建俄罗斯方块游戏文件
        • 文件名:tetris.h
        • 文件名:tetris.c
      • 7.3 俄罗斯方块代码说明
    • 八、主函数整合
      • 8.1 修改main.c文件
    • 九、工程编译与下载
      • 9.1 添加文件到工程
      • 9.2 编译工程
      • 9.3 下载到 STM32F407
    • 十、运行与调试说明
      • 10.1 开机效果
      • 10.2 按键功能
      • 10.3 贪吃蛇规则
      • 10.4 俄罗斯方块规则
    • 十一、常见问题与解决
      • 11.1 TFT 不亮
      • 11.2 TFT 花屏/白屏
      • 11.3 按键无反应
      • 11.4 游戏不移动/卡顿
    • 十二、项目扩展方向(进阶)
    • 十三、总结

一、项目概述

你想要基于STM32F407开发板实现一个怀旧游戏机,核心功能是驱动TFT液晶显示屏,并在屏幕上运行贪吃蛇和俄罗斯方块两款经典游戏。本教程从环境搭建到代码实现,全程面向零基础小白,每一步都提供详细操作和完整代码,确保你能一步步落地实现。

1.1 硬件准备

  • 核心板:STM32F407ZGT6开发板(带最小系统)
  • 显示屏:2.4寸/3.2寸TFT LCD(ILI9341驱动芯片,SPI/I8080并行接口,本教程用SPI接口)
  • 外设:4个独立按键(上下左右)、5V电源、杜邦线若干
  • 工具:ST-Link下载器、Keil5 MDK开发环境、USB转串口线(可选,用于调试)

1.2 软件环境

  • Keil MDK-ARM V5.36及以上
  • STM32CubeMX 6.0及以上(用于生成初始化代码)
  • ST-Link驱动(用于下载程序)
  • 串口调试助手(可选,如SSCOM)

二、硬件电路连接

2.1 TFT LCD(ILI9341)与STM32F407引脚映射

TFT引脚功能STM32F407引脚
VCC 电源(3.3V) 3.3V
GND GND
SCL SPI时钟 PA5(SPI1_SCK)
SDA SPI数据 PA7(SPI1_MOSI)
RES 复位 PB0
DC 数据/命令 PB1
CS 片选 PA4
BL 背光 PB2(PWM可选)

2.2 按键引脚映射

按键功能STM32F407引脚连接方式
KEY1 PC0 下拉(按下接3.3V)
KEY2 PC1 下拉
KEY3 PC2 下拉
KEY4 PC3 下拉
KEY5 确认/变换 PC4 下拉

三、STM32CubeMX初始化配置

3.1 新建工程

  • 打开STM32CubeMX,点击“New Project”,搜索“STM32F407ZGT6”并选择对应芯片;
  • 点击“Start Project”,进入配置界面。
  • 3.2 系统配置

  • RCC配置:选择“HSE”(外部高速时钟),晶振频率设为8MHz,时钟树最终配置为168MHz;
  • SYS配置:Debug选择“Serial Wire”(ST-Link调试);
  • GPIO配置:
    • 按键引脚(PC0-PC4):模式设为“Input Pull-Down”(下拉输入);
    • TFT复位(PB0)、DC(PB1)、CS(PA4)、背光(PB2):模式设为“Output Push Pull”(推挽输出),初始电平设为高;
  • SPI1配置:
    • 模式:Full-Duplex Master(全双工主机);
    • 时钟分频:Prescaler设为2(时钟频率84MHz);
    • 极性(CPOL):Low;相位(CPHA):1 Edge;
    • 数据长度:8 Bits;
    • NSS:Software(软件片选)。
  • 3.3 生成代码

  • 点击“Project Manager”,设置工程名(如“RetroGameConsole”)、工程路径,工具链选择“MDK-ARM”;
  • 点击“Code Generator”,勾选“Generate peripheral initialization as a pair of ‘.c/.h’ files per peripheral”;
  • 点击“Generate Code”,生成后用Keil5打开工程。
  • 四、TFT LCD驱动实现

    4.1 创建驱动文件

    文件名:tft_ili9341.h

    #ifndef __TFT_ILI9341_H
    #define __TFT_ILI9341_H

    #include "stm32f4xx_hal.h"

    // 引脚定义
    #define TFT_CS_PIN GPIO_PIN_4
    #define TFT_CS_PORT GPIOA
    #define TFT_DC_PIN GPIO_PIN_1
    #define TFT_DC_PORT GPIOB
    #define TFT_RST_PIN GPIO_PIN_0
    #define TFT_RST_PORT GPIOB
    #define TFT_BL_PIN GPIO_PIN_2
    #define TFT_BL_PORT GPIOB

    // 屏幕参数
    #define TFT_WIDTH 240
    #define TFT_HEIGHT 320
    #define TFT_PIXEL_MAX (TFT_WIDTH * TFT_HEIGHT)

    // 颜色定义(RGB565格式)
    #define COLOR_BLACK 0x0000
    #define COLOR_WHITE 0xFFFF
    #define COLOR_RED 0xF800
    #define COLOR_GREEN 0x07E0
    #define COLOR_BLUE 0x001F
    #define COLOR_YELLOW 0xFFE0
    #define COLOR_GRAY 0x8410

    // 函数声明
    void TFT_Init(void); // TFT初始化
    void TFT_Write_Cmd(uint8_t cmd); // 写命令
    void TFT_Write_Data(uint8_t data); // 写数据
    void TFT_Set_Window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1); // 设置显示窗口
    void TFT_Draw_Pixel(uint16_t x, uint16_t y, uint16_t color); // 画点
    void TFT_Fill_Rect(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t color); // 填充矩形
    void TFT_Clear_Screen(uint16_t color); // 清屏
    void TFT_Draw_Line(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t color); // 画线
    void TFT_Draw_Rect(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t color); // 画矩形

    #endif

    文件名:tft_ili9341.c

    #include "tft_ili9341.h"
    #include "spi.h"

    // 延时函数(简易版,实际可改用HAL延时)
    static void TFT_Delay_ms(uint32_t ms)
    {
    HAL_Delay(ms);
    }

    static void TFT_Delay_us(uint32_t us)
    {
    uint32_t delay = (HAL_RCC_GetHCLKFreq() / 8000000) * us;
    while (delay) {}
    }

    // 片选使能
    static void TFT_CS_Enable(void)
    {
    HAL_GPIO_WritePin(TFT_CS_PORT, TFT_CS_PIN, GPIO_PIN_RESET);
    }

    // 片选失能
    static void TFT_CS_Disable(void)
    {
    HAL_GPIO_WritePin(TFT_CS_PORT, TFT_CS_PIN, GPIO_PIN_SET);
    }

    // 设置DC为命令模式
    static void TFT_DC_Cmd(void)
    {
    HAL_GPIO_WritePin(TFT_DC_PORT, TFT_DC_PIN, GPIO_PIN_RESET);
    }

    // 设置DC为数据模式
    static void TFT_DC_Data(void)
    {
    HAL_GPIO_WritePin(TFT_DC_PORT, TFT_DC_PIN, GPIO_PIN_SET);
    }

    // SPI写字节
    static void TFT_SPI_Write(uint8_t data)
    {
    HAL_SPI_Transmit(&hspi1, &data, 1, 100);
    }

    // 写命令
    void TFT_Write_Cmd(uint8_t cmd)
    {
    TFT_CS_Enable();
    TFT_DC_Cmd();
    TFT_SPI_Write(cmd);
    TFT_CS_Disable();
    }

    // 写数据
    void TFT_Write_Data(uint8_t data)
    {
    TFT_CS_Enable();
    TFT_DC_Data();
    TFT_SPI_Write(data);
    TFT_CS_Disable();
    }

    // 初始化TFT
    void TFT_Init(void)
    {
    // 复位屏幕
    HAL_GPIO_WritePin(TFT_RST_PORT, TFT_RST_PIN, GPIO_PIN_RESET);
    TFT_Delay_ms(100);
    HAL_GPIO_WritePin(TFT_RST_PORT, TFT_RST_PIN, GPIO_PIN_SET);
    TFT_Delay_ms(100);

    // 背光开启
    HAL_GPIO_WritePin(TFT_BL_PORT, TFT_BL_PIN, GPIO_PIN_SET);

    // ILI9341初始化序列
    TFT_Write_Cmd(0xCF);
    TFT_Write_Data(0x00);
    TFT_Write_Data(0xC1);
    TFT_Write_Data(0X30);
    TFT_Write_Cmd(0xED);
    TFT_Write_Data(0x64);
    TFT_Write_Data(0x03);
    TFT_Write_Data(0X12);
    TFT_Write_Data(0X81);
    TFT_Write_Cmd(0xE8);
    TFT_Write_Data(0x85);
    TFT_Write_Data(0x00);
    TFT_Write_Data(0x78);
    TFT_Write_Cmd(0xCB);
    TFT_Write_Data(0x39);
    TFT_Write_Data(0x2C);
    TFT_Write_Data(0x00);
    TFT_Write_Data(0x34);
    TFT_Write_Data(0x02);
    TFT_Write_Cmd(0xF7);
    TFT_Write_Data(0x20);
    TFT_Write_Cmd(0xEA);
    TFT_Write_Data(0x00);
    TFT_Write_Data(0x00);
    TFT_Write_Cmd(0xC0); // Power control
    TFT_Write_Data(0x23); // VRH[5:0]
    TFT_Write_Cmd(0xC1); // Power control
    TFT_Write_Data(0x10); // SAP[2:0];BT[3:0]
    TFT_Write_Cmd(0xC5); // VCM control
    TFT_Write_Data(0x3e); // Contrast
    TFT_Write_Data(0x28);
    TFT_Write_Cmd(0xC7); // VCM control2
    TFT_Write_Data(0x86);
    TFT_Write_Cmd(0x36); // Memory Access Control
    TFT_Write_Data(0x48); // 屏幕旋转配置(竖屏)
    TFT_Write_Cmd(0x3A);
    TFT_Write_Data(0x55);
    TFT_Write_Cmd(0xB1);
    TFT_Write_Data(0x00);
    TFT_Write_Data(0x18);
    TFT_Write_Cmd(0xB6); // Display Function Control
    TFT_Write_Data(0x08);
    TFT_Write_Data(0x82);
    TFT_Write_Data(0x27);
    TFT_Write_Cmd(0xF2); // 3Gamma Function Disable
    TFT_Write_Data(0x00);
    TFT_Write_Cmd(0x26); // Gamma curve selected
    TFT_Write_Data(0x01);
    TFT_Write_Cmd(0xE0); // Set Gamma
    TFT_Write_Data(0x0F);
    TFT_Write_Data(0x31);
    TFT_Write_Data(0x2B);
    TFT_Write_Data(0x0C);
    TFT_Write_Data(0x0E);
    TFT_Write_Data(0x08);
    TFT_Write_Data(0x4E);
    TFT_Write_Data(0xF1);
    TFT_Write_Data(0x37);
    TFT_Write_Data(0x07);
    TFT_Write_Data(0x10);
    TFT_Write_Data(0x03);
    TFT_Write_Data(0x0E);
    TFT_Write_Data(0x09);
    TFT_Write_Data(0x00);
    TFT_Write_Cmd(0xE1); // Set Gamma
    TFT_Write_Data(0x00);
    TFT_Write_Data(0x0E);
    TFT_Write_Data(0x14);
    TFT_Write_Data(0x03);
    TFT_Write_Data(0x11);
    TFT_Write_Data(0x07);
    TFT_Write_Data(0x31);
    TFT_Write_Data(0xC1);
    TFT_Write_Data(0x48);
    TFT_Write_Data(0x08);
    TFT_Write_Data(0x0F);
    TFT_Write_Data(0x0C);
    TFT_Write_Data(0x31);
    TFT_Write_Data(0x36);
    TFT_Write_Data(0x0F);
    TFT_Write_Cmd(0x11); // Exit Sleep
    TFT_Delay_ms(120);
    TFT_Write_Cmd(0x29); // Display on
    }

    // 设置显示窗口
    void TFT_Set_Window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1)
    {
    TFT_Write_Cmd(0x2A); // 列地址设置
    TFT_Write_Data(x0 >> 8);
    TFT_Write_Data(x0 & 0xFF);
    TFT_Write_Data(x1 >> 8);
    TFT_Write_Data(x1 & 0xFF);

    TFT_Write_Cmd(0x2B); // 行地址设置
    TFT_Write_Data(y0 >> 8);
    TFT_Write_Data(y0 & 0xFF);
    TFT_Write_Data(y1 >> 8);
    TFT_Write_Data(y1 & 0xFF);

    TFT_Write_Cmd(0x2C); // 写显存
    }

    // 画点
    void TFT_Draw_Pixel(uint16_t x, uint16_t y, uint16_t color)
    {
    if(x >= TFT_WIDTH || y >= TFT_HEIGHT) return; // 超出范围则退出
    TFT_Set_Window(x, y, x, y);
    TFT_DC_Data();
    TFT_CS_Enable();
    TFT_SPI_Write(color >> 8);
    TFT_SPI_Write(color & 0xFF);
    TFT_CS_Disable();
    }

    // 填充矩形
    void TFT_Fill_Rect(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t color)
    {
    uint32_t i, area;
    if(x0 > x1 || y0 > y1) return;
    if(x1 >= TFT_WIDTH) x1 = TFT_WIDTH 1;
    if(y1 >= TFT_HEIGHT) y1 = TFT_HEIGHT 1;

    TFT_Set_Window(x0, y0, x1, y1);
    TFT_DC_Data();
    TFT_CS_Enable();
    area = (x1 x0 + 1) * (y1 y0 + 1);
    for(i = 0; i < area; i++)
    {
    TFT_SPI_Write(color >> 8);
    TFT_SPI_Write(color & 0xFF);
    }
    TFT_CS_Disable();
    }

    // 清屏
    void TFT_Clear_Screen(uint16_t color)
    {
    TFT_Fill_Rect(0, 0, TFT_WIDTH 1, TFT_HEIGHT 1, color);
    }

    // 画线(Bresenham算法)
    void TFT_Draw_Line(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t color)
    {
    int16_t dx = abs(x1 x0), sx = x0 < x1 ? 1 : 1;
    int16_t dy = abs(y1 y0), sy = y0 < y1 ? 1 : 1;
    int16_t err = dx + dy, e2;

    while(1)
    {
    TFT_Draw_Pixel(x0, y0, color);
    if(x0 == x1 && y0 == y1) break;
    e2 = 2 * err;
    if(e2 >= dy) { err += dy; x0 += sx; }
    if(e2 <= dx) { err += dx; y0 += sy; }
    }
    }

    // 画矩形(仅边框)
    void TFT_Draw_Rect(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t color)
    {
    TFT_Draw_Line(x0, y0, x1, y0, color); // 上边框
    TFT_Draw_Line(x0, y1, x1, y1, color); // 下边框
    TFT_Draw_Line(x0, y0, x0, y1, color); // 左边框
    TFT_Draw_Line(x1, y0, x1, y1, color); // 右边框
    }

    4.2 驱动代码说明

    • TFT_Init():执行ILI9341芯片的初始化序列,这是屏幕能正常显示的核心步骤,包含电源、伽马、显示模式等配置;
    • TFT_Set_Window():指定屏幕的显示区域,后续写数据只会在该区域生效;
    • TFT_Draw_Pixel():最基础的画点函数,所有图形绘制都基于此;
    • 填充、画线、画矩形等函数是在画点基础上的封装,方便游戏界面绘制。

    五、按键驱动实现

    5.1 创建按键驱动文件

    文件名:key.h

    #ifndef __KEY_H
    #define __KEY_H

    #include "stm32f4xx_hal.h"

    // 按键定义
    #define KEY_UP_PIN GPIO_PIN_0
    #define KEY_UP_PORT GPIOC
    #define KEY_DOWN_PIN GPIO_PIN_1
    #define KEY_DOWN_PORT GPIOC
    #define KEY_LEFT_PIN GPIO_PIN_2
    #define KEY_LEFT_PORT GPIOC
    #define KEY_RIGHT_PIN GPIO_PIN_3
    #define KEY_RIGHT_PORT GPIOC
    #define KEY_CONFIRM_PIN GPIO_PIN_4
    #define KEY_CONFIRM_PORT GPIOC

    // 按键状态
    #define KEY_RELEASED 0
    #define KEY_PRESSED 1

    // 函数声明
    uint8_t KEY_Read(uint8_t key); // 读取单个按键状态
    uint8_t KEY_Scan(uint8_t mode); // 按键扫描(消抖)
    void KEY_Init(void); // 按键初始化(实际已在CubeMX配置,此处仅占位)

    #endif

    文件名:key.c

    #include "key.h"

    // 读取单个按键状态
    uint8_t KEY_Read(uint8_t key)
    {
    switch(key)
    {
    case 0: return HAL_GPIO_ReadPin(KEY_UP_PORT, KEY_UP_PIN);
    case 1: return HAL_GPIO_ReadPin(KEY_DOWN_PORT, KEY_DOWN_PIN);
    case 2: return HAL_GPIO_ReadPin(KEY_LEFT_PORT, KEY_LEFT_PIN);
    case 3: return HAL_GPIO_ReadPin(KEY_RIGHT_PORT, KEY_RIGHT_PIN);
    case 4: return HAL_GPIO_ReadPin(KEY_CONFIRM_PORT, KEY_CONFIRM_PIN);
    default: return KEY_RELEASED;
    }
    }

    // 按键扫描(mode=0:单次触发;mode=1:连续触发)
    uint8_t KEY_Scan(uint8_t mode)
    {
    static uint8_t key_up = 1; // 按键松开标志
    uint8_t key_val = 0;

    if(mode) key_up = 1; // 连续触发模式,重置标志

    if(key_up && (KEY_Read(0) || KEY_Read(1) || KEY_Read(2) || KEY_Read(3) || KEY_Read(4)))
    {
    HAL_Delay(20); // 消抖延时
    key_up = 0;
    if(KEY_Read(0)) key_val = 1; // 上
    else if(KEY_Read(1)) key_val = 2; // 下
    else if(KEY_Read(2)) key_val = 3; // 左
    else if(KEY_Read(3)) key_val = 4; // 右
    else if(KEY_Read(4)) key_val = 5; // 确认
    }
    else if(!KEY_Read(0) && !KEY_Read(1) && !KEY_Read(2) && !KEY_Read(3) && !KEY_Read(4))
    {
    key_up = 1;
    }

    return key_val;
    }

    // 按键初始化(CubeMX已配置GPIO,此处仅做初始化标记)
    void KEY_Init(void)
    {
    // 空函数,GPIO初始化在main.c的MX_GPIO_Init()中
    }

    5.2 按键驱动说明

    • KEY_Read():直接读取引脚电平,按下时引脚为高(因为下拉连接);
    • KEY_Scan():加入20ms消抖延时,避免机械按键的抖动导致误触发;
    • 支持“单次触发”和“连续触发”两种模式,游戏中移动操作适合连续触发。

    六、贪吃蛇游戏实现

    6.1 游戏逻辑流程图

    #mermaid-svg-cxB7KNRXwZuKa3EO{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-cxB7KNRXwZuKa3EO .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-cxB7KNRXwZuKa3EO .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-cxB7KNRXwZuKa3EO .error-icon{fill:#552222;}#mermaid-svg-cxB7KNRXwZuKa3EO .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-cxB7KNRXwZuKa3EO .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-cxB7KNRXwZuKa3EO .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-cxB7KNRXwZuKa3EO .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-cxB7KNRXwZuKa3EO .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-cxB7KNRXwZuKa3EO .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-cxB7KNRXwZuKa3EO .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-cxB7KNRXwZuKa3EO .marker{fill:#333333;stroke:#333333;}#mermaid-svg-cxB7KNRXwZuKa3EO .marker.cross{stroke:#333333;}#mermaid-svg-cxB7KNRXwZuKa3EO svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-cxB7KNRXwZuKa3EO p{margin:0;}#mermaid-svg-cxB7KNRXwZuKa3EO .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-cxB7KNRXwZuKa3EO .cluster-label text{fill:#333;}#mermaid-svg-cxB7KNRXwZuKa3EO .cluster-label span{color:#333;}#mermaid-svg-cxB7KNRXwZuKa3EO .cluster-label span p{background-color:transparent;}#mermaid-svg-cxB7KNRXwZuKa3EO .label text,#mermaid-svg-cxB7KNRXwZuKa3EO span{fill:#333;color:#333;}#mermaid-svg-cxB7KNRXwZuKa3EO .node rect,#mermaid-svg-cxB7KNRXwZuKa3EO .node circle,#mermaid-svg-cxB7KNRXwZuKa3EO .node ellipse,#mermaid-svg-cxB7KNRXwZuKa3EO .node polygon,#mermaid-svg-cxB7KNRXwZuKa3EO .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-cxB7KNRXwZuKa3EO .rough-node .label text,#mermaid-svg-cxB7KNRXwZuKa3EO .node .label text,#mermaid-svg-cxB7KNRXwZuKa3EO .image-shape .label,#mermaid-svg-cxB7KNRXwZuKa3EO .icon-shape .label{text-anchor:middle;}#mermaid-svg-cxB7KNRXwZuKa3EO .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-cxB7KNRXwZuKa3EO .rough-node .label,#mermaid-svg-cxB7KNRXwZuKa3EO .node .label,#mermaid-svg-cxB7KNRXwZuKa3EO .image-shape .label,#mermaid-svg-cxB7KNRXwZuKa3EO .icon-shape .label{text-align:center;}#mermaid-svg-cxB7KNRXwZuKa3EO .node.clickable{cursor:pointer;}#mermaid-svg-cxB7KNRXwZuKa3EO .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-cxB7KNRXwZuKa3EO .arrowheadPath{fill:#333333;}#mermaid-svg-cxB7KNRXwZuKa3EO .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-cxB7KNRXwZuKa3EO .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-cxB7KNRXwZuKa3EO .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cxB7KNRXwZuKa3EO .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-cxB7KNRXwZuKa3EO .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cxB7KNRXwZuKa3EO .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-cxB7KNRXwZuKa3EO .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-cxB7KNRXwZuKa3EO .cluster text{fill:#333;}#mermaid-svg-cxB7KNRXwZuKa3EO .cluster span{color:#333;}#mermaid-svg-cxB7KNRXwZuKa3EO div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-cxB7KNRXwZuKa3EO .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-cxB7KNRXwZuKa3EO rect.text{fill:none;stroke-width:0;}#mermaid-svg-cxB7KNRXwZuKa3EO .icon-shape,#mermaid-svg-cxB7KNRXwZuKa3EO .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cxB7KNRXwZuKa3EO .icon-shape p,#mermaid-svg-cxB7KNRXwZuKa3EO .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-cxB7KNRXwZuKa3EO .icon-shape rect,#mermaid-svg-cxB7KNRXwZuKa3EO .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cxB7KNRXwZuKa3EO .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-cxB7KNRXwZuKa3EO .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-cxB7KNRXwZuKa3EO :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    游戏初始化

    清屏并绘制游戏边界

    生成初始蛇身和食物

    读取按键输入

    按键是否有效?

    更新蛇的移动方向

    移动蛇头

    是否吃到食物?

    蛇身加长+生成新食物

    移动蛇尾

    是否撞墙/撞自身?

    游戏结束,显示得分

    延时控制游戏速度

    是否重新开始?

    返回游戏选择界面

    6.2 创建贪吃蛇游戏文件

    文件名:snake.h

    #ifndef __SNAKE_H
    #define __SNAKE_H

    #include "tft_ili9341.h"
    #include "key.h"
    #include "stdlib.h"
    #include "time.h"

    // 游戏参数
    #define SNAKE_BLOCK_SIZE 10 // 蛇块大小(像素)
    #define SNAKE_MAX_LENGTH 100 // 蛇最大长度
    #define SNAKE_X_MIN 10 // 游戏区域左边界
    #define SNAKE_X_MAX 230 // 游戏区域右边界
    #define SNAKE_Y_MIN 10 // 游戏区域上边界
    #define SNAKE_Y_MAX 310 // 游戏区域下边界

    // 方向定义
    typedef enum
    {
    DIR_UP = 1,
    DIR_DOWN,
    DIR_LEFT,
    DIR_RIGHT
    } Snake_Dir;

    // 蛇身结构体
    typedef struct
    {
    uint16_t x;
    uint16_t y;
    } Snake_Body;

    // 游戏数据结构体
    typedef struct
    {
    Snake_Body body[SNAKE_MAX_LENGTH]; // 蛇身数组
    uint8_t length; // 蛇当前长度
    Snake_Dir dir; // 移动方向
    uint16_t food_x; // 食物x坐标
    uint16_t food_y; // 食物y坐标
    uint16_t score; // 得分
    uint8_t game_over; // 游戏结束标志
    } Snake_Game;

    // 函数声明
    void Snake_Init(void); // 游戏初始化
    void Snake_Draw_Block(uint16_t x, uint16_t y, uint16_t color); // 画蛇块/食物
    void Snake_Generate_Food(void); // 生成食物
    void Snake_Move(void); // 蛇移动
    void Snake_Check_Collision(void); // 碰撞检测
    void Snake_Game_Loop(void); // 游戏主循环
    void Snake_Show_Score(void); // 显示得分
    void Snake_Game_Over(void); // 游戏结束处理

    #endif

    文件名:snake.c

    #include "snake.h"

    Snake_Game snake_game; // 游戏全局变量

    // 初始化游戏
    void Snake_Init(void)
    {
    // 重置游戏状态
    snake_game.length = 3;
    snake_game.dir = DIR_RIGHT;
    snake_game.score = 0;
    snake_game.game_over = 0;

    // 初始化蛇身(居中)
    snake_game.body[0].x = TFT_WIDTH / 2;
    snake_game.body[0].y = TFT_HEIGHT / 2;
    snake_game.body[1].x = TFT_WIDTH / 2 SNAKE_BLOCK_SIZE;
    snake_game.body[1].y = TFT_HEIGHT / 2;
    snake_game.body[2].x = TFT_WIDTH / 2 2 * SNAKE_BLOCK_SIZE;
    snake_game.body[2].y = TFT_HEIGHT / 2;

    // 清屏(黑色背景)
    TFT_Clear_Screen(COLOR_BLACK);

    // 绘制游戏边界(白色)
    TFT_Draw_Rect(SNAKE_X_MIN 1, SNAKE_Y_MIN 1, SNAKE_X_MAX + 1, SNAKE_Y_MAX + 1, COLOR_WHITE);

    // 生成初始食物
    Snake_Generate_Food();

    // 绘制初始蛇身(绿色)
    for(uint8_t i = 0; i < snake_game.length; i++)
    {
    Snake_Draw_Block(snake_game.body[i].x, snake_game.body[i].y, COLOR_GREEN);
    }

    // 绘制初始食物(红色)
    Snake_Draw_Block(snake_game.food_x, snake_game.food_y, COLOR_RED);

    // 初始化随机数种子(基于系统时间,简易版)
    srand(HAL_GetTick());
    }

    // 画蛇块/食物(填充矩形)
    void Snake_Draw_Block(uint16_t x, uint16_t y, uint16_t color)
    {
    TFT_Fill_Rect(x, y, x + SNAKE_BLOCK_SIZE 1, y + SNAKE_BLOCK_SIZE 1, color);
    }

    // 生成食物(避免与蛇身重叠)
    void Snake_Generate_Food(void)
    {
    uint8_t flag; // 食物是否与蛇身重叠的标志
    do
    {
    flag = 0;
    // 生成食物坐标(对齐块大小,且在游戏区域内)
    snake_game.food_x = SNAKE_X_MIN + (rand() % ((SNAKE_X_MAX SNAKE_X_MIN) / SNAKE_BLOCK_SIZE)) * SNAKE_BLOCK_SIZE;
    snake_game.food_y = SNAKE_Y_MIN + (rand() % ((SNAKE_Y_MAX SNAKE_Y_MIN) / SNAKE_BLOCK_SIZE)) * SNAKE_BLOCK_SIZE;

    // 检查是否与蛇身重叠
    for(uint8_t i = 0; i < snake_game.length; i++)
    {
    if(snake_game.food_x == snake_game.body[i].x && snake_game.food_y == snake_game.body[i].y)
    {
    flag = 1;
    break;
    }
    }
    } while(flag); // 重叠则重新生成
    }

    // 蛇移动逻辑
    void Snake_Move(void)
    {
    // 保存蛇尾坐标(用于擦除)
    uint16_t tail_x = snake_game.body[snake_game.length 1].x;
    uint16_t tail_y = snake_game.body[snake_game.length 1].y;

    // 蛇身跟随移动(从尾到头)
    for(uint8_t i = snake_game.length 1; i > 0; i)
    {
    snake_game.body[i].x = snake_game.body[i 1].x;
    snake_game.body[i].y = snake_game.body[i 1].y;
    }

    // 移动蛇头(根据方向)
    switch(snake_game.dir)
    {
    case DIR_UP:
    snake_game.body[0].y -= SNAKE_BLOCK_SIZE;
    break;
    case DIR_DOWN:
    snake_game.body[0].y += SNAKE_BLOCK_SIZE;
    break;
    case DIR_LEFT:
    snake_game.body[0].x -= SNAKE_BLOCK_SIZE;
    break;
    case DIR_RIGHT:
    snake_game.body[0].x += SNAKE_BLOCK_SIZE;
    break;
    default:
    break;
    }

    // 检查是否吃到食物
    if(snake_game.body[0].x == snake_game.food_x && snake_game.body[0].y == snake_game.food_y)
    {
    // 蛇身加长(长度+1)
    snake_game.length++;
    if(snake_game.length > SNAKE_MAX_LENGTH) snake_game.length = SNAKE_MAX_LENGTH; // 限制最大长度

    // 得分增加
    snake_game.score += 10;

    // 重新生成食物
    Snake_Generate_Food();

    // 绘制新食物
    Snake_Draw_Block(snake_game.food_x, snake_game.food_y, COLOR_RED);

    // 绘制新蛇头(无需擦除尾,因为长度增加)
    Snake_Draw_Block(snake_game.body[0].x, snake_game.body[0].y, COLOR_GREEN);
    }
    else
    {
    // 未吃到食物,擦除蛇尾
    Snake_Draw_Block(tail_x, tail_y, COLOR_BLACK);

    // 绘制新蛇头
    Snake_Draw_Block(snake_game.body[0].x, snake_game.body[0].y, COLOR_GREEN);
    }
    }

    // 碰撞检测(撞墙/撞自身)
    void Snake_Check_Collision(void)
    {
    // 撞墙检测
    if(snake_game.body[0].x < SNAKE_X_MIN || snake_game.body[0].x > SNAKE_X_MAX ||
    snake_game.body[0].y < SNAKE_Y_MIN || snake_game.body[0].y > SNAKE_Y_MAX)
    {
    snake_game.game_over = 1;
    return;
    }

    // 撞自身检测
    for(uint8_t i = 1; i < snake_game.length; i++)
    {
    if(snake_game.body[0].x == snake_game.body[i].x && snake_game.body[0].y == snake_game.body[i].y)
    {
    snake_game.game_over = 1;
    return;
    }
    }
    }

    // 显示得分(简易版,固定位置显示)
    void Snake_Show_Score(void)
    {
    // 清屏原有得分区域
    TFT_Fill_Rect(10, 0, 100, 8, COLOR_BLACK);

    // 此处简化,实际可实现字符显示函数,这里仅用颜色块表示得分(每10分画一个块)
    uint8_t score_blocks = snake_game.score / 10;
    for(uint8_t i = 0; i < score_blocks; i++)
    {
    TFT_Fill_Rect(10 + i * 8, 0, 17 + i * 8, 8, COLOR_YELLOW);
    }
    }

    // 游戏结束处理
    void Snake_Game_Over(void)
    {
    // 绘制游戏结束背景(半透明红,简易版用纯色)
    TFT_Fill_Rect(50, 120, 190, 200, COLOR_RED);

    // 绘制游戏结束文字区域(白色背景)
    TFT_Fill_Rect(60, 130, 180, 190, COLOR_WHITE);

    // 绘制结束提示(简易版,用矩形模拟文字,实际可加字库)
    TFT_Fill_Rect(70, 140, 170, 150, COLOR_RED); // GAME OVER 背景
    TFT_Fill_Rect(75, 142, 165, 148, COLOR_BLACK); // 文字区域

    // 显示最终得分
    TFT_Fill_Rect(70, 160, 170, 170, COLOR_YELLOW);
    TFT_Fill_Rect(75, 162, 165, 168, COLOR_BLACK);
    Snake_Show_Score();

    // 等待按键确认
    while(KEY_Scan(0) != 5); // 按确认键退出
    }

    // 游戏主循环
    void Snake_Game_Loop(void)
    {
    uint8_t key_val;
    Snake_Init(); // 初始化游戏

    while(1)
    {
    if(snake_game.game_over)
    {
    Snake_Game_Over();
    break;
    }

    // 读取按键
    key_val = KEY_Scan(1); // 连续触发模式
    switch(key_val)
    {
    case 1: // 上
    if(snake_game.dir != DIR_DOWN) snake_game.dir = DIR_UP;
    break;
    case 2: // 下
    if(snake_game.dir != DIR_UP) snake_game.dir = DIR_DOWN;
    break;
    case 3: // 左
    if(snake_game.dir != DIR_RIGHT) snake_game.dir = DIR_LEFT;
    break;
    case 4: // 右
    if(snake_game.dir != DIR_LEFT) snake_game.dir = DIR_RIGHT;
    break;
    case 5: // 确认(暂停/继续,简易版暂不实现)
    break;
    default:
    break;
    }

    // 移动蛇
    Snake_Move();

    // 碰撞检测
    Snake_Check_Collision();

    // 显示得分
    Snake_Show_Score();

    // 控制游戏速度(数值越小速度越快)
    HAL_Delay(200);
    }
    }

    6.3 贪吃蛇代码说明

    • 蛇的移动逻辑:通过数组保存蛇身坐标,移动时从尾到头依次复制前一个坐标,再更新蛇头坐标;
    • 食物生成:使用随机数,且会检查是否与蛇身重叠,避免食物出现在蛇身上;
    • 碰撞检测:分为撞墙(边界检测)和撞自身(坐标对比);
    • 得分显示:简易版用颜色块表示,实际项目中可加入字库实现字符显示。

    七、俄罗斯方块游戏实现

    7.1 游戏逻辑流程图

    #mermaid-svg-19IFFjGqSLINr4fS{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-19IFFjGqSLINr4fS .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-19IFFjGqSLINr4fS .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-19IFFjGqSLINr4fS .error-icon{fill:#552222;}#mermaid-svg-19IFFjGqSLINr4fS .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-19IFFjGqSLINr4fS .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-19IFFjGqSLINr4fS .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-19IFFjGqSLINr4fS .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-19IFFjGqSLINr4fS .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-19IFFjGqSLINr4fS .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-19IFFjGqSLINr4fS .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-19IFFjGqSLINr4fS .marker{fill:#333333;stroke:#333333;}#mermaid-svg-19IFFjGqSLINr4fS .marker.cross{stroke:#333333;}#mermaid-svg-19IFFjGqSLINr4fS svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-19IFFjGqSLINr4fS p{margin:0;}#mermaid-svg-19IFFjGqSLINr4fS .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-19IFFjGqSLINr4fS .cluster-label text{fill:#333;}#mermaid-svg-19IFFjGqSLINr4fS .cluster-label span{color:#333;}#mermaid-svg-19IFFjGqSLINr4fS .cluster-label span p{background-color:transparent;}#mermaid-svg-19IFFjGqSLINr4fS .label text,#mermaid-svg-19IFFjGqSLINr4fS span{fill:#333;color:#333;}#mermaid-svg-19IFFjGqSLINr4fS .node rect,#mermaid-svg-19IFFjGqSLINr4fS .node circle,#mermaid-svg-19IFFjGqSLINr4fS .node ellipse,#mermaid-svg-19IFFjGqSLINr4fS .node polygon,#mermaid-svg-19IFFjGqSLINr4fS .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-19IFFjGqSLINr4fS .rough-node .label text,#mermaid-svg-19IFFjGqSLINr4fS .node .label text,#mermaid-svg-19IFFjGqSLINr4fS .image-shape .label,#mermaid-svg-19IFFjGqSLINr4fS .icon-shape .label{text-anchor:middle;}#mermaid-svg-19IFFjGqSLINr4fS .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-19IFFjGqSLINr4fS .rough-node .label,#mermaid-svg-19IFFjGqSLINr4fS .node .label,#mermaid-svg-19IFFjGqSLINr4fS .image-shape .label,#mermaid-svg-19IFFjGqSLINr4fS .icon-shape .label{text-align:center;}#mermaid-svg-19IFFjGqSLINr4fS .node.clickable{cursor:pointer;}#mermaid-svg-19IFFjGqSLINr4fS .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-19IFFjGqSLINr4fS .arrowheadPath{fill:#333333;}#mermaid-svg-19IFFjGqSLINr4fS .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-19IFFjGqSLINr4fS .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-19IFFjGqSLINr4fS .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-19IFFjGqSLINr4fS .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-19IFFjGqSLINr4fS .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-19IFFjGqSLINr4fS .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-19IFFjGqSLINr4fS .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-19IFFjGqSLINr4fS .cluster text{fill:#333;}#mermaid-svg-19IFFjGqSLINr4fS .cluster span{color:#333;}#mermaid-svg-19IFFjGqSLINr4fS div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-19IFFjGqSLINr4fS .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-19IFFjGqSLINr4fS rect.text{fill:none;stroke-width:0;}#mermaid-svg-19IFFjGqSLINr4fS .icon-shape,#mermaid-svg-19IFFjGqSLINr4fS .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-19IFFjGqSLINr4fS .icon-shape p,#mermaid-svg-19IFFjGqSLINr4fS .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-19IFFjGqSLINr4fS .icon-shape rect,#mermaid-svg-19IFFjGqSLINr4fS .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-19IFFjGqSLINr4fS .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-19IFFjGqSLINr4fS .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-19IFFjGqSLINr4fS :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    左右

    确认

    游戏初始化

    清屏并绘制游戏区域

    生成随机方块形状和位置

    绘制当前方块

    读取按键输入

    按键类型?

    移动方块(检测边界/碰撞)

    快速下落

    旋转方块(检测边界/碰撞)

    方块自动下落

    是否落地/碰撞?

    固定当前方块到背景

    检测是否有满行

    消除满行+得分+下落上方方块

    检查是否游戏结束(方块顶到上边界)

    游戏结束,显示得分

    是否重新开始?

    返回游戏选择界面

    7.2 创建俄罗斯方块游戏文件

    文件名:tetris.h

    #ifndef __TETRIS_H
    #define __TETRIS_H

    #include "tft_ili9341.h"
    #include "key.h"
    #include "stdlib.h"
    #include "time.h"

    // 游戏参数
    #define TETRIS_BLOCK_SIZE 10 // 方块大小(像素)
    #define TETRIS_ROWS 20 // 游戏区域行数
    #define TETRIS_COLS 10 // 游戏区域列数
    #define TETRIS_X_OFFSET 20 // 游戏区域X偏移
    #define TETRIS_Y_OFFSET 20 // 游戏区域Y偏移

    // 方块形状定义(7种基础形状,每个形状4个旋转状态)
    typedef enum
    {
    SHAPE_I,
    SHAPE_O,
    SHAPE_T,
    SHAPE_L,
    SHAPE_J,
    SHAPE_S,
    SHAPE_Z
    } Tetris_Shape_Type;

    // 方块颜色(对应7种形状)
    #define SHAPE_COLORS {COLOR_CYAN, COLOR_YELLOW, COLOR_PURPLE, COLOR_ORANGE, COLOR_BLUE, COLOR_GREEN, COLOR_RED}

    // 方块结构体
    typedef struct
    {
    Tetris_Shape_Type type; // 形状类型
    uint8_t rotation; // 旋转状态(0-3)
    int8_t x; // 方块中心X坐标(列)
    int8_t y; // 方块中心Y坐标(行)
    uint16_t color; // 方块颜色
    } Tetris_Block;

    // 游戏数据结构体
    typedef struct
    {
    uint16_t bg[TETRIS_ROWS][TETRIS_COLS]; // 游戏背景(保存固定的方块)
    Tetris_Block current_block; // 当前下落的方块
    Tetris_Block next_block; // 下一个方块
    uint16_t score; // 得分
    uint8_t game_over; // 游戏结束标志
    uint16_t fall_speed; // 下落速度(ms)
    } Tetris_Game;

    // 函数声明
    void Tetris_Init(void); // 游戏初始化
    void Tetris_Init_Block(Tetris_Block *block); // 初始化方块
    void Tetris_Draw_Block(Tetris_Block *block, uint8_t erase); // 绘制/擦除方块
    uint8_t Tetris_Check_Collision(Tetris_Block *block, int8_t dx, int8_t dy, uint8_t new_rotation); // 碰撞检测
    void Tetris_Rotate_Block(void); // 旋转方块
    void Tetris_Move_Block(int8_t dx, int8_t dy); // 移动方块
    void Tetris_Fall_Block(void); // 方块下落
    void Tetris_Fix_Block(void); // 固定方块到背景
    void Tetris_Check_Line(void); // 检测并消除满行
    void Tetris_Show_Score(void); // 显示得分
    void Tetris_Game_Over(void); // 游戏结束处理
    void Tetris_Game_Loop(void); // 游戏主循环

    #endif

    文件名:tetris.c

    #include "tetris.h"

    // 7种方块的形状定义(每个形状4个旋转状态,1表示有块,0表示无)
    const uint8_t shape_defs[7][4][4][4] = {
    // I型
    {
    {{0,0,0,0},{1,1,1,1},{0,0,0,0},{0,0,0,0}},
    {{0,0,1,0},{0,0,1,0},{0,0,1,0},{0,0,1,0}},
    {{0,0,0,0},{1,1,1,1},{0,0,0,0},{0,0,0,0}},
    {{0,0,1,0},{0,0,1,0},{0,0,1,0},{0,0,1,0}}
    },
    // O型(无旋转)
    {
    {{0,0,0,0},{0,1,1,0},{0,1,1,0},{0,0,0,0}},
    {{0,0,0,0},{0,1,1,0},{0,1,1,0},{0,0,0,0}},
    {{0,0,0,0},{0,1,1,0},{0,1,1,0},{0,0,0,0}},
    {{0,0,0,0},{0,1,1,0},{0,1,1,0},{0,0,0,0}}
    },
    // T型
    {
    {{0,0,0,0},{1,1,1,0},{0,1,0,0},{0,0,0,0}},
    {{0,1,0,0},{1,1,0,0},{0,1,0,0},{0,0,0,0}},
    {{0,1,0,0},{1,1,1,0},{0,0,0,0},{0,0,0,0}},
    {{0,1,0,0},{0,1,1,0},{0,1,0,0},{0,0,0,0}}
    },
    // L型
    {
    {{0,0,0,0},{1,1,1,0},{1,0,0,0},{0,0,0,0}},
    {{1,1,0,0},{0,1,0,0},{0,1,0,0},{0,0,0,0}},
    {{0,0,1,0},{1,1,1,0},{0,0,0,0},{0,0,0,0}},
    {{0,1,0,0},{0,1,0,0},{0,1,1,0},{0,0,0,0}}
    },
    // J型
    {
    {{0,0,0,0},{1,1,1,0},{0,0,1,0},{0,0,0,0}},
    {{0,1,0,0},{0,1,0,0},{1,1,0,0},{0,0,0,0}},
    {{1,0,0,0},{1,1,1,0},{0,0,0,0},{0,0,0,0}},
    {{0,1,1,0},{0,1,0,0},{0,1,0,0},{0,0,0,0}}
    },
    // S型
    {
    {{0,0,0,0},{0,1,1,0},{1,1,0,0},{0,0,0,0}},
    {{1,0,0,0},{1,1,0,0},{0,1,0,0},{0,0,0,0}},
    {{0,0,0,0},{0,1,1,0},{1,1,0,0},{0,0,0,0}},
    {{1,0,0,0},{1,1,0,0},{0,1,0,0},{0,0,0,0}}
    },
    // Z型
    {
    {{0,0,0,0},{1,1,0,0},{0,1,1,0},{0,0,0,0}},
    {{0,1,0,0},{1,1,0,0},{1,0,0,0},{0,0,0,0}},
    {{0,0,0,0},{1,1,0,0},{0,1,1,0},{0,0,0,0}},
    {{0,1,0,0},{1,1,0,0},{1,0,0,0},{0,0,0,0}}
    }
    };

    // 方块颜色数组
    const uint16_t shape_colors[] = SHAPE_COLORS;

    Tetris_Game tetris_game; // 游戏全局变量

    // 初始化游戏
    void Tetris_Init(void)
    {
    // 重置游戏状态
    tetris_game.score = 0;
    tetris_game.game_over = 0;
    tetris_game.fall_speed = 800; // 初始下落速度(800ms)

    // 清空背景数组
    for(uint8_t i = 0; i < TETRIS_ROWS; i++)
    {
    for(uint8_t j = 0; j < TETRIS_COLS; j++)
    {
    tetris_game.bg[i][j] = COLOR_BLACK;
    }
    }

    // 清屏(黑色背景)
    TFT_Clear_Screen(COLOR_BLACK);

    // 绘制游戏区域边界(白色)
    TFT_Draw_Rect(TETRIS_X_OFFSET 1, TETRIS_Y_OFFSET 1,
    TETRIS_X_OFFSET + TETRIS_COLS * TETRIS_BLOCK_SIZE,
    TETRIS_Y_OFFSET + TETRIS_ROWS * TETRIS_BLOCK_SIZE, COLOR_WHITE);

    // 初始化随机数种子
    srand(HAL_GetTick());

    // 生成当前方块和下一个方块
    Tetris_Init_Block(&tetris_game.current_block);
    Tetris_Init_Block(&tetris_game.next_block);

    // 绘制初始方块
    Tetris_Draw_Block(&tetris_game.current_block, 0);

    // 显示初始得分
    Tetris_Show_Score();
    }

    // 初始化方块(随机形状、初始位置)
    void Tetris_Init_Block(Tetris_Block *block)
    {
    // 随机选择形状
    block->type = (Tetris_Shape_Type)(rand() % 7);
    block->rotation = 0;
    block->color = shape_colors[block->type];

    // 初始位置(游戏区域居中,顶部)
    block->x = TETRIS_COLS / 2 2;
    block->y = 0;

    // 检查游戏结束(初始方块就碰撞)
    if(Tetris_Check_Collision(block, 0, 0, block->rotation))
    {
    tetris_game.game_over = 1;
    }
    }

    // 绘制/擦除方块(erase=1擦除,0绘制)
    void Tetris_Draw_Block(Tetris_Block *block, uint8_t erase)
    {
    uint16_t color = erase ? COLOR_BLACK : block->color;
    const uint8_t (*shape)[4][4] = shape_defs[block->type][block->rotation];

    // 遍历方块的4×4矩阵,绘制有效块
    for(int8_t i = 0; i < 4; i++)
    {
    for(int8_t j = 0; j < 4; j++)
    {
    if(shape[i][j])
    {
    uint16_t x = TETRIS_X_OFFSET + (block->x + j) * TETRIS_BLOCK_SIZE;
    uint16_t y = TETRIS_Y_OFFSET + (block->y + i) * TETRIS_BLOCK_SIZE;

    // 仅绘制游戏区域内的块
    if((block->x + j) >= 0 && (block->x + j) < TETRIS_COLS &&
    (block->y + i) >= 0 && (block->y + i) < TETRIS_ROWS)
    {
    TFT_Fill_Rect(x, y, x + TETRIS_BLOCK_SIZE 1, y + TETRIS_BLOCK_SIZE 1, color);
    }
    }
    }
    }
    }

    // 碰撞检测(dx/dy:移动偏移;new_rotation:新旋转状态)
    uint8_t Tetris_Check_Collision(Tetris_Block *block, int8_t dx, int8_t dy, uint8_t new_rotation)
    {
    const uint8_t (*shape)[4][4] = shape_defs[block->type][new_rotation];

    // 遍历方块矩阵,检查每个有效块的碰撞
    for(int8_t i = 0; i < 4; i++)
    {
    for(int8_t j = 0; j < 4; j++)
    {
    if(shape[i][j])
    {
    int8_t x = block->x + j + dx;
    int8_t y = block->y + i + dy;

    // 边界检测
    if(x < 0 || x >= TETRIS_COLS || y >= TETRIS_ROWS)
    {
    return 1; // 碰撞
    }

    // 背景碰撞检测(y<0时不检测)
    if(y >= 0 && tetris_game.bg[y][x] != COLOR_BLACK)
    {
    return 1; // 碰撞
    }
    }
    }
    }

    return 0; // 无碰撞
    }

    // 旋转方块
    void Tetris_Rotate_Block(void)
    {
    uint8_t new_rotation = (tetris_game.current_block.rotation + 1) % 4;

    // 检测旋转后的碰撞
    if(!Tetris_Check_Collision(&tetris_game.current_block, 0, 0, new_rotation))
    {
    // 擦除原方块
    Tetris_Draw_Block(&tetris_game.current_block, 1);

    // 更新旋转状态
    tetris_game.current_block.rotation = new_rotation;

    // 绘制新方块
    Tetris_Draw_Block(&tetris_game.current_block, 0);
    }
    }

    // 移动方块(dx:左右偏移,dy:上下偏移)
    void Tetris_Move_Block(int8_t dx, int8_t dy)
    {
    // 检测移动后的碰撞
    if(!Tetris_Check_Collision(&tetris_game.current_block, dx, dy, tetris_game.current_block.rotation))
    {
    // 擦除原方块
    Tetris_Draw_Block(&tetris_game.current_block, 1);

    // 更新位置
    tetris_game.current_block.x += dx;
    tetris_game.current_block.y += dy;

    // 绘制新方块
    Tetris_Draw_Block(&tetris_game.current_block, 0);
    }
    }

    // 方块下落
    void Tetris_Fall_Block(void)
    {
    // 尝试向下移动
    if(!Tetris_Check_Collision(&tetris_game.current_block, 0, 1, tetris_game.current_block.rotation))
    {
    Tetris_Move_Block(0, 1);
    }
    else
    {
    // 落地,固定方块到背景
    Tetris_Fix_Block();

    // 检测满行
    Tetris_Check_Line();

    // 生成新方块
    Tetris_Draw_Block(&tetris_game.next_block, 1); // 擦除预览的下一个方块
    tetris_game.current_block = tetris_game.next_block;
    Tetris_Init_Block(&tetris_game.next_block);

    // 绘制新方块
    Tetris_Draw_Block(&tetris_game.current_block, 0);

    // 显示得分
    Tetris_Show_Score();
    }
    }

    // 固定方块到背景
    void Tetris_Fix_Block(void)
    {
    const uint8_t (*shape)[4][4] = shape_defs[tetris_game.current_block.type][tetris_game.current_block.rotation];

    // 将方块的每个有效块写入背景数组
    for(int8_t i = 0; i < 4; i++)
    {
    for(int8_t j = 0; j < 4; j++)
    {
    if(shape[i][j])
    {
    int8_t x = tetris_game.current_block.x + j;
    int8_t y = tetris_game.current_block.y + i;

    if(x >= 0 && x < TETRIS_COLS && y >= 0 && y < TETRIS_ROWS)
    {
    tetris_game.bg[y][x] = tetris_game.current_block.color;
    }
    }
    }
    }
    }

    // 检测并消除满行
    void Tetris_Check_Line(void)
    {
    uint8_t line_count = 0; // 消除的行数

    // 遍历每一行
    for(uint8_t i = 0; i < TETRIS_ROWS; i++)
    {
    uint8_t full = 1;

    // 检查该行是否满
    for(uint8_t j = 0; j < TETRIS_COLS; j++)
    {
    if(tetris_game.bg[i][j] == COLOR_BLACK)
    {
    full = 0;
    break;
    }
    }

    // 满行则消除
    if(full)
    {
    line_count++;

    // 擦除该行
    for(uint8_t j = 0; j < TETRIS_COLS; j++)
    {
    uint16_t x = TETRIS_X_OFFSET + j * TETRIS_BLOCK_SIZE;
    uint16_t y = TETRIS_Y_OFFSET + i * TETRIS_BLOCK_SIZE;
    TFT_Fill_Rect(x, y, x + TETRIS_BLOCK_SIZE 1, y + TETRIS_BLOCK_SIZE 1, COLOR_BLACK);
    tetris_game.bg[i][j] = COLOR_BLACK;
    }

    // 上方方块下落
    for(uint8_t k = i; k > 0; k)
    {
    for(uint8_t j = 0; j < TETRIS_COLS; j++)
    {
    // 复制上一行数据到当前行
    tetris_game.bg[k][j] = tetris_game.bg[k1][j];

    // 绘制下落的块
    uint16_t x = TETRIS_X_OFFSET + j * TETRIS_BLOCK_SIZE;
    uint16_t y = TETRIS_Y_OFFSET + k * TETRIS_BLOCK_SIZE;
    TFT_Fill_Rect(x, y, x + TETRIS_BLOCK_SIZE 1, y + TETRIS_BLOCK_SIZE 1, tetris_game.bg[k][j]);

    // 擦除上一行的块
    y = TETRIS_Y_OFFSET + (k1) * TETRIS_BLOCK_SIZE;
    TFT_Fill_Rect(x, y, x + TETRIS_BLOCK_SIZE 1, y + TETRIS_BLOCK_SIZE 1, COLOR_BLACK);
    }
    }
    }
    }

    // 根据消除的行数加分
    switch(line_count)
    {
    case 1: tetris_game.score += 100; break;
    case 2: tetris_game.score += 300; break;
    case 3: tetris_game.score += 500; break;
    case 4: tetris_game.score += 800; break;
    default: break;
    }

    // 加快下落速度(每消除1000分提速)
    if(tetris_game.score % 1000 == 0 && tetris_game.fall_speed > 200)
    {
    tetris_game.fall_speed -= 100;
    }
    }

    // 显示得分(简易版)
    void Tetris_Show_Score(void)
    {
    // 清屏得分区域
    TFT_Fill_Rect(TETRIS_X_OFFSET + TETRIS_COLS * TETRIS_BLOCK_SIZE + 10, TETRIS_Y_OFFSET,
    TETRIS_X_OFFSET + TETRIS_COLS * TETRIS_BLOCK_SIZE + 100, TETRIS_Y_OFFSET + 30, COLOR_BLACK);

    // 绘制得分背景
    TFT_Draw_Rect(TETRIS_X_OFFSET + TETRIS_COLS * TETRIS_BLOCK_SIZE + 10, TETRIS_Y_OFFSET,
    TETRIS_X_OFFSET + TETRIS_COLS * TETRIS_BLOCK_SIZE + 100, TETRIS_Y_OFFSET + 30, COLOR_WHITE);

    // 简易得分显示(每100分画一个黄色块)
    uint8_t score_blocks = tetris_game.score / 100;
    for(uint8_t i = 0; i < score_blocks && i < 9; i++)
    {
    TFT_Fill_Rect(TETRIS_X_OFFSET + TETRIS_COLS * TETRIS_BLOCK_SIZE + 15 + i * 9, TETRIS_Y_OFFSET + 5,
    TETRIS_X_OFFSET + TETRIS_COLS * TETRIS_BLOCK_SIZE + 23 + i * 9, TETRIS_Y_OFFSET + 25, COLOR_YELLOW);
    }
    }

    // 游戏结束处理
    void Tetris_Game_Over(void)
    {
    // 绘制结束背景
    TFT_Fill_Rect(TETRIS_X_OFFSET + 10, TETRIS_Y_OFFSET + 50,
    TETRIS_X_OFFSET + TETRIS_COLS * TETRIS_BLOCK_SIZE 10,
    TETRIS_Y_OFFSET + TETRIS_ROWS * TETRIS_BLOCK_SIZE 50, COLOR_RED);

    // 绘制结束提示区域
    TFT_Fill_Rect(TETRIS_X_OFFSET + 20, TETRIS_Y_OFFSET + 60,
    TETRIS_X_OFFSET + TETRIS_COLS * TETRIS_BLOCK_SIZE 20,
    TETRIS_Y_OFFSET + TETRIS_ROWS * TETRIS_BLOCK_SIZE 60, COLOR_WHITE);

    // 简易结束文字(矩形模拟)
    TFT_Fill_Rect(TETRIS_X_OFFSET + 30, TETRIS_Y_OFFSET + 70,
    TETRIS_X_OFFSET + TETRIS_COLS * TETRIS_BLOCK_SIZE 30,
    TETRIS_Y_OFFSET + 90, COLOR_RED);
    TFT_Fill_Rect(TETRIS_X_OFFSET + 35, TETRIS_Y_OFFSET + 75,
    TETRIS_X_OFFSET + TETRIS_COLS * TETRIS_BLOCK_SIZE 35,
    TETRIS_Y_OFFSET + 85, COLOR_BLACK);

    // 显示最终得分
    TFT_Fill_Rect(TETRIS_X_OFFSET + 30, TETRIS_Y_OFFSET + 100,
    TETRIS_X_OFFSET + TETRIS_COLS * TETRIS_BLOCK_SIZE 30,
    TETRIS_Y_OFFSET + 120, COLOR_YELLOW);
    TFT_Fill_Rect(TETRIS_X_OFFSET + 35, TETRIS_Y_OFFSET + 105,
    TETRIS_X_OFFSET + TETRIS_COLS * TETRIS_BLOCK_SIZE 35,
    TETRIS_Y_OFFSET + 115, COLOR_BLACK);
    Tetris_Show_Score();

    // 等待确认键退出
    while(KEY_Scan(0) != 5);
    }

    // 游戏主循环
    void Tetris_Game_Loop(void)
    {
    uint8_t key_val;
    uint32_t last_fall_time = 0;

    Tetris_Init(); // 初始化游戏

    while(1)
    {
    if(tetris_game.game_over)
    {
    Tetris_Game_Over();
    break;
    }

    // 读取按键
    key_val = KEY_Scan(1);
    switch(key_val)
    {
    case 1: // 上(旋转)
    Tetris_Rotate_Block();
    break;
    case 2: // 下(快速下落)
    Tetris_Fall_Block();
    break;
    case 3: // 左
    Tetris_Move_Block(1, 0);
    break;
    case 4: // 右
    Tetris_Move_Block(1, 0);
    break;
    case 5: // 确认(暂停,简易版暂不实现)
    break;
    default:
    break;
    }

    // 自动下落(基于时间控制)
    if(HAL_GetTick() last_fall_time > tetris_game.fall_speed)
    {
    Tetris_Fall_Block();
    last_fall_time = HAL_GetTick();
    }

    // 短延时,降低CPU占用
    HAL_Delay(10);
    }
    }

    7.3 俄罗斯方块代码说明

    • 方块形状:用三维数组定义7种基础形状的4个旋转状态,每个状态是4×4的矩阵;
    • 碰撞检测:检测方块移动/旋转后是否超出边界,或与背景中已固定的方块重叠;
    • 消行逻辑:遍历每一行,检测满行后消除,并让上方方块依次下落;
    • 速度控制:随着得分增加,下落速度加快,提升游戏难度。

    八、主函数整合

    8.1 修改main.c文件

    /* USER CODE BEGIN Header */
    /**
    ******************************************************************************
    * @file : main.c
    * @brief : Main program body
    ******************************************************************************
    * @attention
    *
    * Copyright (c) 2024 STMicroelectronics.
    * All rights reserved.
    *
    * This software is licensed under terms that can be found in the LICENSE file
    * in the root directory of this software component.
    * If no LICENSE file comes with this software, it is provided AS-IS.
    *
    ******************************************************************************
    */

    /* USER CODE END Header */
    /* Includes ——————————————————————*/
    #include "main.h"
    #include "gpio.h"
    #include "spi.h"

    /* Private includes ———————————————————-*/
    /* USER CODE BEGIN Includes */
    #include "tft_ili9341.h"
    #include "key.h"
    #include "snake.h"
    #include "tetris.h"
    /* USER CODE END Includes */

    /* Private typedef ———————————————————–*/
    /* USER CODE BEGIN PTD */

    /* USER CODE END PTD */

    /* Private define ————————————————————*/
    /* USER CODE BEGIN PD */

    /* USER CODE END PD */

    /* Private macro ————————————————————-*/
    /* USER CODE BEGIN PM */

    /* USER CODE END PM */

    /* Private variables ———————————————————*/

    /* USER CODE BEGIN PV */

    /* USER CODE END PV */

    /* Private function prototypes ———————————————–*/
    void SystemClock_Config(void);
    /* USER CODE BEGIN PFP */
    void Game_Menu(void); // 游戏选择菜单
    /* USER CODE END PFP */

    /* Private user code ———————————————————*/
    /* USER CODE BEGIN 0 */
    void Game_Menu(void)
    {
    uint8_t key_val;

    // 清屏
    TFT_Clear_Screen(COLOR_BLACK);

    // 绘制菜单标题
    TFT_Fill_Rect(50, 50, 190, 80, COLOR_WHITE);
    TFT_Fill_Rect(55, 55, 185, 75, COLOR_BLUE);

    // 绘制游戏选项1:贪吃蛇
    TFT_Fill_Rect(30, 100, 210, 130, COLOR_GREEN);
    TFT_Fill_Rect(35, 105, 205, 125, COLOR_BLACK);

    // 绘制游戏选项2:俄罗斯方块
    TFT_Fill_Rect(30, 150, 210, 180, COLOR_RED);
    TFT_Fill_Rect(35, 155, 205, 175, COLOR_BLACK);

    // 菜单循环
    while(1)
    {
    key_val = KEY_Scan(0);
    switch(key_val)
    {
    case 1: // 上,选择贪吃蛇
    case 5: // 确认,启动贪吃蛇
    Snake_Game_Loop();
    Game_Menu(); // 返回菜单
    break;
    case 2: // 下,选择俄罗斯方块
    Tetris_Game_Loop();
    Game_Menu(); // 返回菜单
    break;
    default:
    break;
    }
    }
    }
    /* USER CODE END 0 */

    /**
    * @brief The application entry point.
    * @retval int
    */

    int main(void)
    {
    /* USER CODE BEGIN 1 */

    /* USER CODE END 1 */

    /* MCU Configuration——————————————————–*/

    /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
    HAL_Init();

    /* USER CODE BEGIN Init */

    /* USER CODE END Init */

    /* Configure the system clock */
    SystemClock_Config();

    /* USER CODE BEGIN SysInit */

    /* USER CODE END SysInit */

    /* Initialize all configured peripherals */
    MX_GPIO_Init();
    MX_SPI1_Init();
    /* USER CODE BEGIN 2 */
    // 初始化TFT
    TFT_Init();

    // 初始化按键
    KEY_Init();

    // 启动游戏菜单
    Game_Menu();
    /* USER CODE END 2 */

    /* Infinite loop */
    /* USER CODE BEGIN WHILE */
    while (1)
    {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    }
    /* USER CODE END 3 */
    }

    /**
    * @brief System Clock Configuration
    * @retval None
    */

    void SystemClock_Config(void)
    {
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

    /** Configure the main internal regulator output voltage
    */

    __HAL_RCC_PWR_CLK_ENABLE();
    __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);

    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
    RCC_OscInitStruct.HSEState = RCC_HSE_ON;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
    RCC_OscInitStruct.PLL.PLLM = 8;
    RCC_OscInitStruct.PLL.PLLN = 336;
    RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
    RCC_OscInitStruct.PLL.PLLQ = 7;
    if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
    {
    Error_Handler();
    }

    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
    |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;

    if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
    {
    Error_Handler();
    }
    }

    /* USER CODE BEGIN 4 */

    /* USER CODE END 4 */

    /**
    * @brief This function is executed in case of error occurrence.
    * @retval None
    */

    void Error_Handler(void)
    {
    __disable_irq();
    while (1)
    {
    }
    }

    #ifdef USE_FULL_ASSERT
    void assert_failed(uint8_t *file, uint32_t line)
    {
    }
    #endif


    九、工程编译与下载

    9.1 添加文件到工程

  • 在 Keil 中,右键点击工程目录 → Add Existing Files
  • 选中以下文件添加:
    • tft_ili9341.c
    • key.c
    • snake.c
    • tetris.c
  • 确保头文件路径被正确包含。
  • 9.2 编译工程

  • 点击 Build 或 Rebuild
  • 确保无错误、无警告
  • 生成 .hex 或 .elf 文件
  • 9.3 下载到 STM32F407

  • 连接 ST-Link
  • 配置下载工具:Options for Target → Debug
  • 选择 ST-Link Debugger
  • 点击 Load 下载程序

  • 十、运行与调试说明

    10.1 开机效果

  • TFT 屏点亮
  • 进入游戏机主菜单
    • 绿色框:贪吃蛇
    • 红色框:俄罗斯方块
  • 10.2 按键功能

    • 上键:选择贪吃蛇 / 蛇向上移动
    • 下键:选择俄罗斯方块 / 蛇向下移动
    • 左键:左移 / 蛇左移
    • 右键:右移 / 蛇右移
    • 确认键(中键):进入游戏 / 俄罗斯方块旋转

    10.3 贪吃蛇规则

  • 初始长度 3
  • 吃到红色食物变长 + 加分
  • 撞墙或撞自己游戏结束
  • 结束后按确认返回菜单
  • 10.4 俄罗斯方块规则

  • 自动下落
  • 左右键移动
  • 确认键旋转
  • 满行自动消除并加分
  • 堆到顶游戏结束
  • 结束后按确认返回菜单

  • 十一、常见问题与解决

    11.1 TFT 不亮

    • 检查 BL 引脚是否拉高
    • 检查 3.3V 供电
    • 检查复位引脚电平

    11.2 TFT 花屏/白屏

    • SPI 接线错误(SCL、SDA)
    • 初始化指令不对
    • 片选 CS、DC 引脚配置错误

    11.3 按键无反应

    • 上下拉配置错误
    • 引脚定义错误
    • 按键消抖时间不合理

    11.4 游戏不移动/卡顿

    • 延时函数错误
    • 按键扫描模式错误
    • 速度设置过快/过慢

    十二、项目扩展方向(进阶)

  • 加入字库,显示中文/英文菜单、分数
  • 添加音效(无源蜂鸣器)
  • 加入游戏暂停、难度选择
  • 保存最高分到 Flash
  • 增加更多游戏:飞机大战、打砖块、2048
  • 做成掌机外壳 + 锂电池供电

  • 十三、总结

    本项目完整实现了:

    • STM32F407 初始化
    • SPI 驱动 TFT LCD(ILI9341)
    • 按键输入与消抖
    • 贪吃蛇完整逻辑
    • 俄罗斯方块完整逻辑
    • 游戏菜单与切换
    赞(0)
    未经允许不得转载:171主机测评 » 怀旧游戏机项目:STM32F407驱动TFT,贪吃蛇与俄罗斯方块
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址