基于STM32的贪吃蛇游戏实现(OLED屏)

一、项目概述

本项目使用STM32微控制器和OLED显示屏实现经典贪吃蛇游戏,通过按键控制蛇的移动方向,实现食物收集、碰撞检测和分数计算等功能。项目采用模块化设计,包含游戏逻辑、显示驱动和用户输入处理三大核心模块。

二、硬件配置

  • 主控芯片:STM32F103C8T6(ARM Cortex-M3,72MHz)

  • 显示模块:0.96寸OLED(SSD1306驱动,128×64像素,I2C接口)

  • 输入设备:4个轻触按键(上、下、左、右)

  • 电源:3.3V/5V DC供电

三、系统架构

c 复制代码
+-------------------+     +-------------------+     +-------------------+
|    输入处理模块    |     |    游戏逻辑模块    |     |    显示驱动模块    |
| (按键扫描与消抖)   |<--->| (蛇移动/碰撞检测)  |<--->| (OLED显示控制)    |
+-------------------+     +-------------------+     +-------------------+
                                 |  |  |  |  |  |  |  |  |  |  |  |  |
                                 |  |  |  |  |  |  |  |  |  |  +---->+-------------------+
                                 |  |  |  |  |  |  |  |  |  +-------->|  游戏状态管理     |
                                 |  |  |  |  |  |  |  |  +------------>|  分数计算         |
                                 |  |  |  |  |  |  |  +---------------->|  游戏难度控制     |
                                 |  |  |  |  |  |  +-------------------->|  音效反馈(可选)   |
                                 |  |  |  |  |  +-------------------------------->|                 |
                                 |  |  |  |  +------------------------------------>|                 |
                                 |  |  |  +---------------------------------------->|                 |
                                 |  |  +-------------------------------------------->|                 |
                                 |  +--------------------------------------------------->|                 |
                                 +--------------------------------------------------->|                 |

四、核心代码实现

1. 主程序框架

c 复制代码
#include "stm32f1xx_hal.h"
#include "ssd1306.h"
#include "game.h"
#include "input.h"

int main(void) {
    HAL_Init();
    SystemClock_Config();
    OLED_Init();
    Input_Init();
    
    GameState game = {0};
    InitGame(&game);
    
    while (1) {
        ProcessInput(&game);
        UpdateGame(&game);
        RenderGame(&game);
        HAL_Delay(100); // 控制游戏速度
    }
}

2. 游戏逻辑实现

c 复制代码
// game.h
#define GRID_SIZE 8
#define GRID_WIDTH 16
#define GRID_HEIGHT 8

typedef struct {
    int x, y;
} Point;

typedef struct {
    Point body[64];  // 蛇身最多64节
    int length;
    Point food;
    int direction;  // 0:上, 1:右, 2:下, 3:左
    int score;
    int game_over;
} GameState;

// game.c
void InitGame(GameState* game) {
    // 初始化蛇身
    game->body[0] = (Point){4, 4};
    game->body[1] = (Point){4, 5};
    game->body[2] = (Point){4, 6};
    game->length = 3;
    game->direction = 1; // 初始向右移动
    game->score = 0;
    game->game_over = 0;
    
    GenerateFood(game);
}

void GenerateFood(GameState* game) {
    do {
        game->food.x = rand() % GRID_WIDTH;
        game->food.y = rand() % GRID_HEIGHT;
    } while (IsPointOnSnake(game, game->food));
}

int IsPointOnSnake(GameState* game, Point p) {
    for (int i = 0; i < game->length; i++) {
        if (game->body[i].x == p.x && game->body[i].y == p.y) {
            return 1;
        }
    }
    return 0;
}

void UpdateGame(GameState* game) {
    if (game->game_over) return;
    
    // 保存蛇尾位置(可能用于增长)
    Point prev_tail = game->body[game->length - 1];
    
    // 移动蛇身(从尾部向前更新)
    for (int i = game->length - 1; i > 0; i--) {
        game->body[i] = game->body[i - 1];
    }
    
    // 根据方向移动头部
    switch (game->direction) {
        case 0: // 上
            game->body[0].y--;
            break;
        case 1: // 右
            game->body[0].x++;
            break;
        case 2: // 下
            game->body[0].y++;
            break;
        case 3: // 左
            game->body[0].x--;
            break;
    }
    
    // 检查是否吃到食物
    if (game->body[0].x == game->food.x && game->body[0].y == game->food.y) {
        // 增加长度
        game->body[game->length] = prev_tail;
        game->length++;
        game->score += 10;
        GenerateFood(game);
    }
    
    // 检查碰撞
    CheckCollision(game);
}

void CheckCollision(GameState* game) {
    Point head = game->body[0];
    
    // 检查墙壁碰撞
    if (head.x < 0 || head.x >= GRID_WIDTH || 
        head.y < 0 || head.y >= GRID_HEIGHT) {
        game->game_over = 1;
        return;
    }
    
    // 检查自身碰撞
    for (int i = 1; i < game->length; i++) {
        if (head.x == game->body[i].x && head.y == game->body[i].y) {
            game->game_over = 1;
            return;
        }
    }
}

3. 显示驱动实现

c 复制代码
// display.c
void RenderGame(GameState* game) {
    OLED_Clear();
    
    // 绘制蛇身
    for (int i = 0; i < game->length; i++) {
        DrawBlock(game->body[i].x, game->body[i].y, 1);
    }
    
    // 绘制食物
    DrawBlock(game->food.x, game->food.y, 1);
    
    // 显示分数
    char score_str[20];
    sprintf(score_str, "Score: %d", game->score);
    OLED_ShowString(0, 0, score_str);
    
    // 游戏结束提示
    if (game->game_over) {
        OLED_ShowString(20, 3, "GAME OVER!");
        OLED_ShowString(10, 4, "Press RESET");
    }
    
    OLED_Refresh();
}

void DrawBlock(int x, int y, int fill) {
    // 将网格坐标转换为像素坐标
    int pixel_x = x * GRID_SIZE;
    int pixel_y = y * GRID_SIZE;
    
    for (int i = 0; i < GRID_SIZE; i++) {
        for (int j = 0; j < GRID_SIZE; j++) {
            OLED_DrawPixel(pixel_x + i, pixel_y + j, fill);
        }
    }
}

4. 输入处理实现

c 复制代码
// input.c
#define UP_PIN    GPIO_PIN_0
#define DOWN_PIN  GPIO_PIN_1
#define LEFT_PIN  GPIO_PIN_2
#define RIGHT_PIN GPIO_PIN_3
#define GPIO_PORT GPIOA

void Input_Init(void) {
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    
    __HAL_RCC_GPIOA_CLK_ENABLE();
    
    GPIO_InitStruct.Pin = UP_PIN | DOWN_PIN | LEFT_PIN | RIGHT_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(GPIO_PORT, &GPIO_InitStruct);
}

void ProcessInput(GameState* game) {
    static uint32_t last_press = 0;
    uint32_t now = HAL_GetTick();
    
    if (now - last_press < 200) return; // 防抖延时
    
    if (HAL_GPIO_ReadPin(GPIO_PORT, UP_PIN) == GPIO_PIN_RESET) {
        if (game->direction != 2) game->direction = 0; // 不能向下时才能向上
        last_press = now;
    }
    else if (HAL_GPIO_ReadPin(GPIO_PORT, DOWN_PIN) == GPIO_PIN_RESET) {
        if (game->direction != 0) game->direction = 2; // 不能向上时才能向下
        last_press = now;
    }
    else if (HAL_GPIO_ReadPin(GPIO_PORT, LEFT_PIN) == GPIO_PIN_RESET) {
        if (game->direction != 1) game->direction = 3; // 不能向右时才能向左
        last_press = now;
    }
    else if (HAL_GPIO_ReadPin(GPIO_PORT, RIGHT_PIN) == GPIO_PIN_RESET) {
        if (game->direction != 3) game->direction = 1; // 不能向左时才能向右
        last_press = now;
    }
}

五、游戏功能扩展

1. 难度分级

c 复制代码
// 在游戏状态中添加难度级别
typedef struct {
    // ...其他成员
    int difficulty; // 0:简单, 1:中等, 2:困难
} GameState;

// 根据难度调整游戏速度
void UpdateGameSpeed(GameState* game) {
    switch (game->difficulty) {
        case 0: // 简单
            game->speed = 150; // ms
            break;
        case 1: // 中等
            game->speed = 100;
            break;
        case 2: // 困难
            game->speed = 70;
            break;
    }
}

2. 特殊食物

c 复制代码
// 添加特殊食物类型
typedef enum {
    FOOD_NORMAL,
    FOOD_SPEED_UP,
    FOOD_SLOW_DOWN,
    FOOD_REVERSE
} FoodType;

typedef struct {
    Point pos;
    FoodType type;
    uint32_t spawn_time;
} SpecialFood;

// 随机生成特殊食物
void SpawnSpecialFood(GameState* game) {
    if (rand() % 10 == 0) { // 10%几率生成特殊食物
        SpecialFood sf;
        do {
            sf.pos.x = rand() % GRID_WIDTH;
            sf.pos.y = rand() % GRID_HEIGHT;
        } while (IsPointOnSnake(game, sf.pos) || 
                (sf.pos.x == game->food.x && sf.pos.y == game->food.y));
        
        sf.type = rand() % 4;
        sf.spawn_time = HAL_GetTick();
        
        // 添加到特殊食物列表
    }
}

3. 游戏存档功能

c 复制代码
// 使用Flash存储游戏状态
void SaveGameState(GameState* game) {
    // 擦除Flash扇区
    FLASH_EraseInitTypeDef erase;
    erase.TypeErase = FLASH_TYPEERASE_PAGES;
    erase.PageAddress = 0x0800F000; // 选择页地址
    erase.NbPages = 1;
    
    uint32_t page_error;
    HAL_FLASH_Unlock();
    HAL_FLASHEx_Erase(&erase, &page_error);
    
    // 写入数据
    uint32_t data[8];
    data[0] = game->score;
    data[1] = game->length;
    // ...其他数据
    
    for (int i = 0; i < 8; i++) {
        HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, 0x0800F000 + i*4, data[i]);
    }
    
    HAL_FLASH_Lock();
}

参考代码 基于stm32的贪吃蛇游戏实现 OLED屏 www.youwenfan.com/contentcss/183119.html

六、优化与改进

1. 显示优化

c 复制代码
// 使用位图字体提高显示效率
const uint8_t font[][8] = {
    {0x3E, 0x51, 0x49, 0x45, 0x3E, 0x00, 0x00, 0x00}, // 0
    {0x00, 0x42, 0x7F, 0x40, 0x00, 0x00, 0x00, 0x00}, // 1
    // ...其他字符
};

void OLED_ShowChar(uint8_t x, uint8_t y, char c) {
    if (c < ' ') return;
    c -= ' ';
    for (int i = 0; i < 8; i++) {
        OLED_DrawBuffer(x, y+i, font[c][i]);
    }
}

2. 性能优化

c 复制代码
// 使用双缓冲减少闪烁
uint8_t display_buffer[128][8]; // 显存缓冲区

void OLED_Refresh(void) {
    for (int page = 0; page < 8; page++) {
        OLED_SetPage(page);
        OLED_SetColumn(0);
        for (int col = 0; col < 128; col++) {
            OLED_WriteData(display_buffer[col][page]);
        }
    }
}

3. 增加音效

c 复制代码
// 使用PWM驱动蜂鸣器
void PlaySound(int frequency, int duration) {
    uint32_t period = 1000000 / frequency; // 微秒
    uint32_t cycles = duration * 1000 / period;
    
    for (uint32_t i = 0; i < cycles; i++) {
        HAL_GPIO_WritePin(BUZZER_GPIO_Port, BUZZER_Pin, GPIO_PIN_SET);
        Delay_us(period / 2);
        HAL_GPIO_WritePin(BUZZER_GPIO_Port, BUZZER_Pin, GPIO_PIN_RESET);
        Delay_us(period / 2);
    }
}

// 在游戏中添加音效
void UpdateGame(GameState* game) {
    // ...
    if (ate_food) {
        PlaySound(523, 50); // C5音符
    }
    if (game_over) {
        PlaySound(262, 200); // C4音符
    }
}

七、项目资源

1. 硬件连接

OLED引脚 STM32引脚 功能
SDA PB7 I2C数据线
SCL PB6 I2C时钟线
VCC 3.3V 电源
GND GND
按键引脚 STM32引脚 功能
UP PA0 上方向
DOWN PA1 下方向
LEFT PA2 左方向
RIGHT PA3 右方向

2. 开发环境

  • IDE: STM32CubeIDE

  • 编译器: GCC ARM Embedded

  • 调试器: ST-Link V2

  • 库依赖:

  • HAL库(STM32CubeMX生成)

  • SSD1306 OLED驱动库

3. 项目结构

c 复制代码
├── Core/
│   ├── Inc/            // 头文件
│   ├── Src/            // 源文件
│   └── Startup/        // 启动文件
├── Drivers/
│   ├── CMSIS/          // 芯片支持包
│   └── STM32F1xx_HAL_Driver/ // HAL库
├── MDK-ARM/            // Keil工程文件
└── STM32CubeIDE/       // Eclipse工程文件

八、总结

本实现展示了如何使用STM32和OLED显示屏创建经典的贪吃蛇游戏。项目包含完整的游戏逻辑、显示驱动和用户输入处理,并提供了多种扩展功能如难度分级、特殊食物和音效反馈。通过模块化设计和优化技术,实现了流畅的游戏体验和高效的资源利用。

该项目的核心价值在于:

  1. 展示了嵌入式游戏开发的基本流程

  2. 提供了OLED显示和按键输入的完整实现

  3. 演示了游戏状态管理和碰撞检测算法

  4. 为后续开发更复杂的嵌入式游戏奠定了基础

相关推荐
不会计算机的g_c__b7 分钟前
Java坦克大战游戏项目分析与实现
游戏
EdmundXjs18 分钟前
flashrom v1.5.1 Windows
windows·stm32·单片机
Saniffer_SH2 小时前
【市场洞察】一叶知秋 - 从2026年开年Quarch公司PCIe 6.0测试工具销售状况说起
服务器·人工智能·嵌入式硬件·测试工具·fpga开发·自动化·压力测试
llilian_163 小时前
铷原子频率标准 以时频基准破局,为计量校准赋能 时基铷钟
网络·功能测试·单片机·嵌入式硬件·测试工具·算法
潜创微科技3 小时前
CH9339 双主机 USB3.2 协同 + PD 快充对拷芯片方案
嵌入式硬件·音视频
wearegogog1233 小时前
NEC红外线协议编码与解码(STM32实现)
网络·stm32·嵌入式硬件
振南的单片机世界4 小时前
中断向量表:CPU的“紧急联系人”名单
单片机·嵌入式硬件
llilian_164 小时前
频率计生产厂家 高精度通用频率计核心参数设置指南 双频率计 无线频率计
功能测试·单片机·嵌入式硬件·硬件工程
普中科技4 小时前
【普中 51-Ai8051 开发攻略】-- 第 10 章 矩阵按键实验
单片机·嵌入式硬件·矩阵·开发板·普中科技·ai8051u·aicube
前端不太难5 小时前
鸿蒙游戏 Store 设计(AI + 多端)
人工智能·游戏·harmonyos