Конечно, ты можешь купить GPS-приемник в Китае за несколько долларов. А можешь и не покупать — все равно он есть у тебя в телефоне, в навигаторе в машине… Но если ты хочешь быть настоящим хакером-инженером и разобраться в технологии GPS на низком уровне, то добро пожаловать в эту статью. Разберемся так, что мало не покажется!
История разработки глобальной системы позиционирования (Global Positioning System, GPS) уходит корнями в 50-е годы прошлого века, а первый спутник был запущен в 1974 году. Первоначально система использовалась лишь военными, но после трагедии с самолетом авиакомпании Korean Airlines, который был сбит над территорией СССР, гражданские службы также получили возможность работы с GPS. В 1993-м окончательно было решено предоставить GPS для использования гражданскими службами на безвозмездной основе, а после отключения намеренного округления положения точность возросла со ста метров до двадцати. В наши дни точность продолжает увеличиваться, а стоимость приемников — снижаться. Поэтому сделать GPS-логгер, ну или навигатор, может любой, кто родился с паяльником вместо…ладно-ладно, это шутка была. Одно другого не исключает :).
Матчасть
Итак, что же должно делать наше устройство?
- Получить информацию о текущем положении от GPS-приемника.
- Разобрать ее.
- Показать на экране текущее положение приемника, а также видимые спутники.
Для этого придется узнать, что таится за терминами GPS, NMEA-0183 и алгоритм Брезенхема.
GPS
Конечно, детально разбираться в нюансах работы GPS не требуется, поскольку всю работу по вычислению координат, скорости, курса и других параметров за нас возьмет на себя GPS-приемник. Но базу знать надо.
В первую очередь надо понять, что GPS-приемник ничего и никогда не передает спутникам (если ты мне не веришь, то просто обрати внимание, что приемники могут быть размером с флешку, в то время как антенны спутниковых телефонов порой больше самого телефона). Задача спутниковой навигации заключается в определении приемником своих координат, если известны точные координаты передатчиков. Если мы знаем расстояние до спутников, то с помощью элементарных геометрических построений можно с некоторой точностью рассчитать свои координаты.
Для нахождения расстояния между приемником и передатчиком необходимо сначала синхронизировать их часы, а затем посчитать искомое расстояние, зная скорость распространения радиоволны, а также задержку между временем передачи и временем приема.
Как я уже писал выше, приемнику необходимо знать точные положения передатчиков. Подобная информация предоставляется передатчиком и называется «альманах». Естественно, эта информация устаревает, поэтому в зависимости от «свежести» альманаха можно выделить три типа задержки между включением приемника и определения его первых точных координат: «холодный старт», «теплый старт» и «горячий старт».
Существуют способы, которые позволяют уменьшить время старта: AGPS (получение альманаха альтернативными способами — через интернет или Почтой России), DGPS (исключение искажения сигнала атмосферой) и другие. Но я их рассматривать не буду, поскольку для выполнения поставленной задачи этого не нужно.
Теперь разберемся с тем, в каком же виде рассчитанные координаты появляются на выходе устройства. Для этого существует специальный стандарт.
NMEA-0183
NMEA — National Marine Electronics Association, а NMEA-0183 (согласно Википедии) — текстовый протокол связи морского (как правило, навигационного) оборудования (или оборудования, используемого в поездах) между собой. Вот строки, приходящие от моего приемника.
(...)
$GPRMC,174214.00,A,5541.23512,N,03749.12634,E,3.845,178.09,150914,,,A*6F
$GPVTG,178.09,T,,M,3.845,N,7.121,K,A*35
$GPGGA,174214.00,5541.23512,N,03749.12634,E,1,04,4.98,178.2,M,13.1,M,,*56
$GPGSA,A,3,20,14,04,17,,,,,,,,,8.85,4.98,7.31*02
$GPGSV,4,1,16,01,67,242,,02,,,15,03,,,15,04,53,284,35*74
$GPGSV,4,2,16,05,,,23,07,,,19,08,,,21,10,,,24*7F
$GPGSV,4,3,16,11,,,14,12,,,12,14,35,058,37,17,24,311,34*72
$GPGSV,4,4,16,20,40,275,29,22,08,097,,37,25,199,27,39,25,195,32*73
$GPGLL,5541.23512,N,03749.12634,E,174214.00,A,A*6D
(...)
Сначала определим похожие части каждой строки. Легко видеть, что все они начинаются одинаково и более-менее одинаково заканчиваются. $GP
— информация идет от приемника GPS (ты ведь понимаешь, что на корабле куча других датчиков: если бы у нас был аварийный маяк, то строка начиналась бы с $EP
, а если эхолот, то с $SD
, ну и так далее). Каждая строка обязательно заканчивается контрольной XOR-суммой всех байтов в строке начиная от $
и заканчивая *
— это как раз те два символа в конце строки. И не забываем про символы <CR>
и <LF>
после контрольной суммы. Разберем каждую из строк подробнее.
$GPRMC,hhmmss.ss,A,aaaa.aaaa,N,bbbb.bbbb,E,c.c,d.d,DDMMYY,z1,z2,e*ff
GPRMC
— GPS Recommended Minimum Navigation Information sentence C — рекомендуемый минимум навигационной информации, строка типа С.hhmmss.ss
— время по всемирному координированному времени UTC, когда была произведена фиксация положения.A
— флаг достоверности информации. ЕслиV
, то информации верить нельзя.aaaa.aaaaa
— величина широты. Первые две цифры — градусы, вторые две — целое значение количества угловых минут, после точки — дробная часть количества угловых минут (переменной длины).N
— северная широта. ЕслиS
, то южная.bbbb.bbbbb
— величина долготы. Первые две цифры — градусы, вторые две — целое значение количества угловых минут, после точки — дробная часть количества угловых минут (переменной длины).E
— восточная долгота. ЕслиW
, то западная.c.c
— горизонтальная скорость в узлах (умножить на 1,852 для получения скорости в километрах в час), целая и дробная части имеют переменную длину.d.d
— направление скорости (путевой угол, курс) в градусах, целая и дробная части имеют переменную длину.DDMMYY
— текущая дата.z1
— отсутствующая у нас величина направления магнитного склонения.z2
— также отсутствующее у нас направление магнитного склонения.e
— индикатор режима.ff
— контрольная сумма.
$GPVTG,a.a,T,b.b,M,c.c,N,d.d,K,A*e
$GPVTG
— GPS Track Made Good and Ground Speed — строка с информацией о курсе и скорости.a.a
— курс в градусах.T
— True, флаг достоверности информации.b.b
— направление магнитного склонения (у нас его нет).M
— Magnetic, да, действительно магнитное.c.c
— горизонтальная скорость в узлах (умножить на 1,852 для получения скорости в километрах в час).N
— kNots, узлы.d.d
— горизонтальная скорость в километрах в час (и умножать ничего не надо).K
— километры в час.ee
— контрольная сумма.
$GPGGA,hhmmss.ss,a.a,N,b.b,E,c,d,e.e,f.f,M,g.g,M,h.h,*i
$GPGGA
— Global Positioning System Fix Data — строка с информацией о текущем местоположении.hhmmss.ss
— время по всемирному координированному времени UTC, когда была произведена фиксация положения.a.a
— величина широты.N
— северная широта. ЕслиS
, то южная.b.b
— величина долготы.E
— восточная долгота. ЕслиW
, то западная.c
— флаг качества сигнала GPS.d
— количество используемых спутников.e.e
— фактор снижения точности (DOP, Dilution of precision).f.f
— высота расположения приемника над уровнем моря.M
— высота дается в метрах.g.g
— различие между геоидом (истинной формой нашей планеты) и эллипсоидом по WGS84 (трехмерная система координат для позиционирования).M
— различие дается в метрах.h.h
— номер станции, передающей поправки DGPS.i
— контрольная сумма.
$GPGSA,A,x,y1,y2,y3,y4,y5,y6,y7,y8,y9,y10,y11,y12,z1,z2,z3*i
$GPGSA
— GPS DOP and Active satellites — строка с информацией о спутниках, использованных для определения местоположения и о факторах снижения точности.A
— автоматический режим выбора работы в 2D или 3D,M
— ручной режим, когда жестко выбран, например, 2D.x
— режим работы приемника:0
— координаты не определены,1
— режим 2D,2
— режим 3D.y1..y12
— номера спутников, используемых для определения местоположения приемника.z1..z2
— PDOP, HDOP, VDOP (факторы снижения точности по положению, в горизонтальной плоскости и в вертикальной плоскости соответственно).i
— контрольная сумма.
$GPGSV,a,b,c1,d1,e1,f1,c2,d2,e2,f2,c3,d3,e3,f3,c4,d4,e4,f4*i
GPGSV
— GPS Satellites in View — строка содержит в себе информацию о номере, азимуте, высоте над горизонтом и соотношением сигнал/шум спутника. В строке максимально может быть четыре спутника.a
— общее количество строкGPGSV
.b
— номер текущей строки.c1..c4
— номер спутника.d1..d4
— высота над горизонтом в градусах(0..90)
.e1..e4
— азимут спутника в градусах(0..359)
.f1..f4
— соотношение сигнал/шум в дБ(0..99)
.
$GPGLL,5541.23512,N,03749.12634,E,174214.00,A,A*6D
— на этой строке нет смысла останавливаться подробно, поскольку она содержит в себе координаты и время, а это мы уже имеем в строках GPRMC
и GPGGA
.
Разумеется, производителям GPS-приемников не запрещается добавлять собственные строки. У моего приемника можно при запуске увидеть такие:
$GPTXT,01,01,02,u-blox ag - www.u-blox.com*50
$GPTXT,01,01,02,HW UBX-G60xx 00040007 FF7FFFFFp*53
$GPTXT,01,01,02,ROM CORE 7.03 (45969) Mar 17 2011 16:18:34*59
$GPTXT,01,01,02,ANTSUPERV=AC SD PDoS SR*20
$GPTXT,01,01,02,ANTSTATUS=DONTKNOW*33
$GPTXT,01,01,02,ANTSTATUS=INIT*25
$GPTXT,01,01,02,ANTSTATUS=OK*3B
Алгоритм Брезенхема
Этот алгоритм является одним из самых старых алгоритмов компьютерной графики — он был разработан Джеком Брезенхемом (IBM) аж в 1962 году. С его помощью происходит растеризация графического примитива, другими словами, этот алгоритм определяет координаты пикселей, которые необходимо зажечь на экране, чтобы полученный рисунок примитива совпадал с оригиналом.
Представим, что мы рисуем линию, идущую из точки (0; 0)
в (100, 32)
, как ты помнишь, у нашего экрана разрешение 128 x 64 точки. Несложно посчитать, что угол между этой прямой и осью Х составляет менее 45 градусов. Работа алгоритма заключается в последовательном переборе всех координат по оси Х в диапазоне от 0 до 100 и расчете соответствующей координаты Y. Логично, что в большинстве случаев значение координаты Y будет дробным, а это значит, что надо каким-то образом выбрать целочисленное значение координаты. Это делается путем выбора ближайшего пикселя. Для других углов наклона прямой, а также для окружностей, эллипсов и прочего алгоритм имеет аналогичный вид (подробнее о нем можно почитать в Википедии).
Алгоритм Брезенхема использует только операции сложения и вычитания целых чисел: обычно использование арифметики дробных чисел замедляет работу контроллера. Обычно, но не в нашем случае, поскольку внутри контроллера STM32F303VC находится ядро ARM Cortex-M4 с FPU. FPU (Floating Point Unit) — устройство, ускоряющее работу с дробными числами (математический сопроцессор), поэтому нас ничто не ограничивает и мы можем использовать алгоритм DDA-линии. Интересную демонстрацию ускорения работы МК при рисовании фракталов можно посмотреть на YouTube.
Железо
Что же мы используем в проекте?
- Отладочную плату STM32F3-Discovery;
- модуль UART GPS NEO-6M от WaveShare на базе приемника u-blox NEO-6M;
- ЖК-матрицу МТ-12864А.
Про ЖК-экран я рассказывал в сентябрьском номере (№ 188), кратко лишь скажу, что сделан он на базе контроллеров KS0108, а схема подключения к STM32F3-Discovery не изменилась: разъем для подключения экрана содержит в себе 20 пинов, описание представлено в списке по следующей маске: <номер> — <название из даташита> — <описание из даташита> — <куда подключается>.
- 1 — Ucc — питание — к 5V на Discovery.
- 2 — GND — земля — к GND на Discovery.
- 3 — Uo — вход питания ЖК-панели для управления контрастностью — к подстроечному резистору.
- 4..11 — DB0..DB7 — шина данных — к PD0..PD7 на Discovery.
- 12, 13 — E1, E2 — выбор контроллера — к PD8,PD9 на Discovery.
- 14 — RES — сброс — к PD10 на Discovery.
- 15 — R/W — выбор: чтение/запись — к PD11 на Discovery.
- 16 — A0 — выбор: команда/данные — к PD12 на Discovery.
- 17 — E — стробирование данных — к PD13 на Discovery.
- 18 — Uee — выход DC-DC преобразователя — к подстроечному резистору.
NEO-6M
Данный приемник производится швейцарской компанией u-blox, основанной в 1997 году. Линейка модулей Neo-6 представлена разновидностями G, Q, M, P, V и T, каждый из которых обладает своими характерными возможностями: например, Neo-6P имеет возможность очень точного (с ошибкой <1 м) определения положения за счет метода Precise Point Positioning (PPP).
Приемник Neo-6M обладает следующими свойствами:
- время холодного или теплого старта — 27 с;
- время горячего старта — 21 с;
- максимальная частота выдачи информации — 1 Гц;
- диапазон частот импульсов на пин PPS — 0,25 Гц — 1 кГц;
- максимальная точность определения положения — 2,5 м;
- максимальная точность определения скорости — 0,1 м/с;
- максимальная точность определения курса — 0,5 градуса.
Neo-6M умеет использовать SBAS (Satellite Based Augmentation System) — спутниковые системы дифференциальной коррекции, что увеличивает точность определения положения до 2 м, а также AGPS (Assisted GPS) для снижения времени холодного старта. Получение данных AGPS происходит с сайта u-blox с помощью сервисов AssistNow Online и AssistNow Offline (долгосрочный альманах). Модуль обладает поддержкой протоколов NMEA, UBX и RTCM. UBX — проприетарный протокол от u-blox, а RTCM — протокол для передачи модулю данных о дифференциальной коррекции DGPS. Также для связи доступны интерфейсы UART, I2C, SPI и USB.
Для работы с приемниками существует оригинальная утилита u-center, имеющая на момент написания статьи версию 8.11 (рис. 1).
Видно, что Neo-6M обладает огромным потенциалом, но детально описать все его возможности не хватит места, поэтому ограничимся предлагаемыми из коробки: только UART на скорости 9600, только NMEA, частота импульсов — 1 Гц.
В плане подключения все предельно просто: линии VCC, GND, RX и TX на приемнике подключаем к +3.3V, GND, PA9 и PA10 на Discovery соответственно.
Программа
Она должна отображать текущее положение приемника, скорость, направление движения, факторы снижения точности, время, дату, а еще показывать в полярной системе координат используемые спутники. Вот примерно так, как это делает u-center на рис. 2.
Как только строка line
от Neo-6M принимается контроллером, происходит ее разбитие на токены (массив charTokens
) — на подстроки, которые в исходной строке разделены запятыми.
char *token = malloc(strlen(line) + 1);
char *token2 = malloc(strlen(line) + 1);
int currentTokenNumber = 0;
int currentCharInTokenNumber = 0;
strcpy(token, line);
char *delimeter = ",";
while (token != NULL) {
token2 = strpbrk(token + 1, delimeter);
if (token2 == NULL) {
// В конце строки меняем разделитель на "*"
delimeter = "*";
token2 = strpbrk(token + 1, "*");
}
/* Копируем часть строки между разделителями */
currentCharInTokenNumber = 0;
/* Очищаем значение токена */
memset(charTokens[currentTokenNumber], '', MAX_TOKEN_LENGTH);
for (char *ch = token + 1; ch < token2; *(ch++)) {
charTokens[currentTokenNumber][currentCharInTokenNumber] = *ch;
currentCharInTokenNumber++;
}
currentTokenNumber++;
if (delimeter[0] == '*') {
token = NULL;
} else {
token = token2;
}
}
Казалось бы, вполне логично использовать функцию strtok
, но я этого не делаю. Причину покажу на примере. Пусть имеется строка a,b,,,c
. Результат разбития ее на токены с помощью strtok
будет таким: 'a', 'b', 'c'
. Для разбора NMEA это недопустимо, поскольку в этом протоколе значения токенов зависят от положения в строке. Результат работы описанного выше метода включает в себя пустые токены — 'a', 'b', '0', '0' 'c'
.
Для удобного хранения информации о положении приемника, о точности определения положения, а также о параметрах спутников были написаны три структуры данных.
Положение и скорость приемника, а также дата и время:
struct _minimumNavigationInfo {
float latitude;
char latModificator; // East or West
float longitude;
char lonModificator; // North or South
float groundSpeed;
float speedAngle;
float height;
char heightModificator; // Metres or smth else
char time[9]; // "hh:mm:ss"
char date[9]; // "DD.MM.YY"
char isValid;
};
Структура точности определения координат:
struct _fixInfo {
double PDOP;
double HDOP;
double VDOP;
};
Структура о номере спутника, его положении и качестве сигнала:
struct _satelliteInfo {
int satelliteId;
float height;
float azimuth;
float SNR; //signal-to-noise ratio -- соотношение сигнал/шум
int isFull;
};
Если информация о спутнике не полная, например есть информация о высоте и азимуте, но нет о соотношении сигнал/шум, то в поле isFull
записывается нулевое значение. Такие спутники при выводе на «радар» будут игнорироваться.
Заполнение структуры на основе массива токенов происходит очень просто: после разбора строки GPGSA
в массиве charTokens
значения факторов снижения точности *DOP находятся в элементах за номерами 15, 16 и 17.
fixInfo->PDOP = atof(charTokens[15]);
fixInfo->HDOP = atof(charTokens[16]);
fixInfo->VDOP = atof(charTokens[17]);
Теперь можно разобранную информацию смело выводить на экран (рис. 3).
FIN
Теперь ты знаешь, что в GPS тоже нет ничего сложного (если не лезть в дебри), а если тебе хочется понять суть спутниковой навигации, то добро пожаловать на курс от Стэнфорда GPS: An Introduction to Satellite Navigation, with an interactive Worldwide Laboratory using Smartphones или от Университета Миннесоты From GPS and Google Maps to Spatial Computing на Coursera.
А в качестве домашнего задания я поставлю перед тобой три задачи: 1. Добавить возможность записи трека. 2. Заменить монохромный экран на цветной. 3. Вместе с WizFi220 (из номера 188) снабдить устройство возможностью получения A-GPS. Если возникли какие-нибудь вопросы, пиши мне на email, который можно найти в начале статьи. Удачи!
SRC
Весь код ты можешь найти на https://github.com/argrento. Просто скопируй с заменой файлы в папку Template из Standard Peripheral Library.