基于 ESP32 的多人无线答题竞赛系统设计与实现

一、引言

随着教育互动化、竞赛智能化需求的提升,传统答题竞赛模式(如举手抢答、有线按钮)存在响应延迟高、参与人数受限等问题。ESP32 芯片凭借集成 WiFi/BLE 无线通信、低成本、高扩展性的优势,成为构建无线答题系统的理想选择。本文设计的多人无线答题竞赛系统以 ESP32 为核心,实现主持人端统一控制、选手端快速抢答、结果实时反馈的功能,适用于课堂互动、知识竞赛等场景。

二、系统总体设计

系统分为主持人端选手端两部分:

  • 主持人端:负责发起答题、接收选手抢答数据、判定首个答题者并显示结果;
  • 选手端:接收答题开始信号,通过按键抢答并反馈状态。两者通过 WiFi UDP 协议实现低延迟无线通信,整体架构如图 1 所示(文字描述):主持人端→WiFi 广播开始信号→选手端检测按键→UDP 发送抢答信息→主持人端判定→反馈结果至选手端。

三、硬件设计

1. 核心芯片选型

选用ESP32-WROOM-32作为主控芯片:

  • 集成双频 WiFi(2.4GHz)和蓝牙 5.0,满足无线通信需求;
  • 34 个 GPIO 口,支持多路外设扩展;
  • 内置双核处理器,可并行处理通信与外设控制,保证响应速度。

2. 功能模块选型

模块类型 主持人端 选手端
显示模块 SSD1306 0.96 寸 OLED (可选)SSD1306 OLED
输入模块 轻触按键(开始 / 重置) 轻触按键(抢答)
指示模块 LED(运行 / 抢答成功) LED(抢答成功 / 失败)
供电模块 USB-TTL(5V) 锂电池 + TP4056 充电模块

3. 硬件接线

(1)主持人端接线
  • SSD1306 OLED:SDA→GPIO21,SCL→GPIO22,VCC→3.3V,GND→GND;
  • 开始按键:一端接 GPIO0,另一端接 GND(下拉输入,按键按下时电平为高);
  • 重置按键:一端接 GPIO4,另一端接 GND;
  • 状态 LED:正极接 GPIO5(串 220Ω 电阻),负极接 GND。
(2)选手端接线
  • 抢答按键:一端接 GPIO0,另一端接 GND;
  • 结果 LED:成功 LED 接 GPIO2,失败 LED 接 GPIO15(均串 220Ω 电阻);
  • (可选)OLED:同主持人端接线。

四、软件设计

1. 通信协议设计

采用 WiFi UDP 协议,通信参数如下:

  • 主持人端作为 UDP 服务器,IP 固定为192.168.4.1(AP 模式),端口8888
  • 选手端连接主持人端创建的 WiFi 热点(SSID:Quiz_System,密码:12345678);
  • 数据格式:选手端发送PlayerID:X(X 为选手编号,如 1/2/3),主持人端回复Result:Success/X(Success 表示首个抢答,X 表示失败)。

2. 软件流程图

(1)主持人端流程图
(2)选手端流程图

3. 代码实现

(1)主持人端代码(Arduino IDE)
复制代码
#include <WiFi.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

// WiFi配置
const char* ssid = "Quiz_System";
const char* password = "12345678";
WiFiUDP udp;
unsigned int localPort = 8888;

// OLED配置
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

// GPIO定义
#define START_BUTTON 0
#define RESET_BUTTON 4
#define STATUS_LED 5

// 全局变量
bool quizStarted = false;
String firstPlayer = "";
unsigned long startTime = 0;

void setup() {
  Serial.begin(115200);
  
  // GPIO初始化
  pinMode(START_BUTTON, INPUT_PULLDOWN);
  pinMode(RESET_BUTTON, INPUT_PULLDOWN);
  pinMode(STATUS_LED, OUTPUT);
  digitalWrite(STATUS_LED, LOW);
  
  // OLED初始化
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println(F("OLED初始化失败"));
    while(1);
  }
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0, 0);
  display.println("答题竞赛系统");
  display.println("等待开始...");
  display.display();
  
  // WiFi AP模式初始化
  WiFi.softAP(ssid, password);
  IPAddress apIP = WiFi.softAPIP();
  Serial.print("AP IP地址: ");
  Serial.println(apIP);
  udp.begin(localPort);
}

void loop() {
  // 检测开始按键
  if(digitalRead(START_BUTTON) == HIGH && !quizStarted) {
    quizStarted = true;
    firstPlayer = "";
    startTime = millis();
    display.clearDisplay();
    display.setCursor(0, 0);
    display.println("答题开始!");
    display.display();
    digitalWrite(STATUS_LED, HIGH);
    delay(200); // 消抖
  }
  
  // 检测重置按键
  if(digitalRead(RESET_BUTTON) == HIGH) {
    quizStarted = false;
    firstPlayer = "";
    display.clearDisplay();
    display.setCursor(0, 0);
    display.println("答题竞赛系统");
    display.println("等待开始...");
    display.display();
    digitalWrite(STATUS_LED, LOW);
    delay(200);
  }
  
  // 接收选手数据
  if(quizStarted) {
    int packetSize = udp.parsePacket();
    if(packetSize) {
      char incomingPacket[255];
      int len = udp.read(incomingPacket, 255);
      if(len > 0) incomingPacket[len] = 0;
      Serial.print("收到数据: ");
      Serial.println(incomingPacket);
      
      // 解析选手ID
      String data = incomingPacket;
      String playerID = data.substring(data.indexOf(":")+1);
      
      // 判断首个答题者
      if(firstPlayer == "") {
        firstPlayer = playerID;
        display.clearDisplay();
        display.setCursor(0, 0);
        display.println("抢答成功!");
        display.println("选手: " + firstPlayer);
        display.display();
        
        // 回复成功
        udp.beginPacket(udp.remoteIP(), udp.remotePort());
        udp.print("Result:Success");
        udp.endPacket();
      } else {
        // 回复失败
        udp.beginPacket(udp.remoteIP(), udp.remotePort());
        udp.print("Result:Fail");
        udp.endPacket();
      }
    }
  }
}
(2)选手端代码(Arduino IDE)
复制代码
#include <WiFi.h>
#include <WiFiUDP.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

// WiFi配置
const char* ssid = "Quiz_System";
const char* password = "12345678";
const char* host = "192.168.4.1";
unsigned int port = 8888;
WiFiUDP udp;

// OLED配置
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

// GPIO定义
#define QUIZ_BUTTON 0
#define SUCCESS_LED 2
#define FAIL_LED 15

// 选手编号(需修改,如1/2/3)
#define PLAYER_ID "1"

bool quizActive = false;
bool hasAnswered = false;

void setup() {
  Serial.begin(115200);
  
  // GPIO初始化
  pinMode(QUIZ_BUTTON, INPUT_PULLDOWN);
  pinMode(SUCCESS_LED, OUTPUT);
  pinMode(FAIL_LED, OUTPUT);
  digitalWrite(SUCCESS_LED, LOW);
  digitalWrite(FAIL_LED, LOW);
  
  // OLED初始化
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println(F("OLED初始化失败"));
    while(1);
  }
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0, 0);
  display.println("选手" + String(PLAYER_ID));
  display.println("连接WiFi中...");
  display.display();
  
  // WiFi连接
  WiFi.begin(ssid, password);
  while(WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("WiFi连接成功");
  display.clearDisplay();
  display.setCursor(0, 0);
  display.println("选手" + String(PLAYER_ID));
  display.println("等待答题开始...");
  display.display();
}

void loop() {
  // 检测抢答按键
  if(digitalRead(QUIZ_BUTTON) == HIGH && !hasAnswered) {
    hasAnswered = true;
    // 发送抢答数据
    udp.beginPacket(host, port);
    udp.print("PlayerID:" + String(PLAYER_ID));
    udp.endPacket();
    Serial.println("发送抢答请求");
    
    // 等待结果
    delay(100);
    int packetSize = udp.parsePacket();
    if(packetSize) {
      char incomingPacket[255];
      int len = udp.read(incomingPacket, 255);
      if(len > 0) incomingPacket[len] = 0;
      String result = incomingPacket;
      
      if(result.indexOf("Success") != -1) {
        digitalWrite(SUCCESS_LED, HIGH);
        display.clearDisplay();
        display.setCursor(0, 0);
        display.println("抢答成功!");
        display.display();
      } else {
        digitalWrite(FAIL_LED, HIGH);
        display.clearDisplay();
        display.setCursor(0, 0);
        display.println("抢答失败!");
        display.display();
      }
    }
    delay(200); // 消抖
  }
  
  // 重置(主持人端重置后,手动断电重置或加重置按键)
  if(hasAnswered && digitalRead(QUIZ_BUTTON) == HIGH) {
    hasAnswered = false;
    digitalWrite(SUCCESS_LED, LOW);
    digitalWrite(FAIL_LED, LOW);
    display.clearDisplay();
    display.setCursor(0, 0);
    display.println("选手" + String(PLAYER_ID));
    display.println("等待答题开始...");
    display.display();
    delay(200);
  }
}

五、系统测试

  1. 硬件测试:分别给主持人端和选手端供电,主持人端 OLED 显示初始化信息,选手端成功连接 WiFi 热点;
  2. 功能测试:主持人按下开始按键后,首个按下抢答键的选手 LED 亮成功灯,其他选手亮失败灯,主持人端 OLED 显示抢答成功选手 ID;
  3. 稳定性测试:3 名选手同时抢答,系统响应延迟 < 100ms,连续测试 10 次无错误。

六、总结

本系统基于 ESP32 实现了多人无线答题竞赛功能,硬件设计简洁低成本,软件采用 UDP 协议保证低延迟通信。系统可扩展至 10 人以上(增加选手端 ESP32),支持 OLED 显示、LED 指示等功能,适用于各类知识竞赛场景。后续可优化方向:增加答题计时功能、语音提示、分数统计等。

相关推荐
yyycqupt35 分钟前
蓝牙协议栈的学习(二)
stm32·单片机·嵌入式硬件·mcu·物联网·51单片机·iot
ACP广源盛139246256731 小时前
GSV2125D@ACP#GSV6125#HDMI 2.0 转 DisplayPort 1.4 转换器(带嵌入式 MCU)
嵌入式硬件·计算机外设·音视频
ℳ๓. Sweet4 小时前
【从零开发STM32(HAL版)】一、开发硬件环境准备
stm32·单片机·嵌入式硬件
ACP广源盛139246256734 小时前
GSV2202D@ACP#DisplayPort 1.4 到 HDMI 2.0 转换器(带嵌入式 MCU)
单片机·嵌入式硬件·计算机外设·音视频
梓德原5 小时前
【总结】STM32 SPI DMA 的使用
stm32·单片机·嵌入式硬件
三佛科技-134163842125 小时前
LP3610S开关电源45V同步整流芯片17W (5V 3400MA) 典型应用电路
嵌入式硬件·物联网·智能家居·pcb工艺
@good_good_study6 小时前
STM32 TIM+ADC实验
stm32·单片机
hazy1k6 小时前
MSPM0L1306 从零到入门:第六章 UART —— 让单片机与世界“对话”
stm32·单片机·嵌入式硬件·物联网·51单片机·esp32·iot
qq_7391753697 小时前
开源基于STC8的智能浇花与温湿度报警系统
c语言·stm32·单片机·嵌入式硬件