# Matrix R4 Experiments

## Содержание

- [Глава 1. Общая информация и ссылки](#глава-1-общая-информация-и-ссылки)
- [Глава 2. Архитектура платы](#глава-2-архитектура-платы)
- [Глава 3. Тесты моторов](#глава-3-тесты-моторов)
- [Глава 4. Библиотека](#глава-4-библиотека)
- [Глава 5. Движение по энкодерам](#глава-5-движение-по-энкодерам)
- [Глава 6. Веб-интерфейс](#глава-6-веб-интерфейс)
- [Глава 7. Датчик линии](#глава-7-датчик-линии)
- [Визуализаторы](#визуализаторы)

## Глава 1. Общая информация и ссылки

Этот репозиторий будет хранить эксперименты с контроллером **MATRIX R4 / Mini R4**: движение по энкодерам, повороты, работа с IMU, движение по линии и тесты датчиков.

### Контроллер

MATRIX R4 / Mini R4 — робототехнический контроллер MATRIX Robotics на базе **Arduino UNO R4 WiFi**. Он программируется из Arduino IDE обычными `.ino` sketch-файлами через библиотеку `MatrixMiniR4`.

![MATRIX Mini R4 Controller](assets/matrix-r4-official-1.png)

Изображение: MATRIX Robotics, страница [MATRIX Mini R4 Controller Set](https://www.matrixrobotics.com/product/electrical-components/controller/matrix-mini-r4-controller-set).

Кратко по возможностям контроллера:

- 4 порта DC-моторов с поддержкой энкодеров: `M1`, `M2`, `M3`, `M4`;
- 4 RC-servo выхода: `RC1`, `RC2`, `RC3`, `RC4`;
- встроенный 6-осевой IMU/гиро;
- встроенный OLED-дисплей `128x32`;
- 2 кнопки: `BTN_UP`, `BTN_DOWN`;
- Wi-Fi и Bluetooth за счет базы Arduino UNO R4 WiFi;
- 3 аналоговых порта: `A1`, `A2`, `A3`;
- 4 I2C-порта плюс `I2C0` на `A3`;
- мониторинг напряжения питания через `MiniR4.PWR`.

### Полезные ссылки

- [MatrixMiniR4 Arduino Library](https://github.com/Matrix-Robotics/MatrixMiniR4) — официальная Arduino-библиотека.
- [MATRIX Programming API Docs](https://matrix-robotics.github.io/Programming-API-Docs/) — портал документации MATRIX.
- [MatrixMiniR4 Class Reference](https://matrix-robotics.github.io/Programming-API-Docs/MiniR4_Arduino_Lib_API_Docs/class_matrix_mini_r4.html) — API объекта `MiniR4`.
- [MATRIX Products Documentation](https://matrix-robotics.github.io/Products-documents/) — общий репозиторий документации MATRIX.
- [MS-018 MATRIX Line Tracer 10CH](https://www.matrixrobotics.com/product/electrical-components/sensors/matrix-line-tracer-10ch) — страница датчика линии.
- [Arduino UNO R4 WiFi Docs](https://docs.arduino.cc/hardware/uno-r4-wifi) — официальная документация Arduino по базе UNO R4 WiFi.

## Глава 2. Архитектура платы

MATRIX Mini R4 устроен как двухпроцессорный контроллер. Верхний уровень — это Arduino UNO R4 WiFi, который выполняет пользовательский sketch, работает с Arduino API, Wi-Fi/Bluetooth и частью внешних портов. Нижний уровень — STM32F103, который используется как сопроцессор для задач реального времени: моторы, энкодеры, сервоприводы, IMU и служебные данные питания.

Официальную электрическую схему платы не нашел в открытых ресурсах. Ниже логическая схема по официальному описанию MATRIX и структуре библиотеки `MatrixMiniR4`.

```mermaid
flowchart TB
    sketch["Arduino sketch (.ino)"] --> api["MatrixMiniR4 API"]
    api --> r4["Arduino UNO R4 WiFi core"]

    r4 --> wireless["Wi-Fi / Bluetooth"]
    r4 --> usb["USB-C upload / Serial"]
    r4 --> i2c0["Wire / I2C0 on A3"]
    r4 --> i2c1["Wire1"]
    r4 <--> lower["STM32F103 lower MCU"]

    i2c0 --> ms018["MS-018 Line Tracer 10CH"]
    i2c1 --> oled["OLED 128x32, address 0x3D"]
    i2c1 --> exti2c["I2C1-I2C4 external ports"]

    lower --> motorPorts["M1-M4 motor ports"]
    lower --> encoders["Encoder feedback"]
    lower --> servos["RC1-RC4 servo outputs"]
    lower --> imu["Built-in 6-axis IMU"]
    lower --> buttons["BTN_UP / BTN_DOWN"]
    lower --> power["Battery voltage monitor"]
```

### Верхний уровень: Arduino UNO R4 WiFi

На этом уровне выполняется пользовательский код. Когда sketch вызывает `MiniR4.begin()`, библиотека инициализирует объект `MiniR4`, OLED, порты, связь с нижним контроллером и встроенные модули.

Этот уровень отвечает за:

- выполнение `.ino` программы;
- работу с USB-C загрузкой и Serial;
- Wi-Fi/Bluetooth возможности Arduino UNO R4 WiFi;
- OLED `128x32` через `Wire1`;
- внешние I2C-устройства через `I2C0`/`I2C1-I2C4`;
- analog/digital порты, которые проброшены на Arduino-сторону.

### Нижний уровень: STM32F103

STM32F103 работает как отдельный контроллер реального времени. В библиотеке он доступен через внутренний слой `MMLower`: пользователь обычно не вызывает его напрямую, а работает через удобные классы `MiniR4.M1`, `MiniR4.M2`, `MiniR4.Motion`, `MiniR4.PWR` и так далее.

Через этот нижний уровень идут:

- команды скорости/мощности DC-моторов;
- чтение энкодеров;
- торможение моторов;
- управление RC-servo;
- чтение кнопок;
- чтение IMU;
- чтение напряжения питания.

В локальном коде библиотеки связь с нижним контроллером создается как `MMLower mmL(8, 9, 57600)`, то есть через внутренний serial-протокол библиотеки.

### Почему это важно для кода движения

Вызов вида:

```cpp
MiniR4.M1.setSpeed(30);
```

не управляет PWM напрямую с Arduino-процессора. Он отправляет команду в нижний STM32, а тот уже управляет моторным портом. Поэтому поведение `setSpeed()`, `setPower()`, тормоза и энкодеров зависит не только от sketch, но и от прошивки нижнего контроллера.

## Глава 3. Тесты моторов

В наших тестах используются энкодерные TT-моторы MATRIX, подключенные к портам `M1` и `M2`. Для документации важны две разные вещи: паспортные параметры мотора и реальные измерения на конкретном роботе. Паспорт дает ориентир, но код движения нужно настраивать по реальной скорости колес, потому что батарея, вес, трение, покрытие и колеса меняют результат.

### Официальные параметры

Официальная страница [MATRIX TT Encoder Motor with metal gear box](https://www.matrixrobotics.com/product/electrical-components/motor/matrix-tt-encoder-motor-with-metal-gear-box) указывает модель `METT-MG001` и такие свойства:

- металлический редуктор и два выходных вала;
- встроенный энкодер с A/B-фазами;
- измерение скорости, угла и направления вращения;
- поддержка точного движения на расстояние, регулирования скорости и синхронизации моторов;
- разъем `PH2.0-6P`.

Для обычной версии [MATRIX TT Motor with metal gear box](https://www.matrixrobotics.com/product/electrical-components/motor/matrix-tt-motor-with-metal-gear-box) MATRIX указывает скорость редуктора около `130 rpm` при `4.5V`. На странице энкодерной версии эта цифра в текстовом описании не продублирована, поэтому для `METT-MG001` используем ее только как ориентир по семейству TT-моторов, а не как точный предел для нашего робота.

### Как проводился тест

Тестовый sketch подавал на левый и правый мотор одинаковую команду от `0` до `100` с шагом `5`, считывал энкодеры и считал скорость вращения колес. Питание во время теста было примерно `8.04V` в начале и `7.91V` в конце.

Исходные данные:

- [motor_speed_data.csv](data/motor_speed_data.csv) — сырые значения `deg/s`;
- [motor_speed_data_mm.csv](data/motor_speed_data_mm.csv) — пересчет в `mm/s` для колеса `65 mm` и в `rpm`.

Графики строятся скриптом [plot_motor_speed.py](plot_motor_speed.py).

### 3.1 Как получены графики

Для графиков используются два файла:

- `MatrixMotorSpeedGraphTest/MatrixMotorSpeedGraphTest.ino` — sketch на роботе;
- `plot_motor_speed.py` — Python-скрипт на компьютере.

`MatrixMotorSpeedGraphTest` прогоняет моторы `M1` и `M2` по командам от `0` до `100` с шагом `5`. Для каждой точки он:

1. задает одинаковую скорость левому и правому мотору;
2. ждет стабилизацию;
3. сбрасывает энкодеры;
4. измеряет, сколько градусов колеса прошли за фиксированное время;
5. печатает строку CSV в `Serial`.

Формат Serial-вывода:

```text
power,left_deg_s,right_deg_s,avg_deg_s,battery_v
```

`plot_motor_speed.py` читает эти строки с COM-порта или берет уже сохраненный CSV. Потом он:

- сохраняет расширенный CSV в `data/motor_speed_data_mm.csv`;
- пересчитывает `deg/s` в `mm/s` через диаметр колеса `65 mm`;
- пересчитывает `deg/s` в `rpm` по формуле `rpm = deg/s / 6`;
- строит три PNG-графика в `assets/`.

Запуск с живого робота:

```powershell
python plot_motor_speed.py --port COM8
```

Запуск по уже сохраненным данным:

```powershell
python plot_motor_speed.py --input-csv data\motor_speed_data.csv
```

Зависимости Python:

```powershell
python -m pip install -r requirements.txt
```

Для запуска по готовому CSV нужен только `matplotlib`; `pyserial` нужен для чтения данных с COM-порта.

Перед измерением нужно загрузить `MatrixMotorSpeedGraphTest`, открыть порт только в одном месте и нажать кнопку на роботе. Arduino Serial Monitor при этом должен быть закрыт, иначе Python не сможет открыть COM-порт.

### Команда мотора и угловая скорость

![Motor command to wheel angular speed](assets/motor_speed_graph.png)

`deg/s` удобен для энкодеров: это прямая скорость вращения оси колеса. Пересчет в обороты простой:

```text
rpm = deg/s / 6
```

Например, `1025.7 deg/s` при команде `100` — это примерно `171 rpm`.

### Команда мотора и скорость робота

![Motor command to robot speed](assets/motor_speed_graph_mm.png)

`mm/s` полезнее для движения по расстоянию. Пересчет сделан для диаметра колеса `65 mm`:

```text
mm/s = deg/s * pi * 65 / 360
```

По нашим данным команда `100` дает около `582 mm/s` на холостом тесте. На полу под нагрузкой реальная скорость будет ниже.

### Команда мотора и RPM

![Motor command to wheel rpm](assets/motor_speed_graph_rpm.png)

Короткая сводка по точкам:

| Команда | Среднее, deg/s | Среднее, rpm | Среднее, mm/s |
|---:|---:|---:|---:|
| 50 | 513.0 | 85.5 | 291.0 |
| 80 | 822.3 | 137.1 | 466.4 |
| 100 | 1025.7 | 171.0 | 581.8 |

### Вывод для кода движения

Команда `setSpeed(100)` — это не гарантированные `100%` физической скорости и не паспортная скорость мотора. Это верхняя команда контроллера. Для нашего робота при 2S батарее и колесах `65 mm` практический потолок на холостом тесте получился около `170 rpm` или `580 mm/s`.

Поэтому в движении лучше задавать целевую скорость в `mm/s`, а внутри кода переводить ее в команду мотора по измеренной таблице. Если робот едет по полу, таблицу нужно уточнить уже под нагрузкой.

## Глава 4. Библиотека

Основная библиотека для платы — `MatrixMiniR4`. В sketch она подключается так:

```cpp
#include <MatrixMiniR4.h>
```

Почти вся работа идет через глобальный объект `MiniR4`. Минимальный старт:

```cpp
void setup() {
  MiniR4.begin();
}
```

### Шпаргалка по объектам

| Объект | Что делает | Частые методы |
|---|---|---|
| `MiniR4.M1` ... `MiniR4.M4` | DC-моторы с энкодерами | `setSpeed()`, `setPower()`, `getDegrees()`, `resetCounter()`, `setBrake()` |
| `MiniR4.DriveDC` | готовое управление парой моторов | `begin()`, `Move()`, `MoveSync()`, `MoveGyro()`, `TurnGyro()`, `brake()` |
| `MiniR4.RC1` ... `MiniR4.RC4` | RC-servo выходы | `setAngle()` |
| `MiniR4.Motion` | встроенный IMU/гиро | `getGyro()`, `getAccel()`, `getEuler()`, `resetIMUValues()` |
| `MiniR4.PWR` | питание и батарея | `setBattCell()`, `getBattVoltage()`, `getBattPercentage()` |
| `MiniR4.OLED` | OLED `128x32` | `clearDisplay()`, `setCursor()`, `print()`, `display()` |
| `MiniR4.BTN_UP`, `MiniR4.BTN_DOWN` | две кнопки на плате | `getState()` |
| `MiniR4.LED` | встроенные RGB-светодиоды | `setColor()`, `setBrightness()` |
| `MiniR4.Buzzer` | встроенный buzzer | `Tone()`, `NoTone()` |
| `MiniR4.D1` ... `MiniR4.D4` | digital/PWM порты | `getL()`, `getR()`, `setL()`, `setR()`, `setPWML()`, `setPWMR()` |
| `MiniR4.A1` ... `MiniR4.A3` | analog порты | `getAIL()`, `getAIR()` |
| `MiniR4.I2C0` ... `MiniR4.I2C4` | I2C-порты | используются для MATRIX I2C-датчиков |
| `MiniR4.Uart` / `Serial1` | UART-порт | обычный Arduino UART |
| `MiniR4.WiFi` | Wi-Fi через UNO R4 WiFi | API совместим с `WiFiS3` |

### Моторы

`setSpeed()` — регулируемая скорость через нижний STM32. Это не физические `mm/s`, а команда от `-100` до `100`, которую контроллер пытается удерживать по энкодеру.

```cpp
MiniR4.M1.setSpeed(30);
MiniR4.M2.setSpeed(30);
```

`setPower()` — более прямое управление мощностью/PWM без удержания скорости:

```cpp
MiniR4.M1.setPower(30);
MiniR4.M2.setPower(30);
```

Энкодеры:

```cpp
MiniR4.M1.resetCounter();
long deg = MiniR4.M1.getDegrees();
```

### DriveDC

`DriveDC` — встроенный высокоуровневый слой для двухмоторной базы. Он умеет движение двумя моторами, синхронизацию, движение по гиро и поворот по гиро.

```cpp
MiniR4.DriveDC.begin(1, 2, true, false);
MiniR4.DriveDC.MoveSync(30, 30);
MiniR4.DriveDC.brake(true);
```

В библиотеке есть PID-настройки:

```cpp
MiniR4.DriveDC.setMoveSyncPID(kp, ki, kd);
MiniR4.DriveDC.setMoveGyroPID(kp, ki, kd);
MiniR4.DriveDC.setTurnGyroPID(kp, ki, kd);
```

### IMU

Для угла поворота обычно нужен `Yaw`:

```cpp
MiniR4.Motion.resetIMUValues();
double yaw = MiniR4.Motion.getEuler(MiniR4Motion::AxisType::Yaw);
```

Для угловой скорости вокруг вертикальной оси:

```cpp
double zRate = MiniR4.Motion.getGyro(MiniR4Motion::AxisType::Z);
```

### Питание

Официальный диапазон входного питания MATRIX Mini R4: `6V ... 24V`. Для экспериментов в этом репозитории используется 2S аккумулятор: примерно `6.0V ... 8.4V`.

Для 2S аккумулятора:

```cpp
MiniR4.PWR.setBattCell(2);
float v = MiniR4.PWR.getBattVoltage();
```

`setBattCell(2)` не меняет питание моторов. Он задает пороги мониторинга батареи в нижнем STM32: полный заряд `8.4V`, номинал `7.4V`, нижний порог `6.8V`.

Входное напряжение аккумулятора идет на силовую часть платы и влияет на моторы. `setSpeed()` и `setPower()` задают команду контроллеру мотора, но реальная скорость и тяга зависят от напряжения батареи, просадки под нагрузкой, веса робота и трения. Поэтому графики из главы 3 привязаны к конкретному питанию теста.

### Датчик линии MS-018

Для MS-018 в библиотеке есть `MatrixLineTracer`. Он дает сырые значения 10 каналов и готовую ошибку линии.

```cpp
MiniR4.I2C0.MXLineTracer.begin();

uint8_t sensors[10];
MiniR4.I2C0.MXLineTracer.getAllSensors(sensors);
float error = MiniR4.I2C0.MXLineTracer.getError();
```

Для PID по линии удобнее читать датчик часто и держать цикл простым: датчик → ошибка → поправка моторов.

## Глава 5. Движение по энкодерам

В этой главе лежат несколько версий кода движения по энкодерам для двухмоторной базы на `M1` и `M2`. В них нет `DriveDC`, гиро, Serial и библиотечных поворотов. Только свои функции движения по расстоянию и поворота по энкодерам.

Текущие варианты:

- `MatrixEncoderMotionV0/MatrixEncoderMotionV0.ino` — упрощенный профиль: расстояние напрямую превращается в команду `setSpeed` через `smoothstep`, без `mm/s`, ускорения и jerk;
- `MatrixEncoderMotionV1/MatrixEncoderMotionV1.ino` — старая версия профиля с `targetSpeed`, `targetAccel`, `sCurveStopDistance()` и переключением разгон/торможение;
- `MatrixEncoderMotionV2/MatrixEncoderMotionV2.ino` — новая версия с velocity governor: разрешенная скорость считается по оставшемуся расстоянию, затем профиль плавно догоняется через accel/jerk;
- `MatrixEncoderMotionTest/MatrixEncoderMotionTest.ino` — рабочий тестовый sketch, оставлен как промежуточная версия.

Для сравнения профилей без робота:

- [driveMm visualizer](visualizers/drive_mm_visualizer.html);
- [turnDeg visualizer](visualizers/turn_deg_visualizer.html).

Основная идея: пользователь задает расстояние в `mm` или угол в градусах, а код сам считает профиль скорости, синхронизирует моторы по энкодерам и останавливается по фактической остановке колес.

### Настройки робота

Ключевые константы:

| Константа | Значение | Зачем нужна |
|---|---:|---|
| `BATTERY_CELLS` | `2` | настройка контроля 2S аккумулятора |
| `WHEEL_DIAMETER_MM` | `65.0` | диаметр колеса для пересчета энкодеров в миллиметры |
| `WHEEL_BASE_MM` | `142.0` | расстояние между колесами для расчета поворота |
| `LEFT_MOTOR_REVERSED` | `false` | направление левого мотора |
| `RIGHT_MOTOR_REVERSED` | `true` | направление правого мотора |
| `DEFAULT_DRIVE_ACCEL_MM_S2` | `2000.0` | ускорение по умолчанию для прямого движения |
| `DEFAULT_DRIVE_JERK_MM_S3` | `35000.0` | ограничение рывка для плавного старта/торможения |
| `DEFAULT_TURN_ACCEL_DEG_S2` | `720.0` | ускорение поворота |
| `DEFAULT_TURN_JERK_DEG_S3` | `10000.0` | рывок поворота |
| `DRIVE_SYNC_KP` | `0.35` | сила синхронизации левого и правого энкодера |

### Старт sketch

В `setup()` плата инициализируется, включается режим 2S батареи и задаются направления моторов:

```cpp
MiniR4.begin();
MiniR4.PWR.setBattCell(BATTERY_CELLS);
MiniR4.M1.setReverse(LEFT_MOTOR_REVERSED);
MiniR4.M2.setReverse(RIGHT_MOTOR_REVERSED);
```

В `loop()` стоит защита `static bool testDone`, поэтому тест выполняется один раз. Перед стартом на OLED показывается напряжение батареи, движение начинается только после нажатия `BTN_UP` или `BTN_DOWN`.

Текущий тестовый сценарий:

```cpp
driveMm(500, 300);
turnDeg(90, 180);
```

То есть робот проезжает `500 mm`, потом поворачивает на `90` градусов по энкодерам.

### Прямое движение

Главная функция:

```cpp
driveMm(distanceMm, maxSpeedMmS);
driveMm(distanceMm, maxSpeedMmS, accelMmS2);
driveMm(distanceMm, maxSpeedMmS, accelMmS2, jerkMmS3);
```

Примеры:

```cpp
driveMm(500, 300);        // вперед 500 mm, цель 300 mm/s
driveMm(-500, 250);       // назад 500 mm, цель 250 mm/s
driveMm(1000, 500, 600);  // вперед 1000 mm, ускорение 600 mm/s^2
```

Внутри `driveMm()` просто вызывает общий движок:

```cpp
moveTankMm(distanceMm, distanceMm, speed, accel, jerk);
```

### Как работает движение

Алгоритм `moveTankMm()`:

1. Сбрасывает энкодеры `M1` и `M2`.
2. Переводит градусы энкодера в миллиметры через `WHEEL_DIAMETER_MM`.
3. Считает пройденную дистанцию по среднему значению левого и правого колеса.
4. Ведет целевую скорость через S-curve: ускорение меняется не резко, а через `jerk`.
5. По фактической скорости колес подбирает команду мотора `0..100`.
6. Синхронизирует колеса: если левое ушло дальше правого, левое замедляется, правое ускоряется.
7. В конце вызывает `smartStopDrive()`.

Синхронизация простая:

```cpp
syncErrorMm = leftMm - rightMm;
syncCorrection = syncErrorMm * DRIVE_SYNC_KP;
```

Если робот виляет, первым делом меняется `DRIVE_SYNC_KP`. Если поправка слишком большая — робот дергается. Если маленькая — хуже держит прямую.

### S-curve

В этом sketch скорость не прыгает сразу к целевой. Код отдельно хранит:

- `targetSpeed` — текущая целевая скорость;
- `targetAccel` — текущее целевое ускорение;
- `jerkMmS3` — насколько быстро можно менять ускорение.

Упрощенно:

```cpp
targetAccel = approachFloat(targetAccel, desiredAccel, jerkMmS3 * dtS);
targetSpeed += targetAccel * dtS;
```

Это убирает резкий старт. Чем меньше `jerkMmS3`, тем мягче начало и торможение. Чем больше `jerkMmS3`, тем ближе поведение к обычной трапеции.

### Энкодерный поворот

Функция:

```cpp
turnDeg(angleDeg, maxAngularSpeedDegS);
turnDeg(angleDeg, maxAngularSpeedDegS, accelDegS2);
turnDeg(angleDeg, maxAngularSpeedDegS, accelDegS2, jerkDegS3);
```

Она считает, сколько миллиметров должно пройти каждое колесо:

```text
wheelDistance = wheelBase * pi * abs(angle) / 360
```

Потом вызывает `moveTankMm()` с противоположными направлениями колес:

```cpp
moveTankMm(+wheelDistance, -wheelDistance, ...);
```

Для отрицательного угла направления меняются местами.

### Остановка без тупого delay

`smartStopDrive()` не просто делает `delay(1000)`. Она:

1. отправляет моторам `setSpeed(0)`;
2. включает тормоз;
3. читает энкодеры;
4. ждет, пока оба колеса перестанут менять градусы в течение `DRIVE.stopStableMs`;
5. выходит не позже `DRIVE.stopMaxWaitMs`.

Это нужно из-за инерции: мотор может получить `0`, но колесо еще чуть докручивается.

### Что крутить при настройке

| Симптом | Что менять |
|---|---|
| резкий старт | уменьшить `DEFAULT_DRIVE_JERK_MM_S3` |
| слишком вялый старт | увеличить `DEFAULT_DRIVE_ACCEL_MM_S2` или `DEFAULT_DRIVE_JERK_MM_S3` |
| не держит прямую | увеличить `DRIVE_SYNC_KP` |
| дергается при синхронизации | уменьшить `DRIVE_SYNC_KP` |
| расстояние неверное | уточнить `WHEEL_DIAMETER_MM` |
| угол поворота по энкодерам неверный | уточнить `WHEEL_BASE_MM` |

## Глава 6. Веб-интерфейс

Sketch `MatrixWifiTurnPowerTest` показывает, что MATRIX R4 можно использовать не только как автономный робот, но и как маленький Wi-Fi сервер с веб-пультом. Arduino UNO R4 WiFi подключается к домашней сети, запускает HTTP-сервер на порту `80`, показывает IP на OLED, а управление открывается из браузера.

Файл sketch: `MatrixWifiTurnPowerTest/MatrixWifiTurnPowerTest.ino`.

### Что делает пример

После загрузки sketch:

1. робот подключается к Wi-Fi;
2. на OLED появляется IP-адрес;
3. с телефона или компьютера в той же сети открывается `http://IP_РОБОТА/`;
4. браузер показывает кнопки управления поворотом;
5. робот отдает статус в JSON: энкодеры, yaw, скорость колес, напряжение батареи.

Это не загрузка прошивки по Wi-Fi. Это веб-интерфейс управления и телеметрии уже запущенного sketch.

### Настройка Wi-Fi

Перед загрузкой нужно прописать сеть:

```cpp
const char WIFI_SSID[] = "YOUR_WIFI_SSID";
const char WIFI_PASS[] = "YOUR_WIFI_PASSWORD";
```

Пароль не стоит хранить в публичном репозитории. В текущем sketch оставлены заглушки.

### Управление через браузер

Веб-страница отправляет простые HTTP-запросы:

| URL | Что делает |
|---|---|
| `/` | отдает HTML-страницу пульта |
| `/set?p=100` | задает команду поворота |
| `/step?d=10` | меняет команду на шаг |
| `/stop` | останавливает моторы |
| `/status` | возвращает JSON-статус |

Команда `p` сейчас работает в диапазоне от `-1000` до `1000`. В sketch это сделано через:

```cpp
mmL.SetDCMotorSpeedRange(1, 0, SPEED_RANGE_MAX);
mmL.SetDCMotorSpeedRange(2, 0, SPEED_RANGE_MAX);
```

После этого поворот задается через `MiniR4.M1.setSpeed()` и `MiniR4.M2.setSpeed()`. Положительное значение крутит робота в одну сторону, отрицательное — в другую.

### Синхронизация поворота

Даже при одинаковой команде моторы могут вращаться по-разному. Поэтому sketch сравнивает энкодеры и подправляет команды:

```cpp
correction = (leftDeg - rightDeg) * TURN_SYNC_KP;
leftPower = basePower - correction;
rightPower = basePower + correction;
```

Это не точный поворот на заданный угол. Это ручной веб-тест минимальной скорости, синхронизации и поведения моторов при разных командах.

### Телеметрия

`/status` возвращает примерно такие поля:

| Поле | Значение |
|---|---|
| `power` | текущая команда поворота |
| `yaw` | текущий yaw IMU |
| `m1`, `m2` | градусы энкодеров |
| `lds`, `rds` | скорость колес в `deg/s` |
| `lms`, `rms` | скорость колес в `mm/s` |
| `left`, `right` | фактические команды левого и правого мотора |
| `battery` | напряжение батареи |

Страница обновляет статус каждые `500 ms`, поэтому можно менять команду и сразу видеть, как реагируют энкодеры.

### Безопасность

В sketch есть три простые защиты:

- кнопки `BTN_UP` и `BTN_DOWN` сразу вызывают `stopMotors()`;
- если команды не приходят дольше `COMMAND_TIMEOUT_MS`, моторы останавливаются;
- при потере Wi-Fi или ошибке подключения моторы остаются остановленными.

Ограничения тоже важны:

- веб-сервер без пароля и авторизации;
- использовать только в своей локальной сети;
- не оставлять робота на столе с колесами, которые могут зацепиться;
- для публичного репозитория не коммитить настоящий Wi-Fi пароль.

## Глава 7. Датчик линии

Для движения по линии используется **MS-018 MATRIX Line Tracer 10CH**. Это 10-канальный датчик линии, который подключается к контроллеру по I2C и подходит для PID-руления по черной линии.

![MS-018 MATRIX Line Tracer 10CH](assets/ms-018-line-tracer.jpg)

Изображение: MATRIX Robotics, страница [MS-018 MATRIX Line Tracer 10CH](https://www.matrixrobotics.com/product/electrical-components/sensors/matrix-line-tracer-10ch).

Кратко по официальному описанию:

- 10 каналов отражения для более точного определения положения линии;
- I2C-передача данных в контроллер;
- расчет `Error` — смещение линии относительно центра датчика;
- расчет `Line Width` — ширина/количество активных сенсоров;
- определение перекрестков: Left, Right, T-junction, Cross-junction;
- хранение `Last Sensor` перед потерей линии;
- встроенная калибровка кнопкой `CAL` и синий индикатор;
- питание `3.3V ~ 5V`;
- оптимизация для слабого освещения.

На MATRIX R4 текущая версия датчика используется через `I2C0`, то есть порт `A3`:

```cpp
MiniR4.I2C0.MXLineTracer.begin();
MiniR4.I2C0.MXLineTracer.getAllSensors(sensors);
```

### MatrixMS018JSTTest

Файл sketch: `MatrixMS018JSTTest/MatrixMS018JSTTest.ino`.

Это тест подключения и чтения датчика. Он:

- запускает MS-018 на `I2C0`;
- читает 10 сырых каналов;
- считает ошибку линии `-4.5 ... 4.5`;
- показывает на OLED `Error`, `Line Width`, `Last Sensor`, `Junction Type`;
- рисует 10 столбиков по значениям сенсоров.

Этот sketch нужен первым: если он не видит линию или датчик, PID-код трогать рано.

### MatrixLineFollowPIDTest

Файл sketch: `MatrixLineFollowPIDTest/MatrixLineFollowPIDTest.ino`.

Это классическое движение по линии без ограничения по расстоянию. Логика:

- кнопка запускает движение;
- каждые `20 ms` читается датчик;
- ошибка линии идет в PID;
- PID дает поправку к левому и правому мотору;
- при потере линии робот тормозит и возвращается в ожидание;
- кнопка во время движения работает как stop.

Главные настройки:

```cpp
const int BASE_SPEED = 30;
const int MAX_SPEED = 55;
const float KP = 7.0;
const float KI = 0.0;
const float KD = 18.0;
const int STEERING_SIGN = 1;
```

Если робот уезжает от линии, первым делом поменять `STEERING_SIGN` на `-1`.

### MatrixLineDistanceTest

Файл sketch: `MatrixLineDistanceTest/MatrixLineDistanceTest.ino`.

Это движение по линии на заданное расстояние. Он объединяет:

- чтение MS-018;
- PID-поправку по линии;
- энкодеры `M1`/`M2`;
- S-curve разгон и торможение;
- остановку по расстоянию в миллиметрах.

Текущий тест:

```cpp
const long TEST_DISTANCE_MM = 1000;
const int LINE_MAX_SPEED_MM_S = 250;
```

Результат на OLED:

- `DONE` — расстояние пройдено;
- `LOST` — линия потеряна;
- `STOP` — остановлено кнопкой;
- `TIME` — вышел timeout.

Этот sketch полезен для задач, где робот должен не просто ехать по линии, а проехать конкретный участок трассы.

## Визуализаторы

Для GitHub Pages добавлена стартовая страница [index.html](index.html). Через нее можно открыть оба визуализатора:

- [визуализатор driveMm](visualizers/drive_mm_visualizer.html) — прямое движение по энкодерам, сравнение `V0`, `V1`, `V2`;
- [визуализатор turnDeg](visualizers/turn_deg_visualizer.html) — поворот по энкодерам, пересчет угла робота в путь колес через `WHEEL_BASE_MM`.

Эти страницы статические, поэтому их можно открывать локально из файла или через GitHub Pages без сервера.
