
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В).
![Понижающий преобразователь напряжения MP2315 [ +12V до +5V ] Понижающий преобразователь напряжения MP2315 [ +12V до +5V ]](https://habrastorage.org/r/w1560/getpro/habr/upload_files/d77/7b3/151/d777b3151f98dfd96ef264fb08e0579d.png)
!!! 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.