NEO-6/7/8 — GPS-модуль
Работа с GPS-модулем NEO-6M/7M/8M на ESP32: чтение сырых NMEA-данных со спутников, проверка контрольной суммы, статус связи/спутников/координат в консоли.
🛰️ GPS NEO Модуль для STEM Лаборатории
Проект для работы с GPS модулем NEO-6M/7M/8M на ESP32. Программа читает “сырые” NMEA-данные со спутников, проверяет их по контрольной сумме и выводит понятный статус связи/спутников/координат в консоль.
📋 Документация проекта
| Файл | Что внутри | Кому |
|---|---|---|
| GUIDE.md | Учебное пособие: протокол NMEA, разбор кода построчно, диагностика, самопроверка | Изучаешь проект впервые |
| WIRING.md | Схема подключения модуля к ESP32 | Собираешь железо |
| ROADMAP.md | Куда развивать проект: автономный ровер по GPS-координатам | Хочешь пойти дальше |
Описание
Проект демонстрирует базовую работу с GPS на уровне, который понятен и пятикласснику, и старшекласснику:
- Чтение и побайтовый разбор строк NMEA 0183.
- Проверка каждой строки по контрольной сумме (отсев “битых” пакетов).
- Подсчёт спутников, видимых каждой системой (GPS/BeiDou/GLONASS/Galileo) — с суммированием по всем созвездиям.
- Определение статуса Fix (есть ли валидные координаты) и числа спутников, реально использованных в расчёте.
- Разбор координат, скорости, курса (из
$..RMC) и высоты/точности HDOP (из$..GGA) в десятичные градусы и метрические единицы. - Watchdog связи: чёткое сообщение в консоли, если модуль физически не отвечает (обрыв проводов/питания), а не просто “тишина”.
Программная архитектура
Один класс-драйвер, без обёрток и фасадов — стандартный паттерн для всех компонентов этого репозитория (см. Components/2.MPU6050):
| Файлы | Что внутри |
|---|---|
include/GpsNeo.h, src/GpsNeo.cpp | Класс GpsNeo: парсинг RMC/GGA/GSV, проверка checksum, watchdog связи. Публичный API компактный (begin(), process(), hasFix(), getPosition()…), поэтому отдельного упрощённого слоя не требуется. |
src/main.cpp | Тестовый стенд: создаёт единственный экземпляр GpsNeo gps(Serial2) и выводит диагностику — связь → данные → спутники → координаты. |
tools/gps_track_view.py | Живой визуализатор трека движения (см. раздел “Визуализация” ниже). |
Разбор того, как это устроено внутри — в GUIDE.md.
Работа через CLI
cd "Components/1.GPS_NEO_6-8"
pio run -t upload && pio device monitorЕсли прошивка падает с ошибкой
chip stopped responding— снизьupload_speed = 115200вplatformio.ini, закрой Serial Monitor перед прошивкой.
Окружение
esp32c6в текущей версии не собирается (конфликт макросаSerial/USBSerialв ядре Arduino для этой платы) — используйesp32devилиesp32s3.
📊 Визуализация
Голые числа Lat:.../Lon:... в консоли — не самый наглядный формат для группы
учеников. tools/gps_track_view.py рисует накопленный трек движения в реальном
времени в метрах от точки старта:
cd tools
pip install -r requirements.txt
python3 gps_track_view.py /dev/ttyUSB0Работает полностью офлайн (относительная проекция, см.
GUIDE.md про формат
координат и ROADMAP.md про
приближение коротких расстояний). Если нужна подложка настоящей карты
(OpenStreetMap) и есть интернет — добавьте флаг --map (потребует
pip install contextily); без сети скрипт продолжит работу без подложки.
Serial Monitor прошивки на момент запуска скрипта должен быть закрыт — иначе порт занят и Python не сможет его открыть.
Примечание: для работы внутри помещения используйте внешнюю активную антенну или расположите модуль у окна с прямым видом на небо.
Связанные материалы
- Пространство и положение — координаты, точка отсчёта, пересчёт в метры.
- BeaconRadio — проект на этом датчике: SOS-маячок с передачей координат, разбор NMEA и обнаружение GNSS-спуфинга.
- GY-271 — GPS обновляется раз в секунду и «дрожит» на месте; компас даёт направление здесь и сейчас.
Схема подключения GPS NEO (6M/7M/8M)
1. Основная схема подключения (UART2)
Для работы с GPS модулем на ESP32 рекомендуется использовать аппаратный порт Serial2.
┌─────────────────┐
│ ESP32 │
│ │
│ 3V3 ├───────┐ VCC
│ │ │
│ GND ├───────┤ GND
│ │ │
│ (RX2) GPIO16├───────┤ TX
│ │ │
│ (TX2) GPIO17├───────┤ RX
│ │ │
└─────────────────┘ │
│
┌───────┴───────┐
│ GPS NEO │
│ Module │
└───────────────┘2. Таблица соединений
| Модуль GPS | ESP32 Пин | Описание |
|---|---|---|
| VCC | 3.3V / 5V | Питание (зависит от модуля, обычно 3.3V) |
| GND | GND | Общий провод (Земля) |
| TX | GPIO 16 (RX2) | Передача данных от GPS к ESP32 |
| RX | GPIO 17 (TX2) | Прием данных от ESP32 к GPS |
Внимание: Пин TX модуля должен соединяться с пином RX контроллера, и наоборот.
3. Альтернативные пины (UART2)
Если GPIO16 и 17 заняты другими устройствами, можно использовать:
- RX: GPIO 4, GPIO 13, GPIO 36
- TX: GPIO 15, GPIO 12, GPIO 35
4. Рекомендации
- Антенна: Убедитесь, что керамическая антенна направлена в сторону неба.
- Питание: GPS модули могут потреблять до 50-70 мА в режиме поиска. При нестабильной работе проверьте напряжение питания.
- Индикация: После получения координат (Fix) на большинстве модулей начинает мигать светодиод “PPS”.
📘 Учебное пособие: GPS NEO на ESP32
Это пособие проведёт тебя от “что такое GPS вообще” до “могу объяснить каждую строчку GpsNeo.cpp”. Читай по порядку — каждый следующий раздел опирается на предыдущий.
Чему ты научишься
- Как спутники превращаются в координаты (трилатерация, протокол NMEA).
- Как устроена контрольная сумма и зачем она нужна на реальной линии связи с помехами.
- Как прошивка читает GPS побайтово и разбирает строку — с разбором настоящего кода
GpsNeo.cpp. - Почему “спутников видно много”, а координат всё ещё нет.
- Как читать диагностику в консоли и находить неисправность по её виду.
1. Как вообще работает GPS
Вокруг Земли летает группировка спутников. Каждый спутник постоянно передаёт два числа: где он находится и который сейчас час (по очень точным атомным часам).
Приёмник (наш модуль NEO) слушает несколько спутников одновременно и по разнице времени сигнала до каждого высчитывает расстояние до него. Это называется трилатерацией:
- 3 спутника → можно вычислить широту и долготу (2D).
- 4 спутника → добавляется высота (3D), а заодно уточняется собственная погрешность часов приёмника.
Точность. У обычного модуля без RTK/DGPS типичная погрешность — около 2.5 метра (CEP). Зависит от числа видимых спутников и того, насколько равномерно они разбросаны по небу (это называют DOP — Dilution of Precision). Держи это число в голове: пытаться доехать роботом ровно “в точку” бессмысленно, GPS так не умеет — подробнее в ROADMAP.md.
2. Протокол NMEA 0183 — язык, на котором говорит модуль
Модули NEO-6M/7M/8M по умолчанию непрерывно шлют текстовые строки в формате NMEA. Открой Serial Monitor без разбора — увидишь поток вида:
$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47Разберём эту строку по кусочкам:
| Кусок | Значение |
|---|---|
$ | начало строки |
GP | талкер (какая система: GP=GPS, GL=GLONASS, GA=Galileo, BD/GB=BeiDou, GN=комбинированное сообщение) |
GGA | тип сообщения (что именно передаётся) |
,123519,4807.038,N,... | поля данных через запятую |
*47 | контрольная сумма в hex |
Три сообщения важны для нашего проекта:
$..RMC— время, дата, статус (A=valid/V=void), координаты, скорость.$..GSV— сколько спутников видно одной конкретной системе. Каждая система шлёт свою отдельную группу сообщений.$..GGA— качество приёма и число спутников, использованных в расчёте координат (не просто видимых).
⚠️ Важная ловушка. “Видно спутников” в нашем коде — это сумма по всем
$..GSVот разных систем, а не значение из одного сообщения. Если взять только последнее пришедшее GSV, получится “видно 2” (BeiDou), хотя GPS-система отдельно видит 17–20. Каждая система считает только свои спутники — поэтому вGpsNeoони складываются отдельно (раздел 4.4).
2.1 Как ddmm.mmmm превращается в десятичные градусы
В строке $GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,... координаты
записаны не в привычном формате “48.1173°”, а как 4807.038 — это не число
с 4 знаками после запятой в градусах, а формат ddmm.mmmm (широта) /
dddmm.mmmm (долгота):
- Первые 2 цифры (широта) или 3 цифры (долгота) — это целые градусы.
- Всё, что после них (включая дробную часть) — это минуты (1° = 60 минут).
Для 4807.038: 48 — градусы, 07.038 — минуты. Перевод в десятичные градусы:
degrees = 48 + 07.038 / 60 = 48.1173°Именно это делает GpsNeo::parseNmeaCoordinate() в GpsNeo.cpp — находит
позицию точки, отсчитывает от неё 2 (или 3) цифры назад для целых градусов,
остаток строки — минуты. Знак определяется полушарием: N/E — положительный,
S/W — отрицательный (поэтому Австралия и Аргентина получают отрицательную
широту, а США западного побережья — отрицательную долготу).
3. Контрольная сумма (checksum)
Провод между модулем и ESP32 — не идеальная среда: наводки, помехи, дребезг контактов могут испортить один бит на лету. Чтобы не доверять испорченным данным, каждая строка NMEA заканчивается контрольной суммой:
$GPGGA,...,*47
└────┘
XOR всех байт между '$' и '*', записанный как hexЕсли пересчитанная сумма не совпадает с той, что указана в строке — строка отбрасывается целиком, её данные не используются. Это ровно то, что делает GpsNeo::validateChecksum().
4. Как код разбирает строку
Открой src/GpsNeo.cpp — дальше идём по нему сверху вниз.
4.1 Побайтовое чтение — process()
GPS-модуль не присылает строку целиком одним куском — он шлёт байты по одному, как будто печатает по букве в секунду (на самом деле быстрее, но принцип тот же). process() вызывается на каждой итерации loop() и:
- Проверяет, есть ли новый байт (
_serial->available()). - Читает байт, копит его в
_buffer. - Как только пришёл символ
'\n'(конец строки) — строка готова, можно её разбирать.
bool GpsNeo::process(Stream &target) {
if (!_serial->available()) return false;
char c = _serial->read();
...
if (c != '\n') return false; // строка ещё не закончена — выходим
... // а вот тут строка уже полная
}Это классический паттерн для встраиваемых систем: никогда не блокировать loop() ожиданием данных. Функция либо мгновенно обрабатывает один байт, либо мгновенно выходит — контроллер всегда остаётся отзывчивым для остального кода (мигание светодиода, чтение других датчиков и т.д.).
4.2 Проверка суммы — validateChecksum()
for (int i = 1; i < starIndex; i++) {
calculated ^= (uint8_t)line[i];
}Именно тот XOR из раздела 3, реализованный в цикле: 1 — потому что байт $ в сумму не входит, starIndex — позиция символа *.
4.3 Извлечение поля — getField()
NMEA-строка — это данные через запятую. getField(line, len, fieldIndex, ...) возвращает N-е поле по счёту (поле 0 — это тип сообщения после талкера). Функция идёт по строке и считает запятые, пока не насчитает нужный номер поля.
Пример на нашей строке $GPGGA,123519,4807.038,N,...,08,...:
| fieldIndex | значение |
|---|---|
| 0 | GGA |
| 1 | 123519 (время) |
| 6 | качество фикса |
| 7 | 08 (спутников использовано) |
Именно эти номера — 6 и 7 — не magic numbers в коде, а именованные константы в начале файла:
constexpr uint8_t kGgaFieldFixQuality = 6;
constexpr uint8_t kGgaFieldSatellitesUsed = 7;4.4 Спутники “видно” по каждой системе отдельно — updateSatellitesInView()
Как отмечено в разделе 2, каждая система (GP, GL, GA, BD) шлёт свой собственный $..GSV. Код хранит счётчик отдельно для каждого талкера в массиве _talkerSats[kMaxTalkers], и когда нужно узнать общее число (getSatellitesInView()), просто складывает все активные слоты. Так GPS + GLONASS + Galileo + BeiDou не перезатирают друг друга, а суммируются.
4.5 Качество фикса — FixQuality
enum class FixQuality : uint8_t {
NoFix = 0,
GpsFix = 1,
DgpsFix = 2,
};Вместо “голого” числа 0/1/2, разбросанного по коду, используется именованный тип. getFixQuality() == FixQuality::NoFix читается однозначно, а getFixQuality() == 0 пришлось бы каждый раз держать в голове как расшифровку.
5. Почему “видно 17 спутников”, а Fix всё ещё нет
Видеть спутник (поймать несущую частоту, получить уровень сигнала) и иметь Fix (декодировать навигационное сообщение и решить систему уравнений) — разные этапы:
- Эфемериды (точные координаты спутника на орбите) передаются медленно — полный кадр данных занимает до 30 секунд на каждый спутник отдельно, и нужно успешно принять кадр хотя бы от 4 спутников одновременно.
- Холодный старт (нет сохранённого альманаха) — может потребоваться от нескольких минут до 10 на открытом небе.
- Тёплый/горячий старт — секунды, если на плате есть батарейка RTC, сохраняющая альманах между включениями.
- Слабый сигнал (в помещении, под крышей, за стеклом) — кадр эфемерид чаще обрывается из-за помех, декодирование “не успевает” — спутник виден, но бесполезен для Fix.
Практический вывод: если модуль долго не даёт Fix — не спеши искать баг в коде. Вынеси модуль к окну с прямым видом на небо и подожди 1–2 минуты.
6. Как читать диагностику в консоли
Прошивка выводит статус раз в 2 секунды:
[Связь: OK] [Строк: 1942, валидных: 1942] [Видно спутников: 17] [Fix: ДА, исп. в fix: 8]
Lat:48.117300,Lon:11.516700,SpeedKmh:12.3,Course:84.4,Alt:545.4,HDOP:0.9| Что смотреть | Что значит | Если плохо |
|---|---|---|
Связь: OK / !!! GPS НЕ ОТВЕЧАЕТ !!! | Идут ли байты по UART вообще | Проверь TX↔RX (см. WIRING.md), питание, пины |
Строк vs валидных | Сколько строк прошли проверку checksum | Если валидных меньше — помехи на линии |
Видно спутников | Сумма по ВСЕМ созвездиям (GPS+BeiDou+…) | 0 при “Связь: OK” — антенна/обзор неба |
Fix / исп. в fix | Декодированы ли координаты | Долго “нет” при многих видимых — см. раздел 5 |
Lat/Lon/SpeedKmh/Course/Alt/HDOP | Координаты и кинематика из getPosition() — печатаются только когда есть fix | HDOP большой (>2-3) — плохая геометрия спутников, точность ниже |
Строка Lat:.../Lon:... — машиночитаемый формат специально для
tools/gps_track_view.py, который рисует живой трек
движения (см. README.md, раздел «Визуализация»).
Светодиод дублирует уровень 1 без чтения консоли: горит ровно = связь стабильна, мигает быстро = обрыв связи с модулем.
7. Проверь себя
Постарайся ответить, не подглядывая в код, потом сверься.
Строка
$GPGGA,...,08,...*47пришла, но одна цифра в середине строки исказилась помехой. Что произойдёт в коде? Какая функция это поймает и что с строкой сделает дальшеprocess()?Ответ
Пересчитанный XOR вvalidateChecksum()не совпадёт с47._lastLineValidстанетfalse,parseSentence()для этой строки не вызовется — данные из неё нигде не используются, счётчик_validLineCountне увеличится.Консоль десять минут подряд показывает
Видно спутников: 19,Fix: нет. GPS сломан?Ответ
Скорее всего нет — см. раздел 5. Скорее всего декодирование эфемерид не успевает завершиться (слабый сигнал/холодный старт). Проверь, что антенна смотрит в открытое небо, и подожди ещё.Почему
getSatellitesInView()не может просто “взять последнее значение из последнего$..GSV”?Ответ
Потому что разные системы (GPS/GLONASS/Galileo/BeiDou) шлют отдельные наборы$..GSV, и последний пришедший — это только одна система. Нужно суммировать по всем активным талкерам, что и делает_talkerSats[].Зачем
process()выходит из функции сразу после чтения одного байта, а не читает строку целиком в циклеwhile?Ответ
Чтобы не блокироватьloop()в ожидании следующего байта — иначе вся остальная логика прошивки (диагностика, светодиод, будущий контроль моторов) встанет в ожидании GPS.Строка NMEA содержит широту
01131.000(это долгота, три цифры градусов). ПочемуparseNmeaCoordinate()не может использовать те же первые 2 цифры, что и для широты?Ответ
Потому что долгота измеряется от −180° до +180°, а широта только от −90° до +90° — долготе нужна третья цифра для трёхзначных градусов (100-180°). Функция вычисляет число цифр градусов по позиции десятичной точки (dot - fieldминус 2 цифры минут), поэтому одинаково работает и для 2-значной широты, и для 3-значной долготы.
Куда дальше
Разобрался с базовым чтением GPS? Следующий уровень — научить ровер ехать к точке по этим координатам: ROADMAP.md.
🧭 ROADMAP: от чтения GPS к автономному роверу
Этот документ — не готовый компонент, а план развития проекта. GPS-часть примера кода (шаг 6) уже реальна —
GpsNeo::getPosition()действительно парсит координаты (см.include/GpsNeo.h); компас и управление моторами остаются концептом — для реальной сборки потребуется адаптация под твоё железо.
Управление автономным ровером по координатам — классическая задача робототехники. Она разбивается на две составляющие: геометрию (куда ехать?) и алгоритм управления (как крутить моторы, чтобы туда попасть).
1. Зачем ровер GPS + компас, а не только GPS
GPS обновляется довольно редко — обычно 1 раз в секунду (1 Гц), а на низкой скорости или в момент остановки курс движения (course over ground) начинает “дрожать”: по двум близким точкам направление посчитать надёжно нельзя. Если управлять ровером только по спутникам, он будет постоянно вилять и проскакивать точки.
Поэтому нужен компас (магнитометр/гироскоп) — датчик, который знает, куда ровер повёрнут прямо сейчас, с частотой 50–100 Гц, а не 1 Гц. Комбинация “редкие, но абсолютные координаты GPS” + “частый, но дрейфующий поворот компаса” и даёт стабильное управление.
2. Математика навигации: две главные формулы
Микроконтроллер ровера каждую секунду должен знать два параметра:
- Distance — сколько метров осталось до целевой точки.
- Bearing (Target Heading) — на какой угол относительно севера нужно повернуть ровер, чтобы он смотрел прямо на цель.
2.1 Расстояние — формула гаверсинусов
Земля круглая, поэтому обычная теорема Пифагора на больших расстояниях врёт. Для робототехники используют формулу гаверсинусов (Haversine formula).
- Вход:
(Lat1, Lon1)— где ровер сейчас,(Lat2, Lon2)— куда ему надо. - Выход: расстояние в метрах.
2.2 Целевой курс — Bearing
Формула высчитывает угол от 0° (Север) до 359° по часовой стрелке — куда должен смотреть ровер, чтобы двигаться прямо на цель.
Обе формулы — в коде на шаге 6 (
getDistance(),getBearing()).
3. Компас: акселерометр и гироскоп (MPU6050)
3.1 Что измеряет MPU6050
- Акселерометр: линейное ускорение по 3 осям (
ax, ay, az), в единицахg(1 g ≈ 9.81 м/с²). - Гироскоп: угловая скорость по 3 осям (
gx, gy, gz), в °/с.
3.2 Преобразование регистров в физические величины
- Диапазон акселерометра ±2g:
raw / 16384.0→ g. - Диапазон гироскопа ±250°/с:
raw / 131.0→ °/с.
3.3 Расчёт углов по акселерометру
rollacc=atan2(ay,az)⋅π180pitchacc=atan2(−ax,ay2+az2)⋅π1803.4 Фильтрация: комплементарный фильтр
Акселерометр даёт точный угол в статике, но “шумит” при вибрации. Гироскоп даёт точный мгновенный поворот, но дрейфует со временем (ошибка накапливается). Комплементарный фильтр берёт от каждого источника только то, в чём он хорош:
θ=α(θ+ωgyroΔt)+(1−α)θaccгде α∈[0,1] (обычно ≈ 0.98 — почти полностью доверяем гироскопу, но понемногу “подтягиваем” к акселерометру, чтобы скомпенсировать дрейф).
Именно эта комбинация (GPS для абсолютных координат + комплементарный фильтр компаса для стабильного мгновенного курса) даёт ровный ход автономного ровера.
4. Архитектура железа ровера
Для стабильного движения нужна связка из трёх компонентов:
- GNSS-модуль (наш NEO-8M) — даёт абсолютные координаты. Обновляется медленно (1–5 Гц).
- Цифровой компас (магнитометр, например QMC5883L / LSM303, или связка акселерометр+гироскоп MPU6050 из раздела 3) — даёт Current Heading, куда ровер повёрнут прямо сейчас. Обновляется быстро (50–100 Гц). Без него ровер не поймёт, в какую сторону он развёрнут, пока не проедет несколько метров.
- Приводы (драйвер моторов) — выполняет команды контроллера.
5. Алгоритм управления в loop()
Логика автономного движения работает по циклу обратной связи:
[Читаем GPS] ──> Расчёт целевого курса (Bearing)
│
[Читаем компас] ──> Получение текущего курса (Heading)
│
▼
Расчёт ошибки: Error = Bearing - Heading
│
▼
Коррекция моторов (P-регулятор / PID)- Вычисляем ошибку курса. Например, целевой курс (Bearing) = 90° (Восток), компас показывает 70°. Ошибка = +20° (нужно довернуть направо). Важно: ошибку нужно нормализовать в диапазон от −180° до +180°, чтобы ровер не разворачивался на 270° направо, если можно повернуться на 90° налево.
- Применяем пропорциональный регулятор (P-регулятор). Скорость вращения должна быть пропорциональна ошибке: большая ошибка (90°) — крутимся на месте на максимальной скорости, маленькая (5°) — слегка подруливаем на ходу.
- Проверяем финиш. Если расстояние до цели стало меньше, например, 1.5–2 метров — точка достигнута, останавливаемся или переключаемся на следующую точку маршрута. Добиться точности “0 метров” на обычном GPS нельзя — из-за погрешности в 2–3 метра ровер будет бесконечно кружить вокруг точки.
6. Пример кода (концепт для ESP32)
#include <Arduino.h>
#include <Wire.h>
// Целевая точка (куда едем)
const double TARGET_LAT = 55.630340;
const double TARGET_LON = 38.068356;
// Допустимый радиус прибытия (в метрах)
const float ARRIVAL_RADIUS = 2.0;
double toRadians(double degree) { return degree * PI / 180.0; }
double toDegrees(double radian) { return radian * 180.0 / PI; }
// 1. Расчёт расстояния (Haversine)
double getDistance(double lat1, double lon1, double lat2, double lon2) {
double R = 6371000.0; // Радиус Земли в метрах
double dLat = toRadians(lat2 - lat1);
double dLon = toRadians(lon2 - lon1);
double a = sin(dLat / 2) * sin(dLat / 2) +
cos(toRadians(lat1)) * cos(toRadians(lat2)) *
sin(dLon / 2) * sin(dLon / 2);
double c = 2 * atan2(sqrt(a), sqrt(1 - a));
return R * c;
}
// 2. Расчёт целевого угла (Bearing)
double getBearing(double lat1, double lon1, double lat2, double lon2) {
double lat1Rad = toRadians(lat1);
double lat2Rad = toRadians(lat2);
double dLon = toRadians(lon2 - lon1);
double y = sin(dLon) * cos(lat2Rad);
double x = cos(lat1Rad) * sin(lat2Rad) - sin(lat1Rad) * cos(lat2Rad) * cos(dLon);
double bearing = toDegrees(atan2(y, x));
if (bearing < 0) bearing += 360.0;
return bearing;
}
// Имитация чтения компаса (в реальности — опрос датчика, см. раздел 3)
float getCompassHeading() {
return 45.0; // Допустим, ровер сейчас смотрит на Северо-Восток
}
void driveMotors(int forwardSpeed, int turnSpeed) {
// Логика управления драйвером моторов (например, L298N или BTS7960)
// forwardSpeed - езда вперёд, turnSpeed - разность скоростей бортов для поворота
}
// GpsNeo gps(Serial2); — уже реализовано и парсит координаты по-настоящему
// (не концепт): gps.getPosition() отдаёт latitude/longitude/speedKmh/courseDeg,
// см. include/GpsNeo.h. Ниже — как использовать это в цикле ровера.
extern GpsNeo gps;
void loop() {
const Position &pos = gps.getPosition();
if (!pos.valid) return; // ждём валидный fix, см. GUIDE.md, раздел 5
double currentLat = pos.latitude;
double currentLon = pos.longitude;
double distance = getDistance(currentLat, currentLon, TARGET_LAT, TARGET_LON);
if (distance < ARRIVAL_RADIUS) {
Serial.println("Точка достигнута! Стоп.");
driveMotors(0, 0); // Тормозим
return;
}
double targetHeading = getBearing(currentLat, currentLon, TARGET_LAT, TARGET_LON);
double currentHeading = getCompassHeading();
// Считаем ошибку курса
double headingError = targetHeading - currentHeading;
// Нормализация угла в диапазон [-180, 180]
if (headingError > 180) headingError -= 360;
if (headingError < -180) headingError += 360;
// Пропорциональный коэффициент подруливания (настраивается экспериментально)
float Kp = 2.5;
int turnSpeed = headingError * Kp;
// Ограничиваем скорость поворота
turnSpeed = constrain(turnSpeed, -100, 100);
// Едем вперёд и подруливаем
int forwardSpeed = 150; // Базовая скорость хода вперёд
driveMotors(forwardSpeed, turnSpeed);
delay(100);
}7. Проблемы, с которыми ты столкнёшься, и как их решать
- Помехи на компас от моторов. Силовые провода и сами моторы создают сильные магнитные поля. Компас нужно выносить на высокой немагнитной мачте (как на квадрокоптерах), как можно дальше от аккумулятора и двигателей.
- GPS “скачет” на месте. Когда ровер стоит, координаты немного “плывут”. Из-за этого вычисленный
targetHeadingначнёт хаотично прыгать. Решение: если скорость ровера равна 0 или расстояние до цели слишком мало — запретить обновление целевого курса. - Инерция. Ровер тяжёлый и поворачивает не сразу. Если он проскакивает нужный угол и начинает вилять (осциллировать) — нужно уменьшать коэффициент
Kpили переходить на полноценный PID-регулятор (добавить дифференциальную составляющую).
| |
| |
| |