Kontekst AI — Perun BLE Protocol
Plik przeznaczony dla AI pomagającego pisać aplikację kliencką komunikującą się z urządzeniami MWT.
Zawiera pełny opis protokołu BLE: serwis Perun, ramki danych (Data Frame) dla wszystkich modułów, protokół Control (SET/GET), klucze, kalibracje i kody błędów. Firmware dostarcza producent — ten dokument opisuje wyłącznie interfejs po stronie klienta (Android, iOS, Web, PC).
Pobierz plik .md :material-download:
1. BLE — Serwis Perun
Urządzenia MWT eksponują serwis Perun z czterema charakterystykami:
| Charakterystyka | UUID | Właściwości |
|---|---|---|
| Service | 457bbb14-9c79-44a8-9810-f17bd358a200 |
— |
| Data | 457bbb14-9c79-44a8-9810-f17bd358a201 |
Notify |
| Control | 457bbb14-9c79-44a8-9810-f17bd358a202 |
Write + Indicate |
| Device Info | 457bbb14-9c79-44a8-9810-f17bd358a203 |
Read |
| RTCM Stream | 457bbb14-9c79-44a8-9810-f17bd358a204 |
Write Without Response |
Dodatkowo dostępny jest standardowy Battery Service (BAS):
| Charakterystyka | UUID | Właściwości |
|---|---|---|
| BAS Service | 0000180f-0000-1000-8000-00805f9b34fb |
— |
| Battery Level | 00002a19-0000-1000-8000-00805f9b34fb |
Read + Notify |
Varianty urządzeń (z Device Info):
| Wartość | Wariant | Typowe moduły |
|---|---|---|
0x01 |
MWT Base Station | GNSS, Barometer (×4), Battery |
0x02 |
MWT Body Module | AccGyro, Barometer, Battery, GNSS, Magnetometer, Quaternion |
0x03 |
MWT ETU | AccGyro, Battery, Selector, Trigger, Kickback |
0x04 |
MWT Head Module | AccGyro, Barometer, Battery, Magnetometer, Quaternion |
2. Device Info (Charakterystyka a203 — READ)
Po połączeniu odczytaj tę charakterystykę. Payload w formacie TLV (Type-Length-Value):
| Type | Nazwa | Opis |
|---|---|---|
0x01 |
MODULES_AVAILABLE | Tablica 1B ID dostępnych modułów |
0x02 |
VARIANT_ID | uint8_t — wariant urządzenia |
0x03 |
FW_VERSION | 3B: [major][minor][patch] |
Przykład — MWT Body Module, firmware 2.1.0:
01 06 02 03 04 06 09 0A ← MODULES_AVAILABLE: AccGyro(2),Baro(3),Bat(4),Mag(6),Quat(9),UI(10)
02 01 02 ← VARIANT_ID: Body Module
03 03 02 01 00 ← FW_VERSION: 2.1.0
3. Identyfikatory modułów
| ID | Stała | Moduł | Sensor |
|---|---|---|---|
0x01 |
MODULE_SYSTEM | System | — |
0x02 |
MODULE_ACC_GYRO | Akcelerometr + Żyroskop | LSM6DSV32X |
0x03 |
MODULE_BAROMETER | Barometr | BMP581 |
0x04 |
MODULE_BATTERY | Napięcie baterii | ADC |
0x05 |
MODULE_GNSS | GNSS / RTK | ZED-F9P |
0x06 |
MODULE_MAGNETOMETER | Magnetometr | MMC5983MA |
0x07 |
MODULE_SELECTOR | Selektor trybu ognia | TMAG5273C1 |
0x08 |
MODULE_TRIGGER | Trigger | TMAG5273C1 |
0x09 |
MODULE_QUATERNION | Fuzja orientacji | (software) |
0x0A |
MODULE_PLAYER_STATE | Stan gracza | — |
0x0B |
MODULE_UI | UI | — |
0x0C |
MODULE_KICKBACK | Kickback | — |
0x0D |
MODULE_DISPLACEMENT | Estymacja przemieszczenia | (software) |
4. Data Frame — Ramka danych (Charakterystyka a201 — NOTIFY)
Urządzenie wysyła ramki notify gdy moduł jest włączony i aktywny. Każda ramka = 12B header + payload (maks. 232B payload, łącznie maks. 244B przy MTU=247).
Header (12B)
| Offset | Rozmiar | Pole | Format | Opis |
|---|---|---|---|---|
| 0 | 1B | module_id |
uint8_t |
ID modułu źródłowego |
| 1 | 1B | flags |
uint8_t |
Flagi (patrz niżej) |
| 2 | 1B | frame_number |
uint8_t |
Numer ramki (0–255, wrap) |
| 3 | 1B | sample_count |
uint8_t |
Liczba próbek w payloadzie |
| 4 | 5B | timestamp_first |
uint40_t LE |
Znacznik czasu pierwszej próbki [µs] |
| 9 | 3B | timestamp_delta |
uint24_t LE |
Różnica czasu do ostatniej próbki [µs] |
Flagi ramki:
| Bit | Maska | Nazwa | Opis |
|---|---|---|---|
| 7 | 0x80 |
FLAG_ERROR |
Ramka błędu — payload to 9B error_t (patrz sekcja 5) |
| 0 | 0x01 |
Moduł-specyficzna | Znaczenie zależy od modułu (patrz opisy modułów) |
Pomocnik — int24 little-endian (TypeScript)
function readInt24LE(view: DataView, offset: number): number {
const lo = view.getUint8(offset);
const mi = view.getUint8(offset + 1);
const hi = view.getInt8(offset + 2); // sign-extended
return lo | (mi << 8) | (hi << 16);
}
5. Ramka błędu (FLAG_ERROR = 1)
Gdy flags & 0x80 != 0, payload = 9B struktura error_t:
| Offset | Rozmiar | Pole | Format | Opis |
|---|---|---|---|---|
| 0 | 1B | level |
uint8_t |
Poziom błędu |
| 1 | 1B | source |
uint8_t |
Moduł źródłowy |
| 2 | 1B | reason |
uint8_t |
Kod przyczyny |
| 3 | 1B | instance |
uint8_t |
Instancja (np. numer sensora) |
| 4 | 1B | sensor_model |
uint8_t |
Model sensora (opcjonalne) |
| 5 | 1B | detail_type |
uint8_t |
Typ detalu |
| 6 | 3B | detail_value |
uint24_t LE |
Wartość detalu |
6. Payloady modułów
6.1 ACC_GYRO (ID = 2) — Akcelerometr + Żyroskop
Rozmiar próbki: 18B (6 osi × 3B int24 little-endian signed)
| Offset | Rozmiar | Pole | Jednostka | Przelicznik |
|---|---|---|---|---|
| 0 | 3B | acc_x |
deci-milli-g | ÷ 10000 → g |
| 3 | 3B | acc_y |
deci-milli-g | ÷ 10000 → g |
| 6 | 3B | acc_z |
deci-milli-g | ÷ 10000 → g |
| 9 | 3B | gyro_x |
milli-°/s | ÷ 1000 → °/s |
| 12 | 3B | gyro_y |
milli-°/s | ÷ 1000 → °/s |
| 15 | 3B | gyro_z |
milli-°/s | ÷ 1000 → °/s |
Rozdzielczość: acc = 0.1 mg (0.0001 g), gyro = 1 milli-°/s (0.001 °/s). Domyślne zakresy: ±32 g, ±1000 dps. ODR domyślne: 240 Hz.
function parseAccGyroSample(view: DataView, offset: number) {
return {
accX: readInt24LE(view, offset + 0) / 10000, // g
accY: readInt24LE(view, offset + 3) / 10000,
accZ: readInt24LE(view, offset + 6) / 10000,
gyroX: readInt24LE(view, offset + 9) / 1000, // °/s
gyroY: readInt24LE(view, offset + 12) / 1000,
gyroZ: readInt24LE(view, offset + 15) / 1000,
};
}
6.2 BAROMETER (ID = 3) — Barometr
Flags bit 0 = MULTI_SENSOR: 0 = pojedynczy czujnik (5B/próbka), 1 = 4 czujniki (20B/próbka)
Pojedynczy czujnik — 5B/próbka:
| Offset | Rozmiar | Pole | Jednostka | Przelicznik |
|---|---|---|---|---|
| 0 | 3B | pressure |
centipaskal [cPa] | ÷ 100 → hPa |
| 3 | 2B | temperature |
0.01°C | ÷ 100 → °C |
pressure = uint24_t LE (unsigned), temperature = int16_t LE (signed).
4 czujniki (Base Station) — 20B/próbka: 4× powtórzony format 5B.
6.3 BATTERY (ID = 4) — Napięcie baterii
Rozmiar próbki: 2B
| Offset | Rozmiar | Pole | Format | Opis |
|---|---|---|---|---|
| 0 | 2B | voltage |
uint16_t LE |
Napięcie [mV] |
6.4 GNSS (ID = 5) — Pozycja GPS/RTK
Rozmiar próbki: 55B (gnss_pvt_data_t, little-endian)
| Offset | Rozmiar | Pole | Format | Opis |
|---|---|---|---|---|
| 0 | 4B | lat |
int32_t LE |
Szerokość geogr. [×10⁻⁷ °] |
| 4 | 4B | lon |
int32_t LE |
Długość geogr. [×10⁻⁷ °] |
| 8 | 4B | alt_msl |
int32_t LE |
Wysokość nad poziomem morza [mm] |
| 12 | 4B | vel_n |
int32_t LE |
Prędkość North [mm/s] |
| 16 | 4B | vel_e |
int32_t LE |
Prędkość East [mm/s] |
| 20 | 4B | vel_d |
int32_t LE |
Prędkość Down [mm/s] |
| 24 | 4B | head_mot |
int32_t LE |
Kierunek ruchu [×10⁻⁵ °] |
| 28 | 4B | h_acc |
uint32_t LE |
Dokładność pozioma [mm] |
| 32 | 4B | v_acc |
uint32_t LE |
Dokładność pionowa [mm] |
| 36 | 4B | geoid_sep |
int32_t LE |
Separacja geoidy [mm] |
| 40 | 4B | utc_nano |
int32_t LE |
Ułamek sekundy UTC [ns] |
| 44 | 2B | pdop |
uint16_t LE |
pDOP [×0.01] |
| 46 | 2B | hdop |
uint16_t LE |
hDOP [×0.01] |
| 48 | 1B | fix_type |
uint8_t |
0=none, 2=2D, 3=3D, 4=GNSS, 5=Time |
| 49 | 1B | num_sv |
uint8_t |
Liczba satelitów |
| 50 | 1B | flags |
uint8_t |
bit0=gnssFixOK, bit1=diffSoln, bit2–3=carrSoln (0=none,1=float,2=fixed) |
| 51 | 1B | utc_hour |
uint8_t |
Godzina UTC |
| 52 | 1B | utc_min |
uint8_t |
Minuty UTC |
| 53 | 1B | utc_sec |
uint8_t |
Sekundy UTC |
| 54 | 1B | gnss_state |
uint8_t |
Stan wewnętrzny modemu GNSS |
6.5 MAGNETOMETER (ID = 6) — Magnetometr
Rozmiar próbki: 9B (3 osie × 3B int24 little-endian signed)
Flags bit 0 = CALIBRATED: 0 = dane raw (bez korekcji hard/soft iron), 1 = dane skalibrowane.
| Offset | Rozmiar | Pole | Jednostka | Przelicznik |
|---|---|---|---|---|
| 0 | 3B | mag_x |
nanoTesla | ÷ 1000 → µT |
| 3 | 3B | mag_y |
nanoTesla | ÷ 1000 → µT |
| 6 | 3B | mag_z |
nanoTesla | ÷ 1000 → µT |
Rozdzielczość: 1 nT (0.001 µT). Typowe ziemskie pole: 40 000–60 000 nT.
function parseMagSample(view: DataView, offset: number, flags: number) {
return {
x_uT: readInt24LE(view, offset + 0) / 1000,
y_uT: readInt24LE(view, offset + 3) / 1000,
z_uT: readInt24LE(view, offset + 6) / 1000,
isCalibrated: (flags & 0x01) !== 0,
};
}
6.6 SELECTOR (ID = 7) — Selektor trybu ognia
Rozmiar próbki: 1B. Ramka wysyłana przy każdej zmianie pozycji.
| Wartość | Pozycja |
|---|---|
1 |
SAFE |
2 |
SEMI |
3 |
AUTO |
6.7 TRIGGER (ID = 8) — Trigger
Rozmiar próbki: 1B. Ramka wysyłana przy każdej zmianie stanu.
| Wartość | Stan |
|---|---|
1 |
PRESSED |
2 |
RELEASED |
6.8 DISPLACEMENT (ID = 13) — Estymacja przemieszczenia
Rozmiar próbki: 12B (3 osie × 4B int32 little-endian signed)
| Offset | Rozmiar | Pole | Jednostka | Przelicznik |
|---|---|---|---|---|
| 0 | 4B | pos_x |
mm | ÷ 1000 → m |
| 4 | 4B | pos_y |
mm | ÷ 1000 → m |
| 8 | 4B | pos_z |
mm | ÷ 1000 → m |
Pozycja jest względna do ostatniego resetu (KEY_DISP_RESET). Rozdzielczość: 1 mm.
function parseDisplacementSample(view: DataView, offset: number) {
return {
x_m: view.getInt32(offset + 0, true) / 1000,
y_m: view.getInt32(offset + 4, true) / 1000,
z_m: view.getInt32(offset + 8, true) / 1000,
};
}
6.9 QUATERNION (ID = 9) — Fuzja orientacji (Madgwick)
Rozmiar próbki: 9B (4 komponenty Q15 + bajt statusu)
| Offset | Rozmiar | Pole | Format | Opis |
|---|---|---|---|---|
| 0 | 2B | w |
int16_t LE |
Format Q1.15: ÷ 32768 → float |
| 2 | 2B | x |
int16_t LE |
|
| 4 | 2B | y |
int16_t LE |
|
| 6 | 2B | z |
int16_t LE |
|
| 8 | 1B | status |
uint8_t |
Bitmaska statusu fuzji (patrz niżej) |
Bajt statusu fuzji:
| Bit | Maska | Nazwa | Znaczenie |
|---|---|---|---|
| 0 | 0x01 |
MARG |
1 = tryb MARG (mag+acc+gyro); 0 = IMU (acc+gyro only) |
| 1 | 0x02 |
MAG_ENABLED |
1 = magnetometr aktywny (próbka <2 s temu) |
| 2 | 0x04 |
MAG_CALIBRATED |
1 = skalibrowane próbki mag odbierane |
| 3 | 0x08 |
MAG_VALID |
1 = norma pola w zakresie 40–60 µT |
| 4 | 0x10 |
MAG_FRESH |
1 = ostatnia skalibrowana próbka <30 ms temu |
Diagnostyka gdy MARG=0:
| MAG_ENABLED | MAG_CALIBRATED | MAG_VALID | MAG_FRESH | Przyczyna |
|---|---|---|---|---|
| 0 | — | — | — | Magnetometr wyłączony |
| 1 | 0 | — | — | Brak kalibracji magnetometru |
| 1 | 1 | 0 | — | Zakłócenie pola (norma poza 40–60 µT) |
| 1 | 1 | 1 | 0 | Dane nieświeże (>30 ms) |
Yaw z kwaternionu (tylko gdy MARG=1 — odniesiony do północy magnetycznej):
function parseQuaternionSample(view: DataView, offset: number) {
const toFloat = (raw: number) => raw / 32768;
return {
w: toFloat(view.getInt16(offset + 0, true)),
x: toFloat(view.getInt16(offset + 2, true)),
y: toFloat(view.getInt16(offset + 4, true)),
z: toFloat(view.getInt16(offset + 6, true)),
status: view.getUint8(offset + 8),
};
}
function getYawDeg(q: { w: number; x: number; y: number; z: number }): number {
const yawRad = Math.atan2(2 * (q.w * q.z + q.x * q.y),
1 - 2 * (q.y * q.y + q.z * q.z));
return yawRad * 180 / Math.PI;
}
7. Protokół Control (Charakterystyka a202 — WRITE + INDICATE)
Klient wysyła komendy Write na charakterystykę Control. Urządzenie odpowiada Indicate na tę samą charakterystykę.
Format SET (Klient → Urządzenie)
| Offset | Rozmiar | Pole | Opis |
|---|---|---|---|
| 0 | 1B | transaction_id |
Dowolna wartość 0–255, echo w odpowiedzi |
| 1 | 1B | module_id |
ID modułu docelowego |
| 2 | 1B | command |
1 = SET |
| 3 | 1B | key |
Klucz parametru |
| 4+ | 0–239B | value |
Wartość (zależy od klucza) |
Format SET Reply (Urządzenie → Klient)
| Offset | Rozmiar | Pole | Opis |
|---|---|---|---|
| 0–3 | 4B | nagłówek | Echo z komendy |
| 4 | 1B | status |
Kod statusu (patrz niżej) |
Format GET (Klient → Urządzenie)
Bez pola value.
Format GET Reply (Urządzenie → Klient)
| Offset | Rozmiar | Pole | Opis |
|---|---|---|---|
| 0–3 | 4B | nagłówek | Echo z komendy |
| 4 | 1B | status |
Kod statusu |
| 5+ | 0–239B | value |
Wartość (tylko gdy status = OK) |
Kody statusu Control
| Wartość | Nazwa | Opis |
|---|---|---|
0x01 |
OK | Sukces |
0x02 |
UNKNOWN_ERROR | Nieznany błąd |
0x03 |
NOT_CALIBRATED | Brak kalibracji |
0x04 |
UNKNOWN_COMMAND | Nieznana komenda |
0x05 |
UNSUPPORTED_KEY | Klucz nieobsługiwany przez ten moduł |
0x06 |
INVALID_VALUE | Nieprawidłowa wartość |
0x07 |
BUSY | Moduł zajęty (np. kalibracja w toku) |
0x08 |
NOT_READY | Nie gotowy (brak wymaganych danych) |
0x09 |
HARDWARE_ERROR | Błąd sprzętowy |
8. Klucze Control — Uniwersalne (0x01–0x2F)
Identyczna semantyka niezależnie od modułu.
Lifecycle (0x01–0x0F)
| Key | Wartość | Typ | Rozmiar value | Opis |
|---|---|---|---|---|
KEY_CTRL_INFO |
0x01 |
GET | — | Informacje o module: nazwa modelu + TLV obsługiwanych ODR/zakresów. |
KEY_CTRL_ENABLE |
0x02 |
SET/GET | 1B uint8_t |
1 = włącz moduł, 0 = wyłącz. |
KEY_CTRL_INFO — format odpowiedzi:
Każdy rekord TLV:[key (1B)][count (1B)][value[0]..value[count-1] (2B each, uint16 LE)]
Przykładowe klucze w TLV: 0x10 = dostępne ODR, 0x11 = dostępne zakresy Range 1, 0x12 = zakresy Range 2, 0x14 = dostępne bandwidth filter.
Konfiguracja (0x10–0x1F)
| Key | Wartość | Typ | Rozmiar value | Opis |
|---|---|---|---|---|
KEY_CFG_ODR |
0x10 |
SET/GET | 2B uint16_t LE |
Częstotliwość próbkowania [Hz] |
KEY_CFG_MEASURE_RANGE_1 |
0x11 |
SET/GET | 2B uint16_t LE |
Zakres pomiaru 1 (np. acc [g]) |
KEY_CFG_MEASURE_RANGE_2 |
0x12 |
SET/GET | 2B uint16_t LE |
Zakres pomiaru 2 (np. gyro [dps]) |
KEY_CFG_OVERSAMPLING |
0x13 |
SET/GET | 2B uint16_t LE |
Oversampling ratio |
KEY_CFG_FILTER |
0x14 |
SET/GET | 2B uint16_t LE |
Bandwidth filtra [Hz] |
KEY_CFG_RAW_MODE |
0x15 |
SET/GET | 1B uint8_t |
0 = dane kalibrowane, 1 = surowe. SET 0 bez kalibracji → NOT_READY. |
Kalibracja (0x20–0x2F)
| Key | Wartość | Typ | Rozmiar value | Opis |
|---|---|---|---|---|
KEY_CALIB_START |
0x20 |
SET | 0B | Rozpocznij pomiar kalibracyjny. BUSY jeśli trwa. |
KEY_CALIB_ABORT |
0x21 |
SET | 0B | Przerwij aktywny pomiar. |
KEY_CALIB_CAPTURE |
0x22 |
SET | — | (Nieużywane przez większość modułów) |
KEY_CALIB_COMMIT |
0x23 |
SET | 0B | Zatwierdź punkt — zapisz do NVS. NOT_READY jeśli brak danych. |
KEY_CALIB_GET_BLOB |
0x24 |
GET | — | Odczytaj blob kalibracyjny (rozmiar zależy od modułu) |
KEY_CALIB_SET_BLOB |
0x25 |
SET | (rozmiar bloba) | Importuj blob kalibracyjny. Auto-save do NVS. |
KEY_CALIB_STATUS |
0x26 |
GET | — | Status kalibracji (format zależy od modułu) |
9. Klucze Control — Moduł-specyficzne (0x80–0xFF)
Wartości 0x80+ mają różne znaczenia dla różnych modułów — rozróżnia je pole module_id.
ACC_GYRO (module_id = 2)
| Key | Wartość | Typ | Rozmiar value | Opis |
|---|---|---|---|---|
KEY_AG_GET_TEMPERATURE |
0x80 |
GET | 2B reply | Temperatura chipa IMU: int16_t LE [decidegC = 0.1°C] |
KEY_AG_DELETE_CALIB_POINT |
0x81 |
SET | 2B | Usuń punkt kalibracyjny: T = int16_t LE [decidegC]. Brak punktu → INVALID_VALUE. |
GNSS (module_id = 5)
| Key | Wartość | Typ | Rozmiar value | Opis |
|---|---|---|---|---|
KEY_GNSS_UPDATE_RATE |
0x80 |
SET/GET | 2B uint16_t LE |
Częstotliwość aktualizacji [Hz] |
KEY_GNSS_DYNAMIC_MODEL |
0x81 |
SET/GET | 1B uint8_t |
Model dynamiki odbiornika |
TRIGGER (module_id = 8)
| Key | Wartość | Typ | Opis |
|---|---|---|---|
KEY_TRIGGER_SENSITIVITY |
0x80 |
SET/GET | Czułość triggera |
KEY_TRIGGER_MODE |
0x81 |
SET/GET | Tryb detekcji |
KEY_TRIGGER_BASES |
0x82 |
SET/GET | Baza pomiaru |
MAGNETOMETER (module_id = 6)
| Key | Wartość | Typ | Rozmiar value | Opis |
|---|---|---|---|---|
KEY_MAG_NORM_TOL |
0x80 |
SET/GET | 2B uint16_t LE |
Tolerancja normy pola [nT]. Domyślnie 10000 (10 µT). Okno akceptacji: [norm_ref − tol, norm_ref + tol]. SET → NVS. |
KEY_MAG_NORM_REF |
0x81 |
GET | 4B uint32_t LE |
Bieżąca norma referencyjna [nT]. Domyślnie 50000 (50 µT). Aktualizowana automatycznie po imporcie kalibracji. |
SELECTOR (module_id = 7)
| Key | Wartość | Typ | Opis |
|---|---|---|---|
KEY_SELECTOR_HYSTERESIS |
0x80 |
SET/GET | Histereza detekcji pozycji (uint8_t, wartość Manhattan |dy|+|dz|) |
KEY_SELECTOR_RAW_DATA |
0x81 |
GET | Bieżące surowe dane TMAG: value[0] = y (int8_t), value[1] = z (int8_t). Wymaga MODULE_ENABLE=true. |
KICKBACK (module_id = 12)
| Key | Wartość | Typ | Opis |
|---|---|---|---|
KEY_KICKBACK_TIMING |
0x80 |
SET/GET | Parametry czasowe: value[0] = on_time_ms (uint8_t), value[1] = off_time_ms (uint8_t) |
KEY_KICKBACK_TEST |
0x81 |
SET | Uruchom jeden cykl testowy (brak value). BUSY jeśli cykl w toku. |
DISPLACEMENT (module_id = 13)
| Key | Wartość | Typ | Rozmiar value | Opis |
|---|---|---|---|---|
KEY_DISP_RESET |
0x80 |
SET | 0B | Reset KF: pozycja → 0, prędkość → 0, acc_bias → 0. Nowe origin = bieżąca pozycja. |
KEY_DISP_SOFT_R |
0x81 |
SET/GET | 2B uint16_t LE |
Siła soft constraint prędkości. Jednostka: 0.1 m²/s². 0 = wyłączony. Domyślnie 40 (= 4.0 m²/s²). Większe → słabsza korekcja. Zapisywane do NVS. |
10. Kalibracja biasu żyroskopu (ACC_GYRO — wielopunktowa temperaturowa)
Firmware obsługuje do 10 punktów kalibracyjnych, posortowanych rosnąco po temperaturze. Interpolacja liniowa między punktami, ekstrapolacja poza zakres (slope z krawędziowych punktów). Kalibracja jest przezroczysta — firmware automatycznie odejmuje bias od danych żyroskopu.
Kiedy kalibrować
Mierz w różnych temperaturach (minimalna odległość: 5°C między punktami, weryfikacja po stronie klienta). Urządzenie powinno leżeć nieruchomo przez cały czas pomiaru (~20 s).
Sekwencja dodania punktu
1. Ustabilizuj temperaturę
2. Control SET: KEY_CALIB_START (module_id=2, brak value)
3. Polluj Control GET: KEY_CALIB_STATUS co ~500 ms
→ sprawdź byte[1] (postęp 0–100%)
→ sprawdź byte[2-3] (bieżąca temperatura)
4. Gdy byte[1] = 100:
Control SET: KEY_CALIB_COMMIT (module_id=2, brak value)
5. Control GET: KEY_CALIB_GET_BLOB → odśwież listę punktów w UI
KEY_CALIB_STATUS — format odpowiedzi GET (4–16B)
| Offset | Rozmiar | Pole | Opis |
|---|---|---|---|
| 0 | 1B | count |
Liczba zatwierdzonych punktów (0–10) |
| 1 | 1B | byte1 |
Jeśli pomiar aktywny: postęp 0–100%. Jeśli nieaktywny: raw_mode (0/1). |
| 2 | 2B | T_now |
Temperatura chipa: int16_t LE [decidegC] |
| 4 | 12B | avg_bias |
Opcjonalne — tylko gdy pomiar aktywny i są próbki. 3× int32_t LE [milli-°/s]: bias X, Y, Z |
Blob kalibracyjny — format (164B)
Struktura ag_calib_nvs_t — serializacja do/z NVS przez GET_BLOB/SET_BLOB:
| Offset | Rozmiar | Pole | Format | Opis |
|---|---|---|---|---|
| 0 | 1B | count |
uint8_t |
Liczba ważnych punktów (0–10) |
| 1 | 3B | reserved |
— | Ignorować |
| 4 | 160B | points[10] |
patrz niżej | Tablica 10 punktów (indices ≥ count zawierają zera) |
Każdy punkt (ag_bias_point_t, 16B):
| Offset w punkcie | Rozmiar | Pole | Format | Opis |
|---|---|---|---|---|
| 0 | 2B | T |
int16_t LE |
Temperatura [decidegC] |
| 2 | 2B | _pad |
— | Wyrównanie — ignorować |
| 4 | 4B | bias_x |
int32_t LE |
Bias osi X [milli-°/s] |
| 8 | 4B | bias_y |
int32_t LE |
Bias osi Y [milli-°/s] |
| 12 | 4B | bias_z |
int32_t LE |
Bias osi Z [milli-°/s] |
Punkty zawsze posortowane rosnąco po T.
interface GyroBiasPoint {
tempDegC: number;
biasX_dps: number;
biasY_dps: number;
biasZ_dps: number;
}
function parseGyroBiasBlob(data: DataView): GyroBiasPoint[] {
const count = Math.min(data.getUint8(0), 10);
const points: GyroBiasPoint[] = [];
for (let i = 0; i < count; i++) {
const base = 4 + i * 16;
points.push({
tempDegC: data.getInt16(base, true) / 10,
biasX_dps: data.getInt32(base + 4, true) / 1000,
biasY_dps: data.getInt32(base + 8, true) / 1000,
biasZ_dps: data.getInt32(base + 12, true) / 1000,
});
}
return points;
}
Usunięcie punktu kalibracyjnego
Przykład: usuń punkt 25.0°C (= 250 decidegC):
Odczyt temperatury IMU (bez kalibracji)
11. Kalibracja magnetometru (hard-iron + soft-iron)
Kalibracja wykonywana jest po stronie klienta (aplikacja mobilna). Firmware tylko przechowuje i stosuje blob — nie ma interaktywnej sekwencji CALIB_START.
Procedura kalibracji:
1. Pobierz surowe dane mag (Control SET: KEY_CFG_RAW_MODE = 1)
2. Obracaj urządzenie w różnych kierunkach (opisuj sferę)
3. Oblicz hard-iron i soft-iron po stronie klienta (algorytm elipsoidy)
4. Wyślij blob: Control SET KEY_CALIB_SET_BLOB (42B)
5. Firmware zapamiętuje do NVS i wyłącza raw mode
Blob kalibracyjny magnetometru — format (42B)
| Offset | Rozmiar | Pole | Format | Jednostka |
|---|---|---|---|---|
| 0 | 2B | hard_iron_x |
int16_t LE |
deci-µT (1 LSB = 0.1 µT) |
| 2 | 2B | hard_iron_y |
int16_t LE |
deci-µT |
| 4 | 2B | hard_iron_z |
int16_t LE |
deci-µT |
| 6 | 4B | soft_iron[0][0] |
float32 LE |
bezwymiarowa |
| 10 | 4B | soft_iron[0][1] |
float32 LE |
bezwymiarowa |
| 14 | 4B | soft_iron[0][2] |
float32 LE |
bezwymiarowa |
| 18 | 4B | soft_iron[1][0] |
float32 LE |
bezwymiarowa |
| 22 | 4B | soft_iron[1][1] |
float32 LE |
bezwymiarowa |
| 26 | 4B | soft_iron[1][2] |
float32 LE |
bezwymiarowa |
| 30 | 4B | soft_iron[2][0] |
float32 LE |
bezwymiarowa |
| 34 | 4B | soft_iron[2][1] |
float32 LE |
bezwymiarowa |
| 38 | 4B | soft_iron[2][2] |
float32 LE |
bezwymiarowa |
Uwaga: hard-iron w blobie jest w deci-µT (int16_t), natomiast dane w ramce Data są w nT (int24) — to dwie różne reprezentacje.
Domyślne wartości (brak kalibracji): hard_iron = {0, 0, 0}, soft_iron = macierz jednostkowa.
function buildMagCalibBlob(hard_iron_uT: [number,number,number], soft_iron: number[][]): ArrayBuffer {
const buf = new ArrayBuffer(42);
const v = new DataView(buf);
v.setInt16(0, Math.round(hard_iron_uT[0] * 10), true); // µT → deci-µT
v.setInt16(2, Math.round(hard_iron_uT[1] * 10), true);
v.setInt16(4, Math.round(hard_iron_uT[2] * 10), true);
for (let r = 0; r < 3; r++)
for (let c = 0; c < 3; c++)
v.setFloat32(6 + (r * 3 + c) * 4, soft_iron[r][c], true);
return buf;
}
CALIB_STATUS magnetometru — odpowiedź GET (1B)
value[0] |
Znaczenie |
|---|---|
0 |
Brak kalibracji |
1 |
Skalibrowany, tryb calibrated |
2 |
Skalibrowany, tryb raw |
12. RTCM Stream (Charakterystyka a204 — Write Without Response)
Służy do przesyłania korekcji RTK do modemu GNSS (format RTCM 3.x). Strumień należy fragmentować do rozmiarów MTU (max 512B/pakiet). Urządzenie przekazuje dane bezpośrednio do odbiornika u-blox ZED-F9P przez UART.
13. Przykłady kompletnych transakcji
Włączenie modułu Magnetometer
TX: [0x01][0x06][0x01][0x02][0x01]
txId mag SET ENABLE 1
RX: [0x01][0x06][0x01][0x02][0x01]
txId mag SET ENABLE OK
Odczyt statusu kalibracji magnetometru
TX: [0x02][0x06][0x02][0x26]
txId mag GET CALIB_STATUS
RX: [0x02][0x06][0x02][0x26][0x01][0x01]
txId mag GET CALIB_STATUS OK calibrated
Odczyt bloba kalibracji żyroskopu
TX: [0x03][0x02][0x02][0x24]
txId ag GET CALIB_GET_BLOB
RX: [0x03][0x02][0x02][0x24][0x01][164 bajty danych]
txId ag GET CALIB_GET_BLOB OK blob
Rozpoczęcie pomiaru kalibracyjnego żyroskopu
TX: [0x04][0x02][0x01][0x20]
txId ag SET CALIB_START (brak value)
RX: [0x04][0x02][0x01][0x20][0x01]
txId ag SET CALIB_START OK
Polling statusu podczas kalibracji
TX: [0x05][0x02][0x02][0x26]
RX: [0x05][0x02][0x02][0x26][0x01][0x02][0x01][0x32][0xF4][01 02 03 04 05 06 07 08 09 0A 0B 0C]
count prog T_now(little-endian) avg_bias (12B)
count=2 istniejące punkty, progress=1%, T=0x01F4=500 decidegC=50.0°C, avg_bias opcjonalne