
GPS-приемники сегодня используются в самых разных устройствах — от автомобильных трекеров до беспилотных летательных аппаратов, независимо от применения, большинство таких модулей передают информацию о положении в формате NMEA 0183, в этой статье я разберу, как принять эти данные от GPS-модуля на микроконтроллер STM32 и преобразовывать их в удобный для программы вид.
А также в статье будут рассмотрены два варианта подключения GPS-приемников к микроконтроллеру STM32:
-
Модуль GPS c UART-интерфейсом (TTL-уровни), подключаемый на прямую к микроконтроллеру;
-
Модуль GPS с интерфейсом RS-232, данные от такого типа gps, необходимо принимать через преобразователь уровней TTL.
В данном проекте используются GPS-приемники: LS23030 (UART) и LS23036(RS-232).
Схема подключения GPS-UART
Сигнал GPS, подключается к выводу PA10-31_контакт — RX(МК-STM32F103)
Для более стабильного напряжения питания можно использовать следующую схему, в которой работает понижающий преобразователь MP231, но необходим источник +12В, в моем случае используется аккумуляторная сборка (NiMH/Pb +12В).
!!! P.S. заранее прошу прощения за некачественные картинки осциллограмм, осциллограф (с возможностью сохранения данных на флешку), некорректно производит измерения …!!!
Вид осциллограммы передаваемых данных модуля-gps(uart) (линия TX)
Показатель амплитуды данных от модуля gps(uart) = delta [ 3.4V ], можно подключать к микроконтроллеру STM32.
Схема подключения GPS(RS-232)
Сигнал модуля-gps(rs-232), сначала приходит на 13 контакт преобразователя ADM3202, далее преобразованный сигнал (TTL) уходит на PA10-31_контакт — RX(МК-STM32F103)
Схема подключения ADM3202 к МКSTM32 — макет
Также для более стабильного напряжения питания по +5В, можно использовать схему преобразователя напряжения MP2315.
Для более стабильного напряжения питания по +3В, можно использовать следующую схему, в которой работает линейный стабилизатор напряжения LP2985.
Краткая информация о преобразователе ADM3202
Микроконтроллеры STM32, работают с логическими уровнями TTL/CMOS — обычно это 3.3В или 5В, интерфейс RS-232, напротив, использует более высокие и отрицательные напряжения ( от ±3В до ±12В), что делает их напрямую несовместимыми.
Если подключить напрямую модуль-GPS (RS-232) к выводам МК-STM32, это может не только привести к искажению данных, но и физически повредить выводы. ADM3202 решает эту задачу, переводя сигналы из одного уровня в другой, в обоих направлениях.
ADM3202 — это двухканальный приемопередатчик уровней RS-232 — TTL, выполняет сразу две задачи:
-
Преобразование входящих RS-232 сигналов в безопасные TTL-уровни(RX-канал);
-
Преобразование исходящих TTL-сигналов микроконтроллера в RS-232(TX-канал).
Для формирования требуемых амплитуд RS-232, внутри микросхемы используется помповый преобразователь напряжения(chage pump) с четырьмя внешними конденсаторами, это позволяет работать от одного источника питания (от 3В до 5.5В).
Вид осциллограммы передаваемых данных модуля-gps(rs-232) до преобразования ADM3202
Показатель амплитуды данных от модуля gps(rs-232) до преобразования = delta [ 10.6V ], нельзя подключать к микроконтроллеру STM32.
Вид осциллограммы передаваемых данных модуля-gps(rs-232) после преобразования ADM3202
Показатель амплитуды данных от модуля gps(rs-232) после преобразования = delta [ 3.6V ], можно подключать к микроконтроллеру STM32.
Настройка микроконтроллера STM32F103 в CubeIDE
Конфигурация Parametr Settings
В параметрах USART (Parametr Settings) я выбираю:
-
Mode: Asynchronous (асинхронный режим);
-
Baud Rate: 9600 бит/с (в моем примере два модуля-gps (rs-232 и uart) работают на скорости 9600).
все остальные параметры без изменений.
Конфигурация NVIC Settings
Захожу в параметр (NVIC Settings) и включаю глобальное прерывание
Для отслеживания состояния интерфейса USART и обработки важных событий (например, завершения приема или ошибки), в разделе NVIC Settings было включено глобальное прерывание USART, это обеспечивает возможность немедленного реагирования со стороны микроконтроллера на изменения состояния периферии без постоянного опроса регистров.
При работе с GPS-модулями, которые передают NMEA-сообщения раз в секунду (1Hz), важно правильно организовать прием данных, чтобы не пропустить ни одного пакета. Необходимо настроить DMA в режиме Circular данный режим минимизирует нагрузку на процессор и гарантирует надежный прием.
Конфигурация DMA Settings
Захожу в параметр DMA Settings и выполняю следующие настройки:
-
Выбор потока/канала: USART1_RX (прием данных);
-
Mode: Circular ;
-
Increment Memory Address: Enabled (автоинкремент памяти);
-
Data Width: Byte (8 бит, соответствует формату NMEA).
Реализация программного кода(настройка и прием данных)
Коротко о NMEA 0183
Это текстовый протокол, используемый для передачи данных между морским и авиационным навигационным оборудованием, включая GPS-приемники. Большинство современных GPS-модулей выводят информацию именно в этом формате.
Основные особенности:
-
Текстовый формат – данные передаются в виде ASCII-строк;
-
Структура сообщений – каждая строка начинается с
$, содержит идентификатор типа данных и заканчивается контрольной суммой; -
Скорость передачи – обычно 9600 бод (но может быть и выше для высокочастотных модулей);
-
Частота обновления – чаще всего 1 раз в секунду (1Hz), но бывают 5Hz, 10Hz и более.
Пример строки (GGA – Global Positioning System Fix Data):
|
№ |
Поле |
Значение |
Описание |
|
1 |
UTC-время |
112530.000 |
11:25:30 UTC |
|
2 |
Широта |
6012.3456 |
60° 12.3456′ |
|
3 |
Полушарие широты |
N/S |
Север/ЮГ |
|
4 |
Долгота |
03015.6789 |
30° 15.6789′ |
|
5 |
Полушарие долготы |
E/W |
Восток/Запад |
|
6 |
Fix Quality |
1 |
GPS фикс (автономный) |
|
7 |
Спутники |
10 |
10 спутников |
|
8 |
HDOP |
0.95 |
Горизонтальная точность |
|
9 |
Высота |
45.3 |
Высота над уровнем моря |
|
10 |
Ед. высоты |
M |
Метры |
|
11 |
Геоид. высота |
12.5 |
Высота геоида |
|
12 |
Ед. геоида |
M |
Метры |
|
13 |
Дифф. коррекция |
пусто |
Нет данных |
|
14 |
CRC |
*65 |
Контрольная сумма |
После обработки GGA:
-
Время: 11:25:30
-
Широта: 60.20576° N
-
Долгота: 30.261315° E
-
Качество фикса: 1
-
Спутников: 10
-
Высота: 45.3 м
Пример строки (RMC – Recommended Minimum Navigation Information):
|
№ |
Поле |
Значение |
Описание |
|
1 |
UTC-время |
112530.000 |
11:25:30 UTC |
|
2 |
Статус |
А |
Данные действительные |
|
3 |
Широта |
6012.3456 |
60° 12.3456′ |
|
4 |
Полушарие широты |
N/S |
Север/ЮГ |
|
5 |
Долгота |
03015.6789 |
30° 15.6789′ |
|
6 |
Полушарие долготы |
E/W |
Восток/Запад |
|
7 |
Скорость |
5.12 |
5.12 узла |
|
8 |
Курс |
87.45 |
87.45° |
|
9 |
Дата |
110825 |
11 августа 2025 |
|
10 |
Маг. отклонение |
пусто |
— |
|
11 |
Ед. маг. откл |
пусто |
— |
|
12 |
Режим |
A |
Автономный GPS |
|
13 |
CRC |
*6C |
Контрольная сумма |
После обработки RMC:
-
Время: 11:25:30
-
Статус: Данные действительные
-
Широта: 60.20576° N
-
Долгота: 30.261315° E
-
Скорость: 5.12 узла (~9.48 км/ч)
-
Курс: 87.45°
-
Дата: 11.08.2025
Также прикрепляю еще одну ссылку, где в детальности продемонстрирована расшифровка протокола NMEA0183 [https://wiki.iarduino.ru/page/NMEA-0183/].
Определение структур данных в заголовочном файле [ NMEA.h ]
Для удобства работы с навигационной информацией из gps-приемника, здесь я заранее описываю набор структур, каждая из которых отвечает за свой логический блок данных.
#ifndef INC_PROJECT_GNSS_NMEA_H_ #define INC_PROJECT_GNSS_NMEA_H_ // Время в часах, минутах, секундах typedef struct { int hour; int min; int sec; int msec; }TIME; typedef struct{ int calculation; }COORDINATE_CALC; // Координаты + направление (NS/EW) typedef struct { float latitude; char NS; float longitude; char EW; float altitude; char unit; }LOCATION; //Высота и единицы измерения typedef struct { float altitude; char unit; }ALTITUDE; //Дата typedef struct { int Day; int Mon; int Yr; }DATE; //Набор данных из GGA typedef struct { LOCATION lcation; TIME tim; int isfixValid; ALTITUDE alt; int numofsat; COORDINATE_CALC calc; }GGASTRUCT; //Набор данных из RMC typedef struct { DATE date; float speed; float course; int isValid; }RMCSTRUCT; //Объединение наборов GGA и RMC typedef struct { GGASTRUCT ggastruct; RMCSTRUCT rmcstruct; }GPSSTRUCT; int decodeGGA (char *GGAbuffer, GGASTRUCT *gga); int decodeRMC (char *RMCbuffer, RMCSTRUCT *rmc); #endif /* INC_PROJECT_GNSS_NMEA_H_ */
Реализация модуля парсинга протокола NMEA.c
Функция decodeGGA()
Парсит строку $GPGGA и заполняет структуру GGASTRUCT данными: время, координаты, высота, количество спутников, качество фиксации.
Шаги работы функции:
-
Подготовка к поиску нужных данных
-
Переменная inx — это текущий индекс в строке GGAbuffer;
-
Сначала иду пропуск ненужных полей (счетчик двигается до следующей ,)
-
-
Проверка валидации качества позиционирования
-
В NMEA поле качества фиксации (Fix Quality) может быть:
-
0 — нет фикса, 1 — GPS Fix, 2-DGPS Fix, 4/5/6 другие корректные варианты;
-
Если поле содержит из разрешенных цифр, то в gga->isfixValid устанавливается в 1, иначе функция возвращает ошибку.
-
-
-
Чтение времени
-
Извлекается время в формате HHMMSS (UTC);
-
Преобразуются числа;
-
Корректируется по смещению GMT, при необходимости меняется день (daychange++ или daycahnge—).
-
-
Чтение широты (Latitude)
-
Формат в NMEA: DDMM.MMM;
-
Первые цифры — градусы, остальное — минуты;
-
Код выделяет минуты, делит их на 60 и добавляет к градусам;
-
Если NS == ‘S’, широта делается отрицательной.
-
-
Чтение долготы (Longitude)
-
Формат в NMEA: DDMM.MMM;
-
Аналогично широте, но первые 3 цифры — градусы;
-
Если EW == ‘W’, долгота делается отрицательной.
-
-
Чтение типа вычисления координат
-
Из поля после долготы извлекается число (gga->calc.calculation), указывающее метод позиционирования.
-
-
Чтение количества спутников
-
Следующее поле — количество видимых спутников (gga->numofsat).
-
-
Пропуск HDOP
-
HDOP (Horizontal Dilution of Precision) не используется, просто пропускается.
-
-
Чтение высоты
-
Поле с высотой (gga->alt.altitude) и единицами (gga->alt.unit, обычно ‘M’ — метры).
-
-
Завершение
-
Возвращается 0 при успешном разборе.
-
Функция decodeRMC()
Парсит строку $GPRMC и заполняет структуру RMCSTRUCT и извлекает: валидность данных, скорость, курс и дату.
-
Пропуск времени
-
Сначала идет поле времени, но в этой функции оно не сохраняется.
-
-
Проверка валидности
-
Если после времени идет А(Active) — данные актуальны;
-
Если V(Void) нет актуальных данных, функция возвращает ошибку.
-
-
Пропуск координат
-
Пропускаются поля широты, долготы и направления (NS/EW).
-
-
Чтение скорости
-
В NMEA скорость указывается в узлах;
-
Код переводит строку в число с плавающей точкой и записывает в rmc->speed.
-
-
Чтение курса
-
Следующее поле — курс (угол направления движения в градусах относительно севера).
-
-
Чтение даты
-
Формат: DDMMYY;
-
Код выделяет день, месяц, год, корректирует день с учетом daychange (из GGA), и записывает в rmc->date.
-
uint8_t GMT = 0; //RU uint8_t inx = 0; uint8_t hr=0,min=0,day=0,mon=0,yr=0; uint8_t daychange = 0; /*Первый параметр это буфер GGA, в котором сохраняется строка GGA, * Второй параметр это указатель на структуру GGA*/ int decodeGGA (char *GGAbuffer, GGASTRUCT *gga) { inx = 0; char buffer[12]; int i = 0; while (GGAbuffer[inx] != ',') inx++; // 1st ',' inx++; while (GGAbuffer[inx] != ',') inx++; // After time ',' inx++; while (GGAbuffer[inx] != ',') inx++; // after latitude ',' inx++; while (GGAbuffer[inx] != ',') inx++; // after NS ',' inx++; while (GGAbuffer[inx] != ',') inx++; // after longitude ',' inx++; while (GGAbuffer[inx] != ',') inx++; // after EW ',' inx++; //while (GGAbuffer[inx] != ',') inx++; // информация о типе вычисления координат ',' //inx++; // доситиг символа/ знака для определения исправления //проверка шаблона в буфере прошла успешно /*Далее происходит проверка чисел, если в буфере число равно 1 , 2 или 6 * то данные являются действительными*/ if ((GGAbuffer[inx] == '1') || (GGAbuffer[inx] == '2') || (GGAbuffer[inx] == '4') || (GGAbuffer[inx] == '5') || (GGAbuffer[inx] == '6')) // 0 указывает на отсутствие исправления { gga->isfixValid = 1; //данные действительны inx = 0; //сбрасываю индекс, далее начну с inx = 0 и буду извлекать информацию } else { gga->isfixValid = 0; // если данные не действительны return 1; // return error } while (GGAbuffer[inx] != ',') inx++; // 1st ',' /*********************** Get TIME ***************************/ //(Update the GMT Offset at the top of this file) /*Здесь я сначала копирую время в буфер * данные в буфере по прежнему имеют симфольный формат, мне необходимо их изменить * на числовой формат, сделать я это могу с помощью atoi функции исп. для преобразования строки в число*/ inx++; // достижение первого числа, вовремя memset(buffer, '\0', 12); i=0; while (GGAbuffer[inx] != ',') // копирую время до того как поймаю ',' { buffer[i] = GGAbuffer[inx]; i++; // if(i>sizeof(buffer)){ //return 0; // } inx++; } hr = (atoi(buffer)/10000) + GMT/100; // получаю часы из 6-ти значного числа min = ((atoi(buffer)/100)%100) + GMT%100; //данная часть кода предназначена для регулировки времени в соответствии со смещением GMT if (min > 59) { min = min-60; hr++; } if (hr<0) { hr=24+hr; daychange--; } if (hr>=24) { hr=hr-24; daychange++; } //Сохраняю данные в tim, элемент структуры GGA gga->tim.hour = hr; gga->tim.min = min; gga->tim.sec = atoi(buffer)%100; //gga->tim.msec = (hr+min+atoi(buffer)%100)*1000; /***************** Get LATITUDE **********************/ inx++; //Достижение первого числа в широте memset(buffer, '\0', 12); i=0; while (GGAbuffer[inx] != ',') // Копировать до достижения заданной широты ',' { buffer[i] = GGAbuffer[inx]; i++; //if(i>sizeof(buffer)){ //return 0; //} inx++; } if (strlen(buffer) < 6) return 2; //Если длина буфера не подходит, вернуть ошибку //int16_t num;// = (atoi(buffer)); // Изменить буфер на число, он преобразует только до десятичной дроби int j = 0; float grad; while (buffer[j] != '.') j++; // Выяснить, сколько цифр перед десятичной точкой j-=2;//++; grad=atof (&buffer[j])/60.0f; buffer[j]='#'; grad += (atof(buffer)); //int declen = (strlen(buffer))-j; //int dec = atoi ((char *) buffer+j); //float lat = (num/100.0) + (dec/pow(10, (declen+2))); gga->lcation.latitude = grad;//lat; inx++; gga->lcation.NS = GGAbuffer[inx]; if(gga->lcation.NS=='S'){ gga->lcation.latitude=-gga->lcation.latitude; } /*********************** GET LONGITUDE **********************/ inx++; // ',' После символа NS inx++; // Дойти до первой цифры в значении долготы memset(buffer, '\0', 12); i=0; while (GGAbuffer[inx] != ',') // Копировать до достижения заданной высоты ',' { buffer[i] = GGAbuffer[inx]; i++; //if(i>sizeof(buffer)){ //return 0; //} inx++; } //num = (atoi(buffer)); j = 0; while (buffer[j] != '.') { j++; //if (j>sizeof(buffer)){ //return 0; //} } j-=2;//++; grad=atof (&buffer[j])/60.0f; buffer[j]='#'; grad += (atof(buffer)); //declen = (strlen(buffer))-j; //dec = atoi ((char *) buffer+j); //lat = (num/100.0) + (dec/pow(10, (declen+2))); gga->lcation.longitude = grad;//lat; inx++; gga->lcation.EW = GGAbuffer[inx]; if(gga->lcation.EW=='W'){ gga->lcation.longitude=-gga->lcation.longitude; } /**************************************************/ //Пропустить исправление позиции inx++; // ',' after E/W /*************** Информация о типе вычисления координат ********************/ inx++; // position fix memset(buffer, '\0', 12); i=0; while (GGAbuffer[inx] != ',') { buffer[i] = GGAbuffer[inx]; i++; //if(i>sizeof(buffer)){ // return 0; //} inx++; } gga->calc.calculation = atoi(buffer); //int declen_1 = (strlen(buffer)); //int dec_1 = atoi ((char *) buffer); //int calc = (num_1) + (dec_1/pow(10, (declen_1))); // gga->calc.calculation = calc; inx++; // ',' после фиксации позиции; // количесвто спутников memset(buffer, '\0', 12); i=0; while (GGAbuffer[inx] != ',') { buffer[i] = GGAbuffer[inx]; i++; //if(i>sizeof(buffer)){ // return 0; //} inx++; } gga->numofsat = atoi(buffer); inx++; /***************** skip HDOP *********************/ while (GGAbuffer[inx] != ',') inx++; /*************** Altitude calculation ********************/ inx++; memset(buffer, '\0', 12); i=0; while (GGAbuffer[inx] != ',') { buffer[i] = GGAbuffer[inx]; i++; //if(i>sizeof(buffer)){ //return 0; //} inx++; } float alt = (atof(buffer)); //j = 0; //while (buffer[j] != '.') j++; //j++; //int declen = (strlen(buffer))-j; //int dec = atoi ((char *) buffer+j); //int lat = (num) + (dec/pow(10, (declen))); gga->alt.altitude = alt;//изменил inx++; gga->alt.unit = GGAbuffer[inx]; return 0; } int decodeRMC (char *RMCbuffer, RMCSTRUCT *rmc) { inx = 0; char buffer[12]; int i = 0; while (RMCbuffer[inx] != ',') inx++; // 1st , inx++; while (RMCbuffer[inx] != ',') inx++; // После time , inx++; if (RMCbuffer[inx] == 'A') { rmc->isValid = 1; } else { rmc->isValid =0; return 1; } inx++; inx++; while (RMCbuffer[inx] != ',') inx++; // после latitude, inx++; while (RMCbuffer[inx] != ',') inx++; // после NS , inx++; while (RMCbuffer[inx] != ',') inx++; // после longitude , inx++; while (RMCbuffer[inx] != ',') inx++; // после EW , // Получить скорость inx++; i=0; memset(buffer, '\0', 12); while (RMCbuffer[inx] != ',') { buffer[i] = RMCbuffer[inx]; i++; //if(i>sizeof(buffer)){ //return 0; //} inx++; } if (strlen (buffer) > 0){ int16_t num = (atoi(buffer)); int j = 0; while (buffer[j] != '.') j++; // тоже, что и выше j++; int declen = (strlen(buffer))-j; int dec = atoi ((char *) buffer+j); float lat = num + (dec/pow(10, (declen)));// изменил rmc->speed = lat; } else rmc->speed = 0; // Получить курс inx++; i=0; memset(buffer, '\0', 12); while (RMCbuffer[inx] != ',') { buffer[i] = RMCbuffer[inx]; i++; //if(i>sizeof(buffer)){ //return 0; //} inx++; } if (strlen (buffer) > 0){ int16_t num = (atoi(buffer)); int j = 0; while (buffer[j] != '.') j++; j++; int declen = (strlen(buffer))-j; int dec = atoi ((char *) buffer+j); float lat = num + (dec/pow(10, (declen)));//изменил rmc->course = lat; } else { rmc->course = 0; } // Получить дату inx++; i=0; memset(buffer, '\0', 12); while (RMCbuffer[inx] != ',') { buffer[i] = RMCbuffer[inx]; i++; //if(i>sizeof(buffer)){ //return 0; //} inx++; } // Дата в формате 120295 day = atoi(buffer)/10000; mon = (atoi(buffer)/100)%100; yr = atoi(buffer)%100; day = day+daychange;// коррекция из-за сдвига по Гринвичу // сохранить данные в структуру rmc->date.Day = day; rmc->date.Mon = mon; rmc->date.Yr = yr; return 0; }
Реализация модуля обработчика потока от UART-GPS uartProc_GNSS.c
Функция uart_Handler_GNSS — это основной обработчик UART-потока, вызывается постоянно из главного цикла.
Логика работы:
-
Проверяет, сработали ли прерывания DMA (половина буфера или полный буфер);
-
Если пришли новые данные — устанавливает флаг активности GPS (gParams.isGPS = 1);
-
Поиск GGA или RMC
-
В режиме shabloneMode = 0 ищет последовательность GGA или RMC;
-
Когда шаблон найден, переключаемся в режим shabloneMode = 1;
-
В режиме 1 копирует байты до символа конца строки (13 или 10);
-
-
Действие когда собрана строка
-
Записывает строку в буфер buf_GGA или buf_RMC в зависимости от типа;
-
Обновляет время последнего получения GPS (gps_time_receive).
-
-
Декодирование
-
Вызывает decodeGGA() и decodeRMC() для извлечения данных в структуру gpsData.
-
-
Формирование выходного пакета:
-
Если хотя бы одно из сообщений валидно, формирует строку с координатами, временем, количеством спутников, режим фикса, высотой и курсом.
-
Записывает в результат в uart_rezult_buf_out_AB[] с преамбулой 0x5A 0xA5 и длиной пакета.
-
-
Если в течение (DELAY_GPS_STATUS_CONNECT)1000 миллисекунд новых данных нет — GPS считается отключенным (gParams.isGPS= 0).
-
uint8_t* dpi_getGPS_buffer (Возвращает указатель на готовый пакет данных для передачи ведущему устройству, а так же его размер).
-
void uart_startRecieving_GNSS (Запускает прием данных от GPS-приемника в режиме DMA).
extern volatile uint8_t uartRxFullIRDone; //сработало прерывание по полному буферу extern volatile uint8_t uartRxHalfIRDone; //сработало прерывание по половине extern volatile uint8_t uartRxABDone;//сработало прерывание на прием от ведущего устройства extern volatile uint8_t uartTxIRDone_AB; //сработало прерывание на отправку ведущему устройству extern short status_UART; //для проверки что gps отключили #define DELAY_GPS_STATUS_CONNECT 1000//1000 uint32_t gps_time_recieve = 0;//когда получили данные от GPS //uint8_t gps_connect_status=-1;//-1=не подключен 1=подключен //E N D для проверки что gps отключили #define SIZEBUF_result_out_ab 55 //57 //53 0x5A 0xA5 0 unsigned char uart_rezult_buf_out_AB[SIZEBUF_result_out_ab]={0,}; //"00.0000000 00.0000000 00:00:00.000 00 0 000.0 00.0>"; //60.1234567 47.1234567 16:11:33:128 09 2 134.2 34.6 int size_rez_buf_ab=0; //буфер для сырых данных от GPS #define SIZEBUF_uart_rx_buf 100//1000 уменьшил буфер для быстрого заполнения, чтобы у меня быстро не срабатывало время отсутствия gps uint8_t uart_rx_buf[SIZEBUF_uart_rx_buf]={0,}; //буфер для GPS-GGA #define SIZEBUF_buf_GGA 100 char buf_GGA[SIZEBUF_buf_GGA]={0,}; //буфер для GPS-RMC #define SIZEBUF_buf_RMC 100 char buf_RMC[SIZEBUF_buf_RMC]={0,}; // NMEA GPSSTRUCT gpsData; int flagGGA = 0, flagRMC = 0; //для составления строк #define SIZEBUF_shablon 3 char shablonGNGGA[]="GGA"; char shablonGNRMC[]="RMC"; short shablon_iGNGGA=0; short shablon_iGNRMC=0; char shablonMode=0;//0=ищем шаблон//1=ожидаем символ конца строки 13 char shablonMode_1=0; //буфер для сборки строки #define SIZEBUF_result 100 char uart_rezult_buf1[SIZEBUF_result]={0,}; char uart_rezult_buf2[SIZEBUF_result]={0,}; char* uart_rezult_buf=uart_rezult_buf1; short uart_rezult_buf_i=0;//индекс char* uart_bufRow=uart_rezult_buf1;//буфер с целой строкой //E N D буфер для сборки строки //E N D для составления строк void uart_Handler_GNSS(void)//эту функцию нужно вызывать постоянно { uint32_t ms = HAL_GetTick(); char isData=0; char* pData=(char*)uart_rx_buf; if(uartRxFullIRDone){ uartRxFullIRDone = 0; pData=(char*)&uart_rx_buf[SIZEBUF_uart_rx_buf/2]; isData=1; } if(uartRxHalfIRDone){ uartRxHalfIRDone = 0; isData=1; } if(isData){ isData=0; //статус о том что gps активен gParams.isGPS=1; for(int i =0;i<SIZEBUF_uart_rx_buf/2;i++){ switch (shablonMode) { case 0://0=ищем шаблон if(pData[i]==shablonGNGGA[shablon_iGNGGA]){ shablon_iGNGGA++; }else{ shablon_iGNGGA=0; } if(pData[i]==shablonGNRMC[shablon_iGNRMC]){ shablon_iGNRMC++; }else{ shablon_iGNRMC=0; } if(shablon_iGNGGA || shablon_iGNRMC){ uart_rezult_buf[uart_rezult_buf_i]=pData[i];uart_rezult_buf_i++; if(shablon_iGNGGA>=SIZEBUF_shablon || shablon_iGNRMC>=SIZEBUF_shablon){ shablon_iGNGGA=0; shablon_iGNRMC=0; shablonMode=1;//переходим в режим поиска конца строки } }else{ uart_rezult_buf_i=0; } break; case 1://1=ожидаем символ конца строки 13 if(pData[i]==13 || pData[i]==10){ //собрали строку uart_rezult_buf[uart_rezult_buf_i]='\r'; uart_rezult_buf_i++; if(uart_rezult_buf_i>=SIZEBUF_result){uart_rezult_buf_i=SIZEBUF_result;} uart_rezult_buf[uart_rezult_buf_i]='\n'; //переключаемся на следующий буфер if(uart_rezult_buf==uart_rezult_buf1){ uart_rezult_buf=uart_rezult_buf2; uart_bufRow=uart_rezult_buf1; }else{ uart_rezult_buf=uart_rezult_buf1; uart_bufRow=uart_rezult_buf2; } if(uart_bufRow[0] == 'G')//Поток данных от GGA { memcpy(buf_GGA,uart_bufRow,SIZEBUF_buf_GGA); gps_time_recieve=ms; } if(uart_bufRow[0] == 'R')//Поток данных от RMC { memcpy(buf_RMC,uart_bufRow,SIZEBUF_buf_RMC); gps_time_recieve=ms; } //-----------------------------------------Работа с NMEA-------------------------------------------------------------- if (decodeGGA(buf_GGA, &gpsData.ggastruct) == 0) flagGGA = 2; // 2 indicates the data is valid else flagGGA = 1; // 1 indicates the data is invalid //if(ttt){decodeGGA(buf_GGA, &gpsData.ggastruct);}//отладка if (decodeRMC(buf_RMC, &gpsData.rmcstruct) == 0) flagRMC = 2; // 2 indicates the data is valid else flagRMC = 1; // 1 indicates the data is invalid if ((flagGGA == 2) | (flagRMC == 2)) { uart_rezult_buf_out_AB[0]=0x5A; uart_rezult_buf_out_AB[1]=0xA5; //В данном буфере находятся данные от GPS после расшифровки NMEA, их можно уже непосредственно продвигать дальше - - - size_rez_buf_ab=sprintf((char*)&uart_rezult_buf_out_AB[3], "%.6f %.6f %02d:%02d:%02d.%03d %02d %d %.2f %.2f>", gpsData.ggastruct.lcation.latitude,gpsData.ggastruct.lcation.longitude, gpsData.ggastruct.tim.hour,gpsData.ggastruct.tim.min, gpsData.ggastruct.tim.sec, gpsData.ggastruct.tim.msec,gpsData.ggastruct.numofsat,gpsData.ggastruct.calc.calculation, gpsData.ggastruct.alt.altitude,gpsData.rmcstruct.course); gParams.isRTK_GPS = gpsData.ggastruct.calc.calculation;//параметры режима работы GPS uart_rezult_buf_out_AB[2]=size_rez_buf_ab; size_rez_buf_ab+=3;//размер преамбулы+поле размер } //переходим в режим поиска шаблона uart_rezult_buf_i=0; shablonMode=0; }else{ uart_rezult_buf[uart_rezult_buf_i]=pData[i]; uart_rezult_buf_i++; if(uart_rezult_buf_i>=SIZEBUF_result){uart_rezult_buf_i=SIZEBUF_result;} } break; } } //-опустить } if(gps_time_recieve && ((ms - gps_time_recieve) >= DELAY_GPS_STATUS_CONNECT)){ gParams.isGPS=0; //uart_startRecieving_GNSS();//было убрано, потому что когда часто происходило условие, // было обнуление статуса, и перезапуск приема от uart, это и была ошибка, потому что записывалось по верх уже имеющихся данных, новые данные } } //Геттер на отправку uint8_t* dpi_getGPS_buffer(int* ret_buff_size) { if(ret_buff_size){*ret_buff_size=size_rez_buf_ab;} return uart_rezult_buf_out_AB; } void uart_startRecieving_GNSS(void) { status_UART=1;//1=startRecieving 2=RxHalf 3=RxCplt 4=прием от АБ (uart3)//отладка memset(uart_rx_buf,0,sizeof(uart_rx_buf)); HAL_UART_Receive_DMA(&huart1, (uint8_t*)uart_rx_buf, SIZEBUF_uart_rx_buf);//начинаю прием данных от gps на uart1 }
Функции прерываний
void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart) { if(huart == &huart1){ status_UART=2;//1=startRecieving 2=RxHalf 3=RxCplt //отладка uartRxHalfIRDone = 1; //сработало прерывание по половине } } void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) //Callback от UART RX { if(huart == &huart1){//GPS //HAL_UART_DMAStop(huart); status_UART=3;//1=startRecieving 2=RxHalf 3=RxCplt //отладка uartRxFullIRDone = 1; //сработало прерывание по полному буферу } } // Обработчик ошибок UART void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart) { if (huart->Instance == USART1 && enResetUART) { //GPS /* Сброс ошибок и восстановление работы */ HAL_UART_DeInit(huart); HAL_UART_Init(huart); uartRxFullIRDone = 0; uartRxHalfIRDone = 0; uart_startRecieving_GNSS(); // if(status_UART==1){//1-send 2=startRecieving 3=finishRecieving 4=TxCplt // uart_sendData(); // }else{ // uart_startRecieving_GNSS(); // } }
Главный модуль
void proj_main() { volatile const char *ch = ";V-F-BIN;ver: "VER_PROG(VER_a,VER_b,VER_c);(void)ch; uart_GNSS_init(); uart_startRecieving_GNSS();//Здесь я начинаю принимать данные от gps while (1){ //хэндлеры uart_Handler_GNSS(); }//while (1) }
Ссылка на скачивание исходного кода [ https://t.me/ChipCraft В закрепленном сообщении [ #исскуствомк_исходный_код —Исходный код для Module_GPS_NMEA0183]
Немного искусства 🙂
Постарался записать трек своими проходами слово H A B R, но к сожалению из-за плохого качества сигнала, получилось коряво, интересующие для меня данные с gps это (Дата, время, широта, долгота и высота).
Графический GPS-трекер я разработал для тестирования на С#, если Вам будет интересно, пишите в комментариях и я с радостью напишу статью.
Если статья показалась Вам интересной, буду рад выпустить для Вас еще множество статей исследований по всевозможным видам устройств, так что, если не хотите их пропустить – буду благодарен за подписку на мой ТГ-канал: https://t.me/ChipCraft.
ссылка на оригинал статьи https://habr.com/ru/articles/936028/
Добавить комментарий