基于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. 为后续开发更复杂的嵌入式游戏奠定了基础

相关推荐
BackCatK Chen2 小时前
STM32保姆级入门教程|第4章:GPIO输入+外部中断 实现按键控制LED(手把手全流程)
stm32·单片机·外部中断·按键控制 led·stm32cubeid·gpio 输入
悠哉悠哉愿意3 小时前
【单片机学习笔记】第十二届国赛经验复盘
笔记·单片机·嵌入式硬件·学习
rit84324993 小时前
STC8单片机模拟AD转换程序(NTC测温)
单片机·嵌入式硬件
szxinmai主板定制专家3 小时前
基于 STM32 + FPGA 船舶电站控制器设计与实现
arm开发·人工智能·stm32·嵌入式硬件·fpga开发·架构
我不是程序猿儿3 小时前
【嵌入式】编码器计数倍频,机械一格与电气计数
stm32·单片机·嵌入式硬件·学习
Hello World . .3 小时前
51单片机基础外设:GPIO(以LED、按键、数码管为例)
单片机·嵌入式硬件
Crazyong5 小时前
FreeRTOS-CPU使用率统计
单片机·嵌入式硬件
_Ningye12 小时前
STM32 — 6.1 TIM定时中断
stm32·单片机·嵌入式硬件
小白学电子_12 小时前
proteus仿真51单片机通过矩阵按键和数码管制作简单计算器
嵌入式硬件·51单片机·proteus