news 2026/7/19 3:35:39

电赛24E题井字棋无视觉方案:STM32传感器网络与AI算法实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
电赛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, depth+1, 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, depth+1, 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:玩家落子检测

  • 在任意空位放置玩家棋子
  • 观察串口输出应显示检测到的位置
  • 验证棋盘状态是否正确更新

测试2:AI响应动作

  • 玩家落子后,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((i+1) % 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 调试与测试策略

分级测试

  1. 单元测试:单独测试每个模块功能
  2. 集成测试:测试模块间协作
  3. 系统测试:完整流程测试
  4. 压力测试:长时间运行稳定性测试

调试信息分级

// 调试信息分级输出 #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(...) #endif

9. 电赛参赛实用建议

9.1 时间规划与里程碑

第1周:完成硬件选型和机械结构设计

  • 确定传感器方案和机械臂结构
  • 采购所有硬件组件
  • 完成机械结构搭建

第2周:基础功能实现

  • 完成传感器数据采集
  • 实现机械臂基本控制
  • 完成棋盘状态检测

第3周:AI算法与系统集成

  • 实现Minimax算法
  • 完成状态机整合
  • 进行系统联调

第4周:优化与测试

  • 性能优化和精度调整
  • 完整功能测试
  • 准备演示和文档

9.2 电赛评分要点分析

根据电赛评分标准,重点关注:

功能完整性(40%):1-6问全部实现,系统稳定运行创新性(20%):无视觉方案的独特优势体现技术难度(20%):算法复杂度和硬件集成难度完成度(10%):系统完整性和用户体验文档质量(10%):代码注释和设计文档

9.3 现场演示技巧

演示前准备

  • 准备多个测试用例(快速获胜、防守、平局)
  • 准备备用方案(如手动控制模式)
  • 确保设备运输安全

演示过程

  • 先简要介绍方案特点(无视觉的优势)
  • 演示典型对局过程
  • 展示系统稳定性(连续多局运行)
  • 准备应对评委提问的技术细节

无视觉方案在电赛中的真正价值在于展示了扎实的嵌入式系统设计能力。相比依赖现成视觉库的方案,这种从传感器到AI算法的全链路实现更能体现技术水平。

对于大一新生来说,这个项目是嵌入式入门的最佳实践。它涵盖了STM32编程、传感器应用、电机控制、算法实现等核心技能,为后续更复杂的项目打下坚实基础。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/19 3:34:25

AM62L硬件防火墙配置实战:从寄存器解析到安全架构设计

1. 硬件防火墙在SoC安全架构中的核心地位在嵌入式系统&#xff0c;尤其是像TI AM62L这样的高性能Sitara™处理器中&#xff0c;硬件防火墙&#xff08;Hardware Firewall&#xff09;远不止是一个简单的访问控制列表。它构成了整个系统安全架构的基石&#xff0c;是隔离不同软件…

作者头像 李华
网站建设 2026/7/19 3:33:45

Unity 2D游戏A*寻路算法:从原理到工程实现全解析

1. 项目概述&#xff1a;为什么A*是2D游戏寻路的“瑞士军刀”&#xff1f;在Unity里做2D游戏&#xff0c;想让角色自己找到去目标的路&#xff0c;这几乎是每个开发者都会遇到的坎。你可能会先想到Unity自带的NavMesh&#xff0c;但在2D世界里&#xff0c;它总有点水土不服&…

作者头像 李华
网站建设 2026/7/19 3:33:16

Heroku免费档搭建数据科学自动化流水线

1. 项目概述&#xff1a;用 Heroku 免费档跑通数据科学自动化流水线&#xff0c;不是“玩具”&#xff0c;是真实可用的轻量级生产基座“Automate Your Data Science Life for Free on Heroku”——这个标题乍看像一句营销口号&#xff0c;但拆开来看&#xff0c;它精准锚定了三…

作者头像 李华
网站建设 2026/7/19 3:33:07

OpenStack Horizon与Django认证系统深度解析

1. 项目背景与核心问题在OpenStack生态中&#xff0c;Horizon作为官方Dashboard承担着用户交互的重要角色。而Django作为其底层框架&#xff0c;两者的认证系统如何协同工作&#xff0c;是每个OpenStack开发者必须掌握的底层机制。最近在调试一个租户密码修改功能时&#xff0c…

作者头像 李华
网站建设 2026/7/19 3:31:21

HarmonyOS技术精讲-Connectivity Kit:Wi-Fi扫描与连接实战

开篇&#xff1a;一个常见的Wi-Fi开发误区 很多人在HarmonyOS NEXT里第一次接触Wi-Fi扫描时&#xff0c;会直接按照官方示例写代码&#xff0c;然后发现&#xff1a;扫描结果出来了&#xff0c;但连接总是失败。更头疼的是&#xff0c;失败后的错误回调描述不够详细&#xff0c…

作者头像 李华
网站建设 2026/7/19 3:31:08

Dify工作流实现自动化研究报告生成技术解析

1. 项目概述&#xff1a;Dify工作流实现深度研究自动化这个基于Dify平台构建的Deep Researcher工作流&#xff0c;本质上是一个自动化研究报告生成系统。我在实际部署测试中发现&#xff0c;它最惊艳的地方在于能够将原本需要数小时的人工研究过程&#xff0c;压缩到5分钟内完成…

作者头像 李华