基于STM32的无线抢答器设计

基于STM32的无线抢答器设计

一、系统概述与核心功能

1. 系统定位

基于STM32的无线抢答器以“无线信号传输-抢答优先级判断-声光结果反馈-多终端协同”为核心,实现多人抢答场景下的快速响应、公平判罚与实时显示,支持3-8个选手终端、1个主机(裁判端),无线传输距离≥10米,响应延迟≤50ms,适用于知识竞赛、课堂互动、企业团建等场景。

2. 核心功能模块

模块 功能描述
无线传输 选手终端→主机:抢答信号(含终端编号);主机→终端:抢答结果(成功/失败)
抢答判断 主机接收多终端信号,按信号到达顺序判定优先级(先到先得),支持“犯规检测”(提前抢答)
结果显示 主机LCD显示抢答成功选手编号、倒计时(可选),终端LED指示自身状态(待机/抢答中/成功)
声光反馈 抢答成功:主机蜂鸣器鸣响+LED闪烁;犯规:短促蜂鸣+红灯提示
终端控制 选手按键抢答,终端本地显示“已抢答”状态,支持复位(裁判端控制)
低功耗设计 待机时终端关闭无线模块,主机进入低功耗模式(仅保留无线接收)

二、硬件设计方案

1. 核心硬件选型

模块 型号 关键参数 接口方式
主控MCU STM32F103C8T6 72MHz Cortex-M3,64KB Flash,20KB RAM,3个UART,2个SPI,支持外部中断 核心控制器(主机/终端共用)
无线模块 NRF24L01+ 2.4GHz ISM频段,最大传输速率2Mbps,多发一收(1主多从),传输距离10-20米 SPI(SCK/MISO/MOSI/CS/CE)
显示模块 LCD1602(主机) 16×2字符型LCD,HD44780控制器,5V供电,支持自定义字符 并行接口(GPIO模拟)
输入模块 轻触按键(终端) 6×6mm贴片按键,寿命10万次,终端每人1个(编号1-N) GPIO(外部中断)
声光模块 有源蜂鸣器+LED 蜂鸣器(5V,85dB),LED(红/绿双色),终端用绿色(待机)、红色(抢答成功) GPIO(PB0-PB1)
电源模块 5V/2A适配器+AMS1117 输入220V AC,输出5V/2A(主机),3.3V给STM32/NRF24L01(终端用锂电池3.7V) 供电一体化

2. 系统架构与硬件连接

2.1 主机(裁判端)硬件设计

2.2 终端(选手端)硬件设计

三、软件设计与核心代码

1. 系统架构(主机/终端双模式)

2. 核心代码实现(基于HAL库)

2.1 NRF24L01+驱动(主机/终端共用,nrf24l01.c

#include "nrf24l01.h"
#include "spi.h"
#include "gpio.h"

// NRF24L01+寄存器地址
#define CONFIG      0x00
#define EN_AA       0x01
#define EN_RXADDR   0x02
#define SETUP_AW    0x03
#define SETUP_RETR  0x04
#define RF_CH       0x05
#define RF_SETUP    0x06
#define STATUS      0x07
#define RX_ADDR_P0  0x0A
#define TX_ADDR     0x10
#define RX_PW_P0    0x11
#define FIFO_STATUS 0x17

// 全局变量
uint8_t tx_addr[5] = {0x34,0x43,0x10,0x10,0x01};  // 主机接收地址(终端发送目标)
uint8_t rx_addr[5] = {0x34,0x43,0x10,0x10,0x01};  // 终端接收地址(主机发送目标)

// NRF24L01+初始化(主机:PRX模式;终端:PTX模式)
void NRF24L01_Init(uint8_t mode) {
  HAL_GPIO_WritePin(NRF_CE_GPIO_Port, NRF_CE_Pin, GPIO_PIN_RESET);  // CE=0,待机模式
  NRF24L01_WriteReg(CONFIG, 0x0F);  // 使能CRC(2字节),上电,PTX模式(默认)
  if (mode == MODE_PRX) {  // 主机:PRX模式
    NRF24L01_WriteReg(CONFIG, 0x0F | (1<<0));  // PRX模式(PWR_UP=1,PRIM_RX=1)
    NRF24L01_WriteBuf(RX_ADDR_P0, rx_addr, 5);  // 设置接收地址
    NRF24L01_WriteReg(RX_PW_P0, 32);  // 接收数据长度32字节
    NRF24L01_WriteReg(EN_RXADDR, 0x01);  // 使能P0通道
  } else {  // 终端:PTX模式
    NRF24L01_WriteBuf(TX_ADDR, tx_addr, 5);  // 设置发送地址
    NRF24L01_WriteBuf(RX_ADDR_P0, rx_addr, 5);  // 设置接收地址(主机反馈用)
    NRF24L01_WriteReg(EN_AA, 0x01);  // 使能自动应答(P0通道)
  }
  NRF24L01_WriteReg(RF_CH, 40);  // 设置频道40(2.440GHz)
  NRF24L01_WriteReg(RF_SETUP, 0x07);  // 发射功率0dBm,传输速率2Mbps
  NRF24L01_WriteReg(SETUP_RETR, 0x1A);  // 自动重发10次,间隔500us
  HAL_GPIO_WritePin(NRF_CE_GPIO_Port, NRF_CE_Pin, GPIO_PIN_SET);  // CE=1,启动
}

// 发送数据(终端用)
void NRF24L01_SendData(uint8_t *data, uint8_t len) {
  HAL_GPIO_WritePin(NRF_CE_GPIO_Port, NRF_CE_Pin, GPIO_PIN_RESET);
  NRF24L01_WriteReg(CONFIG, 0x0E);  // PTX模式,PWR_UP=1,PRIM_RX=0
  NRF24L01_WriteBuf(0xA0, data, len);  // 写入TX FIFO
  HAL_GPIO_WritePin(NRF_CE_GPIO_Port, NRF_CE_Pin, GPIO_PIN_SET);  // 启动发送
  while (!(NRF24L01_ReadReg(STATUS) & (1<<5)));  // 等待发送完成(TX_DS=1)
  NRF24L01_WriteReg(STATUS, 0x20);  // 清除TX_DS标志
}

// 接收数据(主机用)
uint8_t NRF24L01_ReceiveData(uint8_t *data) {
  if (NRF24L01_ReadReg(STATUS) & (1<<6)) {  // RX_DR=1,接收完成
    NRF24L01_ReadBuf(0x61, data, 32);  // 读取RX FIFO
    NRF24L01_WriteReg(STATUS, 0x40);  // 清除RX_DR标志
    return 1;  // 接收成功
  }
  return 0;  // 无数据
}

2.2 主机主程序(抢答判断与显示,main_host.c

#include "stm32f1xx_hal.h"
#include "nrf24l01.h"
#include "lcd1602.h"
#include "buzzer.h"

// 抢答数据包结构(32字节)
typedef struct {
  uint8_t dev_id;      // 终端编号(1-8)
  uint32_t timestamp;  // 抢答时间戳(ms,基于SysTick)
  uint8_t reserved[27];// 保留字段
} BidData_t;

BidData_t first_bid = {0};  // 首个有效抢答数据
uint8_t bid_success = 0;    // 抢答成功标志(0:无,1:成功,2:犯规)

int main(void) {
  HAL_Init();
  SystemClock_Config();  // 72MHz
  MX_GPIO_Init();        // 初始化GPIO(LCD、蜂鸣器、LED)
  MX_SPI1_Init();        // 初始化SPI1(NRF24L01+)
  LCD1602_Init();        // 初始化LCD1602
  Buzzer_Init();         // 初始化蜂鸣器
  NRF24L01_Init(MODE_PRX);  // 主机:PRX模式
  
  LCD1602_ShowString(0, 0, "Waiting...", 16);  // 初始显示“等待抢答”
  
  while (1) {
    uint8_t rx_buf[32];
    if (NRF24L01_ReceiveData(rx_buf)) {  // 接收终端数据
      BidData_t *bid = (BidData_t *)rx_buf;
      
      if (!bid_success) {  // 首次抢答
        first_bid = *bid;
        bid_success = 1;
        LCD1602_ShowString(0, 0, "Winner: ", 16);
        LCD1602_ShowChar(8, 0, '0' + bid->dev_id, 16);  // 显示终端编号
        Buzzer_Beep(1000);  // 长鸣1秒(成功)
        HAL_GPIO_WritePin(LED_GREEN_GPIO_Port, LED_GREEN_Pin, GPIO_PIN_SET);  // 绿灯亮
      } else {  // 二次抢答(犯规)
        bid_success = 2;
        LCD1602_ShowString(0, 1, "Foul! Dev:", 16);
        LCD1602_ShowChar(10, 1, '0' + bid->dev_id, 16);
        Buzzer_Beep(200);  // 短鸣0.2秒(犯规)
        HAL_GPIO_WritePin(LED_RED_GPIO_Port, LED_RED_Pin, GPIO_PIN_SET);  // 红灯亮
      }
    }
    
    // 裁判复位(按键PA1)
    if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET) {
      bid_success = 0;
      first_bid.dev_id = 0;
      LCD1602_ShowString(0, 0, "Waiting...    ", 16);
      LCD1602_ShowString(0, 1, "            ", 16);
      Buzzer_Off();
      HAL_GPIO_WritePin(LED_GREEN_GPIO_Port, LED_GREEN_Pin, GPIO_PIN_RESET);
      HAL_GPIO_WritePin(LED_RED_GPIO_Port, LED_RED_Pin, GPIO_PIN_RESET);
      HAL_Delay(200);  // 消抖
    }
    
    HAL_Delay(10);  // 10ms轮询一次
  }
}

2.3 终端主程序(抢答发送,main_terminal.c

#include "stm32f1xx_hal.h"
#include "nrf24l01.h"
#include "led.h"

// 终端编号(1-8,通过拨码开关或代码定义)
#define DEV_ID 1  

// 抢答数据包
BidData_t bid_data = {DEV_ID, 0, {0}};

int main(void) {
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();  // 初始化GPIO(按键、LED、NRF24L01+)
  MX_SPI1_Init();
  NRF24L01_Init(MODE_PTX);  // 终端:PTX模式
  LED_Init();  // 初始化LED(绿色待机,红色抢答成功)
  LED_Green_On();  // 初始绿灯(待机)
  
  // 按键中断配置(PA0,下降沿触发)
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  GPIO_InitStruct.Pin = GPIO_PIN_0;
  GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
  GPIO_InitStruct.Pull = GPIO_PULLUP;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
  HAL_NVIC_SetPriority(EXTI0_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(EXTI0_IRQn);
  
  while (1) {
    // 低功耗待机(仅保留中断)
    HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);
  }
}

// 按键中断服务函数(发送抢答信号)
void EXTI0_IRQHandler(void) {
  if (__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_0) != RESET) {
    __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0);
    if (LED_GetState() != RED) {  // 未抢答过
      bid_data.timestamp = HAL_GetTick();  // 记录当前时间戳
      NRF24L01_SendData((uint8_t *)&bid_data, sizeof(bid_data));  // 发送数据
      LED_Red_On();  // 红灯亮(已抢答)
      LED_Green_Off();
    }
  }
}

参考代码 基于STM32的无线抢答器设计 www.youwenfan.com/contentcst/133735.html

四、关键技术与优化

1. 无线传输可靠性

2. 抢答优先级判断

3. 低功耗优化

五、系统调试与扩展

1. 调试步骤

阶段 操作 工具
硬件调试 测量NRF24L01+供电(3.3V),示波器检查SPI时序 万用表、示波器(SCK/MOSI)
无线测试 终端发送“test”数据,主机用串口打印接收内容 串口调试助手(SSCOM)
抢答逻辑 2个终端同时按键,观察主机是否只记录首个 逻辑分析仪(NRF24L01+信号)
声光反馈 抢答成功后,检查蜂鸣器频率(1kHz)和LED状态 示波器(蜂鸣器驱动信号)

2. 扩展功能

六、总结

基于STM32的无线抢答器通过NRF24L01+无线传输实现多终端与主机通信,时间戳优先级判断确保公平性,声光反馈+LCD显示提升交互体验,硬件成本约80元/套(含3个终端+1个主机),软件可扩展性强,适合作为嵌入式通信、实时系统设计的实践项目。

专注于matlab/simulink,电子电路,编程