电赛井字棋无视觉方案:STM32按键矩阵与LED点阵实现
对于大一新生来说电赛的井字棋题目是一个很好的入门项目它综合了硬件控制、逻辑判断和状态管理。题目要求实现一个无视觉方案的井字棋对弈系统这意味着不能使用摄像头等视觉传感器来识别棋盘状态而是需要通过其他传感器如按键、触摸传感器、磁感应等来获取落子位置并通过点阵、LED灯阵或液晶屏来显示棋盘。无视觉方案的核心在于如何可靠地检测玩家的落子动作以及如何清晰地向玩家反馈当前棋盘状态。下面将围绕1到6问的实现提供一个完整的方案涵盖硬件选型、电路设计、核心逻辑、状态机和调试要点。1. 理解题目要求和核心挑战井字棋是一个3x3的棋盘两名玩家轮流在空格放置自己的棋子通常用X和O表示先连成一条直线横、竖、斜者获胜。如果棋盘填满仍未分出胜负则为平局。无视觉方案需要解决几个关键问题落子检测如何知道玩家在哪个位置落子。棋盘显示如何让玩家和裁判看清当前棋盘状态。游戏逻辑如何判断胜负、处理平局、管理游戏流程开始、轮流落子、结束、重置。人机交互除了基本的落子和显示可能还需要有开始按钮、重置按钮、胜负提示如LED灯、蜂鸣器等。对于大一新生建议从最基础的方案入手先实现功能再考虑优化。2. 硬件方案设计按键矩阵与LED点阵一个简单可靠的方案是使用按键矩阵作为输入LED点阵作为输出。2.1 硬件选型清单类别器件说明数量参考主控MCUSTM32F103C8T6 (核心板)资源丰富资料多性价比高1输入器件轻触按键用于9个棋格位置输入9独立按键用于开始/重置等功能1-2输出显示8x8 LED点阵屏 (带MAX7219驱动)可显示3x3棋盘驱动简单1状态指示LED灯红绿LED指示当前玩家、胜负状态2-3声音提示有源蜂鸣器落子提示、胜负提示1其他电阻、杜邦线、面包板/PCB10kΩ上拉电阻220Ω限流电阻若干注意如果题目对成本或功耗有特殊要求可以选用更简单的MCU如STC8系列、ATmega328P和更简单的显示方案如9个LED灯直接表示棋盘但点阵屏方案显示效果更直观专业。2.2 系统连接示意图[按键矩阵] ---- [MCU GPIO] ---- [LED点阵屏 (SPI)] [开始按键] ---- [MCU GPIO] ---- [状态LED] [重置按键] ---- [MCU GPIO] ---- [蜂鸣器]按键连接最简单的做法是9个棋格按键直接连接到MCU的9个GPIO口并配置为上拉输入。当按键按下时GPIO读到低电平。这种接法软件简单但占用IO口较多。如果IO口紧张可以排成3x3的矩阵只需要6个IO口但扫描程序稍复杂。LED点阵屏连接带MAX7219驱动的点阵屏通常通过SPI接口DIN, CLK, CS与MCU连接只需要3个IO口大大节省了资源。MAX7219芯片内部负责扫描显示MCU只需发送显示数据即可。3. 软件逻辑设计与实现软件是项目的核心需要清晰地管理游戏状态。3.1 定义关键数据结构和变量首先在代码开头定义核心的变量和数据结构。// 定义棋盘状态0为空1为玩家1(X)2为玩家2(O) uint8_t board[3][3] {0}; // 定义当前轮到哪位玩家落子 (1 或 2) uint8_t currentPlayer 1; // 定义游戏状态机 typedef enum { GAME_STATE_IDLE, // 空闲等待开始 GAME_STATE_PLAYING, // 对弈中 GAME_STATE_WIN, // 有玩家获胜 GAME_STATE_DRAW // 平局 } GameState_t; GameState_t gameState GAME_STATE_IDLE; // 按键引脚定义以STM32 HAL库为例假设使用9个独立GPIO #define KEY_START_PIN GPIO_PIN_0 #define KEY_START_PORT GPIOA // ... 定义其他9个棋格按键的PIN和PORT3.2 主循环与状态机主程序的核心是一个状态机根据不同的gameState执行不同的操作。int main(void) { // 硬件初始化 HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_SPI1_Init(); // 初始化SPI用于点阵屏 LEDMatrix_Init(); // 初始化点阵屏 Buzzer_Beep(100); // 开机提示音 while (1) { switch (gameState) { case GAME_STATE_IDLE: idle_state_handler(); break; case GAME_STATE_PLAYING: playing_state_handler(); break; case GAME_STATE_WIN: case GAME_STATE_DRAW: game_end_state_handler(); break; } HAL_Delay(50); // 简单的延时避免过于频繁扫描 } }3.3 关键函数实现3.3.1 空闲状态 (GAME_STATE_IDLE)等待玩家按下开始按钮。void idle_state_handler(void) { // 在点阵屏上显示欢迎动画或提示Press Start LEDMatrix_ShowMessage(START); if (HAL_GPIO_ReadPin(KEY_START_PORT, KEY_START_PIN) GPIO_PIN_RESET) { HAL_Delay(20); // 简单消抖 if (HAL_GPIO_ReadPin(KEY_START_PORT, KEY_START_PIN) GPIO_PIN_RESET) { // 确认按键按下初始化游戏 game_init(); gameState GAME_STATE_PLAYING; Buzzer_Beep(50); } while (HAL_GPIO_ReadPin(KEY_START_PORT, KEY_START_PIN) GPIO_PIN_RESET); // 等待按键释放 } } void game_init(void) { // 清空棋盘 for (int i 0; i 3; i) { for (int j 0; j 3; j) { board[i][j] 0; } } // 随机或指定先手玩家 currentPlayer 1; // 更新显示 update_display(); }3.3.2 对弈状态 (GAME_STATE_PLAYING)这是最核心的状态循环扫描9个棋格按键。void playing_state_handler(void) { // 扫描9个按键 for (int row 0; row 3; row) { for (int col 0; col 3; col) { // 获取对应按键的GPIO状态假设有函数get_key_state if (get_key_state(row, col) KEY_PRESSED) { // 处理落子 process_move(row, col); // 处理一次按键后退出扫描避免连续落子 return; } } } } void process_move(int row, int col) { // 检查该位置是否为空 if (board[row][col] ! 0) { // 位置已被占用可以给个提示音或闪烁提示 Buzzer_Beep(20); return; } // 落子有效 board[row][col] currentPlayer; Buzzer_Beep(50); // 落子音效 // 检查胜负 if (check_win(currentPlayer)) { gameState GAME_STATE_WIN; // 闪烁获胜方棋子或显示WIN indicate_winner(currentPlayer); return; } // 检查平局 if (check_draw()) { gameState GAME_STATE_DRAW; // 显示平局提示 indicate_draw(); return; } // 游戏继续切换玩家 currentPlayer (currentPlayer 1) ? 2 : 1; // 更新棋盘显示可以用不同亮度或符号区分玩家 update_display(); }3.3.3 胜负判断函数 (check_win)这是经典的逻辑判断检查8条线3横、3竖、2斜。uint8_t check_win(uint8_t player) { // 检查行 for (int i 0; i 3; i) { if (board[i][0] player board[i][1] player board[i][2] player) { return 1; } } // 检查列 for (int j 0; j 3; j) { if (board[0][j] player board[1][j] player board[2][j] player) { return 1; } } // 检查对角线 if (board[0][0] player board[1][1] player board[2][2] player) { return 1; } if (board[0][2] player board[1][1] player board[2][0] player) { return 1; } return 0; } uint8_t check_draw(void) { for (int i 0; i 3; i) { for (int j 0; j 3; j) { if (board[i][j] 0) { return 0; // 还有空位不是平局 } } } return 1; // 棋盘已满平局 }3.3.4 显示更新函数 (update_display)将内存中的board数组映射到LED点阵屏的显示缓冲区。void update_display(void) { uint8_t displayBuffer[8] {0}; // 8x8点阵的显示缓冲区每字节代表一行 // 遍历3x3棋盘在8x8点阵上画出3x3的格子 // 例如可以用左上角的3x3区域来显示每个棋格占2x2个点 for (int i 0; i 3; i) { for (int j 0; j 3; j) { if (board[i][j] 1) { // 玩家1显示X // 在对应位置画X例如在(i*2, j*2)为中心的2x2区域 draw_x(displayBuffer, i*2, j*2); } else if (board[i][j] 2) { // 玩家2显示O draw_o(displayBuffer, i*2, j*2); } // 为空则什么都不画 } } // 还可以画上棋盘网格线增强可读性 draw_grid(displayBuffer); // 将缓冲区数据发送到点阵屏 LEDMatrix_SendBuffer(displayBuffer); } // 简单的画X函数示例在指定坐标画一个2x2的X void draw_x(uint8_t buf[8], int startRow, int startCol) { buf[startRow] | (1 (7 - startCol)) | (1 (7 - (startCol1))); buf[startRow1] | (1 (7 - startCol)) | (1 (7 - (startCol1))); // 这是一个非常简单的X实际可以画得更复杂美观 }4. 进阶优化与功能扩展实现基本功能后可以考虑以下优化来提升完成度和用户体验以应对电赛的评分要求。4.1 增加人机对战模式题目要求是双人对战但增加一个简单AI作为可选模式是很好的亮点。// 简单的AI寻找获胜机会否则随机落子 void ai_make_move(void) { // 1. 检查AI自己是否能一步获胜 for (int i 0; i 3; i) { for (int j 0; j 3; j) { if (board[i][j] 0) { board[i][j] currentPlayer; // 模拟落子 if (check_win(currentPlayer)) { // 就是这一步了 return; // 棋盘已经修改 } board[i][j] 0; // 撤销模拟 } } } // 2. 检查是否需要阻挡玩家获胜 uint8_t humanPlayer (currentPlayer 1) ? 2 : 1; for (int i 0; i 3; i) { for (int j 0; j 3; j) { if (board[i][j] 0) { board[i][j] humanPlayer; // 模拟玩家落子 if (check_win(humanPlayer)) { board[i][j] currentPlayer; // 实际落下AI的棋子进行阻挡 return; } board[i][j] 0; // 撤销模拟 } } } // 3. 如果中心空着优先占中心 if (board[1][1] 0) { board[1][1] currentPlayer; return; } // 4. 随机选择一个空位落子 int emptyCells[9][2]; int count 0; for (int i 0; i 3; i) { for (int j 0; j 3; j) { if (board[i][j] 0) { emptyCells[count][0] i; emptyCells[count][1] j; count; } } } if (count 0) { int choice rand() % count; board[emptyCells[choice][0]][emptyCells[choice][1]] currentPlayer; } }在playing_state_handler中如果当前是AI回合则调用ai_make_move然后直接进行胜负判断和切换玩家。4.2 完善显示与交互当前玩家提示用不同颜色的LED灯或点阵屏上的特定位置指示当前轮到谁。落子动画落子时让对应的棋子图案有一个短暂的闪烁或放大效果。胜负高亮当有玩家获胜时让连成一条线的三个棋子闪烁高亮。音效多样化为落子、获胜、平局、非法操作设置不同的音调或节奏。4.3 软件抗干扰处理按键消抖上面的代码使用了简单的延时消抖。更可靠的方法是使用状态机或定时器进行软件消抖。防止按键长按在等待按键释放的循环中增加超时判断避免程序卡死。// 改进的按键检测示例状态机消抖 typedef enum {KEY_IDLE, KEY_DEBOUNCE, KEY_PRESSED, KEY_RELEASE} KeyState_t; KeyState_t key_check(GPIO_TypeDef* Port, uint16_t Pin, KeyState_t currentState) { switch (currentState) { case KEY_IDLE: if (HAL_GPIO_ReadPin(Port, Pin) GPIO_PIN_RESET) { currentState KEY_DEBOUNCE; } break; case KEY_DEBOUNCE: HAL_Delay(10); if (HAL_GPIO_ReadPin(Port, Pin) GPIO_PIN_RESET) { currentState KEY_PRESSED; } else { currentState KEY_IDLE; } break; case KEY_PRESSED: if (HAL_GPIO_ReadPin(Port, Pin) GPIO_PIN_SET) { currentState KEY_RELEASE; } break; case KEY_RELEASE: HAL_Delay(10); if (HAL_GPIO_ReadPin(Port, Pin) GPIO_PIN_SET) { currentState KEY_IDLE; } else { currentState KEY_PRESSED; } break; } return currentState; }5. 常见问题与调试要点在实现过程中大一新生最容易遇到以下问题问题现象可能原因检查与解决思路按键无反应1. GPIO模式配置错误应为上拉输入2. 硬件连接虚焊或断路3. 消抖逻辑过于严格或错误1. 用万用表测量按键按下前后引脚电平变化。2. 写一个简单的测试程序只检测一个按键并点亮一个LED。点阵屏不显示或乱码1. SPI时序或模式不匹配2. MAX7219初始化命令错误3. 数据发送顺序MSB/LSB错误1. 查阅MAX7219数据手册确认初始化序列。2. 先尝试发送固定的简单图案如全亮、第一行亮来测试。游戏逻辑混乱如连续落子1. 按键扫描和处理逻辑有漏洞未处理好按键释放2. 状态机切换条件不清晰1. 在按键处理函数中加入while循环等待按键释放。2. 使用调试器或串口打印当前状态和按键值进行逻辑分析。胜负判断错误1.check_win函数逻辑错误2. 棋盘数组board的索引使用错误行列混淆1. 在平局、各种获胜情况下手动设置board数组单步调试check_win函数。程序运行不稳定1. 缺少看门狗2. 中断冲突3. 堆栈溢出1. 开启独立看门狗IWDG。2. 检查是否有不必要的中断或中断服务函数执行时间过长。调试建议分模块调试先让按键和LED灯正常工作再调试点阵屏显示最后整合游戏逻辑。善用串口打印使用printf通过串口输出变量值、函数执行状态这是最有效的调试手段之一。使用逻辑分析仪如果条件允许用逻辑分析仪检查SPI信号可以快速定位点阵屏的通信问题。6. 项目总结与扩展方向通过这个项目你能够掌握嵌入式系统开发的基本流程需求分析、硬件选型、电路搭建、软件模块化设计、状态机编程、调试排错。无视觉方案的核心是构建一个可靠的人机交互闭环。完成基础要求后可以考虑以下扩展来提升项目竞争力更复杂的AI实现极小化极大算法Minimax带Alpha-Beta剪枝使AI不可战胜。多种显示模式增加比分记录、历史对局回顾等功能。无线通信通过蓝牙或Wi-Fi实现两个独立设备的联机对弈。低功耗设计在空闲状态进入低功耗模式通过按键中断唤醒。这个项目很好地融合了硬件和软件是电子设计竞赛中控制类题目的典型代表。把基础功能做稳定、做流畅是取得好成绩的关键。

相关新闻

嵌入式ISP编程实战:寄存器访问模型与帧间操作详解

嵌入式ISP编程实战:寄存器访问模型与帧间操作详解

1. Camera ISP编程模型核心概念解析 在嵌入式视觉系统开发中,图像信号处理器(ISP)扮演着将原始传感器数据转化为可用图像的关键角色。它本质上是一个高度可配置的硬件流水线,而编程的核心,就是通过读写一系列硬件寄存器…

2026/10/17 15:22:23 阅读更多 →
OpenAI实时语音API与Asterisk SIP集成实战指南

OpenAI实时语音API与Asterisk SIP集成实战指南

1. 项目概述:这不是一个“调用API”的玩具,而是一套可落地的语音交互基础设施 你有没有遇到过这样的场景:客服系统永远在说“请按1转人工”,电话接通后却要等三分钟;或者企业想给老客户加个语音回访功能,结…

2026/10/17 4:24:51 阅读更多 →
USB技术演进与转串口开发实战指南

USB技术演进与转串口开发实战指南

1. USB技术基础与协议解析USB(Universal Serial Bus)作为现代计算机最常用的外设连接标准,其技术演进已经深刻影响了我们的数字生活。从1996年的USB 1.0到最新的USB4 2.0,传输速率从1.5Mbps提升至80Gbps,这种指数级增长…

2026/10/19 3:53:27 阅读更多 →

最新新闻

MySQL 8.0 Windows安装避坑指南:从下载到配置的完整教程

MySQL 8.0 Windows安装避坑指南:从下载到配置的完整教程

1. 写在前面:为什么这么多人卡在MySQL 8.0安装这一步如果你搜过“MySQL 8.0安装教程”,大概率跟我当年一样,被各种版本的安装包、缺少的.dll文件、死活连不上的客户端折腾到怀疑人生。MySQL 8.0和5.x版本在安装方式、认证插件、配置思路上都有…

2026/10/19 5:18:16 阅读更多 →
从手工搬运到AI生成:测试报告自动化链路完全拆解

从手工搬运到AI生成:测试报告自动化链路完全拆解

周五下午四点,项目经理在群里催周报,我点开缺陷管理平台、用例管理平台、代码提交历史,临时拼凑。当时就意识到一件事:如果每积累几个月的经验,做这件事的时间成本应该随着熟练度下降,但实际上每周仍然要花…

2026/10/19 5:18:16 阅读更多 →
医疗知识图谱问答系统源码部署避坑指南:Python环境配置与BERT模型启动

医疗知识图谱问答系统源码部署避坑指南:Python环境配置与BERT模型启动

简介:这是一套面向计算机相关专业学生与Python开发者的医疗知识图谱问答系统完整源码,可直接用于毕业设计、期末大作业或课程设计。项目以医疗领域知识图谱为核心,融合意图识别、命名实体识别与知识抽取等模块,构建了从问句理解到…

2026/10/19 5:18:16 阅读更多 →
EWAM:具身世界动作模型的核心范式与工程实践

EWAM:具身世界动作模型的核心范式与工程实践

1. 为什么大家都在谈具身世界动作模型最近半年,我所在的圈子几乎被“具身世界动作模型”(Embodied World Action Model,下文统一用缩写 EWAM)这个词刷屏了。和传统意义上的“大模型”不一样,EWAM 实际上并不是一个单一…

2026/10/19 5:18:16 阅读更多 →
用Vulkan计算着色器实现PP-OCR跨平台GPU推理的完整实践

用Vulkan计算着色器实现PP-OCR跨平台GPU推理的完整实践

先把话放在前面:为什么我盯上了 Vulkan几个月前接了个活儿,要在好几台不同品牌显卡的设备上做文本识别。手头清一色没有支持 CUDA 的机器,有的还是深度定制的嵌入式环境,驱动工具链残缺得让人头疼。我第一反应是老老实实跑 CPU 推…

2026/10/19 5:18:16 阅读更多 →
基于LSTM多时间序列特征提取的道岔故障诊断项目实战

基于LSTM多时间序列特征提取的道岔故障诊断项目实战

简介:本资源面向计算机、人工智能、自动化、通信工程等专业的在校学生与教师,以及从事轨道交通信号故障诊断的工程人员,提供一套基于LSTM多时间序列特征提取的道岔故障诊断完整实现方案。项目以Python为开发语言,围绕道岔动作电流…

2026/10/19 5:17:15 阅读更多 →

日新闻

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

简介:本资源为电源技术方向的学术研究文档,聚焦介质阻挡放电型臭氧发生器电源的频率跟踪型PWM控制策略,面向电力电子、高压电源及臭氧应用领域的科研人员与工程技术人员。针对负载呈容性、等效电容随外加电压升高而增大、谐振频率随之漂移的难…

2026/10/19 0:00:47 阅读更多 →
Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 0:00:47 阅读更多 →
HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

【免费下载链接】HundredCV-Chat 项目地址: https://ai.gitcode.com/hf_mirrors/Jax-dan/HundredCV-Chat 点击查看 免费下载 HundredCV-Chat 是一个由 100 位青年简历数据开发而来的中文多轮对话数据集,共包含 24,750 组日常闲聊对话和 4,871 个话题标签…

2026/10/19 0:00:47 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/19 1:30:52 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 2:54:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →