Сервопривід з веб-повзунком: плавне керування без бібліотек
Керуємо сервоприводом SG90/MG996R з телефона: тягнете повзунок — вал повертається в реальному часі. ШІМ генеруємо вбудованим модулем LEDC, без сторонніх бібліотек.
Ідея
Сервопривід — основа безлічі саморобок: годівниця для кота, поворотна камера, привід жалюзі, заслінка вентиляції. Ним керують імпульсами ШІМ частотою 50 Гц: ширина імпульсу ~0.5 мс — це 0°, ~2.4 мс — 180°.
У ESP32 є апаратний модуль LEDC, який генерує такий сигнал без навантаження на процесор. Тож обійдемося без бібліотек — лише ядро arduino-esp32 3.x.
Компоненти
- ESP32 DevKit
- Сервопривід SG90 (малий) або MG996R (потужний, металеві шестерні)
- Для MG996R — окреме живлення 5–6 В ≥ 2 А
Підключення
| Сервопривід | ESP32 |
|---|---|
| Коричневий / чорний (GND) | GND |
| Червоний (+5 V) | 5V (VIN) — лише для SG90 |
| Помаранчевий / жовтий (сигнал) | GPIO 13 |
Потужний сервопривід при старті споживає піковий струм понад 1 А, і ESP32 перезавантажуватиметься. Живіть його окремо, об'єднавши землі. Конденсатор 470–1000 мкФ біля сервоприводу теж допоможе.
Код
#include <WiFi.h>
#include <WebServer.h>
const char* WIFI_SSID = "YOUR_SSID";
const char* WIFI_PASS = "YOUR_PASSWORD";
const int SERVO_PIN = 13;
const int PWM_FREQ = 50; // 50 Гц → період 20 мс
const int PWM_BITS = 14; // роздільна здатність 0..16383
const int MIN_US = 500; // імпульс для 0°
const int MAX_US = 2400; // імпульс для 180°
WebServer server(80);
int currentAngle = 90;
void writeAngle(int angle) {
angle = constrain(angle, 0, 180);
int us = map(angle, 0, 180, MIN_US, MAX_US);
uint32_t duty = (uint32_t)us * ((1 << PWM_BITS) - 1) / 20000;
ledcWrite(SERVO_PIN, duty);
currentAngle = angle;
}
const char PAGE[] = R"rawliteral(
<!doctype html><html><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Servo</title>
<style>
body{font-family:system-ui;text-align:center;margin:40px 16px}
input{width:100%;max-width:420px;height:40px}
#v{font-size:3rem;font-weight:700}
</style></head><body>
<h2>Servo</h2>
<div id="v">--°</div>
<input id="s" type="range" min="0" max="180">
<script>
const s = document.getElementById('s'), v = document.getElementById('v');
let busy = false, pending = null;
async function send(angle) {
if (busy) { pending = angle; return; }
busy = true;
await fetch('/angle?value=' + angle);
busy = false;
if (pending !== null) { const a = pending; pending = null; send(a); }
}
s.oninput = () => { v.textContent = s.value + '°'; send(s.value); };
fetch('/angle').then(r => r.text()).then(a => { s.value = a; v.textContent = a + '°'; });
</script></body></html>
)rawliteral";
void setup() {
Serial.begin(115200);
ledcAttach(SERVO_PIN, PWM_FREQ, PWM_BITS); // API ядра 3.x
writeAngle(90);
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) delay(500);
Serial.println(WiFi.localIP());
server.on("/", [] { server.send(200, "text/html", PAGE); });
server.on("/angle", [] {
if (server.hasArg("value")) writeAngle(server.arg("value").toInt());
server.send(200, "text/plain", String(currentAngle));
});
server.begin();
}
void loop() {
server.handleClient();
}
Як це працює
ledcAttach()налаштовує апаратний ШІМ на пін: 50 Гц, 14 біт.writeAngle()перетворює кут на ширину імпульсу в мікросекундах, а її — на «заповнення» (duty) від 0 до 16383.- JavaScript на сторінці надсилає новий кут під час руху повзунка, але не частіше, ніж плата встигає відповідати: поки запит у дорозі, запам'ятовується лише останнє значення.
Калібрування
Сервоприводи різних виробників мають різний діапазон. Якщо на краях вал «дзижчить» і впирається — звузьте MIN_US/MAX_US (наприклад, 600 і 2300). Якщо не доходить до 180° — розширте.
Ядро 2.x
У старих версіях ядра замість ledcAttach використовувались канали:
ledcSetup(0, PWM_FREQ, PWM_BITS);
ledcAttachPin(SERVO_PIN, 0);
ledcWrite(0, duty); // пишемо в канал, а не в пін
Ідеї
- Годівниця: за розкладом (NTP) поворот на 90° і назад — порція корму висипається.
- Pan-tilt для ESP32-CAM: два сервоприводи й два повзунки.
- Плавний рух: змінюйте кут по 1° кожні 15 мс замість різкого стрибка.