1. 项目概述从零打造一台属于自己的自动售货机你有没有想过那些在商场、地铁站里默默工作的自动售货机内部到底是怎么运作的是复杂的工业电脑还是精密的机械结构其实它的核心逻辑远比想象中简单。今天我们就来动手做一台属于你自己的DIY自动售货机。这不仅仅是一个有趣的电子制作项目更是一次绝佳的嵌入式系统学习之旅。你将亲手搭建一个集成了传感器、执行器、用户交互和库存管理的微型自动化系统。这台DIY售货机我们计划让它能通过按键选择商品然后由舵机控制的“机械臂”将商品推出货道最终掉落到取物口。听起来是不是有点像游乐场里的抓娃娃机原理上确实有共通之处但我们的设计会更侧重于逻辑控制和成本控制。整个项目非常适合对Arduino、树莓派等开源硬件感兴趣的朋友无论你是想给孩子做一个有趣的玩具还是想深入学习物联网设备的开发流程这个项目都能让你收获满满。我会带你从零开始一步步完成硬件选型、电路搭建、代码编写和整机组装过程中遇到的每一个“坑”和“窍门”我都会毫无保留地分享给你。2. 核心思路与系统架构设计2.1 为什么选择“舵机推杆”方案市面上主流的自动售货机出货方式主要有两种弹簧螺旋货道和履带式货道。前者成本低但结构复杂需要定制弹簧和货道后者稳定但占用空间大。对于我们的DIY项目追求的是结构简单、易于实现且可靠性尚可。因此我选择了“舵机推杆”方案。它的工作原理非常直观每个货道对应一个标准舵机。舵机的旋转轴上安装一个L形的推杆可以用3D打印或激光切割的亚克力板制作。平时推杆处于收回状态挡在商品比如一罐饮料后面。当用户选择该商品并完成支付或模拟支付后控制板会驱动对应的舵机旋转一定角度例如90度推杆向前运动将商品推出货道商品在重力作用下落入取物仓。舵机随后归位。这个方案的优势很明显成本极低一个标准舵机价格仅需十几元整个机械部分的开销很小。控制简单Arduino通过PWM信号就能精确控制舵机角度编程门槛低。模块化每个货道独立控制增加或减少货道数量非常灵活只需复制硬件和代码模块即可。易于调试机械动作肉眼可见出问题容易排查。当然它也有局限主要是推力有限适合售卖轻小商品如小包装零食、盒装饮料、盲盒等。但对于DIY的乐趣和学习目的来说这完全不是问题。2.2 控制系统选型Arduino Uno 为何是首选控制核心的选择很多树莓派、ESP32、STM32都可以。但我强烈推荐初学者使用Arduino Uno。原因如下生态成熟关于舵机控制、按键检测、显示屏驱动的库和教程浩如烟海几乎你遇到的任何问题都能找到现成的解决方案。开发简单使用Arduino IDE代码编写和上传像搭积木一样直观无需复杂的开发环境配置。接口够用一台拥有6个货道的售货机需要6个舵机信号线、6个货道选择按键、1个确认键、可能还需要1个I2C液晶屏。Arduino Uno的14个数字IO口和6个模拟口完全能满足需求。稳定性作为教学和原型开发板Uno的稳定性久经考验。对于想增加网络功能比如手机扫码下单的进阶玩家可以在项目后期引入ESP8266或ESP32模块让Arduino作为下位机负责控制ESP模块作为上位机负责网络通信这是非常经典的架构。2.3 整体系统框图与工作流程在动焊枪之前我们必须把系统逻辑理清楚。整个机器的工作流程是一个清晰的“状态机”待机状态液晶屏显示欢迎语和商品列表及价格。选择状态用户按下对应货道的按键屏幕高亮显示选中商品并计算总价。支付确认状态用户按下“确认”键。这里我们模拟支付实际可以接入投币器或二维码识别模块。出货状态控制板驱动对应货道的舵机动作推出商品。完成状态屏幕提示“请取货”并等待一段时间后复位到待机状态。同时我们还需要一个简单的“库存管理”逻辑。在代码中为每个货道设置一个库存变量每次成功出货后减1。当库存为0时在显示界面将该商品标记为“售罄”并禁用对应的选择按键。3. 硬件清单与电路搭建详解3.1 核心物料清单与选购要点以下是构建一台6货道基础版售货机的完整清单类别名称规格/型号数量备注控制核心Arduino开发板Uno R31建议购买正版或质量可靠的兼容板执行机构舵机SG90 / MG90S6MG90S金属齿轮版扭矩更大更耐用用户输入轻触按键6x6mm 四脚76个选择键1个确认键状态显示LCD显示屏1602A (I2C接口)1务必选I2C版本节省IO口接线简单电源电源适配器DC 7-12V / 2A以上1给Arduino供电电源舵机驱动电源5V / 3A以上1独立给舵机供电关键结构杜邦线公对公、公对母若干用于连接电路结构洞洞板/面包板可选1初期测试用面包板最终制作可用洞洞板焊接结构机箱与货道木板/亚克力/纸箱自定发挥创意用任何坚固材料制作机身和滑道选购避坑指南舵机电源是重中之重绝对不要试图用Arduino板载的5V引脚同时驱动多个舵机Arduino的稳压芯片最大只能提供约500mA电流而一个舵机堵转时瞬时电流可能超过1A。多个舵机同时工作会直接导致Arduino重启或损坏。必须为舵机准备独立的5V电源如手机充电头、稳压模块并将此电源的“地”与Arduino的“GND”连接在一起。I2C液晶屏1602液晶屏有并行和I2C两种驱动方式。并行需要占用至少6个IO口而I2C只需2个SDA, SCL。购买时一定认准背面带一个小蓝色电路板的I2C版本。按键选择带帽的手感更好。也可以直接购买薄膜按键矩阵但编程稍复杂。3.2 电路连接图与关键细节由于平台限制我无法直接绘制图表但我会用文字详细描述每个连接你完全可以按此操作Arduino供电将7-12V电源适配器的DC插头接入Arduino的电源插座。舵机供电将独立的5V电源如USB充电宝的5V输出的正极连接到一个公共的VCC排线。将此5V电源的负极-连接到Arduino的GND引脚。这一步是共地至关重要6个舵机的红线电源都接到上述公共VCC排线上。6个舵机的黑线电源-都接到上述公共GND排线上。舵机信号线6个舵机的黄线或白线信号线分别连接到Arduino的数字引脚 ~9, ~10, 11, ~3, ~5, ~6。选择带波浪线~的引脚因为它们支持PWM是控制舵机的标准做法。按键连接所有按键的一端并联接到Arduino的一个数字引脚如D2并启用内部上拉电阻或者接一个外部10K电阻到VCC。每个按键的另一端分别接到Arduino的数字引脚 D7, D8, D12, D13, A0, A1用于6个选择键确认键接到A2。这种接法需要在代码中采用“循环扫描”的方式检测哪个按键被按下。I2C液晶屏只有4根线。SDA接A4SCL接A5VCC接Arduino的5VGND接GND。关键提示在接通电源前务必再三检查线路特别是电源正负极不能接反。建议先在面包板上搭建测试整个系统确认所有功能正常后再转移到洞洞板上进行焊接制作成固定的控制板。3.3 机械结构设计与制作心得机身是项目的“面子”也是稳定运行的“里子”。你可以用激光切割亚克力板、拼接木板甚至用坚固的硬纸板和热熔胶来制作。我的第一个原型机就是用搬家剩下的快递纸箱做的虽然不美观但验证功能完全没问题。货道设计要点坡度商品滑道需要有一个合适的倾斜角度建议15-30度确保商品能被推出后依靠重力顺利滑落。角度太小容易卡住角度太大会导致商品自然滑落或掉落时冲击力过大。宽度货道宽度比商品宽约5mm即可太宽商品会翻滚太窄容易卡住。舵机安装舵机需要牢固地固定在货道末端的侧面。推杆的长度要计算好其行程应刚好能把商品推出货道但又不会过长导致干涉。可以用热熔胶或螺丝固定舵机。取物仓底部需要一个容器来接住掉落的商品并方便用户伸手拿取。可以加装一个微动开关作为“取货检测”当用户打开仓门或手伸入时触发重置系统状态。4. 核心代码编写与逻辑实现4.1 基础框架与库引入首先我们需要引入控制舵机和液晶屏的库。打开Arduino IDE编写以下代码框架#include Wire.h // I2C通信库 #include LiquidCrystal_I2C.h // I2C液晶屏库 #include Servo.h // 舵机控制库 // 初始化液晶屏地址通常是0x27或0x3F用I2C扫描程序确认 LiquidCrystal_I2C lcd(0x27, 16, 2); // 创建6个舵机对象 Servo servo1, servo2, servo3, servo4, servo5, servo6; Servo servoArray[6] {servo1, servo2, servo3, servo4, servo5, servo6}; // 定义舵机连接的引脚 int servoPins[6] {9, 10, 11, 3, 5, 6}; // 定义按键引脚 int selectPins[6] {7, 8, 12, 13, A0, A1}; // 6个选择键 int confirmPin A2; // 确认键 // 商品信息 char* items[6] {Cola, Water, Chips, Cookie, Gum, Choco}; float prices[6] {3.0, 2.0, 5.0, 4.5, 1.0, 6.0}; int stock[6] {5, 5, 5, 5, 5, 5}; // 初始库存 // 状态变量 int selectedItem -1; // -1表示未选择 float totalPrice 0.0; enum State { IDLE, SELECTING, CONFIRMING, VENDING, COMPLETE }; State currentState IDLE;4.2 状态机主循环与按键扫描Arduino的loop()函数是核心我们将在这里实现状态机void loop() { switch (currentState) { case IDLE: idleState(); break; case SELECTING: selectingState(); break; case CONFIRMING: confirmingState(); break; case VENDING: vendingState(); break; case COMPLETE: completeState(); break; } delay(50); // 一个小延时防止扫描过快 } void idleState() { lcd.clear(); lcd.setCursor(0,0); lcd.print(Select Item:); // 这里可以滚动显示商品和价格简化起见我们显示提示 for (int i0; i6; i) { if (stock[i] 0) { lcd.setCursor(0,1); lcd.print(Press Btn); lcd.print(i1); break; } } // 扫描选择按键 for (int i0; i6; i) { if (digitalRead(selectPins[i]) LOW stock[i] 0) { // 按键按下且库存0 selectedItem i; totalPrice prices[i]; currentState SELECTING; break; } } } void selectingState() { lcd.clear(); lcd.setCursor(0,0); lcd.print(Selected:); lcd.print(items[selectedItem]); lcd.setCursor(0,1); lcd.print(Price:$); lcd.print(totalPrice); lcd.print( Confirm?); // 检测确认键 if (digitalRead(confirmPin) LOW) { currentState CONFIRMING; } // 这里可以增加取消选择的功能如长按某个键 }4.3 舵机控制与出货逻辑出货是最激动人心的部分但控制舵机需要一些技巧void vendingState() { lcd.clear(); lcd.print(Vending...); if (selectedItem 0 selectedItem 6) { Servo currentServo servoArray[selectedItem]; int pin servoPins[selectedItem]; // 1. 连接舵机到指定引脚如果还未连接 if (!currentServo.attached()) { currentServo.attach(pin); } // 2. 移动到出货位置例如90度 currentServo.write(90); delay(1000); // 等待推杆完成动作 // 3. 归位例如0度 currentServo.write(0); delay(500); // 4. 断开舵机以节省电力/防止抖动 currentServo.detach(); // 5. 更新库存 stock[selectedItem]--; currentState COMPLETE; } else { // 错误处理 lcd.clear(); lcd.print(Error!); delay(2000); resetSystem(); } } void completeState() { lcd.clear(); lcd.print(Please take); lcd.setCursor(0,1); lcd.print(your item!); delay(3000); // 给用户3秒取货时间 resetSystem(); } void resetSystem() { selectedItem -1; totalPrice 0.0; currentState IDLE; }舵机控制核心心得attach()与detach()在需要动作时才用attach()连接舵机动作完成后用detach()断开。这能有效防止多个舵机同时连接产生的电源噪声相互干扰也能避免舵机在闲置时因信号干扰而产生的“吱吱”声和抖动。动作延时servo.write()函数是“非阻塞”的它只是发送目标角度指令舵机需要时间转动。必须用delay()给予足够的转动时间通常500-1000ms否则你的代码会立刻执行下一步导致动作不完整。电源隔离再次强调舵机电源必须独立且功率充足。在vendingState中如果同时驱动多个舵机虽然本例是顺序执行独立电源能保证电压稳定不会拉低Arduino的电压导致复位。5. 系统调试与功能优化实战5.1 上电调试与常见问题排查组装好硬件并上传代码后激动人心的第一次上电测试来了。请按以下顺序排查Arduino和液晶屏是否工作如果LCD不亮检查I2C地址用I2C Scanner示例代码扫描、接线和对比度电位器如果有。按键扫描是否正常在idleState中可以添加串口打印输出按下的按键编号确保硬件连接和软件扫描逻辑正确。单个舵机测试先注释掉其他代码在setup里只初始化一个舵机让它来回转动测试其是否正常工作并确定推杆的实际有效角度可能是30度到120度而不是0-180度全范围。多舵机干扰测试依次让每个舵机动一次观察是否会出现某个舵机动作时其他舵机也跟着抖动的现象。如果有说明电源地线连接不良或功率不足重点检查共地线和独立电源的电流输出能力。常见问题速查表现象可能原因解决方案舵机不动发出“吱吱”声电源功率不足机械卡死信号线接触不良检查独立电源5V2A以上手动转动舵机盘看是否顺畅重接信号线。舵机乱转/角度不准信号受到干扰write()角度值超出物理范围确保信号线远离电源线用detach()在闲置时断开通过实验确定安全角度范围。LCD显示乱码/不显示I2C地址错误接线错误背光不亮运行I2C扫描程序检查SDA/SCL是否接反调整对比度或检查背光电源。按键反应不灵/连击按键抖动扫描频率太快在代码中添加防抖逻辑检测到按下后延时20ms再判断或使用Bounce2库。Arduino运行时复位舵机动作时电流过大拉低整体电压绝对确保舵机使用独立电源检查所有GND是否可靠连接。5.2 从原型到产品的功能优化基础功能实现后你可以考虑以下升级让这台售货机更智能、更实用增加投币器或扫码支付购买标准的微型投币器识别1元硬币或集成串口二维码识别模块如GM65。当检测到支付成功后再触发出货流程。这需要修改状态机增加“等待支付”状态。加入库存管理与显示在idleState显示商品时同时显示库存数量。当库存为0时在液晶屏上显示“SOLD OUT”并禁用对应按键。使用EEPROM保存库存Arduino Uno自带EEPROM。将库存数据写入EEPROM这样即使断电重启库存数据也不会丢失。注意EEPROM有写入寿命约10万次不要每次循环都写。添加声光反馈用一个无源蜂鸣器播放简单的出货成功音效。用RGB LED在待机、选择、出货等不同状态显示不同颜色光效。网络化与远程管理进阶增加一个ESP-01sESP8266Wi-Fi模块让售货机连接家庭网络。你可以编写一个简单的网页远程查看库存、销售记录甚至远程补货重置库存。这需要学习简单的网络通信知识如TCP/UDP, HTTP。6. 项目总结与扩展思考经过从硬件采购、结构搭建、电路焊接到代码调试的全过程这台看似简单的DIY自动售货机实际上是一个微缩版的工业控制系统。你不仅学会了如何驱动舵机、管理用户输入、设计状态机更重要的是掌握了解决实际问题的方法论如何将复杂需求分解为模块如何选型如何调试以及如何为未知问题预留解决方案的空间。我个人在制作过程中最大的体会有两点第一是电源设计的重要性它直接决定了系统的稳定性很多诡异的问题追根溯源都是电源问题第二是代码的结构化清晰的状态机让程序逻辑一目了然后续添加支付、网络等功能时只需要增加新的状态和转移条件即可不会变成“屎山代码”。这个项目还有巨大的扩展空间。比如你可以用步进电机配合丝杆制作更精确、推力更大的推送机构可以用摄像头和OpenCV做简单的商品识别甚至可以设计多层的货架用舵机云台配合机械臂实现“取-放”操作。它从一个简单的玩具可以延伸成为一个机器人、物联网、甚至人工智能的综合实践平台。最后一个小技巧在最终封装外壳前不妨把所有线路都用扎带或线槽整理好并用标签纸标记关键连接。这不仅能提升内部美观度更重要的是当半年后你想修改或维修时会感谢当时细心整理的自己。好了现在就去收集材料开始你的自动售货机创造之旅吧期待看到你的作品