// Эталонный скетч: робот-собака идёт рысью.
//
// Рысь: диагональные лапы (передняя левая + задняя правая, передняя правая +
// задняя левая) шагают парами. Пока одна пара переносится вперёд по дуге,
// другая стоит и толкает корпус. Положение лапы задаётся точкой под осью
// бедра, углы считает обратная кинематика (бедро и голень по 21 см, колено
// назад — голень уходит вперёд).
//
// Привод: PCA9685 по I2C (0x40). Каналы: 0–2 передняя левая, 3–5 передняя
// правая, 6–8 задняя левая, 9–11 задняя правая — отведение, бедро, колено.
// Значение «прямо» для write(): отведение и бедро 90, колено 0.
// Курс держит гироскоп MPU6050 (0x68): левые и правые лапы шагают чуть
// по-разному, пока курс не выровняется. TURN — поворот (разница шага).
#include <Wire.h>
#include <math.h>
const uint8_t PCA = 0x40, MPU = 0x68;
const float L1 = 0.21, L2 = 0.21; // бедро, голень, м
const float H = 0.28; // высота оси бедра над стопой при ходьбе, м
const float H0 = 0.4195; // ноги почти прямые (как собака стоит при включении)
const float STEP = 0.10; // длина шага, м
const float LIFT = 0.05; // подъём лапы, м
const float T_STEP = 0.25; // полшага рыси (перенос одной пары), с
const float T_STAND = 1.0; // встать, с
const float T_START = 1.5; // начать идти, с
const float TURN = 0; // поворот: разница шага левых и правых лап, м (+ влево)
const float K_HEAD = 0.002; // выравнивание курса: м разницы шага на градус ошибки
const float ZERO[3] = {90, 90, 0}; // write() при прямом суставе: отведение, бедро, колено
const float R = 180.0 / M_PI;
float smooth(float x) { x = x < 0 ? 0 : (x > 1 ? 1 : x); return x * x * (3 - 2 * x); }
void reg(uint8_t dev, uint8_t r, uint8_t v) {
Wire.beginTransmission(dev); Wire.write(r); Wire.write(v); Wire.endTransmission();
}
void setJoint(uint8_t ch, uint8_t kind, float deg) {
float w = ZERO[kind] + deg;
if (w < 0) w = 0;
if (w > 180) w = 180;
uint16_t us = 500 + (uint16_t)(w * (2000.0 / 180.0));
uint16_t off = (uint32_t)us * 4096UL / 19988UL;
Wire.beginTransmission(PCA);
Wire.write(0x06 + 4 * ch);
Wire.write(0); Wire.write(0); Wire.write(off & 0xff); Wire.write(off >> 8);
Wire.endTransmission();
}
// Лапа: стопа относительно оси бедра — fh вниз, fz вперёд (м).
void paw(uint8_t c0, float fh, float fz) {
float d = sqrt(fh * fh + fz * fz);
if (d > L1 + L2 - 0.0005) d = L1 + L2 - 0.0005;
float k = M_PI - acos((L1 * L1 + L2 * L2 - d * d) / (2 * L1 * L2));
float th = atan2(fz, fh) - acos((L1 * L1 + d * d - L2 * L2) / (2 * L1 * d));
setJoint(c0 + 0, 0, 0);
setJoint(c0 + 1, 1, th * R);
setJoint(c0 + 2, 2, k * R);
}
// --- курс по гироскопу -------------------------------------------------------
float heading = 0; // градусы, + влево
unsigned long lastImu = 0;
void readImu() {
Wire.beginTransmission(MPU); Wire.write(0x47); Wire.endTransmission(false);
Wire.requestFrom(MPU, (uint8_t)2);
int16_t gz = (Wire.read() << 8) | Wire.read();
unsigned long now = micros();
if (lastImu) heading += gz / 131.0 * (now - lastImu) * 1e-6;
lastImu = now;
}
void setup() {
Wire.begin();
Wire.setClock(400000);
reg(PCA, 0x00, 0x10);
reg(PCA, 0xFE, 121); // 50 Гц
reg(PCA, 0x00, 0x20);
delay(5);
reg(PCA, 0x00, 0xA0);
reg(MPU, 0x6B, 0);
}
void loop() {
readImu();
float t = millis() / 1000.0;
float h = H0 + (H - H0) * smooth(t / T_STAND);
float s = t - T_START;
// Диагональные пары: 0 — передняя левая и задняя правая, 1 — две другие.
const uint8_t pair[4] = {0, 1, 1, 0};
float ramp = s > 0 ? min(1.0, s / 1.5) : 0;
// Разница шага левых и правых лап: + — левые короче, поворот влево.
// Прямо (TURN = 0) — курс держит гироскоп: увело влево — шагаем вправо.
float diff = TURN != 0 ? TURN : constrain(-K_HEAD * heading, -0.03, 0.03);
for (uint8_t l = 0; l < 4; l++) {
float fz = 0, lift = 0;
if (s > 0) {
float ph = fmod(s / T_STEP, 2.0);
float mine = pair[l] == 0 ? ph : fmod(ph + 1, 2.0);
float L = (STEP - (l % 2 == 0 ? diff : -diff)) * ramp;
if (mine < 1) { // перенос: сзади вперёд по дуге
fz = -L / 2 + L * smooth(mine);
lift = LIFT * ramp * sin(M_PI * mine);
} else { // опора: спереди назад — толкает корпус
fz = L / 2 - L * (mine - 1);
}
}
paw(l * 3, h - lift, fz);
}
}
Робот-собака Pin идёт рысью
Четвероногий робот Pin (10 кг, 12 приводов) идёт рысью: диагональные лапы шагают парами, курс держит гироскоп.
Что на схеме
Плата Arduino Uno, к ней подключены:
- Робот-собака Pin —
r1, площадка «Ровный пол»
Задействованы выводы платы: GND, A4, A5. Всего соединений на схеме: 3.
Как смотреть
Нажмите ▶ Запустить — после сборки откроется окно робота (потом его открывает кнопка 🤖 над схемой). Мышью камеру можно повернуть, колесом — приблизить; щелчок по суставу показывает номер привода, канал PCA9685 и строку кода для него. Кнопка ⏺ над схемой записывает сцену в GIF — с теми поворотами камеры, что вы делаете.
Робот — не анимация, а модель с массами, шарнирами и трением: скетч только шлёт импульсы приводам и читает датчики, а встанет робот, пойдёт или упадёт — решает физика. Как она устроена — на странице «Физика сцен с роботами».
Как это работает
На каждой лапе три привода: отведение в сторону, бедро и колено (колено «назад», как у собаки). Положение лапы задаётся точкой под осью бедра, углы считает обратная кинематика. Пока одна диагональная пара переносится вперёд по дуге, другая стоит и толкает корпус. Курс держит гироскоп MPU6050: левые и правые лапы шагают чуть по-разному, пока курс не выровняется.
Что попробовать
TURN = 0.02— поворот (разница шага левых и правых лап).- На площадке «Мяч» Pin гонит мяч перед собой.
Библиотеки
Дополнительных библиотек не нужно: всё делается ядром Arduino AVR, которое уже стоит в сборочном окружении.
Как это устроено в коде
Основное, чем пользуется скетч:
MPU6050— акселерометр и гироскоп по I²Cmicros()— микросекунды с запуска, для коротких интерваловWire.begin()— поднимает шину I²C, по ней говорят датчики и дисплеиdelay()— пауза, на время которой ядро занято ожиданиемmillis()— миллисекунды с запуска: на них строят работу без остановкиconstrain()— зажимает значение в границах
В скетче 113 строк; свои функции: reg(), setJoint(), paw(), readImu(); подключено заголовков: 2. Сам скетч открыт на соседней вкладке — его можно менять и запускать заново.
Что попробовать изменить
- Поменяйте период в константе и посмотрите на анализаторе, как меняется картина на выводе.
- Перенесите деталь на другой вывод — на схеме и в коде — и убедитесь, что пример работает так же.
- Нажмите «Форкнуть»: копия станет вашим проектом, и в ней можно менять что угодно.

Смотрите также
- Робот Port идёт и держит равновесие — Человекоподобный робот Port (1,75 м, 50 кг) идёт по ровному полу: походка без остановок, равновесие и курс — по датчику…
- Робот Port поднимается по лестнице — Port поднимается на четыре ступени приставным шагом: места стоп — по плану лестницы, корпус — по расчёту ZMP с предуправ…
- Робот Port проходит лабиринт — Port идёт по коридорам и смотрит дальномером в голове: у стены поворачивает направо, если там занято — налево, и держитс…
- Робот Port доходит до стены и останавливается — Ходьба с дальномером: Port идёт к стене, ближе 60 см переходит на приставной шаг и останавливается.
- Робот Port обходит столб — Port видит столб дальномером, осматривается головой влево и вправо, обходит его с той стороны, где свободнее, и возвраща…
- Робот Port берёт кубик с полки — Правая рука Port сгибает локоть, опускает кисть на кубик, сжимает пальцы и поднимает его. Ноги держат стойку.
Сборка ещё не запускалась.
Печатайте в Serial числа — они станут графиком.
Serial.println(t); — один ряд,
Serial.printf("t:%.1f h:%.1f\n", t, h); — два с подписями.
Формат тот же, что понимает плоттер Arduino IDE.