C51单片机贪吃蛇实战


头像
快乐碳水脑袋
原创
发布时间: 2026-06-27 14:34:39 | 阅读数 0收藏数 0评论数 0
封面
C51单片机学了一段时间,是时候做个小项目检验成果了!本次挑战用8×8 LED点阵显示贪吃蛇的蛇身与轨迹,搭配矩阵键盘控制移动方向。项目涵盖了点阵驱动、按键扫描和游戏逻辑三大核心技能,是入门后绝佳的综合性练手项目。
1

创建项目

  1. 启动软件
  2. 打开 Keil µVision5 集成开发环境。
  3. 创建新工程
  4. 点击顶部菜单栏的 Project > New µVision Project...
  5. 规划存储路径
  6. 在弹出的“Create New Project”对话框中,浏览至常用工作目录。建议新建一个名为 KeilProject 的文件夹,专门用于存放 51 单片机相关代码。
  7. 建立子目录与命名
  8. 进入 KeilProject 文件夹,新建子文件夹并命名为 snake。
  9. 在该子文件夹内,将工程文件命名为 snakeGame。
  10. 完成创建
  11. 点击 Save 保存工程。随后在弹出的设备选择窗口中选择对应的芯片型号,即可开始编写和编译代码。
2

指定单片机类型

直接搜索“AT89C52”单片机并选中。

3

创建可执行文件

完成单片机的选择后,将弹出一个窗口询问是否将启动文件复制到项目中。由于51系列单片机自带有启动文件,因此这里应选择“否”。

  1. 标号1:资源文件
  2. 标号2:资源文件分组
  3. 标号3:创建文件并将其添加到当前分组
  4. 选择文件类型为 C 文件(.c)
  5. 文件名为 main
4

修改工程设置

  1. 生成HEX文件:打开工程选项设置,选择“Output”选项卡,勾选“创建HEX文件”。
  2. 解决中文乱码:依次点击菜单 Edit → Configuration → Encoding,选择 UTF-8 编码格式。
5

编写代码

代码编写完成后,点击工具栏左上角的“构建”按钮。构建成功后,可执行文件将自动生成于项目目录下的 Objects 文件夹中。

/**************************************************************************************
* 贪吃蛇游戏 - 支持穿墙模式,撞自身结束
* 按键映射:S2→上,S6→下,S5→左,S7→右
***************************************************************************************/
#include "reg52.h"

typedef unsigned int u16;
typedef unsigned char u8;

/* ========== 硬件引脚定义(使用唯一名称避免重定义) ========== */
sbit HC595_SRCLK = P3^6; // 74HC595移位时钟
sbit HC595_RCLK = P3^5; // 74HC595锁存时钟
sbit HC595_SER = P3^4; // 74HC595数据输入

#define LEDDZ_COL_PORT P0 // 点阵列选端口(低电平有效)
#define KEY_MATRIX_PORT P1 // 矩阵键盘端口

/* ========== 调试模式:设为1开启,按任意键会在点阵第一行显示键值 ========== */
#define KEY_DEBUG_MODE 0 // 0: 关闭 1: 开启

/* ========== 游戏参数 ========== */
#define SNAKE_MAX_LEN 20
#define GAME_SPEED 150 // 数值越小速度越快

/* ========== 方向常量 ========== */
#define DIR_UP 0
#define DIR_DOWN 1
#define DIR_LEFT 2
#define DIR_RIGHT 3

/* ========== 数据结构 ========== */
struct Point {
u8 x; // 列 (0~7)
u8 y; // 行 (0~7)
};

/* ========== 全局变量 ========== */
struct Point snake[SNAKE_MAX_LEN];
u8 snake_len = 3;
u8 cur_dir = DIR_RIGHT;
u8 next_dir = DIR_RIGHT;
struct Point food;
u8 display_buf[8];

/* ============================================
* 基础驱动函数
* ============================================ */
void delay_10us(u16 ten_us) {
while(ten_us--);
}

void hc595_write_data(u8 dat) {
u8 i;
for(i = 0; i < 8; i++) {
HC595_SER = dat >> 7;
dat <<= 1;
HC595_SRCLK = 0;
delay_10us(1);
HC595_SRCLK = 1;
delay_10us(1);
}
HC595_RCLK = 1;
delay_10us(1);
HC595_RCLK = 0;
}

/* ============================================
* 矩阵键盘扫描(行列式,非阻塞)
* ============================================ */
// 返回值:1~16 对应 S1~S16,0 表示无按键
u8 key_matrix_ranks_scan_nonblock(void) {
u8 key_value = 0;
static u8 last_key = 0; // 防连按

// 扫描第1列 (P1.4=0)
KEY_MATRIX_PORT = 0xf7; // 1111 0111
if(KEY_MATRIX_PORT != 0xf7) {
delay_10us(1000);
if(KEY_MATRIX_PORT != 0xf7) {
switch(KEY_MATRIX_PORT) {
case 0x77: key_value = 1; break;
case 0xb7: key_value = 5; break;
case 0xd7: key_value = 9; break;
case 0xe7: key_value = 13; break;
}
}
}

// 扫描第2列
if(key_value == 0) {
KEY_MATRIX_PORT = 0xfb;
if(KEY_MATRIX_PORT != 0xfb) {
delay_10us(1000);
if(KEY_MATRIX_PORT != 0xfb) {
switch(KEY_MATRIX_PORT) {
case 0x7b: key_value = 2; break;
case 0xbb: key_value = 6; break;
case 0xdb: key_value = 10; break;
case 0xeb: key_value = 14; break;
}
}
}
}

// 扫描第3列
if(key_value == 0) {
KEY_MATRIX_PORT = 0xfd;
if(KEY_MATRIX_PORT != 0xfd) {
delay_10us(1000);
if(KEY_MATRIX_PORT != 0xfd) {
switch(KEY_MATRIX_PORT) {
case 0x7d: key_value = 3; break;
case 0xbd: key_value = 7; break;
case 0xdd: key_value = 11; break;
case 0xed: key_value = 15; break;
}
}
}
}

// 扫描第4列
if(key_value == 0) {
KEY_MATRIX_PORT = 0xfe;
if(KEY_MATRIX_PORT != 0xfe) {
delay_10us(1000);
if(KEY_MATRIX_PORT != 0xfe) {
switch(KEY_MATRIX_PORT) {
case 0x7e: key_value = 4; break;
case 0xbe: key_value = 8; break;
case 0xde: key_value = 12; break;
case 0xee: key_value = 16; break;
}
}
}
}

// 防连按:同一按键未释放则忽略
if(key_value != 0) {
if(key_value == last_key) return 0;
last_key = key_value;
} else {
last_key = 0;
}

return key_value;
}

/* ============================================
* 按键方向处理(映射:2上,6下,5左,7右)
* ============================================ */
void handle_matrix_keys(void) {
u8 key = key_matrix_ranks_scan_nonblock();
if(key == 0) return;

#if KEY_DEBUG_MODE
// 调试模式:在点阵第一行显示键值的二进制(低5位)
display_buf[0] = key;
#endif

switch(key) {
case 2: // S2 → 上
if(cur_dir != DIR_DOWN) next_dir = DIR_UP;
break;
case 6: // S6 → 下
if(cur_dir != DIR_UP) next_dir = DIR_DOWN;
break;
case 5: // S5 → 左
if(cur_dir != DIR_RIGHT) next_dir = DIR_LEFT;
break;
case 7: // S7 → 右
if(cur_dir != DIR_LEFT) next_dir = DIR_RIGHT;
break;
default: break;
}
}

/* ============================================
* 游戏核心逻辑
* ============================================ */
void generate_food(void) {
u8 valid, i;
do {
valid = 1;
food.x = TL0 % 8;
food.y = TH0 % 8;
for(i = 0; i < snake_len; i++) {
if(snake[i].x == food.x && snake[i].y == food.y) {
valid = 0;
break;
}
}
} while(!valid);
}

void update_display_buffer(void) {
u8 i;
for(i = 0; i < 8; i++) display_buf[i] = 0x00;
for(i = 0; i < snake_len; i++) {
display_buf[snake[i].y] |= (0x80 >> snake[i].x);
}
display_buf[food.y] |= (0x80 >> food.x);
}

/* 蛇移动函数,返回1表示移动成功,0表示撞自身(游戏结束) */
u8 move_snake(void) {
struct Point new_head;
u8 i;

new_head.x = snake[0].x;
new_head.y = snake[0].y;
cur_dir = next_dir;

// 计算新蛇头位置,支持穿墙(循环边界)
switch(cur_dir) {
case DIR_UP:
if(new_head.y == 0) new_head.y = 7;
else new_head.y--;
break;
case DIR_DOWN:
if(new_head.y == 7) new_head.y = 0;
else new_head.y++;
break;
case DIR_LEFT:
if(new_head.x == 0) new_head.x = 7;
else new_head.x--;
break;
case DIR_RIGHT:
if(new_head.x == 7) new_head.x = 0;
else new_head.x++;
break;
default: return 0;
}

// 撞自身检测(不检测尾部,因为尾部即将移动)
for(i = 0; i < snake_len - 1; i++) {
if(new_head.x == snake[i].x && new_head.y == snake[i].y) {
return 0; // 撞自身,游戏结束
}
}

// 身体后移(从尾部向头部方向)
for(i = snake_len; i > 0; i--) {
snake[i].x = snake[i-1].x;
snake[i].y = snake[i-1].y;
}
snake[0].x = new_head.x;
snake[0].y = new_head.y;

// 是否吃到食物
if(new_head.x == food.x && new_head.y == food.y) {
if(snake_len < SNAKE_MAX_LEN) snake_len++;
generate_food();
}
return 1;
}

void game_reset(void) {
snake_len = 3;
snake[0].x = 3; snake[0].y = 3;
snake[1].x = 2; snake[1].y = 3;
snake[2].x = 1; snake[2].y = 3;
cur_dir = DIR_RIGHT;
next_dir = DIR_RIGHT;
generate_food();
update_display_buffer();
}

/* ============================================
* 主函数
* ============================================ */
void main(void) {
u8 i;
u16 tick = 0;

game_reset();

// 启动定时器0作为随机数种子
TMOD = 0x01;
TH0 = TL0 = 0x00;
TR0 = 1;

while(1) {
// 动态扫描显示
for(i = 0; i < 8; i++) {
LEDDZ_COL_PORT = ~(0x01 << i);
hc595_write_data(display_buf[i]);
delay_10us(80);
hc595_write_data(0x00); // 消影
}

// 键盘处理
handle_matrix_keys();

// 游戏定时更新
tick++;
if(tick >= GAME_SPEED) {
tick = 0;
if(!move_snake()) {
// 撞自身,游戏复位
game_reset();
}
update_display_buffer();
}
}
}
6

烧录代码

烧录代码前,需先安装 CH341 驱动程序。驱动安装成功后,将单片机连接至电脑,系统会自动识别串口,设备名称通常显示为“CH340”。选择对应串口后,点击“打开程序文件”,定位并选中项目 Objects 目录下生成的 .hex 文件。最后点击“下载”按钮,然后对单片机断电重启即可烧录完成后。

EXE
ch341ser.exe
227.56KB
EXE
stc-isp.exe
3.98MB
7

运行


阅读记录0
点赞0
收藏0
禁止 本文未经作者允许授权,禁止转载
猜你喜欢
评论/提问(已发布 0 条)
头像
评论 评论
收藏 收藏
分享 分享
pdf下载 下载
pdf下载 举报