Как стать автором
Поиск
Написать публикацию
Обновить

Прием и парсинг NMEA-данных от GPS-приемника

Уровень сложностиСредний
Время на прочтение17 мин
Количество просмотров657

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(UART) к микроконтроллеру STM32F103
Подключение модуля-GPS(UART) к микроконтроллеру STM32F103

Сигнал GPS, подключается к выводу PA10-31_контакт - RX(МК-STM32F103)

Для более стабильного напряжения питания можно использовать следующую схему, в которой работает понижающий преобразователь MP231, но необходим источник +12В, в моем случае используется аккумуляторная сборка (NiMH/Pb +12В).

Понижающий преобразователь напряжения MP2315 [ +12V до +5V  ]
Понижающий преобразователь напряжения MP2315 [ +12V до +5V ]

!!! P.S. заранее прошу прощения за некачественные картинки осциллограмм, осциллограф (с возможностью сохранения данных на флешку), некорректно производит измерения ...!!!

Вид осциллограммы передаваемых данных модуля-gps(uart) (линия TX)

Осциллограмма амплитуды данных от модуля-gps(uart)
Осциллограмма амплитуды данных от модуля-gps(uart)

Показатель амплитуды данных от модуля gps(uart) = delta [ 3.4V ], можно подключать к микроконтроллеру STM32.

Схема подключения GPS(RS-232)

Подключение модуля-GPS(RS-232) через ADM3202 к МК-STM32F103
Подключение модуля-GPS(RS-232) через ADM3202 к МК-STM32F103

Сигнал модуля-gps(rs-232), сначала приходит на 13 контакт преобразователя ADM3202, далее преобразованный сигнал (TTL) уходит на PA10-31_контакт - RX(МК-STM32F103)

Схема подключения ADM3202 к МКSTM32 - макет

Макет ADM3202 и подключение к STM32F103
Макет ADM3202 и подключение к STM32F103

Также для более стабильного напряжения питания по +5В, можно использовать схему преобразователя напряжения MP2315.

Для более стабильного напряжения питания по +3В, можно использовать следующую схему, в которой работает линейный стабилизатор напряжения LP2985.

Линейный стабилизатор напряжения LP2985 (+5В  +3В)
Линейный стабилизатор напряжения LP2985 (+5В +3В)

Краткая информация о преобразователе 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) до преобразования ADM3202
Осциллограмма амплитуды данных от модуля-gps(rs-232) до преобразования ADM3202

Показатель амплитуды данных от модуля gps(rs-232) до преобразования = delta [ 10.6V ], нельзя подключать к микроконтроллеру STM32.

Вид осциллограммы передаваемых данных модуля-gps(rs-232) после преобразования ADM3202

Осциллограмма амплитуды данных от модуля-gps(rs-232) после преобразования ADM3202
Осциллограмма амплитуды данных от модуля-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).

все остальные параметры без изменений.

Настройка "Parameter settings"
Настройка "Parameter settings"

Конфигурация NVIC Settings

Захожу в параметр (NVIC Settings) и включаю глобальное прерывание

Для отслеживания состояния интерфейса USART и обработки важных событий (например, завершения приема или ошибки), в разделе NVIC Settings было включено глобальное прерывание USART, это обеспечивает возможность немедленного реагирования со стороны микроконтроллера на изменения состояния периферии без постоянного опроса регистров.

Настройка "NVIC Settings"
Настройка "NVIC Settings"

При работе с GPS-модулями, которые передают NMEA-сообщения раз в секунду (1Hz), важно правильно организовать прием данных, чтобы не пропустить ни одного пакета. Необходимо настроить DMA в режиме Circular данный режим минимизирует нагрузку на процессор и гарантирует надежный прием.

Конфигурация DMA Settings

Захожу в параметр DMA Settings и выполняю следующие настройки:

  1. Выбор потока/канала: USART1_RX (прием данных);

  2. Mode: Circular ;

  3. Increment Memory Address: Enabled (автоинкремент памяти);

  4. Data Width: Byte (8 бит, соответствует формату NMEA).

Настройка "DMA Settings"
Настройка "DMA Settings"

Реализация программного кода(настройка и прием данных)

Коротко о NMEA 0183

Это текстовый протокол, используемый для передачи данных между морским и авиационным навигационным оборудованием, включая GPS-приемники. Большинство современных GPS-модулей выводят информацию именно в этом формате.

Основные особенности:

  • Текстовый формат – данные передаются в виде ASCII-строк;

  • Структура сообщений – каждая строка начинается с $, содержит идентификатор типа данных и заканчивается контрольной суммой;

  • Скорость передачи – обычно 9600 бод (но может быть и выше для высокочастотных модулей);

  • Частота обновления – чаще всего 1 раз в секунду (1Hz), но бывают 5Hz, 10Hz и более.

Пример строки (GGA – Global Positioning System Fix Data):

GGA,112530.000,6012.3456,N,03015.6789,E,1,10,0.95,45.3,M,12.5,M,,*65

Поле

Значение

Описание

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):

RMC,112530.000,A,6012.3456,N,03015.6789,E,5.12,87.45,110825,,,A*6C

Поле

Значение

Описание

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-потока, вызывается постоянно из главного цикла.

Логика работы:

  1. Проверяет, сработали ли прерывания DMA (половина буфера или полный буфер);

  2. Если пришли новые данные - устанавливает флаг активности GPS (gParams.isGPS = 1);

  3. Поиск GGA или RMC

    • В режиме shabloneMode = 0 ищет последовательность GGA или RMC;

    • Когда шаблон найден, переключаемся в режим shabloneMode = 1;

    • В режиме 1 копирует байты до символа конца строки (13 или 10);

  4. Действие когда собрана строка

    • Записывает строку в буфер buf_GGA или buf_RMC в зависимости от типа;

    • Обновляет время последнего получения GPS (gps_time_receive).

  5. Декодирование

    • Вызывает decodeGGA() и decodeRMC() для извлечения данных в структуру gpsData.

  6. Формирование выходного пакета:

    • Если хотя бы одно из сообщений валидно, формирует строку с координатами, временем, количеством спутников, режим фикса, высотой и курсом.

    • Записывает в результат в uart_rezult_buf_out_AB[] с преамбулой 0x5A 0xA5 и длиной пакета.

  7. Если в течение (DELAY_GPS_STATUS_CONNECT)1000 миллисекунд новых данных нет - GPS считается отключенным (gParams.isGPS= 0).

  8. uint8_t* dpi_getGPS_buffer (Возвращает указатель на готовый пакет данных для передачи ведущему устройству, а так же его размер).

  9. 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]

Немного искусства :)

Тестовый проход GPS
Тестовый проход GPS

Постарался записать трек своими проходами слово H A B R, но к сожалению из-за плохого качества сигнала, получилось коряво, интересующие для меня данные с gps это (Дата, время, широта, долгота и высота).

Графический GPS-трекер я разработал для тестирования на С#, если Вам будет интересно, пишите в комментариях и я с радостью напишу статью.


Если статья показалась Вам интересной, буду рад выпустить для Вас еще множество статей исследований по всевозможным видам устройств, так что, если не хотите их пропустить – буду благодарен за подписку на мой ТГ-канал: https://t.me/ChipCraft.

Теги:
Хабы:
+2
Комментарии20

Публикации

Ближайшие события