基于Arduino与状态机的桌面抓棍机:从传感器到舵机的自动化实践

📅 2026/7/28 8:22:39
基于Arduino与状态机的桌面抓棍机:从传感器到舵机的自动化实践
1. 项目缘起从“桌面抓棍机”说起最近在整理工作室的零件盒看着里面散落的各种长度的亚克力棒、碳纤维管和3D打印支撑料每次找特定长度都得翻半天突然就萌生了一个想法能不能做个桌面小装置像抓娃娃机一样但抓的是这些小棍子这个念头一出来就收不住了正好手头有闲置的Arduino、舵机和一些结构件于是“桌面抓棍机”这个项目就这么开始了。它本质上是一个微型的、自动化的物料分拣与抓取装置核心目标是把杂乱堆放的细长杆状物比如M3螺丝、笔芯、模型支撑料一根根识别并抓取出来放到指定的收集槽里。听起来有点像工厂里的自动化分拣线但咱们把它微型化、桌面化、趣味化了。做这个东西远不止是“玩”那么简单。对于搞创客、玩机器人或者经常做手工的朋友来说它涉及了几个非常实用的技能点如何用最基础的Arduino UNO去协调多个舵机完成复杂动作如何用简单的传感器比如红外或超声波来判断“棍子”的位置和状态整个机械结构怎么设计才能既稳固又灵活外壳和抓手部分是用3D打印还是激光切割更合适这些问题恰恰是很多从入门想进阶的爱好者会遇到的坎。这个项目就像一个综合性的练手项目把电路、编程、结构设计、传感器应用甚至一点点简单的控制算法都串起来了。我自己在做的过程中也踩了不少坑比如舵机抖动导致抓取失败、结构干涉、传感器误触发等等这些经验我都会在后面的内容里详细拆解。2. 核心部件选型与设计思路做任何硬件项目第一步永远是“搭骨架”也就是确定核心部件和它们之间的协作关系。对于桌面抓棍机我们可以把它分解为几个核心模块大脑主控、眼睛传感器、手执行机构和身体机械结构。2.1 主控板为什么是Arduino UNO主控的选择很多ESP32、树莓派Pico功能更强STM32更专业。但我最终选择了最经典的Arduino UNO R3。原因很简单生态成熟、资料极多、调试方便。对于这样一个以逻辑控制和舵机驱动为主的项目UNO的ATmega328P芯片性能完全够用。它的14个数字I/O口和6个模拟输入口足以连接2-3个舵机、1-2个传感器以及可能的按钮或指示灯。更重要的是当你遇到问题时无论是“舵机库怎么用”、“引脚冲突”还是“程序卡死”几乎都能在Arduino中文社区或Stack Overflow上找到现成的讨论和解决方案。这对于降低项目门槛、快速验证想法至关重要。注意如果你希望未来加入无线控制比如用手机蓝牙遥控或者更复杂的图像识别那么ESP32是更好的起点。但就这个项目的初版而言我坚持“用最简单的方案实现核心功能”避免过度设计。2.2 执行机构舵机的选型与控制逻辑抓取动作的核心是舵机。这里至少需要两个舵机一个负责横向移动X轴一个负责抓手的开合夹取。对于桌面级的小装置普通9g微型舵机如SG90或扭矩稍大的MG90S就足够了。移动舵机X轴我选择的是MG90S金属齿轮舵机。因为它需要带动整个抓取机构横向移动对扭矩和耐用性有一定要求。塑料齿轮的SG90在反复运动下容易扫齿。抓取舵机夹取这个舵机负责驱动抓手开合。我用了SG90因为抓手本身很轻负载小。关键点在于抓手的设计。我设计了一个双摆杆式的夹爪舵机旋转通过连杆转化为夹爪的平行开合运动这样能保证夹取时受力均匀不会把棍子打飞。舵机控制的心得直接使用Arduino自带的Servo.h库虽然简单但在同时控制多个舵机且要求运动平滑时会遇到问题。库函数servo.write()是立即将舵机转到指定角度动作生硬。我的做法是编写一个平滑移动函数。比如让抓手从打开角度A缓慢闭合到抓住角度B不是直接servo.write(B)而是用for循环每次增加1度并加上一小段延时如15ms。这样动作看起来就柔和、拟人化了很多也减少了机械冲击。代码框架大致如下#include Servo.h Servo gripperServo; // 抓手舵机 void smoothMove(Servo s, int targetAngle, int speedDelay) { int currentAngle s.read(); if (currentAngle targetAngle) { for (int pos currentAngle; pos targetAngle; pos) { s.write(pos); delay(speedDelay); } } else { for (int pos currentAngle; pos targetAngle; pos--) { s.write(pos); delay(speedDelay); } } } void setup() { gripperServo.attach(9); // 抓手舵机接数字引脚9 } void loop() { // 平滑闭合抓手 smoothMove(gripperServo, 60, 15); // 移动到60度每步延时15ms delay(1000); // 平滑打开抓手 smoothMove(gripperServo, 120, 15); // 移动到120度 delay(1000); }2.3 感知模块如何“看到”棍子要让机器自动抓取必须让它知道“棍子在哪”。有几种低成本方案红外对管最经济的选择。在抓取平台两侧安装一对红外发射管和接收管。当有棍子滚落到检测区域时会遮挡红外线接收管信号变化从而判断“有物体”。但缺点是无法区分棍子的具体位置和数量也容易受环境光干扰。超声波传感器HC-SR04这是我最终采用的方案。将其安装在抓取机构上方朝下测量到平台底部的距离。当平台是空的测得的距离是一个固定值D1。当一根棍子进入检测区距离会缩短到一个更小的值D2。通过判断距离是否从D1变成了D2就能知道是否有棍子甚至能粗略估计棍子的直径通过距离差。它的优点是抗干扰能力强价格适中。颜色传感器TCS3200如果你需要按颜色分拣棍子比如不同颜色的模型支撑料这是必备的。但它成本较高编程也稍复杂。我选择HC-SR04的另一个原因是它还可以辅助定位。虽然精度不足以精确到毫米级定位每一根棍子但我们可以设计一个倾斜的、带振动的送料槽让棍子一根根排队滑落到一个固定的“待抓取位”。超声波传感器就用来确认这个“待抓取位”是否有货。这样就把复杂的“寻找”问题简化成了“在固定点抓取”的问题。2.4 机械结构设计Fusion 360从零到一结构是整个装置的物理基础决定了它是否稳固、流畅。我使用Fusion 360进行设计理由是其参数化设计和出色的集成性。设计流程草图与参数首先确定核心尺寸如底盘大小、舵机安装孔位、导轨行程。在Fusion 360中我会用“用户参数”功能定义这些关键尺寸如base_width150mm。这样后期修改时只需改参数所有关联零件自动更新。零件建模分别创建底盘、侧板、滑块、抓手等零件。重点在于考虑装配关系。例如设计滑块时要预留安装舵机的螺丝孔和与直线轴承配合的轴孔。装配体将所有零件组装起来使用“联接”功能模拟真实运动如滑块在光轴上的直线运动、舵机输出轴与抓手的铰链运动。这个阶段能提前发现干涉问题。我就曾发现最初设计的抓手在完全闭合时会碰到自己在虚拟装配中提前修改了。运动仿真可选但推荐利用Fusion 360的运动仿真功能给舵机添加驱动观察整个抓取循环是否顺畅。这能直观地看到极限位置优化运动轨迹。3D打印 vs. 激光切割3D打印适合制作复杂的、异形的、需要内部加强结构的零件。比如抓手、定制化的舵机支架、传感器固定座。我用PLA材料打印抓手因为可以轻松设计出适应不同直径棍子的防滑齿纹。激光切割适合制作大型的、板状的、需要高精度和光滑切面的零件。比如机器的底盘、侧板、隔板。我选择用5mm厚的椴木板进行激光切割速度快、成本低、边缘整齐并且天然的木质纹理也让作品更有质感。混合使用我的方案是主体框架底盘、立柱用激光切割木板而运动部件抓手、滑块、轴承座用3D打印。这样兼顾了强度、精度、成本和制作速度。3. 电路连接与供电方案详解电路是项目的神经系统混乱的接线是调试的噩梦。我的原则是模块化、可调试。3.1 接线图与引脚分配下面是一个清晰的引脚分配表格帮助你理清思路模块型号引脚连接功能说明注意事项主控Arduino UNO-控制中心需单独供电X轴移动舵机MG90S信号线 - D10控制横向移动需外接电源信号线可直连抓手舵机SG90信号线 - D9控制抓手开合需外接电源信号线可直连超声波传感器HC-SR04Vcc - 5V, Gnd - GND, Trig - D6, Echo - D7检测棍子有无确保Trig和Echo引脚附近无大电流干扰启动按钮轻触开关一脚接D2另一脚接GND启动一次抓取循环使用INPUT_PULLUP模式无需外接上拉电阻状态LED5mm LED阳极长脚接D13阴极接220Ω电阻至GND指示运行状态Arduino UNO板载LED也在D13可复用实际接线技巧使用面包板过渡在最终焊接前务必在面包板上搭建完整电路并测试。这能极大避免接错线烧毁元件的风险。舵机电源独立千万不要直接从Arduino UNO的5V引脚给舵机供电UNO的板载稳压芯片最大只能提供约500mA电流而一个舵机堵转时瞬时电流可能超过1A。这会导致UNO重启或损坏。正确的做法是使用一个独立的5V电源如旧的手机充电器或者大电流的DC-DC降压模块正负极接到面包板的电源轨所有舵机的Vcc和Gnd都从这里取电。舵机的信号线仍然接Arduino的数字引脚。共地处理独立电源的GND必须和Arduino的GND连接在一起即“共地”否则信号无法正确传输。3.2 供电方案选择稳定的电源是系统可靠运行的基石。这里有几种方案方案A推荐桌面使用一个5V/3A以上的手机充电头Micro USB线给Arduino UNO供电。同时该充电头通过一个DC母头转接线连接到面包板电源轨为舵机供电。这是最简洁稳定的方案。方案B移动使用使用一块7.4V 2S锂电池通过一个降压模块如LM2596输出稳定的5V同时给Arduino通过Vin引脚和舵机供电。注意电池容量和放电倍率要足够。方案C不推荐仅通过USB线连接电脑供电。这只能用于测试单个传感器或LED驱动舵机绝对不行。我选择了方案A因为我的抓棍机主要放在工作台上使用市电供电最可靠。我在底盘内部固定了一个多口USB充电模块整洁又安全。4. 程序设计从状态机到代码实现程序是项目的灵魂它定义了机器的行为逻辑。对于这种顺序控制的任务使用状态机State Machine模型来设计程序会让逻辑异常清晰易于调试和扩展。4.1 状态机模型设计我们可以把一次完整的抓取过程分解成几个明确的状态STATE_IDLE空闲状态机器待命等待启动信号如按钮按下。STATE_CHECK检测状态移动超声波传感器到待抓取位上方检测是否有棍子。STATE_MOVE_TO_TARGET移动至目标如果检测到棍子控制X轴舵机将抓手移动到棍子正上方。STATE_GRAB抓取控制抓手舵机闭合夹爪抓住棍子。STATE_LIFT_AND_MOVE提起并移送轻微提升抓手如果有Z轴或直接移动然后将棍子运送到收集槽上方。STATE_RELEASE释放打开抓手释放棍子。STATE_RETURN_HOME返回原点X轴舵机回到初始位置准备下一次循环。任何状态执行失败如检测无棍子、移动超时都应能安全地跳转到STATE_IDLE或一个错误处理状态。4.2 核心代码片段解析下面结合状态机展示部分关键代码#include Servo.h #include NewPing.h // 一个优秀的超声波传感器库提供更稳定的读数 // 引脚定义 #define TRIGGER_PIN 6 #define ECHO_PIN 7 #define SERVO_X_PIN 10 #define SERVO_GRIP_PIN 9 #define BUTTON_PIN 2 #define MAX_DISTANCE 15 // 最大检测距离15cm根据实际调整 // 对象声明 NewPing sonar(TRIGGER_PIN, ECHO_PIN, MAX_DISTANCE); Servo servoX; Servo servoGrip; // 状态枚举 enum State { IDLE, CHECK, MOVE_TO_TARGET, GRAB, LIFT_AND_MOVE, RELEASE, RETURN_HOME }; State currentState IDLE; int targetPosX 90; // 假设棍子位置对应舵机90度 int homePosX 10; // 初始位置 int gripOpenAngle 120; int gripCloseAngle 60; void setup() { Serial.begin(9600); servoX.attach(SERVO_X_PIN); servoGrip.attach(SERVO_GRIP_PIN); pinMode(BUTTON_PIN, INPUT_PULLUP); // 按钮上拉输入 // 初始化位置 servoX.write(homePosX); smoothMove(servoGrip, gripOpenAngle, 20); delay(1000); Serial.println(System Ready.); } void loop() { switch (currentState) { case IDLE: if (digitalRead(BUTTON_PIN) LOW) { // 按钮被按下 delay(50); // 简单消抖 if (digitalRead(BUTTON_PIN) LOW) { Serial.println(Button Pressed. Starting...); currentState CHECK; } } break; case CHECK: Serial.println(State: CHECK - Detecting stick...); // 移动传感器到检测位这里简化实际可能需要移动舵机 unsigned int distance sonar.ping_cm(); if (distance 0 distance 10) { // 假设距离小于10cm认为有棍子 Serial.print(Stick detected at ); Serial.print(distance); Serial.println( cm.); currentState MOVE_TO_TARGET; } else { Serial.println(No stick found. Returning to IDLE.); currentState IDLE; } delay(300); // 等待稳定 break; case MOVE_TO_TARGET: Serial.println(State: MOVE_TO_TARGET); smoothMove(servoX, targetPosX, 10); // 平滑移动到目标上方 delay(500); // 等待到位 currentState GRAB; break; case GRAB: Serial.println(State: GRAB); smoothMove(servoGrip, gripCloseAngle, 15); // 缓慢闭合抓手 delay(800); // 确保抓稳 currentState LIFT_AND_MOVE; break; // ... 其他状态类似LIFT_AND_MOVE, RELEASE, RETURN_HOME case RETURN_HOME: Serial.println(State: RETURN_HOME); smoothMove(servoX, homePosX, 10); smoothMove(servoGrip, gripOpenAngle, 15); Serial.println(Cycle Complete.); currentState IDLE; // 回到空闲等待下次触发 break; } } // 平滑移动函数同上文此处省略 void smoothMove(Servo s, int targetAngle, int speedDelay) { ... }编程中的关键坑点超声波传感器读数不稳定这是最常见的问题。NewPing库内置了多次测量取中值的功能比直接使用pulseIn()稳定得多。另外在代码中连续读取2-3次忽略异常值如0或超大值能进一步提高可靠性。舵机堵转检测在实际抓取时可能会因为棍子位置偏移导致抓手卡住舵机堵转电流剧增。一个简单的软件保护是在smoothMove函数中加入超时判断。如果舵机在预期时间内未能到达目标角度通过servo.read()判断则触发错误处理松开抓手并复位。状态切换的延时每个状态执行后适当delay()让机械动作完成、传感器数据稳定再切换到下一个状态。但delay()会阻塞程序。对于更复杂的系统可以考虑使用millis()进行非阻塞计时实现多任务。5. 组装、调试与问题排查实录设计完成代码写好接下来就是最激动人心也最折磨人的环节把一堆零件变成一台能动的机器。5.1 分步组装指南机械总装首先将激光切割的底板和侧板用胶水或螺丝组装成主体框架。确保方正否则会影响运动精度。安装直线光轴和轴承。这是运动顺畅的关键。给光轴涂上一点润滑脂然后将直线轴承压入3D打印的滑块中。这个过程要保证轴承与光轴垂直滑动无卡涩。将X轴舵机MG90S通过3D打印的支架固定在滑块上并确保舵盘与移动部分牢固连接。组装抓手部件。将抓手舵机SG90安装到滑块前端连接好连杆和夹爪。手动转动舵机检查开合范围是否足够有无干涉。最后将超声波传感器用打印的支架固定在抓手附近确保其探测方向垂直向下对准待抓取区域。电路集成将面包板固定在底板上。按照之前的接线图将所有元件Arduino、舵机、传感器、按钮连接到面包板。务必先断开电源仔细检查所有连接特别是电源正负极不能接反舵机信号线顺序正确。用扎带或胶枪整理线束避免运动部件绞线。5.2 上电调试与问题排查组装完毕接上电源上传程序。这时大概率不会一次成功。下面是我遇到并解决的几个典型问题问题一舵机乱转或不动现象上电后舵机发出“吱吱”声并随机转动或者完全没反应。排查检查电源用万用表测量舵机Vcc和GND之间的电压确保是稳定的5V。如果电压被拉低到4V以下说明电源功率不足需要换用电流更大的电源。检查信号线确认信号线是否确实连接到了Arduino指定的数字引脚并且程序中servo.attach(pin)的引脚号一致。检查代码确保在setup()中初始化了舵机对象并且没有在loop()中过于频繁地发送write()指令间隔小于舵机反应时间。我的案例我曾遇到MG90S舵机抖动严重。排查后发现是电源线杜邦线过长过细导致线损过大舵机供电不足。更换为更粗的导线后问题解决。问题二超声波传感器始终返回0或超大值现象串口监视器里看到的距离值一直是0或超过400。排查物理检查传感器模块的Trig和Echo引脚是否接反Vcc是否接5VHC-SR04是5V逻辑接3.3V可能工作不稳定。环境干扰检测面是否平整如果对着海绵、倾斜面或过于小的物体超声波可能散射严重无法收到回波。我最初把传感器对着亚克力棒返回就不稳定后来在棒子下面垫了一块平整的木板作为反射面效果立竿见影。代码层面换用NewPing库并设置合理的MAX_DISTANCE略大于实际测量距离。问题三抓手抓不住或把棍子打飞现象抓手闭合了但棍子没被夹住或者夹取瞬间棍子弹开了。排查与解决抓手力度SG90扭矩有限。检查抓手闭合的最终角度是否足够。可以通过smoothMove函数让抓手缓慢闭合同时用手轻轻感受力度找到一个既能夹紧又不会让舵机堵转的角度。抓手设计这是根本原因。我最初设计的抓手是光滑的摩擦力不够。解决方案在3D打印的抓手指尖部分设计锯齿状或增加一层硅胶管/热缩管增大摩擦力。这是决定抓取成功率的关键改进。抓取策略不要“猛抓”。我的策略是先让抓手快速移动到棍子上方然后非常缓慢地闭合smoothMove速度参数调至30ms以上让棍子有被“引导”进入抓手中心的时间而不是被撞击。问题四运动不同步或顺序错乱现象还没检测到棍子抓手就去抓了或者棍子还没运走抓手就打开了。解决这完全是程序逻辑问题。严格遵循状态机流程在每个状态切换的条件判断上多加Serial.println()打印当前状态和关键变量通过串口监视器观察程序流就能快速定位是哪个判断条件出了问题。6. 优化、扩展与更多玩法当基础功能实现后就可以考虑让它变得更智能、更强大。6.1 基础优化增加限位开关目前舵机移动是靠角度定位长时间运行可能会有累积误差。在X轴运动的起点和终点加装两个微动开关作为限位开关。每次上电或完成循环后让舵机向一个方向缓慢移动直到触发限位开关以此作为“机械原点”进行校准精度和可靠性会大幅提升。加入声音/灯光反馈用一个无源蜂鸣器播放简单的音效如启动音、抓取成功音、错误警报音再用一个RGB LED指示不同状态蓝色待机、黄色运行、绿色成功、红色错误人机交互感立刻提升。设计更友好的送料机构倾斜振动盘是最理想的但制作复杂。一个简单的替代方案是做一个漏斗形的储料仓底部开一个只允许一根棍子通过的窄缝棍子依靠重力自然下落至待抓取位。6.2 功能扩展按长度分拣在抓取位后方固定一个激光测距传感器如VL53L0X精度高当抓手夹住棍子后移动到传感器前扫描就能测出棍子长度。然后通过多设置几个收集槽让X轴舵机根据长度将棍子投放到不同的槽里。无线控制与状态监控将主控换成ESP32通过Wi-Fi连接家庭网络。你可以编写一个简单的网页界面用手机浏览器就能远程启动抓取、查看实时状态如有无棍子、抓取次数甚至上传新的动作序列。视觉识别进阶如果使用树莓派或搭配OpenMV这样的视觉模块就可以实现真正的“视觉抓取”。摄像头识别杂乱堆中棍子的位置和角度然后计算出抓手需要移动的精确坐标通过串口发送给Arduino执行。这就从“固定点抓取”升级到了“任意点抓取”挑战更大成就感也更强。这个“桌面抓棍机”项目从想法到实现贯穿了从设计、选型、编程到调试的完整创客流程。它没有用到特别高深的技术但每一个环节都充满了实践性的细节。我最深的体会是硬件项目调试的时间往往远超搭建的时间。耐心阅读传感器数据手册细心观察机械运动细节善于用串口打印信息来“看见”程序的运行这些能力比单纯会写代码更重要。当你按下按钮看着自己打造的机器“咔哒”一声稳稳夹起一根小棍子并准确投进盒子里时那种快乐是纯粹的、智造带来的快乐。希望我的这些踩坑经验和实现思路能帮你少走弯路更快地享受到这份快乐。