1. 项目概述与核心价值
如果你对Arduino、传感器和简单的机械结构感兴趣,并且一直想动手做一个既有趣又有成就感的项目,那么这个基于Arduino的糖果自动售卖机绝对是一个绝佳的选择。它不仅仅是一个“玩具”,而是一个融合了电子电路、基础编程、简单机械设计和问题解决能力的综合性STEM实践项目。想象一下,当你的朋友伸手到机器前,机器就能“感知”到并自动吐出一颗糖果,这种将虚拟代码转化为实体互动的过程,充满了创造的乐趣。
这个项目的核心逻辑非常清晰:利用一个超声波传感器来模拟“投币”或“按钮”动作——当检测到前方特定距离内有物体(比如手)出现时,Arduino主控板就会向伺服电机发送指令。伺服电机转动,带动一个简单的齿轮或挡板机构,将储存的糖果释放一颗,通过滑道送到取物口。整个过程涵盖了输入(传感器感知)、处理(Arduino判断逻辑)、输出(电机执行动作)这三个自动化系统的基本环节,是学习嵌入式控制和物联网入门的绝佳实体模型。
无论是高中生用于科技节展示,大学生用于单片机课程实践,还是电子爱好者周末消遣,这个项目都能提供从零到一的完整构建体验。它所需的材料常见且成本低廉,代码逻辑直观,机械部分鼓励你利用手边的材料(如纸板、塑料盒)进行创造和迭代。接下来,我将带你深入每一个环节,不仅告诉你“怎么做”,更会详细解释“为什么这么做”,并分享我在多次制作和教学中积累的那些容易踩坑的细节和技巧。
2. 核心设计思路与物料选型解析
2.1 系统架构与工作流程拆解
在动手切割第一块纸板或焊接第一条导线之前,我们必须先把整个系统的逻辑想清楚。一个可靠的自动控制系统,其设计思路决定了后续制作的顺畅度和最终成品的稳定性。
这个糖果机的核心工作流程是一个典型的“事件驱动”循环:
- 休眠待机:系统上电后,超声波传感器持续工作,但Arduino程序处于监控状态,伺服电机保持初始位置(挡住糖果下落通道)。
- 事件触发:当超声波传感器检测到前方一定距离内(例如10厘米)出现物体并持续短暂时间(如0.5秒,用于防误触发),它认为这是一个有效的“取糖请求”。
- 信号处理:Arduino读取到传感器测得的距离值,通过简单的
if判断语句,确认触发条件满足。 - 动作执行:Arduino向伺服电机发出指令,令其旋转一个特定角度(如90度)。这个旋转动作会带动连接在电机轴上的齿轮或挡板,打开一个缺口,允许一颗糖果因重力作用落下。
- 复位与等待:电机在短暂延迟(例如1秒,确保糖果完全落下)后,反向旋转回初始位置,重新挡住通道。系统返回步骤1,等待下一次触发。
这个流程的关键在于去抖判断和机械可靠性。传感器可能会因环境噪音产生瞬时误信号,因此代码中必须加入延时确认。机械结构则需要确保每次动作只释放一颗糖果,且不会卡住。
2.2 关键物料清单与选型依据
原教程提到了一个材料清单,但有些地方比较模糊。这里我结合经验,给出一个更详细、更具可操作性的清单,并解释每样东西为什么需要,以及有没有替代方案。
电子部分:
- Arduino主控板(1个):推荐Arduino Uno R3。它是学习入门的事实标准,引脚布局清晰,资料丰富,USB编程方便,驱动能力强。 Nano虽然更小巧便宜,但需要额外的USB转串口线,对新手稍不友好。
- 微型伺服电机(1个):推荐SG90或MG90S。这类9克舵机扭矩足够推动小糖果,价格低廉,控制简单(仅需一根信号线)。务必注意,必须选择180度标准舵机,而不是连续旋转舵机。
- 超声波传感器(1个):推荐HC-SR04。这是最普及的模块,性价比极高。它需要两个IO口(Trig触发,Echo回响)来工作。有更集成的型号,但HC-SR04的教程最多。
- 面包板与跳线(若干):用于搭建测试电路。建议使用400孔以上的面包板和公-公杜邦线。在最终组装时,可以考虑焊接或用接线端子固定,以提高可靠性。
- 电源:在测试和最终使用时,切勿仅依赖电脑USB供电,特别是当舵机动作时电流可能骤增。建议准备一个5V/2A的直流电源适配器,搭配一个5.5*2.1mm的DC插座,插到Arduino的电源接口上。这是系统稳定运行的关键。
结构部分:
- 机体材料:原教程用纸板,这很好,成本为零,易于加工。但如果你想做得更精致耐用,可以考虑:
- 3mm椴木板/亚克力板:可以用激光切割机做出非常精准漂亮的零件,强度远胜纸板。
- PVC板/泡沫板:手工切割比木板容易,强度也不错。
- 废弃的塑料收纳盒:现成的外壳,只需开孔,非常方便。
- 储糖容器与滑道:原教程用了鱼缸和漏斗,这是一个巧思。关键在于接口要平滑,防止糖果卡在衔接处。滑道(原教程的C型通道)的内壁一定要光滑。可以用硬质塑料片、亚克力条,甚至将吸管剖开粘贴而成。滑道的倾斜角度需要测试,通常在30-45度之间比较合适,角度太小糖果滑不下,角度太大会导致糖果速度过快飞溅。
- 传动机构:这是项目的灵魂。最简单的方式是直接在舵机摇臂上安装一个3D打印或激光切割的齿轮,齿轮充当一个带缺口的转盘。糖果堆在转盘上方,缺口转到下方时,一颗糖果落下。你也可以用冰棍棒做一个简单的“闸门”机构。核心原则是:运动部件摩擦力小,动作行程精准。
- 连接与固定材料:热熔胶枪(用于快速固定非承重部件)、AB胶或UHU(用于固定关键受力点,如舵机底座)、双面胶、螺丝螺母(M3规格套装,用于可拆卸的牢固固定)。
注意:在物料选择上,我的个人心得是“测试先行”。尤其是糖果,不同糖果的大小、形状(球形、片状、长条)、表面光滑度(有无糖粉)对机械结构的要求天差地别。建议先确定你要用什么糖,再根据它来设计滑道宽度和出糖口的大小。
3. 机械结构设计与组装要点
3.1 机体框架与储糖仓构建
机体的主要作用是容纳所有部件、提供支撑并引导糖果。我们从一个简单的六面体纸箱开始设计。
首先,裁切出六块纸板,构成一个长方体。关键尺寸不是固定的,但需要遵循一个比例原则:高度要能容纳你的储糖容器(鱼缸/漏斗)加上下方机械结构的空间;宽度和深度要能稳妥放下Arduino和面包板,并为滑道留出空间。我建议初始尺寸可以定为:高25cm,宽15cm,深15cm。前面板是操作面,需要预留传感器孔和出糖口,顶部面板需要开一个足够大的圆孔,用于安装漏斗。
安装漏斗和储糖容器时,密封和对准是重中之重。用热熔胶将漏斗颈部牢固地粘在顶板圆孔上,确保周围无缝隙,防止小糖果漏出。然后将鱼缸(或其它容器)倒扣在漏斗大口上。这里不要直接粘死!我建议用几条宽胶带或可拆卸的卡扣固定,方便日后补充糖果或清洁。你需要反复测试,确保糖果能顺畅地从容器全部流入漏斗,漏斗内部不应有能卡住糖果的凸起或狭窄处。
3.2 出糖机构:舵机与齿轮/挡板的设计
这是整个机器最核心的机械部分,其可靠性直接决定了用户体验是“惊喜”还是“故障”。
方案一:齿轮转盘式(推荐)这是最经典可靠的方式。你需要一个带有缺口的圆盘(齿轮)。这个缺口的大小略大于一颗糖果的尺寸。圆盘紧密安装在舵机输出轴上,水平放置,位于漏斗出口的正下方。在初始位置,圆盘的实体部分挡住漏斗口。当舵机收到信号旋转90度时,缺口旋转到漏斗口下方,允许一颗糖果落下;随后舵机回转90度,实体部分又将出口封住。
- 设计要点:
- 齿轮/转盘厚度:最好有3-5mm,太薄容易变形,太厚可能增加阻力。
- 缺口形状:对于球形糖,一个半圆形缺口即可;对于方糖或长条糖,可能需要设计一个方形的“仓位”。
- 安装高度:转盘上表面与漏斗出口的间隙应小于一颗糖果的直径,防止一次掉多颗。这个间隙需要精细调整。
- 固定方式:舵机必须被极其牢固地固定。原教程用纸板做支架的思路可行,但更好的方法是使用专用的舵机支架(金属或塑料),或用螺丝将舵机锁在一块坚实的基板上(如小块木板),再将这块基板粘在机体底板上。舵机在启动和停止的瞬间扭矩很大,不牢固的固定会导致整个机构晃动甚至失效。
方案二:闸门式在漏斗出口处设计一个像门一样的挡板,舵机通过摇臂带动挡板横向抽拉或上下起落,从而打开或关闭出口。这种方式对糖果形状适应性更强。
- 设计要点:滑轨必须顺滑,挡板与出口的配合要紧密且平整,否则容易卡住。可以使用雪糕棒做导轨,用塑料片做挡板。
无论哪种方案,组装后都必须进行空载测试和负载测试。先不通电手动拨动机构,检查运动是否顺畅无阻碍。然后上电用程序控制反复动作几十次,观察其一致性和噪音。最后放入糖果进行实际出糖测试。
3.3 糖果滑道与收集槽制作
滑道的作用是引导落下的糖果平稳地滑到取物口,并降低其速度,避免“喷射”出来。
滑道宜采用光滑材料。将硬质塑料片弯折成“U”型或“C”型槽。其倾斜角度需要实验确定:在机体框架内临时放置滑道,从出糖机构处释放糖果,调整角度直到糖果能自然滑下且终点位置合适。角度确定后,在滑道下方用三角支撑(纸板折成三角形)进行多点固定,防止因糖果冲击而变形。
取物口处的收集槽可以简单地用一个裁剪后的酸奶杯或纸盒制作,用热熔胶粘在机体前面板内侧,开口与面板上的出糖孔对齐。确保收集槽有一定深度,防止糖果跳出。
4. 电路搭建与Arduino编程详解
4.1 电路连接原理与实操
电路部分非常简单,但连接错误会导致传感器失灵或舵机乱转。我们使用面包板进行原型搭建。
接线图(文字描述):
- 电源总线:在面包板两侧的垂直电源轨上,一侧标为“+5V”(接Arduino的5V引脚),另一侧标为“GND”(接Arduino的任意GND引脚)。
- HC-SR04超声波模块:
Vcc引脚 -> 面包板“+5V”总线。Gnd引脚 -> 面包板“GND”总线。Trig引脚 -> Arduino数字引脚D9。Echo引脚 -> Arduino数字引脚D10。
- SG90伺服电机:
- 棕色线(或黑色)-> 面包板“GND”总线。
- 红色线-> 面包板“+5V”总线。
- 橙色线(或黄色/白色,信号线)-> Arduino数字引脚
D6。
重要提示:务必确保电源功率充足。将外部5V/2A电源适配器插入Arduino的DC插座。不要同时通过USB和DC插座供电。舵机的电源最好直接从面包板的5V总线取电,而不是从Arduino板载的5V引脚取电,以避免大电流冲击损坏Arduino主板。
4.2 Arduino代码逐行解析与编写
下面是一个稳定、带防抖功能的完整示例代码,我将在注释中详细解释每一部分的作用和原理。
// 引入舵机控制库,这是Arduino IDE自带的,无需额外安装 #include <Servo.h> // 定义引脚常量,提高代码可读性和可维护性 const int trigPin = 9; // 超声波触发引脚 const int echoPin = 10; // 超声波回响引脚 const int servoPin = 6; // 舵机信号引脚 // 定义运行参数,这些是你可以根据实际情况调整的“旋钮” const int detectionDistance = 10; // 触发距离,单位厘米。手放到机器前10cm内触发。 const int debounceTime = 500; // 防抖时间,单位毫秒。手需要持续停留500ms才有效。 const int servoOpenAngle = 90; // 舵机打开角度(初始位置为0度) const int servoOpenDelay = 1000; // 打开后等待糖果掉落的时间,单位毫秒 // 创建舵机对象,用于控制舵机 Servo myServo; // 变量声明 long duration; // 存储超声波传播时间 int distance; // 存储计算出的距离 bool handDetected = false; // 标志位,记录是否检测到手 unsigned long lastDetectionTime = 0; // 记录上次检测到手的时间 void setup() { // 初始化串口通信,用于调试,可以在电脑的“串口监视器”查看数据 Serial.begin(9600); // 设置超声波模块引脚模式 pinMode(trigPin, OUTPUT); pinMode(echoPin, INPUT); // 将舵机对象关联到控制引脚 myServo.attach(servoPin); // 初始化舵机位置到0度(关闭状态) myServo.write(0); delay(500); // 给舵机一点时间归位 Serial.println("糖果售卖机初始化完成!"); } void loop() { // 步骤1: 触发超声波测距 digitalWrite(trigPin, LOW); delayMicroseconds(2); digitalWrite(trigPin, HIGH); delayMicroseconds(10); // 发出一个10微秒的高脉冲触发信号 digitalWrite(trigPin, LOW); // 步骤2: 读取回响脉冲的持续时间 duration = pulseIn(echoPin, HIGH); // 步骤3: 计算距离(声速约340米/秒,除以2因为是往返距离) distance = duration * 0.034 / 2; // 步骤4: 打印距离到串口监视器,用于调试和校准 Serial.print("距离: "); Serial.print(distance); Serial.println(" cm"); // 步骤5: 判断逻辑(带防抖) if (distance > 0 && distance < detectionDistance) { // 如果检测到物体在设定距离内 if (!handDetected) { // 如果是刚检测到,记录开始时间 lastDetectionTime = millis(); handDetected = true; Serial.println("检测到物体,开始计时..."); } else { // 如果已经处于检测状态,检查持续时间是否超过防抖时间 if (millis() - lastDetectionTime > debounceTime) { Serial.println("触发条件满足,开始出糖!"); dispenseCandy(); // 调用出糖函数 handDetected = false; // 重置标志位 } } } else { // 如果物体离开或不在范围内,重置检测状态 handDetected = false; } delay(100); // 主循环延迟,降低CPU占用,100ms的刷新率足够 } // 独立的出糖函数,使主循环逻辑更清晰 void dispenseCandy() { Serial.println("舵机打开..."); myServo.write(servoOpenAngle); // 舵机转到打开位置 delay(servoOpenDelay); // 等待糖果落下 Serial.println("舵机关闭..."); myServo.write(0); // 舵机转回关闭位置 delay(500); // 等待舵机动作完成 Serial.println("一次出糖流程结束。"); // 可以在这里添加其他效果,如蜂鸣器响一声 }代码关键点解析:
- 防抖逻辑:这是避免误触发的关键。代码不是一检测到距离小于10cm就立刻动作,而是启动一个“计时器”(
lastDetectionTime)。只有当物体持续停留在检测区内超过debounceTime(500毫秒),才判定为有效触发。这能过滤掉偶然经过的飞虫或短暂的挥手。 pulseIn函数:它用于测量echoPin引脚保持高电平的时间,即超声波从发出到返回的时间。这个时间是计算距离的基础。millis()函数:获取Arduino开机以来的毫秒数,用于非阻塞式计时。相比delay(),它不会让程序完全停止,更适合需要持续监控的场景。- 模块化函数:将
dispenseCandy()独立出来,使得主循环loop()非常简洁。如果你想修改出糖动作(比如增加一段音乐或灯光),只需要修改这个函数,而不会影响主检测逻辑。
4.3 系统集成与内部布局
电路测试无误后,就可以将电子部分移植到机器内部了。
- 固定Arduino与面包板:在机体底板或侧板规划好位置。可以使用尼龙扎带、螺丝(配合铜柱)或强力双面胶来固定。确保所有连接线不会被运动部件(如舵机摇臂)缠绕。
- 传感器安装:在机体前面板预先开好的孔中固定超声波传感器。可以用热熔胶从内部固定其四个角。确保传感器表面与前面板平齐,且前方没有障碍物(如玻璃),否则会影响测距。
- 走线管理:用扎带或胶带将导线捆扎整齐,避免杂乱。过长的导线可以盘绕起来固定。良好的走线不仅是美观,更能防止日后因导线拉扯导致脱焊或接触不良。
- 电源接入:将外部电源适配器的线从机体后方或底部预留的孔穿入,连接到Arduino的DC插座。确保电源线固定稳妥,不会被轻易扯掉。
5. 调试、优化与问题排查实录
即使严格按照步骤制作,第一次运行时也难免遇到问题。下面是我总结的常见故障及其解决方法,相当于一份“维修手册”。
5.1 机械部分常见问题
- 问题:糖果卡在出糖机构里,下不来。
- 排查:首先断电,手动转动舵机齿轮,观察卡点。
- 解决:
- 间隙调整:检查转盘与漏斗出口的间隙。间隙太小会摩擦,太大会一次掉多颗。用垫片或胶水微调高度。
- 缺口尺寸:缺口是否略大于糖果?对于异形糖,缺口形状可能需要优化。
- 糖果本身:糖纸是否粘连?糖粉是否结块?更换更光滑、独立的糖果(如MM豆、小包装糖果)是立竿见影的办法。
- 问题:一次掉出多颗糖果。
- 排查:观察糖果在储糖仓的状态。是否是“桥接”现象(糖果互相卡住形成拱桥,然后突然崩塌)?
- 解决:
- 增加阻尼:在漏斗颈部内部贴一小块柔软的海绵或毛毡,增加糖果下落阻力。
- 改进机构:将简单的转盘改为“星型轮”结构,每个凹槽只容纳一颗糖。
- 控制存量:不要一次性装满储糖仓,保持半仓以下。
- 问题:舵机动作无力或发热严重。
- 排查:机械阻力是否过大?齿轮或挡板是否刮擦到其他部件?
- 解决:确保所有运动部件顺滑。可以在轴孔处涂抹一点润滑脂(如凡士林)。检查电源电压是否稳定在5V,电流是否足够(使用外接电源)。
5.2 电子与程序部分常见问题
- 问题:超声波传感器读数不稳定,忽大忽小或一直为0。
- 排查:检查接线(特别是
Trig和Echo是否接反)。观察传感器表面是否清洁。 - 解决:
- 代码滤波:在程序中加入软件滤波。例如,连续读取5次距离,去掉最大最小值后取平均,作为最终结果。这能有效消除偶发干扰。
int getAverageDistance() { int sum = 0; int readings[5]; for (int i = 0; i < 5; i++) { // ...(这里插入上述测距代码,将结果存入readings[i]) delay(30); // 每次测量间隔一小会儿 } // 简单的排序去极值(这里省略排序代码) // 假设去掉一个最高一个最低后,取中间三个值的平均 sum = readings[1] + readings[2] + readings[3]; return sum / 3; }- 硬件检查:确保传感器供电电压为5V。如果导线过长,尝试缩短或使用屏蔽线。
- 排查:检查接线(特别是
- 问题:手放过去没反应,或离开后还在不停出糖。
- 排查:打开串口监视器,查看实时距离读数。检查
detectionDistance和debounceTime这两个参数设置是否合理。 - 解决:
- 校准距离:根据串口数据,调整
detectionDistance。如果取糖口较深,可能需要设置为5-7cm。 - 调整防抖时间:如果环境有轻微干扰(如风吹动),适当增加
debounceTime到800ms。如果觉得反应迟钝,则减少到300ms。 - 检查逻辑标志:确保
handDetected标志在出糖动作完成后被正确重置。
- 校准距离:根据串口数据,调整
- 排查:打开串口监视器,查看实时距离读数。检查
- 问题:舵机转到错误的角度或抖动。
- 排查:检查
myServo.attach(pin)中的引脚号是否正确。检查电源。舵机信号线是否受到干扰(如与电机电源线平行紧贴)。 - 解决:为舵机单独供电(仍共地)。在Arduino的5V输出和GND之间,靠近舵机的位置,并联一个100μF以上的电解电容,可以吸收电压波动,立竿见影地消除抖动。
- 排查:检查
5.3 功能扩展与美化建议
基础功能实现后,你可以考虑以下升级,让项目更具挑战性和趣味性:
增加用户反馈:
- 视觉:在面板上加一个LED。待机时呼吸闪烁,检测到手时快速闪烁,出糖时常亮。
- 听觉:添加一个无源蜂鸣器,出糖时播放一小段旋律。
- 代码实现:只需在
dispenseCandy()函数中加入控制LED和蜂鸣器的语句即可。
计数与显示:
- 添加一个I2C LCD屏幕(如1602),实时显示“剩余糖果数量”或“今日已售”等信息。
- 每出糖一次,在代码中用一个变量减1,并更新屏幕显示。
多种触发模式:
- 除了超声波,可以增加一个物理按钮,实现“手动模式”。
- 或者增加一个光敏电阻,实现“天黑自动停止服务”的趣味功能。
外观美化:
- 用彩色卡纸、贴纸或喷漆装饰外壳。
- 用激光切割亚克力板制作一个更酷的前面板,并丝印上Logo和指示灯标识。
- 设计一个带坡度的屋顶,让机器看起来更像真正的自动售货机。
这个项目的魅力在于,它有一个坚实可靠的核心,但边界是开放的。从解决“如何让糖一颗颗稳定落下”这样的具体问题,到思考“如何让它更智能、更美观”,每一步都是学习和创造的过程。我自己的第一个版本也是用纸板和胶带做的,粗糙但能动起来,那种喜悦感是无与伦比的。后来迭代了三四版,用了激光切割和3D打印,增加了计数功能,每一次改进都解决了之前遇到的一个实际问题。所以,不要怕一开始做得简陋,最重要的是动手做,然后在玩的过程中,你自然会知道下一步该优化哪里。