基于Arduino与光敏电阻的物理随机选择器设计与实现

发布时间:2026/6/1 12:41:01

基于Arduino与光敏电阻的物理随机选择器设计与实现 1. 项目概述与核心思路晚上躺在床上和室友或者兄弟为了谁去关灯而互相推诿这种场景估计很多人都经历过。与其靠“石头剪刀布”或者比拼谁更能“装睡”不如让机器来做个公平的裁决。这就是我设计这个“光敏电阻随机选择器”的初衷。它本质上是一个基于物理随机事件的嵌入式小装置核心是利用两个光敏电阻对光照变化的响应来触发一个二选一的随机结果并用LED灯直观显示。这个项目的核心硬件是Arduino Leonardo搭配两个光敏电阻和两个LED。它的工作原理非常巧妙我们不是去读取光敏电阻的绝对光照值来比较谁更亮而是利用“遮挡”这一动作本身带来的电阻值突变作为触发信号。当两个光敏电阻几乎同时被遮挡比如两个人分别用手快速盖住一个Arduino会检测这两个信号的先后顺序其时间差在毫秒级对于人类来说是“同时”的但对于微控制器而言却有细微的先后。我们利用这个极短时间内的、不可预测的先后顺序作为随机数的种子来决定哪个LED亮起从而公平地选出那个“天选之子”去关灯。整个项目涵盖了嵌入式开发的几个基础但关键的环节传感器电路搭建、模拟信号读取、去抖动逻辑处理、基于时间的随机数生成以及数字输出控制。它麻雀虽小五脏俱全非常适合作为从点亮LED迈向传感器交互的入门实践。接下来我会拆解每一个步骤不仅告诉你怎么做更会解释为什么这么做以及我在调试过程中踩过的那些坑。2. 硬件选型与电路设计解析2.1 核心控制器为什么是Arduino Leonardo在众多Arduino板卡中选择Leonardo主要基于两点考虑。首先成本与功能平衡。相比于最基础的UnoLeonardo使用了ATmega32u4芯片其最大优势是原生支持USB通信可以模拟成键盘、鼠标等HID设备。虽然本项目用不到这个高级功能但Leonardo的价格与Uno相差无几却拥有更多的模拟输入引脚12个 vs 6个为未来扩展留有余地。其次引脚布局。Leonardo的模拟输入引脚A0-A5集中在一侧方便我们整齐地连接两个光敏电阻布线会更清晰。当然Uno、Nano等板子完全可以胜任。选择Leonardo算是我个人的一点偏好也提醒大家在项目启动时根据手头资源、扩展需求和成本进行板卡选型是一个很好的习惯。2.2 传感器与执行器光敏电阻与LED光敏电阻也叫光敏电阻器其核心特性是阻值随光照强度的增加而减小。我们正是利用这个特性。当用手遮盖时照度急剧下降电阻值猛增手放开后电阻值恢复。注意光敏电阻的响应不是瞬时的有一定的延迟且不同型号、不同批次的元件其阻值范围如暗阻几MΩ亮阻几KΩ和灵敏度可能差异很大。因此我们的代码不能依赖固定的阈值而应该关注其变化趋势。LED的选择比较随意颜色根据喜好即可。关键点是限流电阻。Arduino的数字引脚输出电流能力有限通常建议不超过20mA必须串联电阻保护LED和引脚。对于常见的红色/绿色LED压降约2V在5V电压下使用220Ω或330Ω电阻都能将电流限制在安全范围内(5V-2V)/220Ω≈13.6mA。我准备了两种电阻220Ω用于LED限流10KΩ棕红黑黑棕即100 * 10^0 Ω 100Ω这里需要纠正棕红黑黑棕是100 * 10^0 Ω 100Ω这显然不对。标准的5环电阻棕(1)红(2)黑(0)黑(0)棕(1%)应该是120 * 10^0 Ω 120Ω。但通常我们用作上拉/下拉的精度电阻是10KΩ其色环应为棕(1)黑(0)黑(0)红(2)棕(1%)即100 * 10^2 Ω 10KΩ。原文可能笔误或色环解读有误用于与光敏电阻组成分压电路。实际上对于光敏电阻的分压电阻10KΩ是一个很常用的值它能与光敏电阻在常见室内光照下的阻值几KΩ到几十KΩ形成合适的分压比使模拟输入引脚读到变化明显的电压值。2.3 电路原理与搭建详解电路分为两个完全对称的部分分别对应“玩家A”和“玩家B”。每一部分都包含一个光敏电阻检测电路和一个LED指示电路。光敏电阻检测电路分压电路 这是读取模拟信号的关键。将光敏电阻与一个固定电阻这里用10KΩ串联在5V和GND之间。光敏电阻和固定电阻的连接点接到Arduino的模拟输入引脚例如A0和A1。这样该点的电压V_sensor 5V * (R_fixed / (R_photoresistor R_fixed))。当光照强时光敏电阻R_photoresistor小V_sensor接近0V当被遮盖时R_photoresistor变得很大V_sensor接近5V。Arduino的模拟输入引脚将这个0-5V的电压映射为0-1023的整数值。我们通过analogRead()函数读取这个值。注意务必确保光敏电阻和固定电阻的连接顺序。常见的接法是5V - 光敏电阻 - 信号点(A0) - 固定电阻(10KΩ) - GND。这种接法下光照越强读数值越小遮盖时读数值变大。你也可以反过来接5V-固定电阻-信号点-光敏电阻-GND此时光照强则读数大。代码里的判断逻辑需要与之对应。我推荐第一种因为遮盖动作通常对应一个读数“上升沿”在逻辑上更直观。LED指示电路 非常简单。Arduino的数字引脚例如D2和D3串联一个220Ω限流电阻后连接到LED的正极长脚LED的负极短脚接GND。将引脚设置为OUTPUT模式HIGH时点亮LEDLOW时熄灭。搭建实操要点布局清晰在面包板上将两套电路左右分开布局避免飞线交叉便于调试和检查。电源5V和地GND用面包板两侧的电源总线整齐连接。先电源后信号先连接好5V和GND总线确保供电稳定再搭建分压电路和LED电路。色环电阻辨识如果不确定电阻值务必用万用表测量确认。误用电阻值可能导致LED过流损坏或模拟读数范围不理想。光敏电阻的朝向两个光敏电阻应并排放置且朝向一致以确保它们所处的环境基础光照大致相同减少初始偏差。可以用热熔胶或胶带稍微固定一下防止在面包板上松动。3. 核心代码逻辑与编程实现代码是实现“随机选择”大脑。其核心任务可以分解为持续监控两个光敏电阻的状态检测到“遮盖”动作精确记录动作发生的时刻比较时间差并根据此时间差生成随机结果控制LED显示。3.1 状态检测与去抖动这是最基础也最容易出问题的一环。我们不是简单地判断当前光照值是否低于某个阈值因为环境光可能缓慢变化或者手在传感器上方晃动会产生抖动信号。思路我们需要检测一个“稳定的下降沿”如果采用前述第一种电路则是“稳定的上升沿”。具体方法是设置一个动态阈值和稳定时间。程序开始时先连续读取一段时间如500ms的光敏电阻值计算其平均值作为当前环境光的“基线值”。设定一个触发阈值比如比基线值高50-100个单位对于遮盖导致读数上升的情况。这个阈值不能是固定值必须相对于基线动态计算。当读数超过阈值时不立即认为触发而是启动一个“稳定检测”。在接下来的几十毫秒内如50ms持续读数如果读数一直维持在阈值之上才最终确认一次有效的“遮盖”动作。这能有效滤除手指无意掠过引起的误触发。// 伪代码逻辑示意 int baselineA 0; int sensorAValue 0; bool sensorATriggered false; unsigned long triggerTimeA 0; const int SENSOR_THRESHOLD_OFFSET 80; // 触发偏移量 const int DEBOUNCE_DELAY 50; // 去抖动稳定时间 void setup() { // 初始化引脚计算基线值 baselineA, baselineB... } void loop() { sensorAValue analogRead(A0); if (!sensorATriggered) { // 未触发时检测上升沿 if (sensorAValue (baselineA SENSOR_THRESHOLD_OFFSET)) { // 疑似触发开始去抖动检查 delay(DEBOUNCE_DELAY); if (analogRead(A0) (baselineA SENSOR_THRESHOLD_OFFSET)) { sensorATriggered true; triggerTimeA micros(); // 使用微秒级时间戳 } } } // 同理处理传感器B... }实操心得DEBOUNCE_DELAY的值需要根据实际测试调整。太短容易误触发太长则影响响应速度让人感觉装置“迟钝”。我用50ms是一个比较折中的值。另外SENSOR_THRESHOLD_OFFSET也需要根据室内光线和光敏电阻特性调整。在搭建好电路后可以先通过串口监视器打印出两个传感器的实时读数观察用手遮盖前后的数值变化范围从而确定一个合适的偏移量。3.2 时间戳记录与随机种子生成当两个传感器都被有效触发后我们就得到了两个时间戳triggerTimeA和triggerTimeB。关键来了我们利用这两个时间的微秒部分差值作为随机数种子。为什么不用millis()因为millis()精度是毫秒对于几乎同时的动作差值很可能为0随机性不够。micros()提供微秒级精度即使两人动作再同步在微秒尺度上也会有差异这个差异对于人类不可控是很好的随机源。if (sensorATriggered sensorBTriggered) { // 计算时间差绝对值 long timeDiff triggerTimeA - triggerTimeB; // 取时间差的微秒部分低位作为随机种子 randomSeed(abs(timeDiff) % 10000); // 取差值对10000取模得到一个0-9999的种子 // 生成一个随机数来决定胜负 int winner random(0, 2); // 生成0或1 if (winner 0) { // A输A的LED亮去关灯 digitalWrite(ledPinA, HIGH); digitalWrite(ledPinB, LOW); } else { // B输B的LED亮 digitalWrite(ledPinA, LOW); digitalWrite(ledPinB, HIGH); } // 结果保持一段时间比如3秒 delay(3000); // 重置状态准备下一轮 resetGame(); }注意事项micros()函数在大约70分钟后会溢出归零但对于我们这个短时间交互的应用毫无影响。randomSeed()的参数需要是一个变化且不可预测的数时间差微秒值完美符合要求。确保在每次开始新的一轮游戏前都重新用新的时间差播种这样每次的随机序列都不同。3.3 状态机与游戏流程控制一个好的程序应该有清晰的状态流转。我们可以定义一个简单的状态机READY状态等待游戏开始。两个LED可能以某种模式闪烁如慢闪提示装置已就绪。LISTENING状态正在监听传感器触发。此时LED常亮或熄灭。TRIGGERED状态一个传感器被触发等待另一个。可以给点反馈比如被触发对应的LED快速闪烁一下。JUDGING状态两个传感器均被触发计算并显示结果。RESULT状态显示结果赢家LED灭输家LED亮持续数秒后自动回到READY状态。用状态机来组织代码逻辑会非常清晰也便于后期添加更多功能比如超时判断如果一个人触发后另一个人迟迟不触发则自动重置。4. 完整代码实现与逐行解析结合以上思路下面提供一份完整的、注释详细的代码。我强烈建议你在理解的基础上自己敲一遍而不是直接复制粘贴。/* 光敏电阻随机选择器 - 完整代码 硬件连接 光敏电阻A: A0引脚 光敏电阻B: A1引脚 LED A: D2引脚 (串联220Ω电阻) LED B: D3引脚 (串联220Ω电阻) 电路接法5V - 光敏电阻 - A0/A1 - 10KΩ电阻 - GND */ // 引脚定义 const int sensorPinA A0; const int sensorPinB A1; const int ledPinA 2; const int ledPinB 3; // 游戏状态 enum GameState { READY, LISTENING, JUDGING, RESULT }; GameState currentState READY; // 传感器相关变量 int baselineA, baselineB; // 环境光基线值 int thresholdA, thresholdB; // 动态触发阈值 bool triggeredA false; bool triggeredB false; unsigned long triggerTimeA 0; unsigned long triggerTimeB 0; // 时间常量 const unsigned long BASELINE_PERIOD 500; // 计算基线的时间(ms) const int THRESHOLD_OFFSET 70; // 触发阈值偏移量 const unsigned long DEBOUNCE_TIME 40; // 去抖动时间(ms) const unsigned long RESULT_DISPLAY_TIME 3000; // 结果显示时间(ms) const unsigned long READY_BLINK_INTERVAL 500; // 就绪状态LED闪烁间隔(ms) // 其他计时变量 unsigned long lastBlinkTime 0; bool ledState false; unsigned long resultStartTime 0; void setup() { // 初始化串口用于调试完成后可注释掉 Serial.begin(9600); // 设置引脚模式 pinMode(ledPinA, OUTPUT); pinMode(ledPinB, OUTPUT); // 传感器引脚为模拟输入默认即为INPUT无需设置 // 初始关闭所有LED digitalWrite(ledPinA, LOW); digitalWrite(ledPinB, LOW); // 计算环境光基线 calculateBaseline(); // 根据基线计算动态阈值 thresholdA baselineA THRESHOLD_OFFSET; thresholdB baselineB THRESHOLD_OFFSET; Serial.println(系统初始化完成进入就绪状态。); Serial.print(传感器A基线); Serial.println(baselineA); Serial.print(传感器B基线); Serial.println(baselineB); } void loop() { // 根据当前状态执行不同操作 switch (currentState) { case READY: handleReadyState(); break; case LISTENING: handleListeningState(); break; case JUDGING: handleJudgingState(); break; case RESULT: handleResultState(); break; } } // --- 状态处理函数 --- void handleReadyState() { // 就绪状态双LED同步慢闪提示可开始游戏 if (millis() - lastBlinkTime READY_BLINK_INTERVAL) { ledState !ledState; digitalWrite(ledPinA, ledState); digitalWrite(ledPinB, ledState); lastBlinkTime millis(); } // 检查是否有传感器被触发作为开始游戏的信号 // 这里简化处理任意传感器被遮盖即进入监听状态 if (analogRead(sensorPinA) thresholdA || analogRead(sensorPinB) thresholdB) { delay(DEBOUNCE_TIME); // 简单去抖 if (analogRead(sensorPinA) thresholdA || analogRead(sensorPinB) thresholdB) { enterListeningState(); } } } void handleListeningState() { // 监听状态LED常亮持续检测两个传感器 digitalWrite(ledPinA, HIGH); digitalWrite(ledPinB, HIGH); // 检测传感器A if (!triggeredA analogRead(sensorPinA) thresholdA) { delay(DEBOUNCE_TIME); if (analogRead(sensorPinA) thresholdA) { triggeredA true; triggerTimeA micros(); Serial.println(传感器A触发); // 可以给一个反馈比如快速闪烁一下A灯 digitalWrite(ledPinA, LOW); delay(50); digitalWrite(ledPinA, HIGH); } } // 检测传感器B if (!triggeredB analogRead(sensorPinB) thresholdB) { delay(DEBOUNCE_TIME); if (analogRead(sensorPinB) thresholdB) { triggeredB true; triggerTimeB micros(); Serial.println(传感器B触发); // 反馈 digitalWrite(ledPinB, LOW); delay(50); digitalWrite(ledPinB, HIGH); } } // 判断是否两者皆触发 if (triggeredA triggeredB) { currentState JUDGING; Serial.println(双方就位开始裁决...); } } void handleJudgingState() { // 裁决状态计算时间差决定胜负 // 关闭所有LED进入“思考”瞬间 digitalWrite(ledPinA, LOW); digitalWrite(ledPinB, LOW); delay(100); // 增加一点悬念感 // 核心随机逻辑利用微秒级时间差作为随机种子 long timeDiff (long)(triggerTimeA - triggerTimeB); int seed abs(timeDiff) % 10000; // 取绝对值并模10000得到0-9999的种子 randomSeed(seed); // 随机选择0或1。0代表A输1代表B输。 int loser random(0, 2); // random(min, max) 生成 min 到 max-1 的整数 Serial.print(时间差(微秒)); Serial.println(timeDiff); Serial.print(随机种子); Serial.println(seed); Serial.print(输家是); Serial.println(loser 0 ? A : B); // 根据结果点亮输家的LED谁亮谁去关灯 if (loser 0) { digitalWrite(ledPinA, HIGH); // A输A灯亮 } else { digitalWrite(ledPinB, HIGH); // B输B灯亮 } // 进入结果展示状态并记录进入时间 resultStartTime millis(); currentState RESULT; } void handleResultState() { // 结果状态保持显示结果一段时间然后重置游戏 if (millis() - resultStartTime RESULT_DISPLAY_TIME) { resetGame(); currentState READY; Serial.println(本轮结束重置回到就绪状态。); } } // --- 辅助函数 --- void calculateBaseline() { // 计算一段时间内传感器读数的平均值作为基线 long sumA 0, sumB 0; int samples 100; Serial.println(正在计算环境光基线请勿遮挡传感器...); for (int i 0; i samples; i) { sumA analogRead(sensorPinA); sumB analogRead(sensorPinB); delay(BASELINE_PERIOD / samples); } baselineA sumA / samples; baselineB sumB / samples; } void enterListeningState() { Serial.println(游戏开始进入监听状态); currentState LISTENING; triggeredA false; triggeredB false; triggerTimeA 0; triggerTimeB 0; // 确保LED是亮的handleListeningState会处理 } void resetGame() { // 重置所有状态变量 triggeredA false; triggeredB false; triggerTimeA 0; triggerTimeB 0; // 关闭LED digitalWrite(ledPinA, LOW); digitalWrite(ledPinB, LOW); // 注意不改变currentState由调用者决定 }代码关键点解析枚举状态使用enum定义状态使程序逻辑清晰易于维护和扩展。动态基线计算calculateBaseline()函数在启动时自动采样计算环境光强度使阈值能适应不同光照环境提高了装置的鲁棒性。状态处理分离每个状态有独立的处理函数(handleXxxState)loop()函数非常简洁只负责状态路由。带反馈的触发检测在LISTENING状态当某个传感器被触发时让其对应的LED快速闪烁一下给用户一个明确的“已登记”反馈体验更好。随机性保障randomSeed(abs(timeDiff) % 10000)确保了每次裁决的种子都基于本次触发瞬间的、不可预测的微秒差随机性足够。串口调试信息代码中包含了丰富的Serial.print语句在开发阶段可以通过串口监视器观察基线值、触发事件、时间差和结果是调试的利器。项目稳定后可以注释掉以减少开销。5. 调试、优化与外壳制作5.1 调试流程与常见问题排查即使按照教程搭建第一次也难免遇到问题。这里提供一个系统的调试流程供电与基础检查确保USB线连接牢固Arduino板上的电源指示灯ON亮起。用万用表检查面包板上5V和GND总线之间的电压是否为5V左右。LED电路单独测试暂时拔掉传感器连接。写一个简单的测试程序分别控制D2和D3引脚输出HIGH观察两个LED是否能正常点亮。如果不能检查LED极性是否接反限流电阻是否虚焊或阻值过大。传感器电路测试上传一个只读取串口数据的程序。void setup() { Serial.begin(9600); } void loop() { Serial.print(A0: ); Serial.print(analogRead(A0)); Serial.print( | A1: ); Serial.println(analogRead(A1)); delay(200); }打开串口监视器波特率9600。观察两个传感器的数值。用手分别遮盖它们看数值是否有明显变化例如从200上升到800。如果数值不变或变化极小检查分压电路连接是否正确特别是光敏电阻和10KΩ电阻的连接点是否接到了正确的模拟引脚。阈值校准在正常光照和完全遮盖两种状态下记录下传感器的读数范围。根据这个范围在代码中调整THRESHOLD_OFFSET的值。确保这个值能让“遮盖”动作稳定触发又不会被环境光波动误触发。逻辑与状态调试打开完整代码的串口输出按照游戏流程操作。观察串口打印的状态转换、触发信息和裁决结果是否符合预期。常见问题状态卡住。检查resetGame()是否在适当时候被调用状态转换条件是否严谨。常见问题速查表问题现象可能原因排查步骤LED完全不亮电源未接通LED或电阻接反引脚定义错误检查USB连接用万用表测LED两端电压检查代码pinMode和digitalWrite引脚号。只有一个LED亮/传感器工作单个电路连接错误代码中引脚定义不对称对比检查两个对称电路的每一个连接点核对代码中A和B的引脚常量是否分别对应。传感器读数无变化或变化小光敏电阻或分压电阻接触不良接线错误如信号线接到了固定电阻接地端用万用表测量分压点电压是否随光照变化检查电路图确认信号线取自光敏电阻与固定电阻的连接点。装置频繁误触发阈值THRESHOLD_OFFSET设置过低环境光不稳定如窗外车灯闪过增大THRESHOLD_OFFSET增加去抖动时间DEBOUNCE_TIME尝试在calculateBaseline()中增加采样次数或加入滤波算法。随机结果总是偏向一方两个光敏电阻特性差异大初始基线计算不准确触发检测逻辑有bug交换两个光敏电阻的位置看偏差是否跟随元件在setup()中增加delay(1000)确保上电时传感器未被遮挡通过串口仔细对比triggerTimeA和triggerTimeB的值。裁决后无法自动重置RESULT_DISPLAY_TIME设置过长handleResultState()中的时间判断逻辑错误检查resultStartTime是否在进入RESULT状态时被正确赋值缩短RESULT_DISPLAY_TIME测试。5.2 性能优化与功能扩展基础版本完成后可以考虑以下优化和扩展让项目更完善软件滤波环境光可能缓慢变化导致基线漂移。可以在loop()中或定时中断里对基线进行滑动平均滤波实现动态基线跟踪。// 简单的滑动平均滤波示例 float baselineA_smooth baselineA; // 初始化 const float SMOOTH_FACTOR 0.01; // 平滑因子越小越平滑 void updateBaseline() { int currentRead analogRead(sensorPinA); baselineA_smooth (SMOOTH_FACTOR * currentRead) ((1 - SMOOTH_FACTOR) * baselineA_smooth); thresholdA (int)baselineA_smooth THRESHOLD_OFFSET; }增加声音反馈连接一个无源蜂鸣器在不同状态就绪、触发、裁决中、结果公布发出不同音调体验更佳。多人模式如果需要从“两人对决”扩展到“多人抽签”可以增加更多的光敏电阻和LED通道。代码逻辑需要改为记录第一个触发者和最后一个触发者的时间或者所有触发者的时间序列然后进行排序或复杂裁决。“耍赖”检测如果有人迟迟不盖住传感器怎么办可以增加超时判断。在LISTENING状态如果第一个触发后超过一定时间如5秒第二个仍未触发则判定先触发者为输家并给出提示如LED快速闪烁。5.3 外壳设计与制作一个好看的外壳能让项目从“实验原型”升级为“产品”。设计时考虑以下几点传感器开孔为两个光敏电阻设计独立的、大小合适的圆孔。孔洞不能太大以免侧面进光干扰可以用一小段黑色热缩管套在光敏电阻头部作为遮光筒提高方向性。LED指示窗对应LED的位置开孔或者使用半透明的亚克力板作为柔光罩让灯光更柔和。操作提示可以在外壳上印上简单的图标或文字比如两个手掌图案对应两个传感器让使用者一目了然。固定与散热确保Arduino板和面包板能稳固地卡在外壳内避免松动。虽然本项目功耗极低但也要确保外壳有适当的通风孔。制作材料可以选择3D打印精度高外观好、激光切割亚克力板通透美观或者甚至用现成的塑料盒改造最快捷。我的第一个版本就是用了一个旧的手机盒挖了几个洞虽然简陋但完全能用。关键是让内部电路受到保护外观看起来更规整。6. 项目总结与延伸思考经过从电路焊接、代码编写到调试封装的整个过程这个小小的随机选择器就算真正完成了。回顾一下它的核心价值在于将简单的物理传感器光敏电阻和微控制器Arduino通过一个巧妙的逻辑时间差随机化结合起来解决了一个真实生活中的趣味问题。这完美体现了嵌入式开发“感知-计算-控制”的基本范式。在实操中我深刻体会到几个要点第一硬件是基础清晰的电路布局和可靠的连接能避免绝大多数莫名其妙的bug。第二软件逻辑要健壮特别是对于传感器信号必须考虑去抖动、动态阈值和异常处理。第三调试是关键串口打印是你的眼睛学会用它观察程序内部状态能极大提升效率。这个项目的扩展潜力很大。你可以把光敏电阻换成按钮就变成了“反应速度测试器”换成声音传感器可以做成“声音触发计时器”。裁决逻辑也可以变化比如不是二选一而是根据时间差的比例来分配“惩罚”的等级。甚至结合Leonardo的HID功能可以让它直接控制电脑随机打开某个软件或播放一首歌。最后关于随机性的思考。我们利用的是物理世界的微观不确定性两人无法真正同时动作来生成随机种子这在大多数趣味应用中是足够“随机”的。但对于需要密码学级别随机数的应用这种方法是不安全的。不过对于决定谁去关灯这件事它的公平性已经绰绰有余了。下次再和室友陷入关灯僵局时不妨把这个小装置拿出来让科技来决定或许还能成为宿舍里一个有趣的谈资。

相关新闻