STM32无视觉井字棋系统:从传感器检测到AI对弈全流程实现
如果你是一名大一新生刚刚接触电子设计竞赛看到井字棋这样的题目可能会觉得无从下手——特别是当大多数解决方案都依赖复杂的视觉识别系统时。但2024年电赛E题真正考验的并不是你是否掌握了高深的图像处理技术而是如何用最基础的硬件和清晰的逻辑思维完成从棋盘状态检测到智能对弈的全流程实现。传统的视觉方案需要摄像头、图像处理算法和较高的计算资源这对大一学生来说门槛较高。而无视觉方案的核心价值在于它用更直接的传感器方案如红外对管、矩阵键盘和状态机逻辑实现了同样可靠的棋盘状态获取大幅降低了硬件成本和调试难度。本文将完整呈现一个从零开始、不依赖视觉的井字棋解决方案覆盖题目要求的1-6问全部功能点。无论你是电赛新手还是正在寻找更简洁可靠的井字棋实现方案这篇文章将带你一步步搭建一个基于STM32的无视觉井字棋对弈系统。我们将重点讲解如何用红外对管阵列检测棋子落子、如何设计状态机管理游戏流程、如何实现不同难度的AI算法以及如何通过OLED显示屏和按键完成人机交互。1. 无视觉方案的核心优势与适用场景为什么在视觉技术成熟的今天还要选择无视觉方案这不仅仅是成本问题更是稳定性和可靠性的考量。1.1 视觉方案的潜在挑战环境光敏感光照变化会影响棋子识别准确率计算资源要求高需要运行图像处理算法对主控性能要求较高调试复杂摄像头标定、图像预处理、特征提取等环节都可能出问题响应延迟图像采集和处理需要一定时间影响实时性1.2 无视觉方案的技术优势稳定性强传感器直接检测物理状态不受光线影响响应快速数字信号直接读取几乎无延迟成本低廉红外对管、按键等基础元件价格远低于摄像头调试简单每个检测点独立工作故障定位容易1.3 适用场景判断无视觉方案特别适合电赛等对可靠性要求高的竞赛场景初学者快速上手的教学项目对成本敏感的原型开发需要快速响应的实时控制系统对于井字棋这种棋盘状态明确的游戏无视觉方案在保证功能完整的前提下大幅降低了实现难度这正是大一新生能够独立完成全题目的关键。2. 系统整体架构设计无视觉井字棋系统的核心在于用硬件传感器替代视觉识别整个系统架构需要围绕状态检测、逻辑控制和交互展示三个层面展开。2.1 硬件系统框图[按键输入] → [STM32主控] → [OLED显示] ↓ ↓ [红外检测] → [状态处理] → [AI决策] → [落子控制]各模块功能说明红外检测模块9组红外对管发射接收对应3×3棋盘格检测棋子放置状态按键输入模块4个方向键1个确认键用于玩家操作和菜单选择主控芯片STM32F103C8T6负责状态管理、AI算法和系统控制显示模块0.96寸OLED显示棋盘状态、游戏结果和菜单界面落子机构舵机控制的机械臂实现自动落子第5、6问要求2.2 软件架构层次应用层游戏逻辑、AI算法、界面管理 逻辑层状态机管理、输入处理、决策引擎 驱动层GPIO控制、定时器、I2C通信、PWM输出 硬件层传感器、执行器、显示设备这种分层设计保证了代码的可维护性每个层次职责明确便于调试和功能扩展。3. 硬件选型与电路设计3.1 核心元器件清单元件名称型号规格数量用途说明主控芯片STM32F103C8T61系统控制核心红外对管TCRT50009棋盘状态检测OLED屏SSD1306 0.96寸1图形化显示舵机SG902X/Y方向移动和落子按键6×6mm轻触开关5用户输入电阻10KΩ9红外接收管上拉3.2 红外检测电路设计每个棋盘格对应一组TCRT5000红外对管电路连接方式VCC → 红外发射管 → 220Ω限流电阻 → GND VCC → 10K上拉电阻 → STM32 ADC引脚 ↑ 红外接收管 → GND工作原理当棋盘格无棋子时红外反射强度较弱ADC读取值较低有棋子时建议使用黑白棋黑色棋子效果更佳反射强度增加ADC值升高。通过阈值判断即可确定格子状态。3.3 接口分配表STM32引脚功能外设连接PA0-PA2红外检测ADCTCRT5000接收端PA4-PA6红外检测ADCTCRT5000接收端PA8-PA10红外检测ADCTCRT5000接收端PB0-PB4按键输入方向键确认键PB6I2C1_SCLOLEDPB7I2C1_SDAOLEDPA11PWMX轴舵机PA12PWMY轴舵机4. 开发环境搭建4.1 软件工具准备IDESTM32CubeIDE集成开发环境固件库HAL库硬件抽象层烧录工具ST-LINK V2编程器串口调试PuTTY或串口助手4.2 工程创建步骤打开STM32CubeIDE新建STM32项目选择芯片型号STM32F103C8T6配置时钟树系统时钟设置为72MHz引脚分配按照接口分配表配置各引脚功能生成代码生成基础工程框架4.3 关键外设配置代码// 文件路径Core/Src/main.c // ADC初始化配置 void MX_ADC_Init(void) { hadc1.Instance ADC1; hadc1.Init.ScanConvMode ADC_SCAN_ENABLE; hadc1.Init.ContinuousConvMode ENABLE; hadc1.Init.DataAlign ADC_DATAALIGN_RIGHT; hadc1.Init.NbrOfConversion 9; if (HAL_ADC_Init(hadc1) ! HAL_OK) { Error_Handler(); } } // I2C初始化OLED显示 void MX_I2C1_Init(void) { hi2c1.Instance I2C1; hi2c1.Init.ClockSpeed 400000; hi2c1.Init.DutyCycle I2C_DUTYCYCLE_2; hi2c1.Init.OwnAddress1 0; hi2c1.Init.AddressingMode I2C_ADDRESSINGMODE_7BIT; if (HAL_I2C_Init(hi2c1) ! HAL_OK) { Error_Handler(); } }5. 棋盘状态检测实现5.1 红外传感器数据采集// 文件路径Core/Src/sensor.c #define GRID_NUM 9 #define DETECT_THRESHOLD 800 // 检测阈值需实际校准 uint16_t grid_values[GRID_NUM]; // 存储9个格子的ADC值 uint8_t board_state[GRID_NUM]; // 棋盘状态0空1玩家2AI void read_all_sensors(void) { ADC_ChannelConfTypeDef sConfig {0}; uint16_t adc_value; for(int i 0; i GRID_NUM; i) { // 配置ADC通道 sConfig.Channel ADC_CHANNEL_0 i; sConfig.Rank i 1; HAL_ADC_ConfigChannel(hadc1, sConfig); // 启动转换并读取结果 HAL_ADC_Start(hadc1); HAL_ADC_PollForConversion(hadc1, 100); adc_value HAL_ADC_GetValue(hadc1); grid_values[i] adc_value; // 状态判断 if(adc_value DETECT_THRESHOLD) { board_state[i] (board_state[i] 0) ? 1 : board_state[i]; } else { board_state[i] 0; // 格子为空 } } }5.2 传感器校准算法由于不同位置的红外对管存在个体差异需要动态校准// 文件路径Core/Src/calibration.c uint16_t baseline_values[GRID_NUM]; // 基准值空棋盘 uint16_t noise_margin 50; // 噪声容限 void calibrate_sensors(void) { // 确保棋盘为空时进行校准 HAL_Delay(1000); for(int i 0; i 10; i) { // 多次采样取平均 read_all_sensors(); for(int j 0; j GRID_NUM; j) { baseline_values[j] grid_values[j]; } HAL_Delay(100); } for(int j 0; j GRID_NUM; j) { baseline_values[j] / 10; } } uint8_t is_piece_placed(uint8_t grid_index) { // 当前值比基准值高一定幅度则认为有棋子 return (grid_values[grid_index] (baseline_values[grid_index] 200)); }6. 游戏逻辑与状态机设计6.1 游戏状态定义// 文件路径Core/Inc/game.h typedef enum { GAME_MENU 0, // 主菜单 GAME_PLAYING, // 对弈中 GAME_AI_THINKING, // AI思考 GAME_AI_MOVING, // AI落子 GAME_OVER, // 游戏结束 GAME_SETTINGS // 设置菜单 } GameState; typedef enum { PLAYER_TURN 1, AI_TURN 2 } TurnState; typedef enum { DIFFICULTY_EASY 0, DIFFICULTY_MEDIUM, DIFFICULTY_HARD } DifficultyLevel;6.2 核心状态机实现// 文件路径Core/Src/game.c GameState current_state GAME_MENU; TurnState current_turn PLAYER_TURN; DifficultyLevel difficulty DIFFICULTY_MEDIUM; void game_state_machine(void) { static uint32_t last_detect_time 0; switch(current_state) { case GAME_MENU: show_main_menu(); handle_menu_input(); break; case GAME_PLAYING: if(current_turn PLAYER_TURN) { // 检测玩家落子 if(HAL_GetTick() - last_detect_time 200) { // 200ms检测一次 detect_player_move(); last_detect_time HAL_GetTick(); } } else { current_state GAME_AI_THINKING; } break; case GAME_AI_THINKING: ai_make_move(); current_state GAME_AI_MOVING; break; case GAME_AI_MOVING: if(execute_ai_move()) { current_turn PLAYER_TURN; current_state GAME_PLAYING; check_game_over(); } break; case GAME_OVER: show_game_result(); if(restart_selected()) { reset_game(); current_state GAME_PLAYING; } break; } }7. AI算法实现三种难度7.1 简单难度 - 随机算法// 文件路径Core/Src/ai_easy.c uint8_t ai_easy_move(void) { uint8_t empty_cells[9]; uint8_t count 0; // 收集所有空位置 for(int i 0; i 9; i) { if(board_state[i] 0) { empty_cells[count] i; } } if(count 0) { // 随机选择一个空位置 uint8_t random_index HAL_GetTick() % count; return empty_cells[random_index]; } return 255; // 无效移动 }7.2 中等难度 - 规则基础算法// 文件路径Core/Src/ai_medium.c uint8_t ai_medium_move(void) { // 1. 检查是否能立即获胜 uint8_t win_move find_winning_move(AI_TURN); if(win_move ! 255) return win_move; // 2. 检查是否需要阻止玩家获胜 uint8_t block_move find_winning_move(PLAYER_TURN); if(block_move ! 255) return block_move; // 3. 优先选择中心位置 if(board_state[4] 0) return 4; // 4. 优先选择角落位置 uint8_t corners[] {0, 2, 6, 8}; for(int i 0; i 4; i) { if(board_state[corners[i]] 0) { return corners[i]; } } // 5. 随机选择 return ai_easy_move(); }7.3 困难难度 - 极小化极大算法// 文件路径Core/Src/ai_hard.c int minimax(uint8_t depth, uint8_t is_maximizing) { int score evaluate_board(); if(score ! 0) return score; // 游戏结束 if(!is_moves_left()) return 0; // 平局 if(is_maximizing) { int best -1000; for(int i 0; i 9; i) { if(board_state[i] 0) { board_state[i] AI_TURN; best max(best, minimax(depth1, !is_maximizing)); board_state[i] 0; } } return best; } else { int best 1000; for(int i 0; i 9; i) { if(board_state[i] 0) { board_state[i] PLAYER_TURN; best min(best, minimax(depth1, !is_maximizing)); board_state[i] 0; } } return best; } } uint8_t ai_hard_move(void) { int best_val -1000; uint8_t best_move 255; for(int i 0; i 9; i) { if(board_state[i] 0) { board_state[i] AI_TURN; int move_val minimax(0, 0); board_state[i] 0; if(move_val best_val) { best_move i; best_val move_val; } } } return best_move; }8. 机械臂控制与落子实现8.1 舵机PWM控制// 文件路径Core/Src/servo.c #define SERVO_MIN 500 // 0.5ms #define SERVO_MAX 2500 // 2.5ms #define SERVO_MID 1500 // 1.5ms void set_servo_position(TIM_HandleTypeDef *htim, uint32_t channel, uint16_t position) { // 限制位置范围 if(position SERVO_MIN) position SERVO_MIN; if(position SERVO_MAX) position SERVO_MAX; // 设置PWM占空比 __HAL_TIM_SET_COMPARE(htim, channel, position); } void move_to_grid(uint8_t grid_index) { // 将格子索引转换为坐标 uint8_t x grid_index % 3; uint8_t y grid_index / 3; // 计算舵机目标位置需要根据机械结构校准 uint16_t x_pos SERVO_MIN (SERVO_MAX - SERVO_MIN) * x / 2; uint16_t y_pos SERVO_MIN (SERVO_MAX - SERVO_MIN) * y / 2; // 先移动X轴 set_servo_position(htim1, TIM_CHANNEL_1, x_pos); HAL_Delay(500); // 再移动Y轴 set_servo_position(htim1, TIM_CHANNEL_2, y_pos); HAL_Delay(500); // 执行落子动作电磁铁或舵机夹持 execute_drop_action(); }8.2 落子动作序列// 文件路径Core/Src/mechanism.c void execute_ai_move_sequence(uint8_t target_grid) { // 1. 移动到目标位置上方 move_to_grid(target_grid); // 2. 下降并放置棋子 lower_arm(); HAL_Delay(300); release_piece(); HAL_Delay(300); raise_arm(); // 3. 更新棋盘状态 board_state[target_grid] AI_TURN; // 4. 返回初始位置 move_to_grid(4); // 回到中心位置待机 }9. 显示界面与用户交互9.1 OLED图形显示// 文件路径Core/Src/display.c void draw_chessboard(void) { // 清屏 OLED_Clear(); // 绘制棋盘网格 for(int i 1; i 2; i) { // 竖线 OLED_DrawLine(21*i, 10, 21*i, 52); // 横线 OLED_DrawLine(10, 1014*i, 52, 1014*i); } // 绘制棋子 for(int i 0; i 9; i) { uint8_t x i % 3; uint8_t y i / 3; uint8_t pos_x 10 x * 21 10; uint8_t pos_y 10 y * 14 7; if(board_state[i] PLAYER_TURN) { // 绘制X玩家 OLED_DrawLine(pos_x-3, pos_y-3, pos_x3, pos_y3); OLED_DrawLine(pos_x3, pos_y-3, pos_x-3, pos_y3); } else if(board_state[i] AI_TURN) { // 绘制OAI OLED_DrawCircle(pos_x, pos_y, 4); } } OLED_Refresh(); }9.2 菜单系统实现// 文件路径Core/Src/menu.c void show_main_menu(void) { OLED_Clear(); OLED_ShowString(20, 10, Tic-Tac-Toe AI, 16); OLED_ShowString(10, 30, Easy Medium Hard, 12); // 显示当前选择 uint8_t cursor_pos[] {10, 50, 90}; OLED_ShowString(cursor_pos[difficulty], 45, , 12); OLED_Refresh(); } void handle_menu_input(void) { if(key_pressed(KEY_LEFT)) { difficulty (difficulty 0) ? difficulty-1 : DIFFICULTY_HARD; } else if(key_pressed(KEY_RIGHT)) { difficulty (difficulty DIFFICULTY_HARD) ? difficulty1 : DIFFICULTY_EASY; } else if(key_pressed(KEY_OK)) { current_state GAME_PLAYING; reset_game(); } }10. 系统集成与主循环10.1 主程序框架// 文件路径Core/Src/main.c int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_ADC1_Init(); MX_I2C1_Init(); MX_TIM1_Init(); // 外设初始化 OLED_Init(); servo_init(); calibrate_sensors(); // 显示欢迎界面 show_welcome_screen(); HAL_Delay(2000); while(1) { // 状态机调度 game_state_machine(); // 传感器数据更新 read_all_sensors(); // 显示更新 update_display(); HAL_Delay(10); // 10ms周期 } }10.2 游戏重置函数// 文件路径Core/Src/game.c void reset_game(void) { // 清空棋盘状态 for(int i 0; i 9; i) { board_state[i] 0; } // 重置游戏状态 current_turn PLAYER_TURN; game_result GAME_ONGOING; // 机械臂回原点 move_to_home_position(); // 更新显示 draw_chessboard(); }11. 调试技巧与常见问题解决在实际开发过程中你可能会遇到以下典型问题11.1 传感器检测不稳定问题现象棋子检测时有时无误触发频繁解决方案增加软件滤波采用多次采样取平均值的策略动态阈值调整根据环境变化自动调整检测阈值状态保持检测到棋子后需要持续确认才更新状态// 改进的检测算法 uint8_t stable_detection_count[9] {0}; void stable_detect_pieces(void) { for(int i 0; i 9; i) { if(is_piece_placed(i)) { if(stable_detection_count[i] 5) { stable_detection_count[i]; } else { // 稳定检测到棋子更新状态 board_state[i] PLAYER_TURN; } } else { stable_detection_count[i] 0; if(board_state[i] PLAYER_TURN) { board_state[i] 0; // 棋子被取走 } } } }11.2 机械臂定位不准问题现象落子位置偏差重复精度差解决方案机械结构加固确保舵机安装牢固减少晃动运动轨迹优化采用先X后Y的直线运动避免斜线运动位置校准每个格子单独校准舵机角度值11.3 AI响应速度慢问题现象困难模式下AI思考时间过长优化方案算法优化添加alpha-beta剪枝预处理预计算常见棋形的评分深度限制限制极小化极大算法的搜索深度12. 电赛实战建议与评分要点12.1 功能实现完整性检查根据电赛24E题要求确保以下功能全部实现✅ 棋盘状态自动检测第1问✅ 人工落子与棋盘显示第2问✅ AI自动对弈第3问✅ 难度分级第4问✅ 机械臂自动落子第5问✅ 完整对弈流程第6问12.2 评分关键点检测准确性误检率低于5%响应时间小于0.5秒AI智能程度困难模式应达到不败水平机械精度落子位置误差小于2mm系统稳定性连续运行30分钟无故障人机交互操作简便显示清晰12.3 现场调试准备准备多种颜色的棋子测试检测适应性准备备用传感器和舵机以防硬件故障编写详细的测试用例文档准备视频演示材料展示完整功能这个无视觉方案的优势在于硬件成本低、稳定性高特别适合电赛现场的环境条件。通过模块化的代码设计和充分的调试即使是大一新生也能在有限时间内完成全部功能要求。最重要的是理解每个模块的工作原理和相互之间的协作关系这样在遇到问题时能够快速定位和解决。建议在正式比赛前进行多次全流程测试确保系统在各种边界条件下都能稳定运行。