Arduino光电传感随机抽奖机:从硬件选型到状态机编程全解析

Arduino光电传感器直流电机控制
于 2026-05-29 12:00:51 修改
·本内容遵循CC 4.0 BY-SA版权协议

1. 项目概述与核心思路

做嵌入式开发久了,总想搞点既有趣又能把多个知识点串起来的小项目。最近我就用Arduino Uno、几个光电传感器和一个直流电机,攒了一个物理版的“随机抽奖机”。这东西看起来简单,就是一个带四个洞的转盘,小球掉进哪个洞就给你加对应的分数。但真做起来,你会发现它巧妙地把随机数生成、实时状态检测、电机精准控制和用户交互界面这几个嵌入式系统的核心模块都揉在了一起,是个非常棒的练手项目。

这个装置的核心逻辑是这样的:一个由电机驱动的圆盘在不停地旋转,你把一个小球(比如一颗小钢珠或者玻璃弹珠)从顶部的入口放进去。小球在离心力的作用下会滚向圆盘边缘,那里有四个开孔,每个孔下面都埋着一组光电传感器和LED。小球掉进哪个孔,对应的光电传感器就会检测到光线被遮挡,Arduino随即判定你中了哪个“奖”,并在屏幕上给你加上对应的分数(比如10分、20分、30分、50分)。整个过程充满了不确定性,因为小球落点的随机性由电机转速、圆盘平整度、小球释放时机等多个物理因素共同决定,这比单纯在代码里调用random()函数要有趣得多。

这个项目非常适合有一定Arduino基础,想从点亮LED、读取按钮信号这类简单实验,进阶到“系统集成”阶段的爱好者。你会接触到如何用PWM信号精细控制电机转速、如何搭建并调试光电传感电路来可靠检测微小物体、如何用状态机思想来管理复杂的游戏流程,以及如何将物理世界的随机事件转化为稳定的数字信号。接下来,我就把从电路设计、机械结构搭建到代码编写的完整过程,以及我踩过的那些坑,毫无保留地分享给你。

2. 核心硬件选型与电路设计解析

2.1 主控与核心传感器:为何是Arduino与光电对管?

项目主控选择了经典的Arduino Uno R3。原因很简单:它IO口足够(我们只需要6-7个数字口和1个模拟口),社区资源丰富,驱动直流电机和屏幕的库都很成熟。有朋友问能不能用更便宜的Nano,当然可以,引脚定义基本一致,只是需要注意Nano的电源输出能力稍弱,如果电机启动电流大,最好单独供电。

传感器的选择是项目的关键。检测小球是否落入指定孔位,常见方案有微动开关、红外对管和光电电阻。微动开关需要小球施加一定压力触发,对于滚动的小球来说不够可靠,且机械结构复杂。我最终选择了光电对管(Phototransistor + LED),也就是一个红外发射管和一个红外接收管配对使用。当小球落入孔中,会阻断发射管射向接收管的光路,接收管的电阻会急剧变化,从而被检测到。

注意:这里容易混淆“光敏电阻”和“光电对管”。光敏电阻检测的是环境光强变化,容易受环境光干扰。而光电对管是自成体系的“光路开关”,发射管发出的红外光被接收管接收,形成一个稳定的信号。只有当物体阻断这条光路时,信号才变化,抗干扰能力强得多,非常适合这种需要精准检测的场合。

我用的就是常见的KY-033循迹模块的核心部件:一个红外发射二极管和一个红外接收三极管。你需要为每个检测点准备一对。电路连接上,发射管串联一个220Ω的限流电阻接到5V,接收管的集电极接一个10kΩ的上拉电阻到5V,发射极接地,从集电极引出信号线到Arduino的模拟输入引脚。这样,无遮挡时,接收管导通,模拟引脚读到接近0V的低电平;有遮挡时,接收管截止,引脚读到接近5V的高电平。

2.2 动力与执行机构:电机驱动方案详解

驱动圆盘旋转,我选择了一个普通的5V直流减速电机。减速电机扭矩大、转速适中且平稳,比普通直流电机更适合带动有一定惯量的圆盘。Arduino的数字引脚输出电流很小(约40mA),无法直接驱动电机,必须借助驱动电路。

这里有两个主流方案:L298N双H桥驱动模块晶体管开关电路。L298N功能强大,可以控制转向和调速,但体积和成本都偏高。对于本项目,电机只需单向匀速旋转,我采用了更经济简单的方案:用一个NPN型三极管(如S8050) 作为开关。具体接法是:Arduino的一个PWM引脚(如~9)通过一个1kΩ电阻连接到三极管的基极;电机的正极接电源正极(注意:电机电源最好与Arduino分开),负极接三极管的集电极;三极管的发射极接地。这样,通过给PWM引脚输出不同的占空比,就能控制三极管的导通程度,从而无级调节电机转速。

实操心得:电机会产生反向电动势和电火花干扰,可能造成Arduino复位。务必在电机两端并联一个续流二极管(如1N4007),阴极接电源正,阳极接电机负,以吸收反向电流。同时,在Arduino的电源入口处加一个100μF的电解电容,可以有效平滑电源波动。

2.3 辅助模块:显示与交互设计

为了实时显示分数,我选用了一块0.96英寸的OLED显示屏(I2C接口)。相比LCD1602,OLED无需背光、显示效果更细腻、功耗更低,且I2C通信只占用两个IO口(A4, A5)。代码上使用成熟的Adafruit_SSD1306Adafruit_GFX库,很容易就能驱动。

用户交互方面,我设置了一个自锁式按钮作为游戏开始/重置开关。之所以用自锁式而非点动式,是因为游戏流程可能持续十几秒,自锁开关可以保持状态,逻辑更清晰。另外,在顶部小球投放口,我安装了一个微动开关作为“小球就位”检测。只有当检测到小球放入,且开始按钮被按下,电机才会启动,防止空转。

3. 机械结构设计与搭建要点

3.1 转盘与轨道的制作

转盘是整个装置的“心脏”,其平整度和平衡性直接决定了小球滚动的随机性和公平性。我用的材料是5mm厚的白色亚克力板,切割成直径20cm的圆形。亚克力板容易加工、表面光滑,能减少摩擦。在圆心处开一个与电机轴直径匹配的孔,用热熔胶或螺丝将转盘牢牢固定在电机轴上。务必确保安装后转盘是水平的,可以放在平整桌面上观察,必要时在电机底座下垫小纸片调整。

在距离转盘边缘2cm处,均匀地钻出四个直径略大于小球的圆孔(例如,用8mm钻头)。这四个孔就是我们的“抽奖区域”。关键一步来了:在转盘的正下方,你需要固定另一层基板(我用的是厚纸板)。在这层基板上,对应四个孔的位置,精确安装我们之前准备好的四组光电对管。安装时,发射管和接收管要正对,且管头尽可能靠近转盘下表面,但不要接触,确保小球落下时能瞬间阻断光路。可以用小块泡沫胶或热熔胶固定传感器。

3.2 外围轨道与箱体的搭建

为了让小球能顺利滚到转盘上,需要搭建一个引导轨道。我用PVC线槽的盖子作为轨道材料,它本身有U型凹槽,非常适合小球滚动。将一段线槽盖从顶部入口处开始,以一定的倾斜角度(大约15-20度)用热熔胶粘接到转盘的上方边缘。轨道的末端不要完全接触转盘,留出1-2mm缝隙,让小球可以“掉”到转盘上,而不是“滑”上去,这样能增加初始随机性。

整个装置需要一个外壳来容纳所有电路、屏幕,并提供一个稳定的平台。我用的是激光切割的椴木板拼插而成的盒子,尺寸大约25x25x15cm。前面板为OLED屏幕开窗,顶部开孔固定轨道入口和开始按钮,侧面开孔引出电源线。所有内部走线要用扎带整理好,避免缠绕到旋转的转盘。电机需要用螺丝或强力胶单独固定在外壳底板上,防止振动。

4. 核心电路连接与接线图

下面给出详细的接线清单,建议在面包板上先测试所有功能,再焊接成正式电路。

电源部分:

  • 建议采用双电源供电:一个5V/2A的USB电源适配器给Arduino供电;另一个6V的电池盒或电源单独给直流电机供电。两地共地(GND连接在一起)。
  • 在电机的电源正负极之间并联一个100μF的电解电容(稳压)和一个0.1μF的瓷片电容(滤高频噪声)。

Arduino引脚分配:

  • A0 ~ A3:分别连接四路光电接收管的信号线(即接收管集电极与10kΩ上拉电阻的连接点)。
  • ~9:连接NPN三极管基极的1kΩ电阻,用于PWM控制电机速度。
  • 数字引脚 2:连接“开始/重置”自锁按钮的一端,按钮另一端接地。引脚内部启用上拉电阻。
  • 数字引脚 3:连接顶部入口的微动开关,开关另一端接地。引脚内部启用上拉电阻。
  • A4 (SDA), A5 (SCL):连接OLED显示屏的I2C接口。
  • 5V, GND:为OLED屏幕、光电发射管、按钮上拉等提供电源。

光电对管连接(以第一路为例):

  1. 红外发射管正极 → 串联220Ω电阻 → 接5V。
  2. 红外发射管负极 → 接GND。
  3. 红外接收管(光电三极管)的发射极(E) → 接GND。
  4. 红外接收管的集电极(C) → 连接10kΩ电阻一端,该电阻另一端接5V。同时,从集电极引出一根线到Arduino的A0引脚。

电机驱动连接:

  1. Arduino ~9引脚 → 连接1kΩ电阻一端。
  2. 1kΩ电阻另一端 → 连接NPN三极管(如S8050)的基极(B)。
  3. 三极管的发射极(E) → 接GND(此处是电机电源的GND,需与Arduino GND相连)。
  4. 三极管的集电极(C) → 连接直流电机的负极。
  5. 直流电机的正极 → 连接电机独立电源的正极(如6V)。
  6. 在电机两端并联续流二极管:二极管阴极接电机正极,阳极接电机负极。

5. 软件逻辑与代码实现深度剖析

代码不仅仅是让硬件动起来,更重要的是构建一个稳定、可靠的状态机,来管理整个游戏流程。整个流程可以分为以下几个状态:IDLE(待机)、READY(准备就绪)、SPINNING(旋转中)、SCORING(计分中)、RESULT(显示结果)。

5.1 核心状态机与游戏流程控制

我们使用枚举类型来定义状态,并在loop()函数中用switch-case语句进行状态切换。

CPP
enum GameState { IDLE, READY, SPINNING, SCORING, RESULT };
GameState currentState = IDLE;
 
// 引脚定义
const int motorPin = 9;
const int startButtonPin = 2;
const int ballSensorPin = 3;
const int photoSensorPins[4] = {A0, A1, A2, A3};
 
// 分数与阈值
int scores[4] = {10, 20, 30, 50};
int scoreThreshold = 800; // 模拟值阈值,需根据实测调整
unsigned long spinStartTime;
const unsigned long SPIN_DURATION = 5000; // 旋转5秒
 
void loop() {
switch (currentState) {
case IDLE:
// 显示欢迎界面
displayWelcome();
// 检测小球是否放入入口
if (digitalRead(ballSensorPin) == LOW) { // 假设微动开关按下为LOW
currentState = READY;
}
break;
 
case READY:
// 显示“准备就绪,按下开始”
displayReady();
// 检测开始按钮是否按下
if (digitalRead(startButtonPin) == LOW) {
currentState = SPINNING;
spinStartTime = millis();
startMotor(); // 启动电机
}
break;
 
case SPINNING:
// 显示旋转动画或倒计时
displaySpinning(SPIN_DURATION - (millis() - spinStartTime));
// 检查旋转时间是否结束
if (millis() - spinStartTime >= SPIN_DURATION) {
stopMotor(); // 停止电机
currentState = SCORING;
}
break;
 
case SCORING:
// 轮询四个光电传感器,判断小球落入哪个孔
int winningHole = detectBallHole();
if (winningHole != -1) {
addScore(scores[winningHole]);
currentState = RESULT;
} else {
// 超时未检测到,视为无效,返回IDLE
currentState = IDLE;
}
break;
 
case RESULT:
// 显示中奖分数和总分数
displayResult();
delay(3000); // 显示3秒结果
currentState = IDLE; // 回到待机状态
break;
}
}

5.2 光电传感器信号处理与去抖算法

光电传感器的模拟读数会波动,直接判断高低电平不可靠。我们需要进行模拟值读取软件去抖

CPP
int detectBallHole() {
int detectionTimeWindow = 1000; // 检测窗口为1秒
unsigned long detectionStart = millis();
int stableCount[4] = {0}; // 记录每个传感器连续达到阈值的次数
 
while (millis() - detectionStart < detectionTimeWindow) {
for (int i = 0; i < 4; i++) {
int sensorValue = analogRead(photoSensorPins[i]);
// 当小球遮挡时,接收管截止,模拟值接近1023(5V)
if (sensorValue > scoreThreshold) {
stableCount[i]++;
// 如果连续5次读数都超过阈值,则认为检测有效
if (stableCount[i] > 5) {
return i; // 返回检测到的孔洞索引
}
} else {
stableCount[i] = 0; // 读数不稳定,计数器清零
}
delay(10); // 短暂延迟,避免过于频繁读取
}
}
return -1; // 超时未检测到
}

重要提示scoreThreshold这个阈值需要实际测量确定。先用Serial.println()分别打印出四个传感器在有遮挡和无遮挡时的模拟值,取一个中间的安全值。例如,无遮挡时读数为200-300,有遮挡时为900-1023,那么阈值可以设为800。

5.3 电机平滑控制与显示界面优化

电机的突然启停会造成较大振动,可能影响小球轨迹。我们可以使用analogWrite()实现软启动和软停止。

CPP
void startMotor() {
for (int speed = 0; speed <= 200; speed += 10) { // 加速到PWM值200
analogWrite(motorPin, speed);
delay(50);
}
}
 
void stopMotor() {
for (int speed = 200; speed >= 0; speed -= 10) { // 减速停止
analogWrite(motorPin, speed);
delay(50);
}
analogWrite(motorPin, 0);
}

OLED显示方面,除了显示分数,还可以增加一些动态效果提升体验。例如,在SPINNING状态,可以显示一个旋转的圆圈或递减的倒计时;在RESULT状态,可以用大字体显示中奖分数并伴有闪烁效果。使用Adafruit_SSD1306库的display.display()函数进行局部刷新,可以避免屏幕全局闪烁。

6. 系统调试与故障排查实录

6.1 光电传感器不触发或误触发

这是最常见的问题。首先,用串口监视器观察每个传感器的实时模拟值,确认光路是否对准。如果无遮挡时值很低(如<100),有遮挡时值变化不大,可能是:

  1. 发射管或接收管极性接反:红外二极管有正负极,长脚为正。
  2. 上拉电阻过大或过小:10kΩ是常用值,如果环境光很强,可以减小到4.7kΩ以增强信号对比度。
  3. 环境光干扰:虽然光电对管抗干扰较好,但极强的环境光(如太阳直射)仍可能影响。可以为传感器加装一段黑色热缩管或小纸筒,形成“遮光罩”。
  4. 小球材质问题:如果小球是透明或半透明的,可能无法完全阻断红外光。换成不透明的小球(如钢珠、实心塑料球)。

6.2 电机不转或转动无力

  1. 检查电源:首先确认电机独立电源有电,电压是否足够(5V电机用5V,6V电机用6V)。用万用表测量电机接线端电压。
  2. 检查三极管电路:测量三极管基极(连接1kΩ电阻处)在Arduino输出PWM时的电压,应有约0.7V变化。如果没有,检查电阻是否虚焊,Arduino引脚是否配置正确。
  3. 检查续流二极管:务必确认二极管方向正确,阴极接电源正。接反了会短路。
  4. PWM值过低:Arduino的PWM输出范围是0-255。对于某些电机,可能需要较高的值(如>150)才能启动。尝试逐步提高analogWrite()的值。

6.3 小球随机性不佳,总是掉入同一个孔

这说明你的装置物理结构上有偏向性。检查以下几点:

  1. 转盘绝对水平:这是最重要的因素。用水平仪或手机APP的水平仪功能仔细校准。
  2. 轨道出口位置:确保轨道出口在转盘上方是居中的,并且小球是垂直“掉”落,而不是带有侧向力“推”到转盘上。
  3. 转盘转速:转速太快,小球会被直接甩飞;转速太慢,小球可能滚不到边缘。通过调整PWM值,找到一个能让小球在离心力作用下缓慢而随机地滚向边缘的速度。
  4. 孔洞边缘处理:用砂纸将孔洞边缘打磨光滑,避免有毛刺钩住小球。

6.4 Arduino运行不稳定,偶尔自动复位

这通常是电机干扰导致的电源问题。

  1. 加强电源滤波:在Arduino的Vin和GND引脚之间,尽可能靠近板子焊接一个100μF以上的电解电容。
  2. 检查共地:确保电机电源的GND和Arduino的GND是可靠连接在一起的。
  3. 分离供电:如果以上方法不行,彻底将电机电源和Arduino电源隔离,仅共地。可以使用光耦隔离模块来传递Arduino的控制信号给电机驱动端。

7. 项目优化与扩展思路

完成基础功能后,你可以从这个原型出发,进行很多有趣的扩展:

  1. 增加声音反馈:加入一个无源蜂鸣器,在不同状态(如开始、中奖、游戏结束)播放不同的提示音,体验更佳。
  2. 实现网络功能:加装一个ESP8266或ESP32模块,让抽奖机连接Wi-Fi。可以将每次的中奖分数上传到服务器,实现排行榜功能,或者通过网络远程控制抽奖机的启停。
  3. 升级显示效果:将OLED换成一块全彩TFT屏幕,可以显示更丰富的动画、图片和分数特效。
  4. 引入更多随机元素:在代码中,让电机的旋转时间在一个范围内随机(如3-7秒),或者让转速在旋转过程中随机变化,进一步增加不可预测性。
  5. 设计更复杂的游戏规则:例如,设定一个目标分数,玩家需要多次抽奖累积达到目标;或者加入“风险”孔洞,掉进去会扣分。

这个项目从构思到实现,最深的体会是:嵌入式开发不仅仅是写代码,更是对物理世界进行建模和交互。光电传感器的阈值需要你亲手调试,电机的转速需要你耳朵听、眼睛看,转盘的平衡需要你耐心调整。每一次调试,都是和硬件的一次对话。当小球最终在转盘上欢快地跳跃,并随机落入一个孔洞,屏幕亮起得分时,那种软硬件协同工作带来的满足感,是纯软件仿真无法比拟的。希望这个详细的分享能帮你少走弯路,成功做出属于自己的互动装置。

如何使用Arduino单片机和光电传感器构建一个自动往返电动小车?请详细说明硬件连接和编程步骤。
本文详细介绍了如何使用Arduino单片机和光电传感器构建一个自动往返电动小车。内容包括硬件组件的准备、硬件连接步骤、Arduino程序编写以及调试准备。通过参考《电子课程报告-基于Arduino光电传感器的自动往返电动小车》,读者可以掌握从基础到进阶的制作和编程知识。
Verilog零基础学习
如何利用Arduino光电传感器实现小车自动循迹往返?请说明硬件搭建与代码逻辑。
本文详细介绍了如何使用Arduino单片机和光电传感器构建一个自动往返电动小车。内容包括硬件组装、连接方法和编程步骤,旨在帮助读者理解项目设计思路并实现自动控制小车。参考资源链接提供了完整的电子课程报告。
Verilog零基础学习
arduino红外光电传感器模块
本文介绍了Arduino红外光电传感器模块的基本概念、连接方式、所需材料、示例程序解析以及购买渠道推荐。红外光电传感器是一种利用红外光检测物体存在的设备,广泛应用于自动化控制系统。文章详细说明了如何将红外光电传感器与Arduino控制器板子连接,并通过示例程序展示了如何实现基本的功能验证实验。
qq_50714127
电子课程报告-基于Arduino光电传感器的自动往返电动小车
总结体会项目实施过程中,学生将学习到硬件电路设计、编程、问题解决等多方面技能,加深了对Arduino光电传感器应用的理解。7.
Verilog零基础学习
192
智能arduino小车源程序
编程中,我们需要解析接收到的红外信号,并将其转化为对应的指令执行。其次,蓝牙遥控功能则利用蓝牙模块与手机或其他蓝牙设备建立无线连接。
32nani
2304
光电传感器与arduino通讯实现计数
本文介绍了如何使用Arduino光电传感器进行通信以实现计数功能。详细步骤包括准备工作、导入库、设置引脚和变量、初始化、读取传感器值、计数逻辑、输出结果以及循环延时。
在制作自动往返电动小车时,如何精确控制Arduino单片机通过光电传感器识别路线,并实现顺畅的转向和制动?
本文介绍了如何通过Arduino单片机和光电传感器精确控制自动往返电动小车的路线识别、转向和制动。详细说明了硬件连接、编程步骤以及电机控制逻辑,强调了电机加速减速和电源管理的重要性,并推荐了相关的电子课程报告作为学习资源。
Verilog零基础学习
arduinoTag:Arduino 编程的激光标签游戏
**硬件组装**除了编程,你还得了解如何将激光发射器、传感器和其他电子元件安装在适当的位置,确保游戏的可玩性和安全性。9.
起名什么的最烦啦
12
Arduino光电传感与热敏打印构建数字佛像互动装置全解析
lcpl
基于arduino的光电鼠标A3050数据读取
Arduino是一种开源硬件平台,以其简单易用的编程环境和丰富的扩展库,为DIY爱好者和开发者提供了便利。
午前言
1048