12浏览
查看: 12|回复: 0

[活动] [ESP8266/ESP32]ESP32-C5的五轴机械臂控制系统(一):硬件连接...

[复制链接]
## 一、项目概述

本项目基于Firebeetle 2 ESP32-C5开发板设计一款五轴机械臂控制系统,通过NRF24L01无线模块实现远程控制。机械臂配备5个舵机,分别控制底座旋转、大臂抬升、小臂伸缩、手腕旋转和夹爪开合,实现灵活的三维空间操作。

本教程为第一部分,主要介绍:
- 硬件组成与接线
- Arduino IDE环境配置
- 舵机基础控制代码实现
- 接收端核心功能开发

### 技术特点
- **五自由度设计**: 5个舵机实现底座、大臂、小臂、手腕、夹爪的独立控制
- **无线远程控制**: 采用NRF24L01模块,2.4GHz通信,传输距离10-30米
- **精准PWM驱动**: ESP32-C5的LEDC PWM功能实现0-180度精确定位
- **实时响应**: 50Hz控制频率,延迟低于20ms
- **模块化代码**: 易于扩展和维护

## 二、硬件组成与介绍

### (一) 硬件清单

| 序号 | 名称 | 数量 | 说明 |
|------|------|------|------|
| 1 | Firebeetle 2 ESP32-C5开发板 | 1 | 接收端主控 |
| 2 | NRF24L01无线模块 | 1 | 2.4GHz无线接收 |
| 3 | SG90舵机(或类似) | 5 | 控制机械臂各关节 |
| 4 | 5V电源适配器 | 1 | 为舵机供电(建议≥2A) |
| 5 | 面包板/万能板 | 1 | 电路连接 |
| 6 | 杜邦线 | 若干 | 信号与电源连接 |

### (二) 核心硬件介绍

#### 1. Firebeetle 2 ESP32-C5开发板

![ESP32-C5开发板](https://mc.dfrobot.com.cn/data/a ... 5gooxpisise1ee1.png)

**核心特性**:
- **芯片**: 乐鑫ESP32-C5,RISC-V架构
- **主频**: 160MHz
- **内存**: 400KB SRAM
- **无线**: Wi-Fi 6 + Bluetooth 5.3
- **GPIO**: 丰富的数字/模拟IO口
- **PWM**: 16路LEDC PWM通道,适合多舵机控制
- **电源**: 3.3V/5V双路供电,支持USB-C充电

**适用场景**: 低功耗物联网、机器人控制、智能家居

#### 2. NRF24L01无线模块

![NRF24L01模块](https://mc.dfrobot.com.cn/data/a ... 0x26rnl0z6lmtm3.jpg)

**技术参数**:
- **工作频段**: 2.4GHz ISM频段
- **传输速率**: 250Kbps/1Mbps/2Mbps可选
- **通信距离**: 10-30米(开阔环境)
- **接口**: SPI通信
- **工作电压**: 3.3V
- **功耗**: 发射15mA,接收13.5mA

**通信特点**: 支持多通道、自动重传、自动应答,适合低延迟控制应用

#### 3. SG90舵机

**技术参数**:
- **工作电压**: 4.8V-6V(典型5V)
- **转动角度**: 0-180度
- **控制方式**: PWM信号(50Hz,脉宽0.5ms-2.5ms)
- **扭矩**: 1.8kg·cm(5V)
- **响应速度**: 0.12秒/60度
- **信号线定义**:
  - 棕色:GND(-)
  - 红色:VCC(+5V)
  - 橙色:PWM控制信号

**控制原理**:
- 脉宽0.5ms → 0度
- 脉宽1.5ms → 90度
- 脉宽2.5ms → 180度

## 三、硬件接线图

### (一) NRF24L01与ESP32-C5连接

| NRF24L01引脚 | ESP32-C5引脚 | 说明 |
|-------------|-------------|------|
| VCC | 3.3V | 电源(注意:必须3.3V) |
| GND | GND | 地线 |
| CE | GPIO9 | 芯片使能 |
| CSN | GPIO10 | SPI片选 |
| SCK | GPIO6 | SPI时钟 |
| MOSI | GPIO7 | SPI主出从入 |
| MISO | GPIO2 | SPI主入从出 |
| IRQ | 不连接 | 中断引脚(可选) |

**注意事项**:
- NRF24L01只能使用3.3V供电,5V会损坏模块
- CE和CSN引脚在代码中需与接线对应
- 建议在VCC和GND间并联10uF电容,稳定供电

### (二) 五个舵机连接

| 舵机功能 | ESP32-C5引脚 | 说明 |
|---------|-------------|------|
| 舵机1(底座旋转) | GPIO12 | PWM输出 |
| 舵机2(大臂抬升) | GPIO13 | PWM输出 |
| 舵机3(小臂伸缩) | GPIO20 | PWM输出 |
| 舵机4(手腕旋转) | GPIO21 | PWM输出 |
| 舵机5(夹爪开合) | GPIO8 | PWM输出 |

**供电方案**:
```
外部5V电源(≥2A)
    |
    ├─→ 舵机1红线(VCC)
    ├─→ 舵机2红线(VCC)
    ├─→ 舵机3红线(VCC)
    ├─→ 舵机4红线(VCC)
    ├─→ 舵机5红线(VCC)
    └─→ 所有舵机棕线(GND) ←─→ ESP32-C5 GND(共地)
```

**关键要点**:
1. **独立供电**: 舵机必须使用外部5V电源,不可直接从ESP32取电(电流不足)
2. **共地连接**: 外部电源GND必须与ESP32-C5的GND相连
3. **信号线**: 舵机橙色信号线连接到ESP32的PWM引脚
4. **电流考虑**: 5个舵机同时工作电流可达1.5A以上,建议使用2A电源

### (三) 完整接线图

```
┌─────────────────────────────────────────┐
│     Firebeetle 2 ESP32-C5              │
│                                         │
│  GPIO12 ──→ 舵机1(底座)PWM             │
│  GPIO13 ──→ 舵机2(大臂)PWM             │
│  GPIO20 ──→ 舵机3(小臂)PWM             │
│  GPIO21 ──→ 舵机4(手腕)PWM             │
│  GPIO8  ──→ 舵机5(夹爪)PWM             │
│                                         │
│  GPIO9  ──→ NRF24L01 CE                │
│  GPIO10 ──→ NRF24L01 CSN               │
│  GPIO6  ──→ NRF24L01 SCK               │
│  GPIO7  ──→ NRF24L01 MOSI              │
│  GPIO2  ──→ NRF24L01 MISO              │
│                                         │
│  3.3V   ──→ NRF24L01 VCC               │
│  GND    ──→ NRF24L01 GND               │
│         └─→ 外部5V电源 GND(共地)       │
└─────────────────────────────────────────┘

外部5V电源(2A) ──→ 所有舵机VCC(红线)
```

## 四、软件环境配置

### (一) Arduino IDE安装

从[Arduino官网](https://www.arduino.cc/en/software)下载并安装Arduino IDE(建议2.x版本)。

### (二) ESP32核心库安装

#### 1. 添加开发板管理器URL

打开Arduino IDE,依次点击:
- **文件 > 首选项**
- 在"附加开发板管理器网址"中添加:
```
https://jihulab.com/esp-mirror/espressif/arduino-esp32/-/raw/gh-pages/package_esp32_dev_index_cn.json
```

#### 2. 安装ESP32核心库

- 点击**工具 > 开发板 > 开发板管理器**
- 搜索"ESP32"
- 选择"esp32 by Espressif Systems"(版本≥3.3.0)
- 点击"安装"

#### 3. 选择开发板

- 点击**工具 > 开发板 > ESP32 Arduino**
- 选择"**FireBeetle-ESP32-C5**"(如果列表中没有,选择"ESP32C5 Dev Module")

### (三) 安装RF24库

点击**工具 > 管理库**,搜索"RF24",安装"**TMRh20**"维护的RF24库。

## 五、核心代码实现(接收端)

### (一) 头文件与宏定义

```cpp
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <ESP32Servo.h>  // ESP32专用舵机库

// NRF24L01模块引脚定义
#define CE_PIN   9
#define CSN_PIN  10

// 五个舵机控制引脚
#define SERVO1_PIN 12  // 底座旋转
#define SERVO2_PIN 13  // 大臂抬升
#define SERVO3_PIN 20  // 小臂伸缩
#define SERVO4_PIN 21  // 手腕旋转
#define SERVO5_PIN 8   // 夹爪开合

// 无线通信地址(与发射端保持一致)
const uint64_t pipeIn = 0x123456789;

// 初始化NRF24L01对象
RF24 radio(CE_PIN, CSN_PIN);

// 创建5个舵机对象
Servo servo1, servo2, servo3, servo4, servo5;
```

### (二) 数据结构定义

```cpp
// 接收数据结构体(与发射端一致)
struct Signal {
  int ele;   // 通道1:对应舵机1(底座)
  int ail;   // 通道2:对应舵机2(大臂)
  int thr;   // 通道3:对应舵机3(小臂)
  int rud;   // 通道4:对应舵机4(手腕)
  byte aux1; // 通道5:对应舵机5(夹爪)
  byte aux2; // 通道6:预留扩展
};

Signal receivedData;  // 接收数据缓存
unsigned long lastReceiveTime = 0;  // 最后接收时间
const unsigned long timeout = 1000; // 超时阈值(1秒)
```

**设计说明**:
- 数据范围:-512至512(中心值为0)
- 需要映射到舵机角度:0-180度
- aux1用于夹爪控制(两段式:开/合)

### (三) 初始化函数

```cpp
void setup() {
  Serial.begin(115200);
  Serial.println("机械臂接收端初始化...");

  // ===== NRF24L01初始化 =====
  if (!radio.begin()) {
    Serial.println("NRF24L01初始化失败!");
    while (1); // 停止运行
  }
  
  radio.openReadingPipe(1, pipeIn);  // 打开接收管道
  radio.startListening();             // 切换为接收模式
  
  // 优化接收参数
  radio.setDataRate(RF24_250KBPS);    // 低速率,高可靠性
  radio.setPALevel(RF24_PA_HIGH);     // 高功率,增强信号
  radio.setAutoAck(true);             // 启用自动应答
  
  Serial.println("NRF24L01初始化成功!");

  // ===== 舵机初始化 =====
  // 使用ESP32Servo库附加到引脚
  servo1.attach(SERVO1_PIN, 500, 2500);  // 底座
  servo2.attach(SERVO2_PIN, 500, 2500);  // 大臂
  servo3.attach(SERVO3_PIN, 500, 2500);  // 小臂
  servo4.attach(SERVO4_PIN, 500, 2500);  // 手腕
  servo5.attach(SERVO5_PIN, 500, 2500);  // 夹爪
  
  // 设置初始位置(所有舵机90度中位)
  servo1.write(90);
  servo2.write(90);
  servo3.write(90);
  servo4.write(90);
  servo5.write(90);
  
  Serial.println("舵机初始化完成!");
  delay(1000); // 等待舵机到位
}
```

**关键参数说明**:
- `attach(pin, min, max)`: min和max是脉宽范围(微秒)
  - 500us → 0度
  - 2500us → 180度
  - 根据舵机型号可能需要调整

### (四) 核心控制函数

#### 1. 数据映射函数

```cpp
// 将遥控器数据(-512~512)映射到舵机角度(0~180)
int mapToServoAngle(int value) {
  // value范围: -512 到 512
  // 输出范围: 0 到 180
  return map(value, -512, 512, 0, 180);
}

// 将两段开关数据映射到舵机角度(夹爪专用)
int mapAuxToServo(byte value) {
  // -512 → 30度(夹爪打开)
  // 512 → 150度(夹爪闭合)
  return (value == -512) ? 30 : 150;
}
```

#### 2. 舵机控制函数

```cpp
void controlServos() {
  // 将接收数据映射到舵机角度
  int angle1 = mapToServoAngle(receivedData.ele);  // 底座
  int angle2 = mapToServoAngle(receivedData.ail);  // 大臂
  int angle3 = mapToServoAngle(receivedData.thr);  // 小臂
  int angle4 = mapToServoAngle(receivedData.rud);  // 手腕
  int angle5 = mapAuxToServo(receivedData.aux1);   // 夹爪
  
  // 限制角度范围(安全保护)
  angle1 = constrain(angle1, 0, 180);
  angle2 = constrain(angle2, 0, 180);
  angle3 = constrain(angle3, 0, 180);
  angle4 = constrain(angle4, 0, 180);
  angle5 = constrain(angle5, 0, 180);
  
  // 写入舵机角度
  servo1.write(angle1);
  servo2.write(angle2);
  servo3.write(angle3);
  servo4.write(angle4);
  servo5.write(angle5);
  
  // 调试输出
  Serial.print("舵机角度 -> ");
  Serial.print("1:"); Serial.print(angle1);
  Serial.print(" 2:"); Serial.print(angle2);
  Serial.print(" 3:"); Serial.print(angle3);
  Serial.print(" 4:"); Serial.print(angle4);
  Serial.print(" 5:"); Serial.println(angle5);
}
```

### (五) 主循环函数

```cpp
void loop() {
  // 检查是否有数据可读
  if (radio.available()) {
    // 读取数据
    radio.read(&receivedData, sizeof(Signal));
    lastReceiveTime = millis();  // 更新最后接收时间
   
    // 控制舵机
    controlServos();
   
  } else {
    // 检查通信超时
    if (millis() - lastReceiveTime > timeout) {
      // 超时保护:所有舵机回到安全位置
      Serial.println("警告:通信超时,进入安全模式");
      servo1.write(90);
      servo2.write(90);
      servo3.write(90);
      servo4.write(90);
      servo5.write(30);  // 夹爪打开
      delay(100);
    }
  }
  
  delay(20);  // 50Hz控制频率
}
```

**安全机制说明**:
- **超时检测**: 超过1秒未接收到数据,认为通信中断
- **安全位置**: 所有舵机回到90度中位,夹爪打开
- **防止误动作**: 避免信号丢失时机械臂失控

## 六、完整代码

```cpp
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <ESP32Servo.h>

// ===== 引脚定义 =====
#define CE_PIN   9
#define CSN_PIN  10
#define SERVO1_PIN 12  // 底座旋转
#define SERVO2_PIN 13  // 大臂抬升
#define SERVO3_PIN 20  // 小臂伸缩
#define SERVO4_PIN 21  // 手腕旋转
#define SERVO5_PIN 8   // 夹爪开合

// ===== 全局对象 =====
const uint64_t pipeIn = 0x123456789;
RF24 radio(CE_PIN, CSN_PIN);
Servo servo1, servo2, servo3, servo4, servo5;

// ===== 数据结构 =====
struct Signal {
  int ele;
  int ail;
  int thr;
  int rud;
  byte aux1;
  byte aux2;
};
Signal receivedData;
unsigned long lastReceiveTime = 0;
const unsigned long timeout = 1000;

// ===== 辅助函数 =====
int mapToServoAngle(int value) {
  return map(value, -512, 512, 0, 180);
}

int mapAuxToServo(byte value) {
  return (value == -512) ? 30 : 150;
}

void controlServos() {
  int angle1 = constrain(mapToServoAngle(receivedData.ele), 0, 180);
  int angle2 = constrain(mapToServoAngle(receivedData.ail), 0, 180);
  int angle3 = constrain(mapToServoAngle(receivedData.thr), 0, 180);
  int angle4 = constrain(mapToServoAngle(receivedData.rud), 0, 180);
  int angle5 = constrain(mapAuxToServo(receivedData.aux1), 0, 180);
  
  servo1.write(angle1);
  servo2.write(angle2);
  servo3.write(angle3);
  servo4.write(angle4);
  servo5.write(angle5);
  
  Serial.print("舵机角度 -> ");
  Serial.print("1:"); Serial.print(angle1);
  Serial.print(" 2:"); Serial.print(angle2);
  Serial.print(" 3:"); Serial.print(angle3);
  Serial.print(" 4:"); Serial.print(angle4);
  Serial.print(" 5:"); Serial.println(angle5);
}

// ===== 初始化 =====
void setup() {
  Serial.begin(115200);
  Serial.println("机械臂接收端初始化...");

  if (!radio.begin()) {
    Serial.println("NRF24L01初始化失败!");
    while (1);
  }
  
  radio.openReadingPipe(1, pipeIn);
  radio.startListening();
  radio.setDataRate(RF24_250KBPS);
  radio.setPALevel(RF24_PA_HIGH);
  radio.setAutoAck(true);
  Serial.println("NRF24L01初始化成功!");

  servo1.attach(SERVO1_PIN, 500, 2500);
  servo2.attach(SERVO2_PIN, 500, 2500);
  servo3.attach(SERVO3_PIN, 500, 2500);
  servo4.attach(SERVO4_PIN, 500, 2500);
  servo5.attach(SERVO5_PIN, 500, 2500);
  
  servo1.write(90);
  servo2.write(90);
  servo3.write(90);
  servo4.write(90);
  servo5.write(90);
  
  Serial.println("舵机初始化完成!");
  delay(1000);
}

// ===== 主循环 =====
void loop() {
  if (radio.available()) {
    radio.read(&receivedData, sizeof(Signal));
    lastReceiveTime = millis();
    controlServos();
  } else {
    if (millis() - lastReceiveTime > timeout) {
      Serial.println("警告:通信超时,进入安全模式");
      servo1.write(90);
      servo2.write(90);
      servo3.write(90);
      servo4.write(90);
      servo5.write(30);
      delay(100);
    }
  }
  
  delay(20);
}
```



您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4 备案 沪公网安备31011502402448

© 2013-2025 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail