Замість вступу
У цій статті описано базовий принцип роботи GNSS, розглянуто можливості приймача uBlox ZED F9P а також пропрієтарного протоколу uBlox - UBX. Посилання на документацію uBlox (Integration manual та Interface description) на які я буду посилатися у цій статті, можна знайти тут.
Кілька слів про GNSS
GNSS — Global Navigation Satellite System, або Глобальна Навігаційна Супутникова Система. Не слід плутати з GPS, хоча в не предметній розмові ці поняття часто вживаються як тотожні. GNSS - загальне позначення усіх супутників позиціонування. В свою чергу супутники відносяться до різних сузір’їв - як GPS (США), Galileo (Європа), ГЛОНАСС (московія), BeiDou (Китай), QZSS (Японія) та NAVIC (Індія).
Дуже коротко про принцип роботи
Супутник GNSS випромінює сигнал в сторону Землі, який приймається та обробляється GNSS приймачем. Приймач може вирахувати свої координати, якщо бачить сигнали одночасно від мінімум 4ох різних супутників.
Сигнал може випромінюватися на різних частотах, один супутник може працювати одразу на декількох частотах. GNSS використовує частоти L-діапазону - частоти від 1 ГГц до 2 ГГц.
Частоти, які використовують супутники GNSS, можна знайти за цим посиланням.
Приймач uBlox ZED F9P
uBlox ZED F9P — дводіапазонний GNSS приймач професійного рівня.

Може надавати координати сантиметрової точності, може працювати в режимі RTK (виходить за рамки цієї статті). З інтерфейсів має 2 UART і по одному SPI, I2C та USB. Працює з протоколами NMEA (увімкнено за замовчуванням), UBX, RTCM та SPARTN (в останніх версіях софту). У цій статті ми розглянемо протокол UBX.
Кілька слів про протокол NMEA
NMEA — National Marine Electronics Association. Це стандарт, який визначає характеристики протоколу обміну даними між різними пристроями морського електронного обладнання, і який став дуже популярним серед GNSS приймачів. Це ASCII протокол, тому його дуже легко аналізувати просто дивлячись у термінал, але використовувати його для отримання даних від приймача в програмі, наприклад під мікроконтролер - не найкраща ідея.
Протокол UBX
Бінарний пропрієтарний протокол, розвитком якого займається компанія uBlox. Повідомлення передаються у вигляді потоку байтів, а тому очіма в текстовому вигляді в терміналі без попередньої обробки сприймати його неможливо.
Повідомлення складається з заголовку, корисних даних та чексуми.
Заголовок повідомлення містить преамбулу (0xb5 0x62), айді класу та повідомлення (class ID, message ID) та довжину корисної інформації.

Такий заголовок можна описати у вигляді структури на мові С наступним чином
typedef struct {
uint8_t sync1;
uint8_t sync2;
uint8_t class_id;
uint8_t msg_id;
uint16_t payload_len;
} __attribute__((packed)) ubx_header_t;
Зверніть увагу на __attribute__((packed))
- структура має бути упакованою, щоб компілятор не зробив додаткових відступів між полями.
Кілька слів про Class ID та Message ID
Типи повідомлень протоколу UBX поділені на класи (Class ID), які в свою чергу поділені на підкласи (Message ID).
Наприклад, клас навігаційних повідомлень NAV - всі повідомлення, які мають class ID 0x01. Клас NAV має різні типи повідомлень - про статус приймача, про координати, про координати з високою точністю, про азімут, ітд. Це все - навігаційні дані, але різного типу. Вони відрізняються між собою по Message ID.
Висновок - Message ID це не ідентифікатор кожного окремого отриманного повідомлення. Це ідентифікатор підтипу повідомлення в середині загального класу.
Кілька прикладів Class ID - CFG (налаштування), NAV (навігація), MON (моніторинг), LOG (логування).
Чексума
Розрахунок чексуми описано в файлі Interface Description в розділі 3.4. Чексума розраховується від поля Class включно, до останнього байта пейлоада включно.
Наведено наступний псевдо-код, де N - довжина корисних даних в байтах (Length або payload_len), а Buffer - масив цих даних.
CK_A = 0, CK_B = 0
For (I = 0; I < N; I++)
{
CK_A = CK_A + Buffer[I]
CK_B = CK_B + CK_A
}
Отримання координат
Працюючи з протоколом UBX є два способи отримувати поточні координати від модуля - у відповіді на запит відповідного меседжа (class ID + message ID) або слухати інтерфейс на наявність регулярних повідомлень (налаштовується додатково).

Особисто я використовую UBX-NAV-PVT як основний меседж для отримання координат, оскільки він містить не лише координати, а також інформацію про час, статус модуля (чи отримано стабільний сигнал, for ex.), вектор руху, швидкість, інформацію про точність.
Повний опис пейлоаду можна знайти у документі Interface Description.pdf
Запит потрібного меседжа
Якщо треба запитати якусь інформацію у модуля, треба відправити йому повідомлення із заголовком UBX, в якому вказані Class та ID, але довжина вказана 0 (нуль), після чого додана чексума.
У відповідь прийде меседж з такими ж Class та ID, але не нульовою довжиною та наявним пейлоадом.
Приклад запиту UBX-NAV-PVT
Уявімо, що у нас є Raspberry Pi 5, до UART якої підключено UART1 модуля uBlox ZED F9P. Тоді для відправки запиту і отримання відповіді потрібен приблизно наступний код.
typedef struct {
uint8_t sync1;
uint8_t sync2;
uint8_t class_id;
uint8_t msg_id;
uint16_t payload_len;
} __attribute__((packed)) ubx_header_t;
typedef struct {
/* попередні поля */
int32_t lon; /* longitude 1e^-7 */
int32_t lat; /* latitude 1e^-7 */
/* наступні поля */
} __attribute__((packed)) ubx_nav_pvt_t;
#define CHECKSUM_LEN 2
/* заповнена структура ubx_header_t + чексума, щоб не розтягувати приклад */
uint8_t request[] = {0xb5, 0x62, 0x01, 0x07, 0x00, };
uint8_t response[128] = {0};
ubx_nav_pvt_t *pvt = (ubx_nav_pvt_t *)(response + sizeof(ubx_header_t));
int fd = open("/dev/ttyS0"); /* відкриваємо інтерфейс, до якого підключено модуль GNSS */
/* відравляємо запит в УАРТ */
write(fd, request, sizeof(request));
/* читаємо відповідь з УАРТу */
read(fd, response, sizeof(ubx_header_t) + sizeof(ubx_nav_pvt_t) + CHECKSUM_LEN );
close(fd); /* закриваємо інтерфейс */
/* виводимо довготу та широту в термінал */
printf("lon %.7f lat %.7f\r\n",
(float)pvt->lon * 1e-7,
(float)pvt->lat * 1e-7
);
Регулярні повідомлення
У версіях протоколу, старіших за 23.01 можна було використати меседж UBX-CFG-MSG для налаштування регулярності відправки потрібного повідомлення, вказавши Class, ID та частоту.

У версіях протоколу від 23.01 і новіше треба використовувати процедуру налаштування модуля UBX-CFG. Ця процедура буде детально розглянута у наступній статті.