Skip to content

NMEA 0183

NMEA 0183 là giao thức văn bản ASCII mà gần như mọi module GNSS đều xuất ra qua UART. Trang này trình bày lịch sử và phạm vi ứng dụng của giao thức, sau đó đến cấu trúc câu và các bản tin thường gặp. Cách đọc NMEA bằng gpsd và dùng cùng PPS xem GNSS trên Linux.

Lịch sử và ứng dụng

Tổ chức NMEA

NMEA (National Marine Electronics Association) là hiệp hội thương mại của ngành điện tử hàng hải, thành lập năm 1957 tại Mỹ. Hiệp hội không phát triển GNSS mà định nghĩa chuẩn kết nối các thiết bị điện tử trên tàu, để thiết bị của các hãng khác nhau giao tiếp được với nhau.

Vì sao có NMEA 0183

Trên tàu, mỗi thiết bị (máy dò sâu, la bàn, radar, máy đo gió, bộ thu định vị) ban đầu dùng giao tiếp riêng của hãng. Để hiển thị vị trí lên máy vẽ hải đồ hoặc đưa hướng đi cho lái tự động, cần một định dạng dữ liệu chung. NMEA 0183, công bố từ năm 1983, đáp ứng nhu cầu này:

  • Một chiều, một nguồn phát, nhiều thiết bị nhận: thiết bị nguồn (talker) liên tục phát, các thiết bị khác (listener) chỉ nhận. Không cần thiết lập kết nối hay địa chỉ.
  • Văn bản ASCII: đọc trực tiếp được, gỡ lỗi bằng terminal.
  • Tốc độ thấp: chuẩn gốc dùng 4800 baud, 8 bit dữ liệu, không parity, 1 stop bit, đường tín hiệu kiểu RS-422. Tốc độ này đủ cho vài câu mỗi giây, phù hợp với dữ liệu hàng hải vốn thay đổi chậm.

Khi bộ thu GPS dân dụng xuất hiện, NMEA 0183 là giao diện sẵn có để đưa dữ liệu vị trí ra các thiết bị hiển thị, nên trở thành định dạng đầu ra phổ biến của bộ thu GPS.

Từ tàu biển ra mọi nơi

Giai đoạn Điều xảy ra
Thập niên 1980 NMEA 0183 ra đời cho điện tử hàng hải
Thập niên 1990 GPS dân dụng phổ biến, bộ thu thường xuất dữ liệu theo NMEA
Thập niên 2000 Module GNSS được tích hợp vào thiết bị nhúng và di động, NMEA là định dạng chung để lấy vị trí và giờ
2001 trở đi NMEA 2000 (nền CAN) thay thế dần NMEA 0183 trên tàu mới
Về sau NMEA OneNet (nền Ethernet) cho tàu cần băng thông lớn

Hiện nay NMEA 0183 có hai vai trò: trên tàu, nó đang dần được thay bằng NMEA 2000 và OneNet; trong hệ thống nhúng, nó vẫn là định dạng chung của module GNSS nhờ đơn giản và phổ biến.

Các phiên bản sau chuẩn gốc bổ sung dần các trường mới. Ví dụ bộ chỉ thị chế độ (mode indicator) trong RMC, GLL, VTG, và các talker ID cho thêm hệ thống vệ tinh như Galileo, BeiDou khi chúng ra đời. Do đó parser không nên giả định số trường cố định, xem phần lưu ý bên dưới.

Về tài liệu chuẩn

Đặc tả NMEA 0183 do NMEA phát hành và không công khai miễn phí. Tài liệu thường dùng là mô tả của nhà sản xuất (như u-blox, Quectel) và tài liệu của gpsd, đã tổng hợp lại các câu phổ biến. Các định dạng bên dưới dựa trên những nguồn này, nên với một module cụ thể cần đối chiếu với datasheet của module đó.

Cấu trúc một câu

Cấu trúc một câu NMEA 0183, phân rã theo từng trường

Dạng tổng quát:

$<talker><sentence>,<field1>,<field2>,...,<fieldN>*<checksum><CR><LF>
Phần Ý nghĩa
$ Ký tự bắt đầu câu. Câu đóng gói (như AIS) dùng ! thay cho $
Talker ID (2 ký tự) Nguồn của câu: GP (GPS), GL (GLONASS), GA (Galileo), GB (BeiDou), GN (kết hợp nhiều hệ thống)
Sentence ID (3 ký tự) Loại bản tin: GGA, RMC, GSV...
Các trường Phân cách bằng dấu phẩy. Trường rỗng vẫn giữ dấu phẩy
* + checksum 2 chữ số hex, là XOR của mọi ký tự nằm giữa $ và *
CR LF Kết thúc câu. Độ dài tối đa là 82 ký tự gồm cả $ và CR LF

Tính checksum

Ví dụ câu $GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47. Lấy XOR từng ký tự của chuỗi GPGGA,123519,...,M,, (không tính $ và *) thì được 0x47:

from functools import reduce

def nmea_checksum(body: str) -> str:
    return "%02X" % reduce(lambda a, c: a ^ ord(c), body, 0)

body = "GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,"
print(nmea_checksum(body))   # 47

Parser nên bỏ qua câu có checksum sai, đặc biệt khi đường UART có nhiễu.

Định dạng vị trí và thời gian

  • Vĩ độ: ddmm.mmmm, sau đó là N hoặc S. Kinh độ: dddmm.mmmm, sau đó là E hoặc W. Đây là độ và phút thập phân, không phải độ thập phân.
  • Đổi sang độ thập phân: độ + phút/60. Ví dụ 4807.038,N là 48 + 7.038/60 = 48.1173°.
  • Thời gian: hhmmss hoặc hhmmss.ss, tính theo UTC.
  • Ngày: ddmmyy.

Các bản tin thường gặp

Bản tin Nội dung
GGA Dữ liệu fix: vị trí, độ cao, chất lượng fix, số vệ tinh, HDOP
RMC Dữ liệu tối thiểu: giờ UTC, ngày, vị trí, tốc độ, hướng, trạng thái hợp lệ
GSA Chế độ fix và vệ tinh đang dùng, PDOP/HDOP/VDOP
GSV Vệ tinh đang thấy: elevation, azimuth, C/N0
GLL Vị trí và giờ (dạng rút gọn)
VTG Hướng và tốc độ trên mặt đất
ZDA Ngày giờ đầy đủ và múi giờ

GGA: Global Positioning System Fix Data

$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47
# Trường Ví dụ Ý nghĩa
1 Thời gian UTC 123519 12:35:19
2, 3 Vĩ độ, N/S 4807.038,N 48°07.038' Bắc
4, 5 Kinh độ, E/W 01131.000,E 11°31.000' Đông
6 Chất lượng fix 1 0 không có fix, 1 GNSS fix, 2 DGPS, 4 RTK fixed, 5 RTK float
7 Số vệ tinh dùng 08
8 HDOP 0.9 Độ loãng độ chính xác theo phương ngang, càng nhỏ càng tốt
9, 10 Độ cao so với mực nước biển 545.4,M Mét
11, 12 Độ lệch geoid 46.9,M Mét
13, 14 Tuổi dữ liệu DGPS, ID trạm (rỗng) Chỉ có khi dùng hiệu chỉnh

Trường 6 cho biết đã có fix hay chưa. Nếu bằng 0 thì các trường vị trí thường rỗng hoặc không đáng tin.

$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A
# Trường Ví dụ Ý nghĩa
1 Thời gian UTC 123519
2 Trạng thái A A dữ liệu hợp lệ, V cảnh báo (chưa hợp lệ)
3, 4 Vĩ độ, N/S 4807.038,N
5, 6 Kinh độ, E/W 01131.000,E
7 Tốc độ mặt đất 022.4 Knot (hải lý/giờ)
8 Hướng thật 084.4 Độ
9 Ngày 230394 23/03/1994
10, 11 Độ lệch từ 003.1,W
12 Chỉ thị chế độ (có ở bản mới) A tự động, D vi sai, E ước lượng, N không hợp lệ

RMC là câu quan trọng khi lấy thời gian vì có cả ngày và giờ. Trường 2 phải là A thì mới dùng được. Đây là câu gpsd dùng để xác định giây (xem GNSS trên Linux).

GSA: DOP and Active Satellites

$GPGSA,A,3,04,05,,09,12,,,24,,,,,2.5,1.3,2.1*39
# Trường Ý nghĩa
1 Chế độ chọn A tự động 2D/3D, M thủ công
2 Loại fix 1 không fix, 2 2D, 3 3D
3 đến 14 ID vệ tinh dùng Tối đa 12, trường không dùng để rỗng
15, 16, 17 PDOP, HDOP, VDOP Càng nhỏ càng tốt

Ở bản mới có thêm trường cuối cho biết hệ thống (GPS, GLONASS...) vì câu GN không còn đủ thông tin.

GSV: Satellites in View

$GPGSV,3,1,11,03,03,111,00,04,15,270,00,06,01,010,00,13,06,292,00*74
# Trường Ý nghĩa
1 Tổng số câu GSV Danh sách vệ tinh dài nên chia nhiều câu
2 Số thứ tự câu
3 Số vệ tinh nhìn thấy
4 đến 7 Vệ tinh thứ nhất PRN, elevation (độ), azimuth (độ), C/N0 (dB-Hz)
8 đến 19 Tối đa 3 vệ tinh tiếp theo Cùng cấu trúc 4 trường

Mỗi câu GSV chứa tối đa 4 vệ tinh. Khi thu nhiều hệ thống, module xuất GSV riêng cho từng hệ thống (GPGSV, GLGSV, GAGSV...). C/N0 trống nghĩa là vệ tinh thấy nhưng chưa bám được. C/N0 là chỉ số dùng để đánh giá chất lượng ăng-ten và phát hiện nhiễu.

Các câu còn lại

  • GLL: vĩ độ, kinh độ, giờ, trạng thái. Cùng thông tin vị trí như GGA nhưng gọn hơn.
  • VTG: hướng thật, hướng từ, tốc độ bằng knot và km/h.
  • ZDA: giờ, ngày, tháng, năm và độ lệch múi giờ. Ví dụ $GNZDA,123519.00,23,03,1994,00,00*72 cho đầy đủ năm 4 chữ số, hữu ích vì RMC chỉ có năm 2 chữ số.

Câu riêng của hãng

Câu bắt đầu bằng $P theo sau là mã nhà sản xuất 3 ký tự là câu proprietary, nằm ngoài chuẩn. Ví dụ u-blox dùng $PUBX,... để cấu hình module hoặc xuất thêm thông tin (PUBX,00 cho vị trí, PUBX,04 cho thời gian). Phần này khác nhau nhiều giữa các hãng, cần tra tài liệu của hãng.

Lưu ý khi viết parser

  • Không giả định số trường cố định. Phiên bản mới có thể thêm trường ở cuối, parser nên bỏ qua trường thừa thay vì báo lỗi.
  • Trường rỗng là trường hợp bình thường, nhất là khi chưa fix: kiểm tra RMC trạng thái A hoặc GGA chất lượng khác 0 trước khi dùng vị trí.
  • Kiểm tra checksum trước khi xử lý.
  • Câu có thể bị cắt hoặc ghép khi UART bị tràn, nên đọc theo từng dòng và bỏ câu không đủ.
  • Talker ID GN xuất hiện khi module kết hợp nhiều hệ thống. Không nên chỉ lọc GP.
  • Độ trễ: NMEA đi qua UART ở tốc độ baud hữu hạn, nên câu đến trễ so với thời điểm đo. Để lấy thời gian chính xác thì dùng PPS, xem GNSS trên Linux.

Kiểm tra nhanh

stty -F /dev/ttyS1 9600 raw
cat /dev/ttyS1 | grep -E '^\$G[A-Z]RMC'

Hoặc dùng gpspipe -r để xem câu NMEA thô khi đang chạy gpsd.

Đọc thêm