一、研究背景
传统射靶训练是提升手眼协调能力与动态平衡能力的有效手段,但存在训练数据反馈滞后、结果主观性强等问题。现有弹弓射靶训练中,运动员需依赖肉眼判断弹着点位置,无法实时获取精准的环数数据,导致训练效率低下且难以量化进步。随着物联网技术与智能传感器的发展,运动器材的智能化改造成为可能。
本团队基于凯里市第三中学的校本课程实践,发现红外避障传感器在动态检测中具有高灵敏度与低延迟特性,结合ESP-32的物联网能力,提出开发一种智能靶环装置。该装置旨在解决传统射靶训练的痛点:通过实时检测弹着点环数、记录训练数据并同步至手机端,帮助用户精准分析训练效果,优化运动计划。此外,3D打印与激光切割技术的应用可定制化靶环结构,兼顾功能性与成本控制,为校园体育训练提供创新解决方案。
二、研究目的
本研究旨在开发一套基于物联网的智能靶环装置,整合红外避障传感器、ESP-32单片机和Blynk物联网平台,实现以下目标:
1. 精准检测:通过优化传感器布局与算法,实时识别弹着点环数,误差控制在2cm内;
2. 数据可视化:构建手机端交互界面,训练反馈;
3. 结构创新:采用模块化设计结合3D打印技术,实现轻量化靶环结构。
通过技术融合,提升射靶训练的科学性与趣味性,推动体育运动智能化发展。
三、主要创新点
1. 红外避障传感器动态校准技术
传统红外传感器在静态检测中易受环境光干扰,本作品提出动态校准算法:
弹性结构补偿:将传感器与弹簧结构结合,通过弹珠冲击触发传感器位移,利用位移差区分不同环数区域。当弹珠击中靶面时,弹簧压缩量对应环数,传感器检测距离变化并转换为环数信号。
四、作品实现过程
1. 原型设计阶段
· 问题发现:初始设计中传感器垂直安装,弹珠斜向冲击导致误触发。
解决方案:将传感器倾斜15°安装,配合弧形挡板减少非正向冲击干扰。
2. 算法调试阶段
· 问题发现:多传感器信号串扰导致环数误判。
· 解决方案:引入时分复用(TDM)技术,ESP-32按0.1ms间隔轮询各传感器信号,消除交叉干扰。
3. 结构优化阶段
· 迭代案例:初代3D打印靶体重量达2kg,影响便携性。通过拓扑优化算法对模型镂空处理,重量降至0.5kg,强度保持90%以上,而且优化大量代码。
4. 系统集成测试
· 问题发现:传感器的模拟数值出现乱输入的情况。
五、作品成果
1. 硬件结构
· 靶体尺寸:30×30cm方形结构,最大环直径为20cm,5环同心圆布局;外壳:30×30×6cm外壳,用于套嵌靶体。
· 材料:PLA 3D打印框架+木板激光切割面板,总重1.5kg。
· 检测硬件:红外避障传感器13个,ESP-32-s3开发板,外部电源。
2. 核心功能
数据看板:Blynk APP展示命中率数量。
3. 使用说明
· 步骤1:手机连接装置Wi-Fi热点;
· 步骤2:APP点击“开始训练”,5米外射击靶面
·
六、作品测试情况
模拟真实使用场景,进行射击测试,变化检测方面,红外避障传感器与弹簧组合检测的变化数据吻合,测量精度达到预期目标,误差在2CM范围内。
数据处理与记录功能正常,系统能够准确计算并记录每环的变化、数据大小等。
物联网数据传输稳定,实时接收并展示变化数据,历史数据对比和展示功能运行良好。
七、总结与展望
成果:成功实现高精度环数检测与物联网数据传输,成本控制在90元内,具备校园推广价值。
展望:未来拟加入AI姿态分析功能,通过摄像头捕捉射击动作,结合环数数据生成综合训练报告,使用deepsskAI大模型进行数据分析,使用语音模块进行播报。
代码
#include <Arduino.h>
#define BLYNK_PRINT Serial
#include <BlynkSimpleEsp32.h>
char auth[] = "rsBIdzIp1NLZ2Q322TxHr4s5kn2dAlHb"; //Blynk密钥
char ssid[] = " "; // WiFi名称
char pass[] = " ";// WiFi密码
char server[] = "XXXXXXXXXXXXXXXXXXXXXXXXX"; //Blynk服务器地址
uint16_t port = 8080;
struct SensorConfig {
int pin; // 传感器引脚
int lower; // 阈值下限
int upper; // 阈值上限
bool triggered; // 当前触发状态
int triggerCount; // 累计触发次数
};
struct Region {
SensorConfig sensors[4]; // 每个区域最多4个传感器
int sensorCount; // 实际传感器数量
int sendValue; // 区域得分值
bool regionTriggered; // 区域触发状态
};
//设备配置 统一整理方便测试 由于每个传感器的模拟值不一样,我使用独立的阈值来识别
Region regions[] = {
// 区域1(单传感器)
{ {{4, 180, 285, false, 0}}, 1, 5, false },
// 区域2(双传感器)
{ {{5, 253, 265, false, 0},
{6, 260, 275, false, 0}}, 2, 4, false },
// 区域3(双传感器)
{ {{7, 240, 260, false, 0},
{8, 280, 195, false, 0}}, 2, 3, false },
// 区域4(四传感器)
{ {{15, 100, 100, false, 0},
{16, 100, 100, false, 0},
{17, 100, 100, false, 0},
{18, 100, 100, false, 0}}, 4, 2, false },
// 区域5(四传感器)
{ {{3, 105, 105, false, 0},
{12, 100, 100, false, 0},
{9, 100, 100, false, 0},
{10, 150, 100, false, 0}}, 4, 1, false }
};
//定义传感器连接的阈值 防止传感器没有在连接时乱报数据创新点
const int MIN_CONNECTED_VALUE = 170;
const int MAX_CONNECTED_VALUE = 350;
int totalScore = 0;
// ================ Blynk虚拟引脚映射 ================
#define TOTAL_SCORE_PIN V0 // 总分虚拟引脚
#define SENSOR_DATA_START V1 // 传感器数据起始引脚
// ================ 初始化设置 ================
void setup() {
Serial.begin(115200);
Blynk.begin(auth, ssid, pass, server, port);
// 初始化所有传感器引脚
for(int r = 0; r < 5; r++){
for(int s = 0; s < regions[r].sensorCount; s++){
pinMode(regions[r].sensors[s].pin, INPUT);
}
}
delay(1000); // 等待硬件稳定
}
// ================ Blynk数据发送函数 ================
void sendSensorData() {
// 发送总分
Blynk.virtualWrite(TOTAL_SCORE_PIN, totalScore);
// 发送各传感器触发次数(按区域顺序)
int virtualPin = SENSOR_DATA_START;
for(int r = 0; r < 5; r++){
for(int s = 0; s < regions[r].sensorCount; s++){
Blynk.virtualWrite(virtualPin++, regions[r].sensors[s].triggerCount);
// 调试输出
Serial.print("传感器");
Serial.print(regions[r].sensors[s].pin);
Serial.print("触发次数:");
Serial.println(regions[r].sensors[s].triggerCount);
}
}
}
// ================ 主循环 ================
void loop() {
XXXXXXXXXXXXXXXX(); // 维持Blynk连接
int currentScore = 0;
String output = "\n======== 传感器数据 ========\n";
// 遍历所有区域
for(int r = 0; r < 5; r++){
bool regionActive = false;
output += "区域" + String(r+1) + ":\n";
// 遍历区域内的传感器
for(int s = 0; s < regions[r].sensorCount; s++){
SensorConfig &sensor = regions[r].sensors[s];
int value = analogRead(XXXXXXXXXXXXXXXXX);
bool isConnected = (value >= MIN_CONNECTED_VALUE) &&
(value <= MAX_CONNECTED_VALUE);
bool sensorActive = false;
// 生成传感器信息
output += " 引脚" + String(XXXXXXXXXXXXXXXXX) +
" (" + String(sensor.lower) +
"-" + String(sensor.upper) + "): ";
if(isConnected){
sensorActive = (value > sensor.lower) && (value < sensor.upper);
output += String(value) + " - " +
(sensorActive ? "触发" : "未触发") + "\n";
// 更新触发计数器
if(sensorActive && !sensor.triggered){
sensor.triggerCount++;
sensor.triggered = true;
}
else if(!sensorActive){
sensor.triggered = false;
}
} else {
output += "未连接\n";
}
if(sensorActive) regionActive = true;
}
// 处理区域得分
if(regionActive && !regions[r].regionTriggered){
currentScore += regions[r].sendValue;
regions[r].regionTriggered = true;
}
else if(!regionActive && regions[r].regionTriggered){
regions[r].regionTriggered = false;
}
}
// 更新总分
totalScore += currentScore;
// 发送数据到Blynk
sendSensorData();
// 串口输出
output += "============================\n";
output += "实时触发分数: " + String(currentScore) + "\n";
output += "累计总分: " + String(totalScore) + "\n";
output += "============================\n";
Serial.print(output);
delay(500); // 控制数据更新频率
}