零基础学 Arduino(四)

零基础学 Arduino(四):简易机械臂

第三篇学会了控制单个舵机,这篇我们用三个幻尔 LD-25MG 舵机搭一个简易机械臂——底座左右转、大臂上下抬、爪子开合抓东西。把前面三篇学的 API 全部用上。


1. 你手上的硬件

硬件 品牌 作用
Arduino UNO R3 — 主控
幻尔 Arduino 扩展板(C款) 幻尔科技 引脚即插即用,供电集中管理
LD-25MG 数字舵机 ×3 幻尔科技 三关节驱动:底座 + 大臂 + 爪子


2. 机械臂结构

三个舵机各管一个关节,组装起来就是一个三自由度机械臂:

           ┌──────────────┐
           │   舵机3:爪子  │ ← 开合,抓取物体
           └──────┬───────┘
                  │
           ┌──────┴───────┐
           │  舵机2:大臂   │ ← 上下抬放,控制高度
           └──────┬───────┘
                  │
           ┌──────┴───────┐
           │  舵机1:底座   │ ← 左右旋转,控制方向
           └──────┬───────┘
                  │
            ┌─────┴─────┐
            │  扩展板    │
            │  (插UNO上)  │
            └───────────┘
舵机编号 关节 功能 角度范围 信号脚
舵机1 底座 整臂左右旋转 0°~180° D3
舵机2 大臂 大臂上下抬放 0°~180° D5
舵机3 爪子 夹爪开合 30°~150° D6

3. 新知识点

3.1 多个 Servo 对象

一个舵机一个对象,互不干扰:

#include <Servo.h>

Servo servo1;   // 底座
Servo servo2;   // 大臂
Servo servo3;   // 爪子

void setup() {
    servo1.attach(3);
    servo2.attach(5);
    servo3.attach(6);
}

3.2 用数组管理多个舵机

舵机多了之后,用数组统一管理更简洁:

Servo servos[3];           // 3 个舵机
int pins[3] = {3, 5, 6};  // 对应引脚

void setup() {
    for (int i = 0; i < 3; i++) {
        servos[i].attach(pins[i]);
    }
}

3.3 步进式移动——平滑过渡

一次 write() 是瞬间到位的。想平滑运动,需要从一个角度"一步步"走到目标角度:

void moveSmoothly(Servo &servo, int from, int to, int speedDelay) {
    if (from < to) {
        for (int angle = from; angle <= to; angle++) {
            servo.write(angle);
            delay(speedDelay);   // 数字越小越快
        }
    } else {
        for (int angle = from; angle >= to; angle--) {
            servo.write(angle);
            delay(speedDelay);
        }
    }
}

4. 实战示例

示例 1:三舵机回零——初始化到默认位置

上电后先把三个舵机归位到安全角度:

#include <Servo.h>

Servo servo1, servo2, servo3;

void setup() {
    servo1.attach(3);   // 底座
    servo2.attach(5);   // 大臂
    servo3.attach(6);   // 爪子

    // 归零:底座居中,大臂放下,爪子打开
    servo1.write(90);
    servo2.write(0);
    servo3.write(150);
}

void loop() {
    // 保持初始位置不动
}

示例 2:顺序动作——底座转 → 大臂抬 → 爪子抓

模拟"找东西 → 伸过去 → 抓起来"的动作序列:

#include <Servo.h>

Servo servo1, servo2, servo3;

void setup() {
    servo1.attach(3);
    servo2.attach(5);
    servo3.attach(6);

    // 回到初始姿态
    servo1.write(90);
    servo2.write(0);
    servo3.write(150);    // 爪子张开
    delay(2000);
}

void loop() {
    // 第1步:底座右转 45°,对准目标
    servo1.write(135);
    delay(1000);

    // 第2步:大臂抬到 120°,伸过去
    servo2.write(120);
    delay(1000);

    // 第3步:爪子合拢,抓住
    servo3.write(50);
    delay(1000);

    // 第4步:大臂抬起,举起来
    servo2.write(50);
    delay(1000);

    // 第5步:底座转回来
    servo1.write(90);
    delay(1000);

    // 第6步:放下,爪子张开
    servo2.write(0);
    servo3.write(150);
    delay(1000);
}

这就是一个完整的"抓取-搬运-放下"流程。改每个步骤的角度和延时,就能编排出不同的动作。

示例 3:多舵机同步运动

三个舵机同时平滑动起来,不是一个个排队等:

#include <Servo.h>

Servo servos[3];
int pins[3] = {3, 5, 6};       // 底座、大臂、爪子
int current[3] = {90, 0, 150}; // 当前角度
int target[3] = {90, 0, 150};  // 目标角度

void setup() {
    for (int i = 0; i < 3; i++) {
        servos[i].attach(pins[i]);
        servos[i].write(current[i]);
    }
    Serial.begin(9600);
}

void loop() {
    // 目标姿态:底座左转、大臂抬起、爪子半合
    target[0] = 45;
    target[1] = 100;
    target[2] = 80;

    // 三个舵机同时一步步走向目标
    for (int step = 0; step < 50; step++) {
        for (int i = 0; i < 3; i++) {
            if (current[i] < target[i]) current[i]++;
            if (current[i] > target[i]) current[i]--;
            servos[i].write(current[i]);
        }
        delay(15);
    }

    delay(1000);

    // 回到初始姿态
    target[0] = 90;
    target[1] = 0;
    target[2] = 150;

    for (int step = 0; step < 50; step++) {
        for (int i = 0; i < 3; i++) {
            if (current[i] < target[i]) current[i]++;
            if (current[i] > target[i]) current[i]--;
            servos[i].write(current[i]);
        }
        delay(15);
    }

    delay(1000);
}

核心思想:每一帧每个舵机只动 1°,50 帧后全部到位。step 越多越平滑。

示例 4:用旋钮选关节,逐个调试

调试机械臂时需要一个关节一个关节地找角度。接一个旋钮,每次控制一个关节:

#include <Servo.h>

Servo servos[3];
int pins[3] = {3, 5, 6};
int joint = 0;   // 当前控制的关节(0=底座, 1=大臂, 2=爪子)

void setup() {
    for (int i = 0; i < 3; i++) {
        servos[i].attach(pins[i]);
        servos[i].write(90);
    }
    Serial.begin(9600);
    Serial.println("输入 0/1/2 选择关节(底座/大臂/爪子)");
}

void loop() {
    // 串口输入切换关节
    if (Serial.available()) {
        int newJoint = Serial.parseInt();
        if (newJoint >= 0 && newJoint <= 2) {
            joint = newJoint;
            Serial.print("当前控制:");
            Serial.println(joint == 0 ? "底座" : joint == 1 ? "大臂" : "爪子");
        }
    }

    // 旋钮控制当前关节角度
    int val = analogRead(A0);
    int angle = map(val, 0, 1023, 0, 180);
    servos[joint].write(angle);

    Serial.print("关节");
    Serial.print(joint);
    Serial.print(" → ");
    Serial.println(angle);

    delay(30);
}

先在串口监视器输入 0 选底座,拧旋钮调到最佳角度记下来;再输入 1 调大臂,输入 2 调爪子。全部定好后把角度值填进示例 2 的动作序列里。

5. API 速查

API 作用 示例
myservo.attach(pin) 舵机绑定引脚 servo1.attach(3);
myservo.write(deg) 转到指定角度 servo2.write(120);
myservo.detach() 释放舵机 servo3.detach();
map(val, a,b, c,d) 数值范围映射 map(val, 0,1023, 0,180)
constrain(x, min, max) 限制数值区间 constrain(angle, 30, 150)
Serial.parseInt() 串口读取整数 int n = Serial.parseInt();