用 Arduino Uno 做 4 舵机机械手臂:供电、共地与摇杆控制

Cartoon of a four-joint robot arm made from bright translucent-blue SG90 micro servos on a light-brown cardboard frame gripping an orange cube, with two black joystick modules, a black four-cell AA battery case, and a blue Arduino board cropped at the edge

Arduino Uno 的 5 V 脚撑得住的,大约是一只空载的 SG90 —— 不是四只。四只微型舵机在手臂动起来的时候要吃 0.4–1 A,全部一起弹到位的瞬间可以到 2.8 A,而 USB 那边能给的大约只有 450 mA。给舵机自己一组 4×AA 电池盒,把地接在一起,再用增量的方式驱动每一个关节 —— 手放开,手臂才停得住。

一组属于自己的电源,加上放手也停得住的控制方式:这两件事是四关节手臂逼着你做,而两轴云台永远不会逼你做的。剩下的就是纸皮和耐心。这里全程不超过 6 V,所以没有触电的风险 —— 会受伤的是堵转的舵机(会烧)和尼龙齿轮组(会崩牙)。

Arduino Uno 的 5V 供电,实际撑得住几只 SG90 舵机?

Arduino Uno 可以指挥十二只舵机,却只供得起大约一只。发信号几乎不花成本:Servo 库占用一个 16 位定时器,把它分给最多十二只舵机,每 20 ms 给每只送一个 544–2400 µs 的脉冲。ATmega328P 只有一个 16 位定时器 Timer1 —— 这既是十二这个上限的由来,也是为什么只要挂上一只舵机,pin 9 和 pin 10 上的 analogWrite() 就失效了:定时器被库拿走了。这些都写在库的 Servo.h 里。

真正咬住你的预算是电流。一只 SG90 的电路板本身待机大约 10 mA,但它后面接着一颗有刷直流马达和一组尼龙齿轮,吃多少电,要看它干多重的活:

舵机在做什么 一只 SG90 四只加起来
通电,空手保持姿势 ~10 mA ~40 mA
带着一支纸皮手臂在动 100–250 mA 0.4–1 A
堵转 —— 卡住、超载,或者上电瞬间弹到位 350–700 mA 1.4–2.8 A

堵转电流是一个范围,不是数据表上的一个数字:公开的 SG90 数据表给了扭力、速度和脉宽,却完全没给电流值,所以电源要按这个范围的上限来配。Uno 那边能拿出来对付它的,被两个元件卡住:

Uno 的供电方式 5V 供电被什么限制 5V 实际给得出多少
USB 2.0 端口 USB 输入端 500 mA 的自恢复保险丝 约 500 mA,扣掉板子自己吃的,剩大约 450 mA
电源座输入 9 V SOT-223 封装线性稳压器的散热 约 270 mA
电源座输入 12 V 同一颗稳压器,压差更大 约 155 mA

电源座那两个数字,是你可以自己算一遍的算术。Uno 的 5 V 来自一颗 SOT-223 封装的 1117 系列线性稳压器,而线性稳压器不会把多余的电压转换掉 —— 它是烧掉,烧掉 (Vin − 5) × 电流 瓦。一个封装在热保护动作之前散得掉 (TJ(max) − 环境温度) ÷ θJA 瓦:以 125 °C 的结温上限、SOT-223 的 88 °C/W 和 30 °C 的工作台环境温度来算,大约是 1.08 W。9 V 输入时压差 4 V,所以 1.08 W ÷ 4 V ≈ 270 mA;12 V 输入时压差 7 V,同样这一瓦只换得到 155 mA。输入电压越高,5 V 那边给得出的电流越少,不是越多。

最先绊倒人的是上电那一刻:attach() 和第一次 write() 会让每一只舵机同时猛地弹到被指定的角度,电流接近堵转值。那颗自恢复保险丝是 PTC 元件 —— 它是发热、电阻跟着爬升,所以供电是往下塌,而不是干脆断掉。塌到低于 ATmega328P 的欠压复位门槛,芯片就复位,setup() 再跑一遍,舵机再弹一次:手臂在复位循环里抽搐,看起来完全像是程序写错了。

选配 —— 手臂垂下来、想接市电,或者关节不够用的时候

ItemPriceQty
SG90 MG90 Tower Pro Micro Servo Motor 9g 180 360 Servo Motor for Arduino Robotic - MG90- 180°SG90 MG90 Tower Pro Micro Servo Motor 9g 180 360 Servo Motor for Arduino Robotic - MG90- 180°MG90180RM11.55
Power Supply Adapter DC Universal AC to DC Converter PSU 5V2A 5V3A 9V2A 12V2A - P.S. ADAPTOR (5V3A)Power Supply Adapter DC Universal AC to DC Converter PSU 5V2A 5V3A 9V2A 12V2A - P.S. ADAPTOR (5V3A)PSA0503RM12.95
5.5x2.1mm DC Power Male Connector Plug Jack Adapter for Arduino DIY Electronics Projects - 5.5x2.1 DC CONNECTOR (F)5.5x2.1mm DC Power Male Connector Plug Jack Adapter for Arduino DIY Electronics Projects - 5.5x2.1 DC CONNECTOR (F)5521DCFRM1.20
16 Channel PWM Servo Motor Driver PCA9685 16 PWM Output with I2C Communication For Arduino Robotic16 Channel PWM Servo Motor Driver PCA9685 16 PWM Output with I2C Communication For Arduino RoboticPCA9685RM18.95

金属齿轮的 MG90 舵机:手臂被自己的重量压得垂下来时,把底座和肩关节换成它。5V 3A 适配器配一条电源座转接线,是比电池更整齐的工作台方案。PCA9685 驱动板只有在你用完四个直接输出脚之后才需要。

怎样给四只舵机一组属于它们自己的电源?

4×AA 电池盒是最简单的正确答案,而且它的电压刚好落在该落的地方:四颗碱性电池标称 6.0 V,四颗镍氢 4.8 V,两者都在 SG90 额定的 4.8–6 V 窗口里,也都拿得出安培级的电流。刚拆封的碱性电池空载每颗接近 1.6 V,所以新电池盒量出来大约 6.4 V —— 这没关系,因为舵机一开始吃电,内阻就把它拉回窗口里;这也正是不要去拿更大电压电池组的理由,因为 7.2 V 的电池组没有可以往下塌的空间。

把电池盒的红线接到面包板的红色电源条,黑线接到蓝色那条 —— 多股软线插进面包板孔里会散开,所以先上锡,或者把它绕在一根公头杜邦针上。一只舵机都还没插进去之前,先量这两条电源条:SG90 上没有防反接二极管,所以正负接反一次,四只会一起报销,而且悄无声息。之后四条舵机红线全部进红色电源条,四条棕线全部进蓝色。

然后把地接在一起。这一步就是最常被跳过的那一步。舵机并不是把你的脉冲当成一个抽象的「脉冲」来读 —— 它读的是橙线上的电压相对于它自己那条棕线的值。如果电池的负极和 Uno 的地不是同一个电气节点,这两个参考点就会各自漂移,舵机看到的是一个它解不出来的信号:手臂发抖、自己爬动,或者干脆不动。从 Uno 的 GND 排针拉一条黑色杜邦线到蓝色电源条,就解决了。

绝对不要把电池盒接到 Uno 的 5V 脚。那只脚在稳压器之后,直接连到一颗 ATmega328P,而这颗芯片的规格是最高工作电压 5.5 V、绝对最大值 6.0 V。一盒新的碱性电池正好压在这个天花板上,而且 USB 还插着的话,你等于把它一路推回电脑的 USB 口里去。VIN 是另一种失败方式:线性稳压器需要大约一伏的压差余量,所以 6 V 进去撑不出 5 V 出来。Uno 就让它吃 USB。想用市电代替电池的话,5 V 3 A 适配器配一条电源座转接线接到电源条上,四只舵机同时堵转也够。

要让手臂脱离笔记本电脑独立跑,就从板子的电源座喂电:7–12 V 的电池组或者一颗 9 V PP3 方形电池在那里很舒服,因为 Uno 加两支摇杆加起来,离那 270 mA 还远得很。禁令是针对舵机那条电源,不是针对板子。

四个关节和两支摇杆要怎么接线?

一支最精简的 Arduino 机械手臂有四个关节:绕垂直轴转的底座、把大臂抬起来的肩关节、把小臂折起来的肘关节,还有一个夹爪。两个摇杆模块刚好给出四个模拟轴 —— 左边那支管底座和肩,右边那支管肘和夹爪。

卡通侧视图:一支用四只亮蓝色半透明微型舵机搭起来的纸皮机械手臂,深蓝色弧形箭头分别标出底座旋转、肩关节抬升、肘关节折叠和夹爪开合
一支最精简手臂的四个关节:底座旋转、肩、肘、夹爪。两支摇杆刚好给出四个模拟轴 —— 一个关节一个。

每只 SG90 的引线末端是一个黑色的三针母座,所以要用公对公杜邦线去接 Uno 的母排针和面包板电源条。摇杆模块正好相反 —— 它是弯针的公头 —— 所以要用公对母杜邦线;我们的杜邦线选用教程把哪一头配哪一头讲得很清楚。

接到 备注
舵机橙线(信号)×4 D9 底座、D10 肩、D11 肘、D3 夹爪 任何数字脚都行;不管接哪里,Servo 库都会拿走 9 和 10 的 PWM
舵机红线 ×4 面包板红色电源条 来自电池 —— 绝不来自 Uno
舵机棕线 ×4 面包板蓝色电源条
电池 + / − 红色电源条 / 蓝色电源条 电池盒上的开关就是总断电开关
Uno GND 面包板蓝色电源条 共地 —— 必须接
Uno 5V 面包板另一边的红色电源条 逻辑电源 —— 只给摇杆,绝不给舵机
两条蓝色电源条 用一条杜邦线接起来 电池、Uno 和摇杆同一个地
摇杆 1:+5V、GND、VRx、VRy 逻辑红色电源条、蓝色电源条、A0、A1 底座和肩
摇杆 2:+5V、GND、VRx、VRy 逻辑红色电源条、蓝色电源条、A2、A3 肘和夹爪
摇杆 2:SW D2 归位按钮,用内部上拉

摇杆的 5 V 可以从 Uno 拿,而且它们是唯一可以这样拿的东西:每一块 HW-504 模块就是两个 10 kΩ 电位器跨在电源上,一个轴大约半毫安。VRx 和 VRy 是那两个模拟轴;SW 是一个对地短路的按压开关,不是第三个轴,所以它需要一个上拉。

这条规则的方向,和规则本身一样重要。摇杆就是一个电位器,所以 VRx 输出的是喂给它那个电压的一个比例,而 analogRead() 是拿 Uno 自己的 5 V 当满量程去量这个比例:改用 6 V 电池组喂摇杆,回中的轴读到的就不是 512 而是大约 614,程序会当成摇杆被一直推着不放 —— 关节自己就漂走了。Uno 只有一只 5 V 脚,而两支摇杆各要一组 5 V 和 GND,所以把面包板另外那条长边交给这件事:一条杜邦线从 Uno 5V 接到那条红色电源条,两支摇杆的 +5V 都插进去,它们的 GND 插旁边那条蓝色,再用一条杜邦线把那条蓝色和电池那条蓝色接起来。两组电源共用地,绝不共用正极。

为什么摇杆要控制速度,而不是角度?

模拟摇杆是弹簧回中的:一放手它就回到中间,那里 analogRead() 报出来大约是 1023 里的 512。把它直接映射成角度 —— 对云台来说这是对的做法,我们的摇杆云台教程就是这样做的 —— 那么中间就等于 90°。于是你一放手,每一个关节都被命令回到 90°,手臂就折下来了。

增量控制把这件事反过来:推的幅度决定速度,不是位置。每 20 ms,程序量一次摇杆离中间有多远,再按比例把零点几度加到一个存起来的目标角度上。推一半,关节慢慢爬;推到底,大约每秒走 60 °;放手回中,什么都不加,手臂就停在原地。

有三个细节让它真的听话。先减掉 512 两侧大约 40 个计数的死区,再去加 —— 因为一支静止的摇杆从来读不出一个干净的 512,电位器的机械中点和 ADC 噪声会飘上好几个计数,没有死区手臂就会自己爬。constrain() 把每个目标夹在各关节自己的上下限之间,这样关节就不会被推进自己的结构里去。目标值用的是 float,所以轻轻的推动累积的是零点几度,而不是四舍五入成零。那 20 ms 的节拍本身就是舵机的一个刷新帧 —— 库每帧给每只舵机发一个脉冲,所以更新得比这更快的目标值,根本到不了马达。

卡通对比图:同一支装在亮蓝色半透明舵机上的纸皮机械手臂,左边是摇杆回中、直接映射角度时整支塌下来,右边是增量控制下手臂稳稳保持抬起的姿势
同样是放开的摇杆,两种程序。直接映射角度会把每个关节送回 90°,手臂塌下来;增量控制在摇杆回中时什么都不加,姿势就留住了。

Arduino 机械手臂的程序长什么样?

这份程序只需要 Arduino IDE 自带的 Servo 库,编译出来占 5162 bytes 的 Flash(Uno 的 16%)和 282 bytes 的 RAM。先把限位数组改成你自己手臂的真实行程。AXIS_PIN 决定哪一支摇杆管哪一个关节,INVERT 决定它往哪一边转:某个关节转反了,就翻那一项 INVERT;动的是错的关节,就把两项 AXIS_PIN 对调。

// Arduino Uno 上的 4 舵机机械手臂,由两支模拟摇杆驱动。
// 摇杆控制的是速度,不是角度:推着它,关节就往那边慢慢走,
// 放开手,关节就停在原地。手臂能保持姿势靠的正是这一点:
// 直接把 analogRead 映射成角度的话,摇杆一弹回中间,
// 手臂就会立刻塌下来。
//
// 舵机的电不来自 UNO。四条舵机红线接到另外一组 4xAA 电池盒
//(或者一个 5V 电源);只有地是共用的。

#include <Servo.h>

Servo joint[4];                                     // 底座、肩、肘、夹爪

const uint8_t  SERVO_PIN[4] = {  9,  10,  11,   3 };  // 信号脚(舵机橙线)
const uint8_t  AXIS_PIN[4]  = { A0,  A1,  A2,  A3 };  // 摇杆 1 VRx/VRy,摇杆 2 VRx/VRy
const bool     INVERT[4]    = { false, true, false, false };
const uint8_t  MIN_DEG[4]   = { 10,  30,  20,  25 };  // 机械限位 - 量你自己那支手臂
const uint8_t  MAX_DEG[4]   = { 170, 150, 160,  90 };
const uint8_t  HOME_DEG[4]  = { 90,  90,  90,  60 };  // 归位姿势

const uint8_t  PARK_BUTTON  = 2;                    // 摇杆 2 的 SW 脚,接到 D2
const int      CENTRE       = 512;                  // 静止时的 analogRead(),0..1023
const int      DEADBAND     = 40;                   // 中间这么多计数的抖动直接忽略
const float    MAX_STEP     = 1.2;                  // 推到底时每个节拍走多少度
const unsigned long TICK_MS = 20;                   // 一个节拍等于舵机的一个刷新帧

float target[4];                                    // 我们正在下达的姿势,单位是度
unsigned long lastTick = 0;

void setup() {
  Serial.begin(9600);
  pinMode(PARK_BUTTON, INPUT_PULLUP);               // 按下时 SW 对 GND 短路

  for (uint8_t i = 0; i < 4; i++) {
    target[i] = HOME_DEG[i];
    joint[i].attach(SERVO_PIN[i]);
    joint[i].write(HOME_DEG[i]);
    delay(400);                                     // 一次只归位一个关节:四只舵机
  }                                                 // 同时弹到位,是这支手臂
                                                    // 会遇到的最大一次电流冲击
  Serial.println(F("Arm parked. Servos must run from their own supply."));
}

void loop() {
  unsigned long now = millis();
  if (now - lastTick < TICK_MS) return;             // 两帧之间没事可做
  lastTick = now;

  bool parking = (digitalRead(PARK_BUTTON) == LOW);

  for (uint8_t i = 0; i < 4; i++) {
    if (parking) {
      // 用推到底同样的速度,慢慢爬回归位姿势。
      if (target[i] < HOME_DEG[i]) target[i] = min(target[i] + MAX_STEP, (float)HOME_DEG[i]);
      else                         target[i] = max(target[i] - MAX_STEP, (float)HOME_DEG[i]);
    } else {
      int offset = analogRead(AXIS_PIN[i]) - CENTRE;
      if (abs(offset) <= DEADBAND) continue;        // 摇杆静止:不写入,关节保持

      // 减掉死区,让关节从零速开始,而不是跳一下。
      offset += (offset > 0) ? -DEADBAND : DEADBAND;

      float step = (float)offset * MAX_STEP / (float)(CENTRE - DEADBAND);
      if (INVERT[i]) step = -step;

      target[i] = constrain(target[i] + step, (float)MIN_DEG[i], (float)MAX_DEG[i]);
    }
    joint[i].write((int)(target[i] + 0.5));         // 四舍五入,再送出这一帧的角度
  }
}

有两行的分量比看起来重:delay(400) 把舵机错开,让它们一只一只回到归位角度,把整个使用过程中最大的一次电流冲击拆成四次小的;而 continue 跳过了 write() —— 一只什么都没收到的舵机会保持它上一次的角度,所以「什么都不做」本身就是保持。

手臂的框架要用什么做?

我们卖的是电子件,不是框架 —— 我们的目录里没有手臂结构件,也没有现成的打印件。这件事没有听起来那么要紧:瓦楞纸皮,用美工刀裁、热熔胶粘,就是第一版最合适的材料,因为这个几何形状你会重画三次。亚克力或者 3 mm 夹板是之后的升级。

决定这个几何形状的是扭力。我们这只 SG90 的额定值是 4.8 V 下 1.3 kg/cm —— 也就是 1.3 kg 挂在离轴心一厘米的地方,所以挂到十厘米就只剩大约 130 g,还要再扣掉手臂自己的重量。每一节控制在 10–12 cm,夹爪做轻,然后可以预期最先撑不住的是肩关节:它要同时抬起小臂、肘关节舵机、夹爪和负载,用的还是整支手臂上最长的那根杠杆。MG90 是直接替换的答案:一样的外形和引线,6 V 下 2.2 kg/cm,齿轮是金属的,不是尼龙的。

每个关节都要在舵机对面那一侧做一个枢轴 —— 一根螺丝,或者一颗螺丝穿过一片纸皮垫圈,位置就在关节的转轴线上。SG90 的输出是尼龙齿轮组上一根小小的尼龙花键轴,它是设计来转动负载的,不是用来承受负载的,所以把小臂单独吊在它身上,等于把所有弯曲的力都灌进那组齿轮里。底板也要压重,不然转起来的是底座,不是手臂。

舵盘最后才装。先让每个关节走到它的归位角度,然后才拧舵盘的螺丝,而且每一片舵盘都要装在行程的中段:在行程的一端把舵盘装上去,你的动作范围就有一半消失在结构里了。我们的180° / 270° / 360° 舵机怎么选把舵盘和角度范围讲清楚了 —— 手臂要的是 180° 的定位型舵机,绝对不是连续旋转型的。这里看着一支四关节框架装起来会有帮助:

什么时候四个直接输出脚就不够用了?

有三件事会把你推向 PCA9685 16 路驱动板:关节数量超过了手上方便用的脚位;想把 pin 9 和 pin 10 拿回来做 analogWrite();或者用到会长时间关闭中断的库 —— SD 卡、某些显示屏 —— 那会让定时器产生的脉冲抖动,而 PCA9685 是用自己的振荡器发脉冲,不受影响。我们的 PCA9685 教程讲的就是这一步。它做不到的是变出电流来:它的 V+ 螺丝端子照样要接同一组外部电源。

我们常看到的错误

用一只舵机接在 Uno 的 5 V 脚上,证明「这样可以」。一只 SG90 确实跑得动那只脚,所以第一次测试通过了,接线看起来就定案了。两只往往也没事 —— 然后第三只或者第四只关节把供电压垮,全部一起失灵,而背黑锅的是最后改过的那个地方,不是电源。

没有把地接起来。电池接舵机接得完全正确,但电池负极那条和 Uno 的 GND 之间什么都没有:接线看起来是对的,手臂的行为却是随机的。在动手改程序之前,先查这一点。

关节限位靠猜。一只被命令超出框架允许范围的舵机不会认输 —— 它会停在堵转状态,吃着最大电流发热,直到有东西崩掉。这就是 MIN_DEGMAX_DEG 要防的事:每个关节先保守地从 60–120° 开始,一次放宽五度,因为关节开始嗡嗡叫的那个角度,就是它碰到自己限位的角度。夹爪要一样小心,理由却不同:它的闭合限位不是一个机械挡点,而是手指刚好碰到物体的那个角度,再过一度,只要你还夹着这个东西,它就一直堵转。

把嗡嗡声当成故障。定位型舵机是一个闭环:它拿自己内部的电位器和你送进去的脉冲比较,然后驱动马达直到两者一致。带着负载的时候它永远差那么一点,所以它会在目标附近来回微动 —— 那阵轻微的嗡嗡声就是关节在干活。大声到整个框架都在震的嗡嗡声,那是关节到了扭力极限:把这一节缩短、做轻,或者换上 MG90

拿一颗 9 V 电池去喂舵机。两个地方都错:9 V 高过 SG90 的 6 V 上限,而且 PP3 方形电池那几颗小电芯,在四只舵机要的电流下会塌得很厉害。

常见问题

Arduino Uno 带得动 4 只 SG90 舵机吗?

带不动。四只 SG90 动起来吃 0.4–1 A,堵转时最高 2.8 A;Uno 那条供电用 USB 时大约只给得出 450 mA。四条信号线它驱动得很好 —— 电流要有自己的电池组。

为什么机械手臂一动,我的 Arduino 就复位?

舵机把 5 V 供电拉到了 ATmega328P 的欠压复位门槛之下。芯片复位,舵机弹回归位角度,供电又塌下去。给它们自己的电源,并且把地接在一起。

Arduino 机械手臂该用什么电池?

四颗 AA:碱性 6.0 V 或者镍氢 4.8 V,两者都在 SG90 的 4.8–6 V 窗口里,也都拿得出安培级电流。5 V 3 A 适配器是更整齐的替代方案。9 V PP3 方形电池不要碰舵机那条电源 —— 接在 Uno 自己的电源座上倒是没问题。

为什么我一放开摇杆,机械手臂就掉下来?

因为程序把摇杆读数直接映射成了角度,而放开的摇杆读数大约是 1023 里的 512 —— 换算过去就是每个关节 90°。改成按推的幅度、每个节拍加零点几度,回中时什么都不加。

做一支 4 舵机机械手臂,需要 PCA9685 驱动板吗?

不需要。四只舵机直接接 Uno 的四个数字脚就能跑。PCA9685 是在你需要的通道数超过脚位、想把 pin 9 和 pin 10 拿回来做 analogWrite(),或者用了会干扰定时器脉冲的库时,才有它的价值。它不提供电流。

最后更新:2026 年 8 月。有问题?WhatsApp 联系我们

Leave a Reply

Your email address will not be published. Required fields are marked *