行业资讯
电赛24E题井字棋无视觉方案:STM32传感器网络与AI算法实现
如果你是一名大一新生面对电赛24E题井字棋项目第一反应可能是没有视觉方案这题还能做吗这正是很多参赛者的误区。实际上24E题的无视觉方案不仅可行而且对大一新生来说可能更友好。它考察的核心不是复杂的图像识别而是嵌入式系统设计能力、逻辑算法实现和硬件控制精度。本文将带你完整实现电赛24E题1-6问的无视觉方案。与传统视觉方案相比无视觉方案的优势在于硬件成本更低无需摄像头模块开发门槛更适合初学者系统稳定性更高不受光照条件影响更聚焦于嵌入式核心能力培养1. 这篇文章真正要解决的问题电赛24E题的无视觉方案核心是解决如何在不用摄像头的情况下实现井字棋的自动对弈。这需要解决三个关键问题位置感知问题没有视觉识别如何知道棋子在棋盘上的精确位置棋子识别问题如何区分玩家棋子O和AI棋子X动作执行问题机械臂如何准确放置棋子到指定位置无视觉方案的技术路径是通过矩阵式传感器网络状态机逻辑替代视觉识别。具体来说使用9个红外对管或压力传感器构成3×3检测矩阵通过STM32的ADC采集传感器数据基于状态机模型跟踪棋盘状态变化机械臂采用坐标定位方式动作这种方案虽然放弃了视觉的通用性但获得了更高的可靠性和更低的实现复杂度特别适合嵌入式基础较好的大一学生。2. 基础概念与核心原理2.1 井字棋游戏规则与状态空间井字棋是3×3棋盘的双人游戏玩家轮流在空位放置棋子O和X先连成一线者胜。游戏共有9个位置理论上有3⁹19683种可能状态实际有效状态约5478种。对于无视觉方案我们需要重点关注的是棋盘状态表示和状态转移逻辑。// 棋盘状态表示 - 使用3×3矩阵 typedef enum { EMPTY 0, PLAYER 1, // O棋子 AI 2 // X棋子 } CellState; CellState board[3][3]; // 3×3棋盘状态2.2 无视觉检测原理无视觉检测的核心是分布式传感器网络。每个棋盘格子下方安装一个检测单元常用的方案有红外对管方案每个格子安装一对红外发射管和接收管有棋子遮挡时接收管信号变化通过ADC读取电压值判断有无棋子压力传感器方案使用FSR力敏电阻或薄膜压力传感器棋子放置时压力值变化需要校准每个传感器的阈值霍尔传感器方案棋子底部嵌入磁铁霍尔传感器检测磁场变化抗干扰能力强但成本较高2.3 机械臂定位原理机械臂采用直角坐标系定位将棋盘划分为3×3的网格坐标映射关系 (0,0) (0,1) (0,2) → 第1行 (1,0) (1,1) (1,2) → 第2行 (2,0) (2,1) (2,2) → 第3行机械臂需要实现两个基本动作移动到指定坐标通过步进电机控制X/Y轴移动放置棋子动作通过舵机控制夹爪的抓取/释放3. 环境准备与前置条件3.1 硬件组件清单组件规格要求数量备注主控芯片STM32F103C8T61最小系统板即可步进电机28BYJ-48 ULN20032X/Y轴移动舵机SG901夹爪控制传感器红外对管/压力传感器9每个格子1个电机驱动L298N或TB66121可选如需更大功率电源5V/3A1独立供电避免干扰3.2 软件开发环境IDE: STM32CubeIDE 或 Keil MDK固件库: HAL库或标准库调试工具: ST-Link V2下载器串口工具: 用于调试信息输出3.3 机械结构设计要点机械结构需要保证刚性足够机械臂移动时不会晃动定位准确重复定位误差小于2mm传感器对齐每个检测单元正对棋盘格子中心建议使用亚克力板或3D打印制作机械结构先进行手动测试确保机械运动顺畅。4. 核心流程拆解4.1 系统初始化流程系统上电后需要完成一系列初始化操作void system_init(void) { // 1. 时钟配置 SystemClock_Config(); // 2. GPIO初始化 MX_GPIO_Init(); // 3. ADC初始化传感器读取 MX_ADC_Init(); // 4. 定时器初始化PWM控制 MX_TIM_Init(); // 5. UART初始化调试输出 MX_USART_UART_Init(); // 6. 机械臂回零 mechanical_arm_homing(); // 7. 传感器校准 sensor_calibration(); // 8. 棋盘状态清零 board_reset(); }4.2 主循环状态机设计系统采用状态机模式运行确保逻辑清晰typedef enum { STATE_IDLE 0, // 空闲状态 STATE_WAIT_PLAYER, // 等待玩家落子 STATE_DETECT_MOVE, // 检测玩家落子位置 STATE_AI_THINKING, // AI计算落子位置 STATE_AI_MOVING, // AI执行落子动作 STATE_CHECK_WIN, // 检查胜负 STATE_GAME_OVER // 游戏结束 } GameState; GameState current_state STATE_IDLE; void main_loop(void) { while(1) { switch(current_state) { case STATE_IDLE: idle_state_handler(); break; case STATE_WAIT_PLAYER: wait_player_handler(); break; case STATE_DETECT_MOVE: detect_move_handler(); break; // ... 其他状态处理 } HAL_Delay(10); // 10ms周期执行 } }4.3 传感器数据采集与处理传感器数据的可靠性直接影响系统性能#define SENSOR_THRESHOLD 800 // ADC阈值需要实际校准 // 读取单个传感器状态 uint8_t read_sensor_state(int row, int col) { uint32_t adc_value 0; uint8_t sensor_index row * 3 col; // 选择对应的ADC通道 ADC_ChannelConfTypeDef sConfig {0}; sConfig.Channel sensor_channels[sensor_index]; sConfig.Rank 1; HAL_ADC_ConfigChannel(hadc1, sConfig); // 启动ADC转换 HAL_ADC_Start(hadc1); HAL_ADC_PollForConversion(hadc1, 100); adc_value HAL_ADC_GetValue(hadc1); HAL_ADC_Stop(hadc1); // 判断是否有棋子 return (adc_value SENSOR_THRESHOLD) ? 1 : 0; } // 扫描整个棋盘状态 void scan_entire_board(void) { for(int i 0; i 3; i) { for(int j 0; j 3; j) { uint8_t current_state read_sensor_state(i, j); // 状态变化检测逻辑 if(current_state ! last_board_state[i][j]) { handle_board_change(i, j, current_state); } } } }5. 完整示例与代码实现5.1 机械臂控制核心代码机械臂控制需要精确的坐标映射和运动控制// 文件路径Core/Src/mechanical_arm.c // 机械臂坐标结构体 typedef struct { uint16_t x_steps; // X轴步数 uint16_t y_steps; // Y轴步数 } Coordinate; // 棋盘坐标到机械臂步数的映射 Coordinate board_to_arm(int row, int col) { Coordinate coord; // 每个格子间距假设为50mm步进电机每步0.1mm coord.x_steps (col * 500); // 50mm 500步 coord.y_steps (row * 500); return coord; } // 移动机械臂到指定位置 void move_to_position(int row, int col) { Coordinate target board_to_arm(row, col); // X轴移动 move_stepper(X_AXIS, target.x_steps, STEPPER_CW); HAL_Delay(100); // Y轴移动 move_stepper(Y_AXIS, target.y_steps, STEPPER_CW); HAL_Delay(100); // 放置棋子动作 servo_grip(GRIP_OPEN); // 张开夹爪 HAL_Delay(200); servo_arm(ARM_DOWN); // 手臂下降 HAL_Delay(300); servo_grip(GRIP_CLOSE); // 夹紧棋子 HAL_Delay(200); servo_arm(ARM_UP); // 手臂抬起 HAL_Delay(300); } // 步进电机控制函数 void move_stepper(uint8_t axis, uint16_t steps, uint8_t direction) { // 设置方向引脚 HAL_GPIO_WritePin(axis_dir_port, axis_dir_pin, direction); // 发送脉冲信号 for(uint16_t i 0; i steps; i) { HAL_GPIO_WritePin(axis_step_port, axis_step_pin, GPIO_PIN_SET); HAL_Delay(1); // 脉冲宽度 HAL_GPIO_WritePin(axis_step_port, axis_step_pin, GPIO_PIN_RESET); HAL_Delay(1); // 脉冲间隔 } }5.2 AI算法实现井字棋AI采用极小化极大算法Minimax确保不败// 文件路径Core/Src/ai_algorithm.c // 评估函数 - 计算当前棋盘得分 int evaluate_board(CellState board[3][3]) { // 检查行 for(int i 0; i 3; i) { if(board[i][0] board[i][1] board[i][1] board[i][2]) { if(board[i][0] AI) return 10; else if(board[i][0] PLAYER) return -10; } } // 检查列 for(int j 0; j 3; j) { if(board[0][j] board[1][j] board[1][j] board[2][j]) { if(board[0][j] AI) return 10; else if(board[0][j] PLAYER) return -10; } } // 检查对角线 if(board[0][0] board[1][1] board[1][1] board[2][2]) { if(board[0][0] AI) return 10; else if(board[0][0] PLAYER) return -10; } if(board[0][2] board[1][1] board[1][1] board[2][0]) { if(board[0][2] AI) return 10; else if(board[0][2] PLAYER) return -10; } return 0; // 平局 } // Minimax算法实现 int minimax(CellState board[3][3], int depth, bool is_maximizing) { int score evaluate_board(board); // 如果AI获胜返回正分 if(score 10) return score - depth; // 如果玩家获胜返回负分 if(score -10) return score depth; // 如果没有空位平局 if(!is_moves_left(board)) return 0; if(is_maximizing) { // AI回合最大化分数 int best -1000; for(int i 0; i 3; i) { for(int j 0; j 3; j) { if(board[i][j] EMPTY) { board[i][j] AI; best max(best, minimax(board, depth1, false)); board[i][j] EMPTY; // 回溯 } } } return best; } else { // 玩家回合最小化分数 int best 1000; for(int i 0; i 3; i) { for(int j 0; j 3; j) { if(board[i][j] EMPTY) { board[i][j] PLAYER; best min(best, minimax(board, depth1, true)); board[i][j] EMPTY; // 回溯 } } } return best; } } // 找到AI的最佳落子位置 void find_best_move(CellState board[3][3], int *best_row, int *best_col) { int best_val -1000; *best_row -1; *best_col -1; for(int i 0; i 3; i) { for(int j 0; j 3; j) { if(board[i][j] EMPTY) { board[i][j] AI; int move_val minimax(board, 0, false); board[i][j] EMPTY; // 回溯 if(move_val best_val) { *best_row i; *best_col j; best_val move_val; } } } } }5.3 主控制程序完整实现// 文件路径Core/Src/main.c #include main.h #include mechanical_arm.h #include ai_algorithm.h #include sensor.h // 全局变量定义 CellState board[3][3] {0}; GameState current_state STATE_IDLE; uint8_t last_board_state[3][3] {0}; int main(void) { // 硬件初始化 HAL_Init(); system_init(); printf(井字棋系统启动完成\r\n); printf(等待玩家先手...\r\n); current_state STATE_WAIT_PLAYER; while(1) { switch(current_state) { case STATE_WAIT_PLAYER: // 持续扫描传感器检测玩家落子 scan_entire_board(); break; case STATE_DETECT_MOVE: // 确认玩家落子位置并更新棋盘状态 confirm_player_move(); current_state STATE_CHECK_WIN; break; case STATE_CHECK_WIN: if(check_win_condition(PLAYER)) { printf(玩家获胜\r\n); current_state STATE_GAME_OVER; } else if(is_board_full()) { printf(平局\r\n); current_state STATE_GAME_OVER; } else { current_state STATE_AI_THINKING; } break; case STATE_AI_THINKING: printf(AI思考中...\r\n); ai_make_move(); current_state STATE_AI_MOVING; break; case STATE_AI_MOVING: // 执行机械臂动作 execute_ai_move(); current_state STATE_CHECK_WIN_AI; break; case STATE_CHECK_WIN_AI: if(check_win_condition(AI)) { printf(AI获胜\r\n); current_state STATE_GAME_OVER; } else if(is_board_full()) { printf(平局\r\n); current_state STATE_GAME_OVER; } else { current_state STATE_WAIT_PLAYER; } break; case STATE_GAME_OVER: game_over_handler(); break; } HAL_Delay(50); // 50ms周期执行 } } // 玩家落子确认函数 void confirm_player_move(void) { for(int i 0; i 3; i) { for(int j 0; j 3; j) { uint8_t current_sensor read_sensor_state(i, j); if(current_sensor !last_board_state[i][j] board[i][j] EMPTY) { board[i][j] PLAYER; last_board_state[i][j] 1; printf(玩家落子位置: (%d, %d)\r\n, i, j); return; } } } } // AI执行落子函数 void execute_ai_move(void) { int best_row, best_col; find_best_move(board, best_row, best_col); if(best_row ! -1 best_col ! -1) { printf(AI落子位置: (%d, %d)\r\n, best_row, best_col); move_to_position(best_row, best_col); board[best_row][best_col] AI; last_board_state[best_row][best_col] 1; } }6. 运行结果与效果验证6.1 系统启动验证系统上电后通过串口调试工具观察输出井字棋系统启动完成 机械臂回零中... 传感器校准完成 等待玩家先手...此时机械臂应移动到初始位置如棋盘外或指定等待位置所有传感器指示灯正常。6.2 功能测试流程测试1玩家落子检测在任意空位放置玩家棋子观察串口输出应显示检测到的位置验证棋盘状态是否正确更新测试2AI响应动作玩家落子后AI应在3-5秒内做出响应机械臂应准确移动到目标位置棋子放置后传感器应检测到状态变化测试3胜负判定故意制造玩家连胜局面验证判胜逻辑测试AI的防守能力应能阻止玩家获胜6.3 性能指标验证指标期望值测试方法响应时间5秒从玩家落子到AI开始动作定位精度2mm测量多次放置的偏差检测准确率98%连续测试100次落子系统稳定性连续运行1小时长时间运行测试7. 常见问题与排查思路问题现象可能原因排查方式解决方案机械臂不动电机驱动故障检查电机接线和供电确保5V电源充足检查ULN2003传感器误检测环境光干扰测量ADC值波动调整阈值增加红外调制AI不落子算法陷入死循环添加超时检测限制Minimax搜索深度定位偏差大机械结构松动检查联轴器和导轨加固机械结构重新校准系统重启电源功率不足测量电流波形电机和主控分开供电7.1 传感器调试技巧传感器调试是最常见的难点// 传感器调试函数 - 用于校准阈值 void sensor_debug_mode(void) { printf(传感器调试模式启动\r\n); printf(格式: 传感器编号(0-8) - ADC值 - 状态(0/1)\r\n); while(1) { for(int i 0; i 9; i) { int row i / 3; int col i % 3; uint32_t value read_adc_value(row, col); uint8_t state (value SENSOR_THRESHOLD) ? 1 : 0; printf(S%d: %4d - %d | , i, value, state); if((i1) % 3 0) printf(\r\n); } printf(阈值: %d\r\n, SENSOR_THRESHOLD); HAL_Delay(1000); } }7.2 机械臂精度优化机械臂精度问题通常通过软件补偿解决// 机械臂精度补偿表 // 根据实际测量得到的每个位置的偏差进行补偿 const int16_t compensation_table[3][3][2] { {{0, 0}, {2, -1}, {1, 0}}, // 第1行补偿值 {{-1, 1}, {0, 0}, {1, -1}}, // 第2行补偿值 {{0, 2}, {-1, 1}, {0, 0}} // 第3行补偿值 }; // 带补偿的移动函数 void move_to_position_compensated(int row, int col) { Coordinate target board_to_arm(row, col); target.x_steps compensation_table[row][col][0] * 10; // 每单位补偿10步 target.y_steps compensation_table[row][col][1] * 10; move_stepper(X_AXIS, target.x_steps, STEPPER_CW); move_stepper(Y_AXIS, target.y_steps, STEPPER_CW); }8. 最佳实践与工程建议8.1 代码架构设计原则模块化设计将系统分解为独立的功能模块mechanical_arm.c - 机械臂控制sensor.c - 传感器数据处理ai_algorithm.c - AI算法实现game_logic.c - 游戏规则逻辑接口清晰模块间通过定义良好的接口通信// 机械臂模块接口 void mechanical_arm_init(void); void move_to_position(int row, int col); void mechanical_arm_homing(void); // 传感器模块接口 void sensor_init(void); uint8_t read_sensor_state(int row, int col); void scan_entire_board(void); // AI模块接口 void ai_init(void); void find_best_move(CellState board[3][3], int *row, int *col);8.2 可靠性设计要点电源管理电机驱动与主控板分开供电添加电源滤波电容使用稳压模块确保电压稳定错误处理// 带错误检测的机械臂移动 uint8_t safe_move_to_position(int row, int col) { if(row 0 || row 2 || col 0 || col 2) { printf(错误: 无效坐标(%d, %d)\r\n, row, col); return 0; } if(board[row][col] ! EMPTY) { printf(错误: 位置(%d, %d)已有棋子\r\n, row, col); return 0; } move_to_position(row, col); return 1; }超时保护// 机械臂动作超时检测 uint8_t move_with_timeout(int row, int col, uint32_t timeout_ms) { uint32_t start_time HAL_GetTick(); move_to_position(row, col); while(!is_move_complete()) { if(HAL_GetTick() - start_time timeout_ms) { printf(错误: 机械臂动作超时\r\n); emergency_stop(); return 0; } HAL_Delay(10); } return 1; }8.3 调试与测试策略分级测试单元测试单独测试每个模块功能集成测试测试模块间协作系统测试完整流程测试压力测试长时间运行稳定性测试调试信息分级// 调试信息分级输出 #define DEBUG_LEVEL 1 // 0:关闭 1:错误 2:警告 3:信息 4:详细 #if DEBUG_LEVEL 1 #define LOG_ERROR(...) printf([ERROR] __VA_ARGS__) #else #define LOG_ERROR(...) #endif #if DEBUG_LEVEL 3 #define LOG_INFO(...) printf([INFO] __VA_ARGS__) #else #define LOG_INFO(...) #endif9. 电赛参赛实用建议9.1 时间规划与里程碑第1周完成硬件选型和机械结构设计确定传感器方案和机械臂结构采购所有硬件组件完成机械结构搭建第2周基础功能实现完成传感器数据采集实现机械臂基本控制完成棋盘状态检测第3周AI算法与系统集成实现Minimax算法完成状态机整合进行系统联调第4周优化与测试性能优化和精度调整完整功能测试准备演示和文档9.2 电赛评分要点分析根据电赛评分标准重点关注功能完整性40%1-6问全部实现系统稳定运行创新性20%无视觉方案的独特优势体现技术难度20%算法复杂度和硬件集成难度完成度10%系统完整性和用户体验文档质量10%代码注释和设计文档9.3 现场演示技巧演示前准备准备多个测试用例快速获胜、防守、平局准备备用方案如手动控制模式确保设备运输安全演示过程先简要介绍方案特点无视觉的优势演示典型对局过程展示系统稳定性连续多局运行准备应对评委提问的技术细节无视觉方案在电赛中的真正价值在于展示了扎实的嵌入式系统设计能力。相比依赖现成视觉库的方案这种从传感器到AI算法的全链路实现更能体现技术水平。对于大一新生来说这个项目是嵌入式入门的最佳实践。它涵盖了STM32编程、传感器应用、电机控制、算法实现等核心技能为后续更复杂的项目打下坚实基础。
郑州网站建设
网页设计
企业官网