// Рука Port на ESP32 с панелью GyverLibs/Settings (площадка «Полка с кубиком»).
//
// Три привода прямо на выводах платы (библиотека ESP32Servo):
// S0 плечо ← D25 (write(60) — рука вниз, больше — вперёд-вверх),
// S1 локоть ← D26 (write(0) — прямой, больше — согнут),
// S2 пальцы ← D27 (write(0) — ладонь, 90 — кулак).
//
// Кнопка «Веб-интерфейс» над схемой открывает панель: три ползунка ведут руку
// вручную, кнопка «Взять кубик» проигрывает те же позы, что рука из примера
// arm_pick.ino, а «Разжать» отпускает кубик. Хват в модели: пальцы сжаты
// сильнее 60° и кубик у ладони — он держится в кисти; меньше 30° — отпускает.
#include <WiFi.h>
#include <LittleFS.h>
#include <ESP32Servo.h>
#include <SettingsESP.h>
// --- сеть -------------------------------------------------------------
// В модели сеть PinPort-GUEST есть всегда. Не подключилась за 15 секунд —
// плата поднимает свою точку доступа «Arm».
const char *SSID = "PinPort-GUEST", *PASSWORD = "";
const char *AP_SSID = "Arm", *AP_PASSWORD = "robotarm1";
// --- рука --------------------------------------------------------------
const byte PIN_SHOULDER = 25, PIN_ELBOW = 26, PIN_GRIP = 27;
Servo shoulder, elbow, grip;
// Углы привода (write), которые задаёт страница.
uint8_t angShoulder = 60, angElbow = 0, angGrip = 0;
// Время (с), плечо, локоть, пальцы - градусы от «прямо» (как в arm_pick.ino).
const float KEYS[][4] = {
{0, 0, 0, 0}, {1, 0, 0, 0},
{2, 0, 70, 0}, // локоть вверх - кисть к груди
{3, 10, 70, 0}, // кисть над кубиком
{4.5, 20, 30, 0}, // опустить на кубик
{5.3, 20, 30, 90}, // сжать пальцы
{7, 30, 70, 90}, // поднять
};
const uint8_t NKEYS = sizeof(KEYS) / sizeof(KEYS[0]);
float seqAt = -1; // когда началась последовательность, с; -1 - не идёт
SettingsESP sett("Рука Port");
enum : size_t { W_STATE = 1 };
float smooth(float x) { x = x < 0 ? 0 : (x > 1 ? 1 : x); return x * x * (3 - 2 * x); }
String stateText() {
String s = seqAt >= 0 ? "берёт кубик" : "вручную";
return s + ": плечо " + String((int)angShoulder) + "°, локоть " + String((int)angElbow) +
"°, пальцы " + String((int)angGrip) + "°";
}
// build() зовётся и чтобы нарисовать страницу, и чтобы применить действие с
// неё: тронутый виджет возвращает true и уже записал значение по указателю.
void build(sets::Builder &b) {
if (b.beginGroup("Рука")) {
b.Slider("Плечо", 0, 180, 1, "°", &angShoulder);
b.Slider("Локоть", 0, 180, 1, "°", &angElbow);
b.Slider("Пальцы", 0, 90, 1, "°", &angGrip);
b.endGroup();
}
if (b.beginGroup("Действия")) {
if (b.beginButtons()) {
if (b.Button("Взять кубик", sets::Colors::Aqua) && seqAt < 0) seqAt = millis() / 1000.0;
if (b.Button("Разжать", sets::Colors::Red)) { seqAt = -1; angGrip = 0; }
b.endButtons();
}
b.endGroup();
}
if (b.beginGroup("Состояние")) {
b.Label(W_STATE, "Рука", stateText());
b.endGroup();
}
}
// Раз в секунду страница спрашивает, что поменялось.
void update(sets::Updater &u) {
u.update(W_STATE, stateText());
}
void startNetwork() {
WiFi.mode(WIFI_STA);
WiFi.begin(SSID, PASSWORD);
unsigned long t0 = millis();
while (WiFi.status() != WL_CONNECTED && millis() - t0 < 15000) delay(250);
String ip;
if (WiFi.status() == WL_CONNECTED) {
ip = WiFi.localIP().toString();
} else {
WiFi.mode(WIFI_AP);
WiFi.softAP(AP_SSID, AP_PASSWORD);
ip = WiFi.softAPIP().toString();
}
if (!LittleFS.begin(true)) Serial.println("LittleFS не поднялась");
sett.begin();
sett.onBuild(build);
sett.onUpdate(update);
sett.setUpdatePeriod(1000);
Serial.printf("Рука: http://%s/\n", ip.c_str());
}
void setup() {
Serial.begin(115200);
shoulder.attach(PIN_SHOULDER, 500, 2500);
elbow.attach(PIN_ELBOW, 500, 2500);
grip.attach(PIN_GRIP, 500, 2500);
startNetwork();
}
void loop() {
sett.tick();
// Последовательность «Взять кубик»: между ключевыми позами углы меняются
// плавно. Закончилась - ползунки остаются на последней позе.
if (seqAt >= 0) {
float t = millis() / 1000.0 - seqAt;
if (t >= KEYS[NKEYS - 1][0]) {
angShoulder = 60 + KEYS[NKEYS - 1][1];
angElbow = KEYS[NKEYS - 1][2];
angGrip = KEYS[NKEYS - 1][3];
seqAt = -1;
Serial.println("кубик взят");
} else {
for (uint8_t i = 0; i + 1 < NKEYS; i++) {
if (t < KEYS[i + 1][0]) {
float u = smooth((t - KEYS[i][0]) / (KEYS[i + 1][0] - KEYS[i][0]));
angShoulder = 60 + KEYS[i][1] + (KEYS[i + 1][1] - KEYS[i][1]) * u;
angElbow = KEYS[i][2] + (KEYS[i + 1][2] - KEYS[i][2]) * u;
angGrip = KEYS[i][3] + (KEYS[i + 1][3] - KEYS[i][3]) * u;
break;
}
}
}
}
shoulder.write(angShoulder);
elbow.write(angElbow);
grip.write(angGrip);
// Между кадрами ядро спит в delay(): эмулятор проскакивает сон, не считая
// его по такту.
delay(10);
}
Рука Port: ползунки на GyverLibs/Settings
Рука Port на ESP32 ведётся со страницы Settings: ползунки плеча, локтя и пальцев, кнопки «Взять кубик» и «Разжать». Страница открывается кнопкой «Веб-интерфейс».
Что на схеме
Плата ESP32 DevKit v1, к ней подключены:
- Рука Port —
r1, площадка «Полка с кубиком»
Задействованы выводы платы: GND, D25, D26, D27. Всего соединений на схеме: 4.
Как смотреть
Нажмите ▶ Запустить — после сборки откроется окно робота (потом его открывает кнопка 🤖 над схемой). Мышью камеру можно повернуть, колесом — приблизить; щелчок по суставу показывает номер привода, канал PCA9685 и строку кода для него. Кнопка ⏺ над схемой записывает сцену в GIF — с теми поворотами камеры, что вы делаете.
Робот — не анимация, а модель с массами, шарнирами и трением: скетч только шлёт импульсы приводам и читает датчики, а встанет робот, пойдёт или упадёт — решает физика. Как она устроена — на странице «Физика сцен с роботами».
Как это работает
Три привода рука получает прямо с выводов ESP32 через ESP32Servo (attach(пин, 500, 2500), как Servo на Uno в примере «Рука Port берёт кубик»), на выводах D25, D26 и D27. Скетч хранит три угла и в build() перечисляет, что стоит на странице: три Slider и две Button. Библиотека Settings рисует страницу и записывает новое значение по указателю, а loop() просто шлёт углы приводам.
Откройте панель кнопкой «Веб-интерфейс». Ползунки ведут руку вручную: плечо 60° - рука вниз, больше - вперёд-вверх; локоть 0° - прямой; пальцы 0° - ладонь, 90° - кулак. «Взять кубик» проигрывает те же позы, что пример на Uno, «Разжать» отпускает кубик.
Что попробовать
- Возьмите кубик вручную: опустите кисть на кубик ползунками, сожмите пальцы больше чем на 60°, поднимите.
- Добавьте ползунок скорости последовательности (время поз в
KEYSсейчас константа).
Между кадрами скетч спит в delay(10): сон эмулятор ESP32 проскакивает, не считая по такту.
Библиотеки
Пример собирается с библиотеками:
- WiFi
- LittleFS
- ESP32Servo
- Settings
Они уже установлены на сервере сборки — отмечать их в проекте вручную не нужно, список приезжает вместе с примером.
Как это устроено в коде
Основное, чем пользуется скетч:
Servo— управление сервоприводом по длительности импульсаmillis()— миллисекунды с запуска: на них строят работу без остановкиWiFi.begin()— подключается к точке доступа виртуального Wi-Fidelay()— пауза, на время которой ядро занято ожиданиемWiFi.softAP()— поднимает собственную точку доступаSerial.println()— печатает строку в монитор портаSerial.printf()— печатает в монитор порта с форматированиемSerial.begin()— открывает монитор порта на заданной скорости
В скетче 145 строк; свои функции: stateText(), build(), update(), startNetwork(); подключено заголовков: 4. Сам скетч открыт на соседней вкладке — его можно менять и запускать заново.
Что попробовать изменить
- Поменяйте период в константе и посмотрите на анализаторе, как меняется картина на выводе.
- Проверьте, что делает скетч, когда сеть недоступна: обрыв связи — обычное состояние, а не исключение.
- Добавьте свои
Serial.println()в интересных местах — монитор порта здесь и есть отладчик. - Перенесите деталь на другой вывод — на схеме и в коде — и убедитесь, что пример работает так же.
Смотрите также
- Робот-собака Pin: пульт на GyverLibs/Settings — Pin на ESP32 управляется со страницы Settings: переключатель «Идти», ползунки шага и времени полшага, поворот и кнопка «…
- Робот Port идёт и держит равновесие — Человекоподобный робот Port (1,75 м, 50 кг) идёт по ровному полу: походка без остановок, равновесие и курс — по датчику…
- Робот Port поднимается по лестнице — Port поднимается на четыре ступени приставным шагом: места стоп — по плану лестницы, корпус — по расчёту ZMP с предуправ…
- Робот Port проходит лабиринт — Port идёт по коридорам и смотрит дальномером в голове: у стены поворачивает направо, если там занято — налево, и держитс…
- Робот Port доходит до стены и останавливается — Ходьба с дальномером: Port идёт к стене, ближе 60 см переходит на приставной шаг и останавливается.
- Робот Port обходит столб — Port видит столб дальномером, осматривается головой влево и вправо, обходит его с той стороны, где свободнее, и возвраща…
Сборка ещё не запускалась.
Печатайте в Serial числа — они станут графиком.
Serial.println(t); — один ряд,
Serial.printf("t:%.1f h:%.1f\n", t, h); — два с подписями.
Формат тот же, что понимает плоттер Arduino IDE.