以下是完整的 ESP32双舵机无线控制系统 解决方案,包含硬件清单、接线图和完整代码:
硬件清单
组件名称 | 规格参数 | 数量 | 单价参考 | 购买链接示例 |
---|---|---|---|---|
ESP32开发板 | ESP32-WROOM-32D | 2 | ¥28 | [某宝链接] |
双轴摇杆模块 | X/Y轴模拟量输出 | 1 | ¥5 | [某宝链接] |
SG90微型舵机 | 4.8-6V/180°旋转 | 2 | ¥8 | [某宝链接] |
轻触按钮开关 | 6×6×5mm 四脚 | 2 | ¥0.5 | [某宝链接] |
10KΩ电阻 | 1/4W 碳膜电阻 | 4 | ¥0.1 | [某宝链接] |
100nF陶瓷电容 | 0603封装 | 2 | ¥0.2 | [某宝链接] |
5V电源模块 | AMS1117-5.0 稳压模块 | 1 | ¥3 | [某宝链接] |
杜邦线 | 20cm 公对公/母 | 1包 | ¥5 | [某宝链接] |
系统接线图
发送端(控制器)
┌──────────────┐ ┌──────────────┐
│ ESP32 │ │ 摇杆模块 │
│ │ │ │
│ GPIO34 ─────┼───────┤ X轴(ADC1_CH6)│
│ GPIO35 ─────┼───────┤ Y轴(ADC1_CH7)│
│ GPIO25 ─┬───┘ ├───── 3.3V │
│ │10KΩ ├───── GND │
│ GPIO26 ─┼───┬───┐ └──────────────┘
│ │ │ │
│ GND ────┴─┐ │ │ ┌──────────────┐
│ │ │ └───┤ PWM按钮 │
│ └─┴───────┤ 复位按钮 │
└──────────────┘ └──────────────┘
接收端(执行器)
┌──────────────┐ ┌──────────────┐
│ ESP32 │ │ SG90舵机1 │
│ │ │ │
│ GPIO13 ─────┼───────┤ 信号线(黄) │
│ GPIO14 ─────┼───────┤ 信号线(黄) │
│ GPIO12 ─────┼───┐ ├───── 5V │
│ GPIO27 ───┐ │ │ ├───── GND │
│ │ │ │ └──────────────┘
│ │ │ │
│ │ │ │ ┌──────────────┐
│ │ │ └───┤ PWM输出接口 │
│ │ │ │ (可接电机/LED)│
│ │ └───────┤ 状态指示灯LED│
│ GND ──────┴───┬─────┼───── GND │
│ │ └──────────────┘
│ Vin(5V) ──────┘
└──────────────┘
完整代码实现
发送端代码(Controller)
#include <WiFi.h>
#include <esp_now.h>
// 接收端MAC地址(烧录后通过串口获取)
uint8_t receiverMac[] = {0xXX,0xXX,0xXX,0xXX,0xXX,0xXX};
typedef struct {
int16_t joyX;
int16_t joyY;
bool pwmCmd;
bool resetCmd;
} ControlData;
#define BTN_PWM 25
#define BTN_RESET 26
#define DEBOUNCE_TIME 50
void setup() {
pinMode(BTN_PWM, INPUT_PULLUP);
pinMode(BTN_RESET, INPUT_PULLUP);
Serial.begin(115200);
WiFi.mode(WIFI_STA);
if (esp_now_init() != ESP_OK) {
Serial.println("ESP-NOW初始化失败");
while(1);
}
esp_now_peer_info_t peerInfo;
memcpy(peerInfo.peer_addr, receiverMac, 6);
peerInfo.channel = 0;
peerInfo.encrypt = false;
esp_now_add_peer(&peerInfo);
analogReadResolution(12);
analogSetAttenuation(ADC_11db);
}
bool debounce(int pin) {
static unsigned long lastTime[2] = {0};
uint8_t idx = (pin == BTN_PWM) ? 0 : 1;
if (digitalRead(pin) == HIGH) return false;
if (millis() - lastTime[idx] < DEBOUNCE_TIME) return false;
lastTime[idx] = millis();
return true;
}
void loop() {
static bool pwmState = false;
ControlData data;
// 读取摇杆
data.joyX = analogRead(34) - 2048;
data.joyY = analogRead(35) - 2048;
// 处理按钮
data.pwmCmd = debounce(BTN_PWM) ? !pwmState : pwmState;
data.resetCmd = debounce(BTN_RESET);
// 更新状态
if (data.pwmCmd != pwmState) {
pwmState = data.pwmCmd;
Serial.println(pwmState ? "PWM启用" : "PWM关闭");
}
esp_err_t result = esp_now_send(receiverMac, (uint8_t*)&data, sizeof(data));
if (result != ESP_OK) {
Serial.println("发送失败,尝试重连...");
esp_now_del_peer(receiverMac);
esp_now_add_peer(&peerInfo);
}
delay(20);
}
接收端代码(Receiver)
#include <WiFi.h>
#include <esp_now.h>
#include <ESP32Servo.h>
#define SERVO_X 13
#define SERVO_Y 14
#define PWM_OUT 12
#define LED_STATUS 27
Servo servoX, servoY;
bool pwmActive = false;
typedef struct {
int16_t joyX;
int16_t joyY;
bool pwmCmd;
bool resetCmd;
} ControlData;
void setup() {
pinMode(LED_STATUS, OUTPUT);
// PWM通道配置
ledcSetup(0, 5000, 8); // 通道0, 5kHz, 8bit
ledcAttachPin(PWM_OUT, 0);
ledcWrite(0, 0);
// 舵机初始化
servoX.attach(SERVO_X);
servoY.attach(SERVO_Y);
servoX.write(90);
servoY.write(90);
WiFi.mode(WIFI_STA);
if (esp_now_init() != ESP_OK) {
Serial.println("ESP-NOW初始化失败");
while(1);
}
esp_now_register_recv_cb([](const uint8_t *mac, const uint8_t *data, int len) {
ControlData cmd;
memcpy(&cmd, data, sizeof(cmd));
// 处理复位指令
if (cmd.resetCmd) {
servoX.write(90);
servoY.write(90);
return;
}
// 更新PWM状态
if (pwmActive != cmd.pwmCmd) {
pwmActive = cmd.pwmCmd;
ledcWrite(0, pwmActive ? 128 : 0); // 50%占空比
digitalWrite(LED_STATUS, pwmActive);
}
// 处理舵机控制
int angleX = map(cmd.joyX, -2048, 2048, 0, 180);
int angleY = map(cmd.joyY, -2048, 2048, 0, 180);
servoX.write(constrain(angleX, 0, 180));
servoY.write(constrain(angleY, 0, 180));
});
}
void loop() {
// 保持连接状态检测
static unsigned long lastMsg = 0;
if (millis() - lastMsg > 5000) {
Serial.println("等待控制信号...");
lastMsg = millis();
}
delay(100);
}
系统调试指南
首次上电步骤
- 获取MAC地址:
- 烧录接收端代码后打开串口监视器
- 记录显示的MAC地址并填入发送端代码
- 舵机校准:
# 通过串口发送校准命令 echo "CALIBRATE" > /dev/ttyUSB0
- 信号测试:
- 使用万用表测量PWM_OUT引脚电压
- PWM关闭时应为0V,开启时应有1.65V(50%占空比)
常见问题排查
现象 | 可能原因 | 解决方法 |
---|---|---|
舵机抖动 | 电源功率不足 | 外接5V 2A电源 |
按钮响应延迟 | 去抖时间设置过长 | 调整DEBOUNCE_TIME参数 |
PWM输出不稳定 | GPIO12被默认配置为MTDI | 在setup()添加pinMode(12, OUTPUT) |
进阶优化建议
- 硬件增强:
- 在PWM输出端添加MOSFET驱动电路(如IRF540N)
- 为摇杆模块添加RC滤波电路(10kΩ+100nF)
- 软件升级:
// 添加运动轨迹规划 void smoothMove(Servo &s, int target) { const int step = 2; int current = s.read(); while(abs(current - target) > step) { current += (target > current) ? step : -step; s.write(current); delay(20); } s.write(target); }
- 安全保护:
// 添加温度保护 #ifdef TEMP_SENSOR void checkTemperature() { if(temperatureRead() > 60.0) { ledcWrite(0, 0); servoX.detach(); servoY.detach(); } } #endif
本方案可实现100米级可靠控制(开放环境),如需更远距离建议:
- 更换高增益天线
- 使用NRF24L01+PA/LNA模块
- 添加中继节点