
1. 项目概述如果你也喜欢和朋友一起玩UNO、扑克或者“爆炸猫”这类卡牌游戏那你肯定对一件事深有体会发牌。一轮又一轮洗牌、发牌简单重复的动作不仅耗时还打断了游戏的流畅感。更别提在一些需要快速分发名片或者排队叫号的场合手动操作总是显得效率不高。我一直琢磨着能不能做一个装置只要手一伸过去卡牌就自动“飞”到你手里听起来有点像科幻电影里的场景但实现起来其实用我们手边常见的电子元件就能搞定。这个想法就是“Card Please!”自动发牌机的起点。它的核心逻辑非常简单非接触式触发。你不需要按任何按钮只需要把手靠近装置顶部的超声波传感器就会像眼睛一样“看到”你然后指挥内部的伺服电机快速转动通过一个特制的橡胶轮精准地将最上面的一张卡牌“弹射”出来直接送到你手上。同时为了增加一点趣味性每次发牌时蜂鸣器还会随机播放一小段欢快的旋律。整个项目融合了Arduino控制、超声波传感器测距、伺服电机驱动和简单的机械结构设计是一个典型的嵌入式系统小应用非常适合用来理解传感器如何与环境交互并驱动执行器完成特定任务。无论你是电子爱好者想动手做个有趣的玩具还是学生朋友需要一个结合软硬件的课程项目亦或是开发者想寻找一个物联网交互的实体案例这个项目都能提供一条清晰的路径。它不涉及复杂的算法硬件成本低廉但完整涵盖了从传感器信号采集、微控制器逻辑处理到电机动作执行的全流程。接下来我会详细拆解整个设计思路、制作步骤并分享我在调试过程中踩过的那些“坑”和总结出的实用技巧。2. 核心设计思路与方案选型做一个自动发牌机听起来目标明确但具体怎么实现选用什么方案里面有不少门道。我的核心诉求就三点非接触触发、稳定出牌、结构简单可靠。围绕这三点我经历了从DC电机到伺服电机的方案迭代最终确定了现在的技术路线。2.1 为什么选择超声波传感器伺服电机的组合最初我考虑过使用红外传感器或者触摸传感器。红外传感器容易受环境光干扰而触摸传感器又违背了“非接触”的初衷。超声波传感器成了最合适的选择。它通过计算声波发射和反射的时间差来测量距离几乎不受光线影响在10-20厘米这个典型交互距离内精度足够且成本极低。这完美契合了“伸手即触发”的需求。执行机构的选择则是一波三折。我最开始设想用一个高速旋转的DC电机带动轮子来“甩”出卡牌力量应该足够。但在实际测试中我发现两个致命问题一是控制精度DC电机通常需要额外的驱动电路如H桥才能实现正反转和调速对于简单的“转一下停”这种动作控制起来代码稍显复杂二是力矩和位置DC电机旋转起来后很难精确停在某个初始位置这对于需要重复、精准推送卡牌的机构来说是个隐患。这时伺服电机进入了我的视线。伺服电机我用的就是最常见的SG90内部自带控制电路可以通过PWM信号精确控制其旋转角度通常是0-180度。我只需要在代码里写一句servo.write(180)它就会转到180度位置写servo.write(0)它就回到0度。这种开环位置控制对于本项目来说简直是“降维打击”。我不需要它连续旋转我只需要它快速地从A点转到B点再回来这个往复运动就足以带动一个轮子将卡牌搓出去。而且Arduino的Servo库使得驱动它异常简单几乎不需要考虑底层驱动问题。注意伺服电机的扭矩有限。SG90在4.8V电压下扭矩大约为1.8kg·cm。这意味着如果你的机械结构阻力太大比如轮子压得太紧或者卡牌太厚、摩擦力过大它可能会“堵转”不仅无法完成任务还可能烧坏电机。这是设计机械部分时必须考虑的核心约束。2.2 机械结构设计的核心挑战自适应压紧解决了触发和执行的问题最大的挑战来自机械部分如何保证无论牌堆剩下多少张轮子都能有效地接触到最上面那张牌并将其推出如果轮子位置是固定的那么当牌堆很厚时轮子可能压得太紧导致电机带不动或卡牌变形当牌堆变薄时轮子又可能完全接触不到牌面导致“空转”。这就是一个典型的自适应压紧问题。我的解决方案是设计一个浮动式压轮机构。整个轮子及其支架连着伺服电机作为一个整体不是刚性固定的而是可以通过一个转轴或滑槽在一定范围内上下浮动。在这个浮动支架的尾部我增加了一个配重块或者使用弹簧提供下压力。这样轮子依靠自身重力或弹力始终“趴”在牌堆的最上层。当伺服电机转动轮子推出顶牌后牌堆变薄整个浮动支架在配重作用下会自动下沉一点点确保轮子继续紧贴新的顶牌。这个设计妙处在于完全被动机械实现无需任何额外的传感器或电机控制极大地简化了系统复杂度和代码逻辑。整个装置的动力流非常清晰手靠近传感器→ Arduino发出指令代码→ 伺服电机转动电信号转机械运动→ 橡胶轮摩擦卡牌机械传动→ 卡牌被弹出功能实现。2.3 交互与反馈设计增加仪式感一个纯粹的机械发牌动作略显枯燥。为了提升用户体验我加入了声音反馈。使用一个无源蜂鸣器在每次成功发牌时播放一段简短的随机旋律。这不仅仅是为了“好玩”从交互设计角度看声音提供了明确的操作确认反馈。用户听到音乐就知道机器已经接收到了指令并完成了一次发牌动作这比单纯静默地出牌要友好和清晰得多。在代码中我预定义了两段简单的旋律可以很容易地扩展更多每次触发时通过随机数函数选择其中一段播放。这种不可预测的小惊喜能有效增加装置的趣味性和互动感尤其适合用在游戏场景中。3. 硬件清单与电路连接详解工欲善其事必先利其器。下面列出的是完成本项目所需的所有硬件以及它们如何正确连接。我会解释每个元件的作用以及在选购和连接时需要注意的细节。3.1 核心元件清单与作用元件名称型号/规格数量主要作用选购与注意事项主控制器Arduino Uno R31系统大脑读取传感器数据控制电机和蜂鸣器兼容板亦可确保有足够的数字I/O口至少4个测距传感器HC-SR04 超声波模块1检测手或物体是否进入触发距离非常常见注意VCC接5V别接反执行机构SG90 9g 微型伺服电机1提供精确的角度旋转驱动发牌轮注意工作电压4.8-6V和扭矩本项目扭矩足够声音反馈无源蜂鸣器1播放发牌提示音务必区分“有源”通电就响和“无源”需频率驱动这里用无源电源5V/2A DC电源适配器1为整个系统供电强烈建议使用外接电源USB供电可能不足导致伺服电机工作不稳定连接线杜邦线公对公若干连接各元件准备多种长度方便布线辅助材料MDF板3mm厚、橡胶泡棉、强力胶、双面胶、小钉子、焊锡、洞洞板1套制作机身结构、增加轮子摩擦力、固定电路MDF易于激光切割橡胶泡棉是摩擦力的关键3.2 电路连接原理图与实操要点整个电路的连接并不复杂核心是给Arduino、传感器、伺服电机和蜂鸣器正确供电并连接信号线。下图是接线示意图的文字描述电源总线这是最易出错的地方。将外接5V电源适配器的正极连接到洞洞板或面包板的一个电源正极总线上负极-连接到电源负极总线GND。Arduino的Vin引脚可以不接因为我们主要通过这个外接电源总线为其他模块供电。超声波传感器 HC-SR04VCC- 接电源正极总线5V。GND- 接电源负极总线。Trig(触发) - 接 Arduino 数字引脚7。Echo(回声) - 接 Arduino 数字引脚6。伺服电机 SG90棕色线 (GND)- 接电源负极总线。红色线 (VCC)- 接电源正极总线5V。注意伺服电机启动瞬间电流较大务必确保电源能提供至少1A的电流否则Arduino可能重启。橙色线 (信号)- 接 Arduino 数字引脚3。该引脚需要支持PWM输出Arduino Uno上带~标记的引脚均可。无源蜂鸣器长脚正极或有标记 - 接 Arduino 数字引脚9另一个PWM引脚用于产生不同频率。短脚负极 - 接电源负极总线。共地操作至关重要必须将外接电源的GND、Arduino的GND引脚、以及所有模块的GND连接在一起。这确保了所有设备有相同的电压参考点信号才能被正确识别。我通常会用一根较粗的导线从外接电源GND接到Arduino的GND引脚再连接到面包板的GND总线。实操心得供电是稳定的基石我在第一次测试时使用电脑USB口给整个系统供电。当伺服电机转动时Arduino会突然重启。这是因为伺服电机在启动和堵转时电流可能瞬间超过500mA而电脑USB口的输出能力有限通常500mA导致电压被拉低单片机复位。解决方案就是使用独立的5V/2A以上的电源适配器直接为电机和传感器供电Arduino也可以通过这个电源取电通过电源插座或Vin引脚或者依然用USB供电但只做控制电机电源独立。确保动力部分与控制部分的电源有共同的GND即可。3.3 焊接与集成建议为了项目的稳固和美观建议将除了超声波传感器需要外露之外的其他电路焊接在一块小洞洞板上。规划布局在洞洞板上先摆放好Arduino排针、伺服电机接口、蜂鸣器接口和电源接口。尽量让电源走线短而粗。焊接电源线先焊接正极VCC和负极GND总线。可以使用跳线或直接利用洞洞板背面的铜箔如果是一整片的话需要用裁纸刀划断不需要连接的部分。焊接信号线按照原理图将各元件的信号线连接到对应的Arduino引脚排针上。测试后再固定焊接完成后先不要装入外壳连接所有元件和Arduino进行完整功能测试。确认一切正常后再用双面胶或螺丝将洞洞板固定在机壳内部。4. 机械结构制作与组装电路是神经机械结构则是骨骼和肌肉。这部分决定了发牌是否流畅、可靠。我的机壳使用3mm MDF板激光切割而成你也可以用亚克力、甚至厚纸板配合手工切割来制作核心在于理解各个部件的功能。4.1 核心部件发牌轮与自适应机构发牌轮是整个装置的“心脏”。我尝试过多种材料初代原型用一个小的木质圆柱体比如擀面杖切一小段包裹 painter‘s tape美纹纸胶带。胶带表面有一定粘性能提供摩擦力。测试发现直径太小接触面积不足经常搓不动牌。改进版本换用一卷胶带的内芯直径更大同样包裹美纹纸胶带。效果显著提升大直径带来了更大的线速度和更长的接触弧线能更可靠地将牌“卷”出去。最终方案在胶带芯外包裹一层中等厚度的橡胶泡棉。橡胶泡棉的摩擦系数远高于胶带而且有弹性能更好地贴合牌面即使牌稍有弯曲也能抓得住。这是成本最低、效果最好的方案。自适应压紧机构的实现 我设计了一个简单的“杠杆”结构。固定伺服电机的木片我们称之为“电机座”的一端通过一根细轴可以用粗铁丝或长螺丝与主机身侧板连接使得电机座可以像跷跷板一样上下摆动。在电机座的另一端远离轮子的一端粘贴一个配重块如几枚硬币或一小块金属。这样轮子端的重量较轻配重端较重。在自然状态下配重端下沉轮子端抬起。当放入牌堆后牌堆将轮子端顶起此时配重块的重力就转化为轮子压在牌堆上的正压力。牌被发出牌堆变薄轮子在配重块作用下自动下沉继续保持压力。4.2 机身切割与组装我用Adobe Illustrator绘制了所有激光切割文件包含侧板、底板、顶板、隔板等。如果你没有激光切割机完全可以用尺子、铅笔和美工刀在MDF或厚纸板上手工测量、画线、切割。关键尺寸就几个内部宽度略大于你的卡牌宽度扑克牌约6.3cmUNO牌约6.6cm。我留了7cm给卡牌一点活动空间。牌仓高度能容纳一摞牌约2-3cm高并且上方要留出轮子及其摆动空间。出牌口高度略高于一张牌的厚度确保每次只出一张牌。可以在出牌口内侧粘贴两条薄塑料片或硬纸片作为“刮片”防止多张牌同时滑出。传感器开孔在前面板合适位置通常正中偏上开一个圆孔用于固定超声波传感器让其探测面朝外。组装时建议使用木工白胶或快干胶在接合处涂抹后按压片刻即可固定。对于需要承重或经常活动的部分如电机座的转轴处可以辅以小钉子或螺丝加强。确保机身组装牢固各面板垂直否则会影响卡牌滑出的轨迹。4.3 总装与调试安装核心机构先将伺服电机用螺丝或强力胶固定在“电机座”上然后将发牌轮紧紧套在伺服电机的舵盘上可能需要用胶水加固。接着将组装好的电机座通过转轴安装到机身侧板上。安装电路将焊接好的洞洞板、Arduino板用双面胶固定在机壳内空余位置。将超声波传感器从前板外侧塞入开孔在内侧用热熔胶或螺母固定。连接伺服电机、蜂鸣器和传感器到洞洞板的接口上。连接电源与测试最后连接外部电源。上电前再次检查所有接线特别是电源正负极不能接反。上电后伺服电机通常会归中转到90度位置。此时手动调整发牌轮的初始位置使其在未触发时橡胶轮刚好轻微脱离牌堆或刚好接触但不产生压力避免待机时持续摩擦。5. 软件代码深度解析与优化代码是项目的灵魂它定义了装置的“行为”。下面我将逐部分解析提供的代码并分享一些优化和调试技巧。5.1 库文件与全局变量#include pitches.h // 导入音调定义文件 #include Servo.h // 导入伺服电机库 Servo servo; // 创建伺服电机对象 #define trigPin 7 // 超声波触发引脚 #define echoPin 6 // 超声波回声引脚 int buzzer 9; // 蜂鸣器引脚 int randomSong; // 随机旋律选择变量 // 旋律1定义 int melody[] { NOTE_C4, NOTE_G3, NOTE_G3 }; int noteDurations[] { 4, 4, 8 }; // 4分音符4分音符8分音符 // 旋律2定义 int melody2[] { NOTE_D5, NOTE_E5, NOTE_A4 }; int noteDurations2[] { 4, 4, 8 };pitches.h这个文件包含了钢琴上各音调频率的宏定义如NOTE_C4代表中音C。你需要单独创建一个名为pitches.h的文件并将Arduino官方示例中的音调定义代码复制进去和主文件CardPlease_Code.ino放在同一个项目文件夹下。Servo.hArduino标准库用于轻松控制伺服电机。引脚定义将使用的引脚号定义为常量方便后期修改。trigPin和echoPin必须接在数字引脚上。旋律定义这里定义了两个简短的旋律。noteDurations数组定义的是音符的时值数字越大音符持续时间越短4代表四分音符8代表八分音符。你可以通过修改这里的音符和时值来创作自己的提示音。5.2 初始化设置setup()void setup() { pinMode(buzzer, OUTPUT); Serial.begin(9600); // 初始化串口用于调试输出距离值 pinMode(trigPin, OUTPUT); pinMode(echoPin, INPUT); servo.attach(3); // 将伺服电机对象绑定到3号引脚 randomSeed(analogRead(A0)); // 用未连接的模拟引脚噪声作为随机种子 randomSong random(1, 3); // 初始化随机旋律选择 (1 或 2) }Serial.begin(9600)这是调试神器。在后续的loop()中我们可以将超声波测得的距离打印到串口监视器这对于校准触发距离至关重要。servo.attach(3)告诉Servo库伺服电机的信号线接在3号引脚。randomSeed(analogRead(A0))这是一个小技巧。A0引脚如果悬空不接任何东西其读取的模拟值会因环境电磁噪声而轻微随机波动。用这个值作为随机数生成器的种子可以使每次上电后的随机序列更“真随机”。如果直接使用random()而不设种子每次重启后的随机序列是一样的。5.3 主循环loop()与距离检测void loop() { long duration, distance; // 发送一个10微秒的高脉冲触发测距 digitalWrite(trigPin, LOW); delayMicroseconds(2); digitalWrite(trigPin, HIGH); delayMicroseconds(10); digitalWrite(trigPin, LOW); // 读取回声引脚高电平持续时间并计算距离 duration pulseIn(echoPin, HIGH); distance (duration / 2) / 29.1; // 单位厘米 randomSong random(1, 3); // 每次循环都生成新的随机数可选 // 如果距离大于20厘米或大于10厘米则停止蜂鸣器伺服电机归零 if (distance 20 distance 10) { // 注意这个条件判断有逻辑问题 noTone(buzzer); servo.write(0); } // 如果距离在0到10厘米之间则执行发牌动作 if (distance 10 distance 0) { Serial.println(distance is onder 10); // 调试信息 servo.write(180); // 电机转到180度位置 delay(330); // 等待电机动作完成并给予出牌时间 servo.write(0); // 电机转回0度位置 // 根据随机数播放旋律 switch (randomSong) { case 1: MelodyPlayer(); break; case 2: MelodyPlayer2(); break; } } }关键点解析与优化建议距离计算公式distance (duration / 2) / 29.1是标准算法。声速在空气中约为340m/s即每微秒0.034cm。duration是声波往返的时间微秒除以2得到单程时间再除以29.1约等于1/(0.034)即得到距离厘米。逻辑判断Bug原代码中if (distance 20 distance 10)这个条件永远为真因为只要distance 20它必然也10。这行代码的本意可能是想设置一个“无效区域”比如10-20厘米之间不触发也不停止但写法有误。更合理的逻辑是if (distance 10) { // 距离大于等于10厘米视为无物体停止 noTone(buzzer); servo.write(0); // 确保电机归位 } else if (distance 0 distance 10) { // 距离在0-10厘米触发 // ... 触发动作代码 ... } // 如果distance为0或极大值超时可能是传感器错误可以忽略或处理电机动作与延时servo.write(180)和servo.write(0)控制电机在两个极限位置间运动。delay(330)非常重要它有两个作用一是等待伺服电机从0度转到180度所需的时间SG90大约需要0.2-0.3秒二是让轮子有足够的时间将卡牌完全推出。这个330毫秒的值可能需要根据你的轮子大小和电机速度微调。连续触发机制注意只要手保持在传感器10厘米内loop()函数会不断执行触发部分的代码。由于有delay(330)发牌动作大约是每秒3次。这实现了“手不放开发牌不停”的效果。如果你希望每次靠近只发一张牌需要修改逻辑加入状态检测例如记录上次触发状态只有从“无物体”变为“有物体”时才发牌。5.4 旋律播放函数void MelodyPlayer() { for (int thisNote 0; thisNote 3; thisNote) { int noteDuration 1000 / noteDurations[thisNote]; // 计算毫秒时长 tone(buzzer, melody[thisNote], noteDuration); // 播放音符 int pauseBetweenNotes noteDuration * 1.30; // 音符间间隔为时长的1.3倍 delay(pauseBetweenNotes); noTone(buzzer); // 停止当前音符 } }tone(pin, frequency, duration)函数用于驱动无源蜂鸣器发出特定频率音高的声音持续指定时间毫秒。noteDuration 1000 / noteDurations[thisNote]如果noteDurations[thisNote]是4则noteDuration 1000/4 250ms即一个四分音符的时长。在音符间加入一个短暂的停顿pauseBetweenNotes通常比音符本身稍长一点这里是1.3倍可以使旋律听起来更清晰不会连成一片。6. 调试、问题排查与功能扩展即使按照步骤制作也难免会遇到问题。下面是我在开发过程中遇到的一些典型问题及其解决方法以及如何让这个项目变得更有趣。6.1 常见问题排查表现象可能原因排查步骤与解决方案上电后无任何反应1. 电源未接通或电压不足。2. Arduino未正确烧录程序。3. 核心电路断路。1. 检查电源适配器是否插好用万用表测量输出电压是否为5V。2. 打开Arduino IDE尝试上传一个简单的Blink示例程序确认开发板和端口选择正确。3. 检查主板上的电源指示灯是否亮起。用万用表通断档检查从电源到Arduino Vin/5V再到各模块VCC的线路。超声波传感器读数不稳定或始终为01. 接线错误Trig/Echo接反。2. 传感器损坏或质量差。3. 供电不足。4. 物体不在检测角度内或太近。1. 对照原理图确认Trig和Echo引脚是否接对。2. 将Trig和Echo短接读数应为很近的距离几厘米。如果不行可能传感器坏了。3. 确保传感器VCC接的是稳定的5V。4. HC-SR04最小检测距离约2-3厘米小于此距离可能无法检测。确保被测物体在传感器正前方锥形区域内。伺服电机不转动或抖动1. 电源电流不足最常见。2. 信号线接触不良。3. 机械负载过重卡死。4. 代码中引脚定义错误。1.立即断开电源使用独立的外接5V/2A电源为伺服电机供电并与Arduino共地。2. 检查信号线是否插牢尝试更换引脚。3. 手动拨动发牌轮检查是否被卡住。减轻轮子对卡牌的压力调整配重。4. 检查servo.attach()语句中的引脚号是否正确。蜂鸣器不响或一直长鸣1. 使用了“有源”蜂鸣器。2. 引脚定义错误或接触不良。3. 代码中tone()函数参数错误。1. 确认你用的是无源蜂鸣器。有源蜂鸣器给电就响无法播放旋律。2. 检查蜂鸣器正负极是否接反引脚连接是否牢固。3. 用tone(9, 1000, 1000)测试看是否能发出1秒的1kHz声音。一次弹出多张牌1. 出牌口间隙过大。2. 卡牌太旧或太滑摩擦力不足导致粘连。3. 轮子转动速度/时间过长。1. 在出牌口内侧加装“刮片”用塑料片或硬卡纸制作使其间隙仅比一张牌略厚。2. 使用较新、干燥的卡牌。在橡胶轮上缠绕橡皮筋增加摩擦力。3. 减少delay(330)的时间让轮子只转动刚好能推出一张牌的角度和时间。可以尝试servo.write(120)和更短的延时。发牌机反应迟钝或不停止1. 超声波检测阈值设置不合理。2. 代码逻辑有误如前述的BUG。3. 传感器前方有障碍物干扰。1. 打开串口监视器观察实际的distance值。根据实测值调整代码中的触发阈值如将10改为15。2. 修正loop()中的距离判断逻辑参考上一节的优化建议。3. 清理传感器表面确保其探测路径畅通。6.2 功能扩展与创意玩法基础功能实现后你可以尝试以下扩展让项目更具挑战性和趣味性增加状态指示灯添加一个RGB LED或两个普通LED红/绿。绿灯常亮表示待机手靠近时红灯闪烁或变为呼吸灯模式发牌时快速闪烁。这提供了更丰富的视觉反馈。实现“单次触发”模式修改代码逻辑使得手靠近一次只发一张牌即使手一直放着也不连续发。这需要引入状态变量来记录上次是否已触发。bool cardDispensed false; // 全局变量记录是否已发牌 void loop() { // ... 测量距离 ... if (distance 10 distance 0) { if (!cardDispensed) { // 如果还没为这次靠近发过牌 // 执行发牌动作... cardDispensed true; // 标记已发牌 } } else { cardDispensed false; // 手离开了重置状态 } }加入游戏模式利用蜂鸣器播放不同的旋律代表不同的牌或指令。例如在玩“UNO”时可以预设几种旋律对应“2”、“转向”、“禁止”等功能牌。当发牌机弹出牌时玩家需要根据旋律快速做出反应增加游戏紧张感。升级为“智能发牌机”加入一个蓝牙模块如HC-05或Wi-Fi模块如ESP8266让手机可以连接并控制发牌模式、触发距离、播放自定义音乐等。你甚至可以做一个简单的App实现远程发牌或设置发牌序列。改变应用场景这个装置的核心是“非接触触发”和“自动送出扁平物体”。你可以很容易地把它改造成自动名片分发机放在展会摊位、自动纸巾抽取机、或者一个有趣的糖果分配器。只需要调整出物口的尺寸和内部传送机构即可。这个项目从构思到实现最深的体会是嵌入式开发是一个不断在硬件限制和软件逻辑之间寻找平衡点的过程。伺服电机的扭矩、电源的电流、传感器精度、机械结构的摩擦力每一个细节都可能成为系统失效的原因。调试时一定要有耐心采用“分治法”——先确保电源和Arduino最小系统工作再单独测试传感器读数然后单独测试电机转动最后集成测试。串口打印是你最好的朋友它能让你看到程序“眼里”的世界是什么样子。最后关于机械部分我的建议是“快速原型迭代优化”。不要一开始就追求完美的激光切割外壳。用纸板、胶带和热熔枪做出第一个可动原型验证核心机构浮动压轮、出牌的可行性。在这个原型上调试修改成本极低效率极高。当机械原理完全跑通后再着手设计并制作最终的精美外壳。记住在创客项目中功能优先于形式一个能可靠工作的粗糙原型远比一个漂亮但无法运行的模型有价值得多。