一、引言
随着教育互动化、竞赛智能化需求的提升,传统答题竞赛模式(如举手抢答、有线按钮)存在响应延迟高、参与人数受限等问题。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);
}
}
五、系统测试
- 硬件测试:分别给主持人端和选手端供电,主持人端 OLED 显示初始化信息,选手端成功连接 WiFi 热点;
- 功能测试:主持人按下开始按键后,首个按下抢答键的选手 LED 亮成功灯,其他选手亮失败灯,主持人端 OLED 显示抢答成功选手 ID;
- 稳定性测试:3 名选手同时抢答,系统响应延迟 < 100ms,连续测试 10 次无错误。
六、总结
本系统基于 ESP32 实现了多人无线答题竞赛功能,硬件设计简洁低成本,软件采用 UDP 协议保证低延迟通信。系统可扩展至 10 人以上(增加选手端 ESP32),支持 OLED 显示、LED 指示等功能,适用于各类知识竞赛场景。后续可优化方向:增加答题计时功能、语音提示、分数统计等。