Привет, Хабр!

В данной статье будет рассматриваться разработка коммутатора, для приема, обработки и передачи потока данных от GNSS-приемника и энкодера, осуществляться передача ведущему устройству будет по интерфейсу RS-485.

Интерфейс RS-485 - представляет собой промышленный стандарт физического уровня передачи данных, широко применяемый в распределенных системах управления, телеметрии и автоматизации, его ключевая особенность - использование дифференциального сигнала, что обеспечивает высокую помехоустойчивость и надежность передачи данных на значительные расстояния (до 1200 метров).

В основе работы лежит двухпроводная линия (выводы А и В), по которой передается информация в виде разности потенциалов:

  • Логическая "1" фиксируется при условии, что напряжение на линии А ниже, чем на линии B;

  • Логический "0" соответствует ситуации, когда потенциал линии А выше потенциала линии В.

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

В стандартных трансиверах интерфейса RS-485 передатчик и приемник интегрированы в одном корпусе и подключены к общей двухпроводной линии, направление выбирается выводом микроконтроллера, уровень на которой определяет в каком режиме микросхема, в приеме или в передаче.

Подробную информацию по теме интерфейса RS-485, можно изучить вот здесь [https://easyelectronics.ru/interfejs-rs-485.html].

Верхняя схема соединения устройств

Принцип взаимодействия устройств

Данная схема реализует архитектуру «ведущий-ведомый» (Master-Slave). Процесс взаимодействия можно разделить на два ключевых этапа:

  1. Этап опроса и обнаружения устройств
    При инициализации системы ведущее устройство отправляет ведомому устройству (коммутатору) запрос на получение списка активных подключенных устройств. В данном примере запрашивается статус энкодера и приемника GNSS.

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

  2. Этап сбора данных
    После успешного обнаружения активных устройств ведущее устройство инициирует цикл опроса для сбора данных. Последовательно отправляются запросы на получение данных: дистанции с энкодера и навигационной информации с приемника GNSS.

    Ведомое устройство агрегирует запрашиваемые параметры, формирует пакет данных и передает его ведущему устройству для последующей обработки.

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

В данной схеме используется GNSS-приемник работающий по RS-232, о том как с ним работать я писал статью [https://habr.com/ru/articles/936028/], в этой статье коммутатор будет принимать данные от GNSS приемника, собирать их в пакет и отправлять ведущему устройству по RS-485.

Для взаимодействия приема и передачи данных, коммутатор и ведущее устройство работают по внутреннему протоколу по аналогии MODBUS ASCII - являющийся одним из вариантов широко распространенного промышленного протокола Modbus, предназначаемого для организации обмена между ведущим и ведомым устройствами по последовательным линиям связи (RS-232/RS-422/RS-485).

Минимальный пример формата кадра MODBUS ASCII

  • Символ начала сообщения - ( : );

  • Адрес устройства - указывает, какому устройству предназначен кадр;

  • Код функции - определяет тип выполняемой операции (чтение, запись, диагностика);

  • Данные - содержат полезную нагрузку (адреса регистров, значения, количество слов);

  • Контрольная сумма - обеспечивает контроль целостности передачи данных;

  • Символы конца строки - (\r\n) - фиксация завершения кадра.

Подробную информацию на тему протокола Modbus, можно изучить вот здесь [https://habr.com/ru/articles/682292/], [https://habr.com/ru/articles/281430/].

Реализация протокола связи по RS-485, описывающая форматы пакетов для приема и передачи данных между ведущим и ведомым устройством.

Краткое объяснение пакетов:

Ведущее устройство (Master) формирует и отправляет запрос:

0xAB, 0xBA, <длина команды>, 0x09, <тип энкодера>, <тип GNSS>, <режим GNSS>, '>'

Этот пакет служит командой опроса статуса:

  • Байты 0xAB и 0xBA образуют преамбулу, по которой ведомое устройство синхронизирует начало кадра;

  • Поле «Длина команды» содержит длину в байтах начиная с поля 4 и заканчивая полем с символом 0x3E (‘>’) включительно.

  • Байт 0x09 - является кодом команды (в данном примере, запрос статуса);

  • Поля <тип энкодера>, <тип GNSS> задают конфигурацию активных модулей;

  • Символ '>' (0x3E)обозначает конец пакета.

Ведомое устройство (Slave) приняв пакет, формирует ответ:

0x5A, 0xA5, <длина команды>, 0x09, <установленный энкодер>, <установленный GNSS>, <тип GNSS>, '>' Здесь преамбула от ведомого устройства будет такая 0x5A, 0xA5.

Запрос от ведущего устройства (активные подключенные устройства)

№ поля

Код (HEX) MSB<  >LSB

Формат поля

Описание поля

Длина поля (байт)

1

0xАВ

Byte

Преамбула

1

2

0xВА

Byte

Преамбула

1

3

5

Byte

Длинна команды в байтах

1

4

9

Byte

Код команды 9

1

5

хх

Byte

Установка активного энкодера

1

6

xx

Byte

Установка активного GNSS

1

7

xx

Byte

Тип GNSS

1

xx

0x3E

Byte

‘>’ конец данных

1

Ответ от ведомого устройства (активные подключенные устройства)

№ поля

Код (HEX) MSB<  >LSB

Формат поля

Описание поля

Длина поля (байт)

1

0x5A

Byte

Преамбула

1

2

0xA5

Byte

Преамбула

1

3

5

Byte

Длинна команды в байтах

1

4

9

Byte

Код команды 9

1

5

хх

Byte

Установленный энкодер

1

6

хх

Byte

Установленный GNSS

1

7

хх

Byte

Тип GNSS

1

хх

0x3E

Byte

‘>’ конец данных

1

Если параметр команды «Установка активного экодера» равен 1 и энкодер физически не подключен, то значение текущего типа энкодера «Установленный энкодер» становится равным -1.

Если в ответе на команду[9] параметр «Установленный GNSS » равен -1, значит GNSS не подключен, если 1 – то подключен.

Тип GNSS: 1 – одиночный режим, 4 – режим с дифференциальной коррекцией, 5 – режим дифференциальной коррекции с плавающим решением.

Запрос от ведущего устройства (чтение данных GNSS)

№ поля

Код (HEX) MSB<  >LSB

Формат поля

Описание поля

Длина поля (байт)

1

0xАВ

Byte

Преамбула

1

2

0xВА

Byte

Преамбула

1

3

2

Byte

Длинна команды в байтах

1

4

‘G’

Byte

Код команды ‘G’ (0x47) Запрос данных GNSS 

1

xx

0x3E

Byte

‘>’ конец данных

1

Ответ от ведомого устройства (передача данных GNSS)

№ поля

Код (HEX) MSB<  >LSB

Формат поля

Описание поля

Длина поля (байт)

1

0x5A

Byte

Преамбула

1

2

0xA5

Byte

Преамбула

1

3

xx

Byte

Длинна команды в байтах

1

4

xx

Byte (string)

Пример ответа 90.1234567 57.1234567 16:11:33:128 09  2 Строка данных с координатами GNSS

xx Длина строки плавающая

xx

0x3E

Byte

‘>’ конец данных

1

Запрос от ведущего устройства (чтение данных с энкодера)

№ поля

Код (HEX) MSB<  >LSB

Формат поля

Описание поля

Длина поля (байт)

1

0xАВ

Byte

Преамбула

1

2

0xВА

Byte

Преамбула

1

3

2

Byte

Длина команды в байтах

1

4

‘Y’

Byte

Код команды ‘Y’ (0x59) Запрос дистанции 

1

xx

0x3E

Byte

‘>’ конец данных

1

Ответ от ведомого устройства (передача данных с энкодера)

№ поля

Код (HEX) MSB<  >LSB

Формат поля

Описание поля

Длина поля (байт)

1

0x5A

Byte

Преамбула

1

2

0xA5

Byte

Преамбула

1

3

xx

Byte

Длинна команды в байтах

1

4

xx

Byte (string)

Пример ответа 125728 Строка данных дистанции

xx

xx

0x3E

Byte

‘>’ конец данных

1

Запрос от ведущего устройства (чтение версии ПО для STM32 - код 2)

№ поля

Код (HEX) MSB<  >LSB

Формат поля

Описание поля

Длина поля (байт)

1

0xАВ

Byte

Преамбула

1

2

0xВА

Byte

Преамбула

1

3

2

Byte

Длина команды в байтах

1

4

2

Byte

Код команды 2 чтение версии 

1

xx

0x3E

Byte

‘>’ конец данных

1

Ответ от ведомого устройства (передача данных о версии ПО для STM32 - код 2)

№ поля

Код (HEX) MSB<  >LSB

Формат поля

Описание поля

Длина поля (байт)

1

0x5A

Byte

Преамбула

1

2

0xA5

Byte

Преамбула

1

3

xx

Byte

Длинна команды

1

4

xx

Byte (string)

Пример ответа Vers 1.2 Строка данных с версией ПО для контроллера ДП на STM32

xx Длина строки плавающая

xx

0x3E

Byte

‘>’ конец данных

1

Cхема электрическая принципиальная

Перечень компонентов

Резисторы

Конденсаторы, чип-дроссели, резонатор

Микросхемы

R1 - 0805 - 0 Ом

C1, C3, C5, C6, C7, C8, C10, C11, C12, C16, C17, C19, C21 0805 – (X7R – 50B - 0,1 мкФ ± 10%)

DD1 - ADM3202 (Приемопередатчик RS-232)

R2 - (0805 - 100 кОм ± 5%)

C13, C14 - (X7R – 50B - 12 пФ ± 10%)

DD2 - микроконтроллер STM32F030CCT6

R3, R6, R17, R19 - (0805 - 1,5  кОм ± 5%)

C2, C16  - (Корпус A 10 В - 22 мкФ ± 10% )

DA1 - преобразователь напряжения MP2315GJ(+12 -- +5V)

R5, R18 - (0805 - 120 Ом ± 5%)

C15 - (Корпус C 16 В - 47 мкФ ± 10%)

DA2 - линейный стабилизатор напряжения (+5 -- +3.3V)

R7- (0805 - 20 Ом ± 5%)

C4, C9, C20 - (X7R – 50B - 4,7 мкФ ± 10%)

R8 -  (0805 - 20 кОм ± 5%)

R9 - (0805 - 7,5  кОм ± 5%)

L1 - (10мкФ, CDRH64)

R10 - (0805 - 39 кОм ± 5%)

BQ1 - кварцевый резонатор - 8 МГц

R11, R12, R13, R14, R15, R16 - (0805 - 10  кОм ± 5%)

R4 -  (0805 - 65 кОм ± 5%)

Объяснение по схеме

DD2 Микроконтроллер STM32F030CCT6 выполняет функции центрального управляющего узла коммутатора данных.

Характеристики МК

  • Ядро: ARM Cortex-M0, тактовая частота до 48 МГц;

  • Память: 256 КБ Flash для хранения ПО, 32 КБ SRAM для оперативных данных;

  • Периферия:

    • 6 универсальных синхронно-асинхронных приемопередатчиков (UART);

    • Интерфейсы: I2C, SPI;

    • 12-битный АЦП (16 каналов);

    • таймеры общего назначения и ШИМ-модули;

    • системные таймеры (SysTick, watchdog).

Ссылка на техническую документацию МК [https://static.chipdip.ru/lib/915/DOC012915875.pdf]

В микроконтроллере будет задействовано сразу 3 UART-порта:

  • UART_1 - предназначен для работы с GNSS-приемником;

  • UART_2 - предназначен для работы с энкодером;

  • UART_3 - предназначен для работы с ведущим устройством.

Выводы NRST и BOOT0

Вывод NRST(reset) используется для аппаратного сброса МК, подключается через резистор R16(10кОм) к питанию +3В — подтягивает NRST к логической «1», конденсатор С19(0,1мкФ), формирует RC-цепочку, используется для подавления помех и автосброса при включении питания, данный пример схемы гарантирует корректный старт МК после подачи питания, защищает от ложных срабатываний при скачках напряжения.

Вывод BOOT0 определяет, откуда МК будет загружать программу после сброса:

  • BOOT0 = 0 — загрузка из Flash‑памяти(основной режим работы);

  • BOOT0 = 1 — загрузка из системной памяти (встроенный загрузчик через UART, I2C, SPI).

В схеме вывод подтянут резистором R13(10кОм) к земле (логический «0»), это обеспечивает автоматическую загрузку программы из flash‑памяти после старта, если потребуется использовать встроенный загрузчик, можно временно подать «1» на BOOT0.

Обвязка питания VCC и VA

МК имеет несколько выводов питания:

  • VCC — основное цифровое питание (3.3В);

  • VA — питание аналоговой части (АЦП, компараторы и т. д.).

На выводах VCC и VA уставлен конденсатор С18 (0.1мкФ), он фильтрует высокочастотные помехи, возникающие при переключении логики, конденсатор ставиться как можно ближе к выводам МК, также дополнительно установлен танталовый конденсатор С20 (4.7мкФ), он сглаживает низкочастотные колебания и стабилизирует питание аналоговой части.

Узел кварцевого резонатора BQ

Кварцевый резонатор подключается к выводам МК (5 и 6), также добавляются нагрузочные конденсаторы С13 и С14 (12пФ), они обеспечивают корректный запуск и устойчивую работу генератора, формируя необходимую нагрузочную емкость.

После подачи напр.питания МК запускает внутренний RC-генератор, но при активации внешнего кварцевого генератора на выводах (5 и 6) начинает работать схема усилителя с положительной обратной связью, при которой:

  • Кварцевый резонатор задает стабильную резонансную частоту колебаний;

  • Нагрузочные конденсаторы формируют требуемую емкость, обеспечивая корректное возбуждение и работу генератора;

  • Полученная частота передается во внутреннюю систему тактирования МК.

Узел RS-485

В данной схеме я использую MAX14841 - это дифференциальный трансивер RS-485/RS-422, предназначенный для передачи данных в промышленных и встраиваемых системах, техническая документация [https://www.alldatasheet.com/datasheet-pdf/pdf/332495/MAXIM/MAX14841EASA%2B.html].

На выводы 6(А) и 7(В) микросхемы DA3, поступают данные от энкодера, выводы подключены через согласующие резисторы:

  • Резистор R5(120 Ом) - предотвращает отражение сигналов в линии;

  • Резисторы R3 и R6(1,5 кОм) - подтягивающие, обеспечивающие корректное определение логических уровней на линии.

Резисторы R11 и R12(10 кОм) - устанавливают стабильный логический уровень на управляющих выводах RE и DE, таким образом они предотвращают ложные переключения при старте системы.

Подключение выводов к МК:

  • RO(выход приемника) - подключается к PA3;

  • DI(вход) - подключается к PA2;

  • DE и RE - подключаются PA1;

  • выводы А и B - подключаются к энкодеру.

Микросхема DA4, используется для приема и передачи данных ведущему устройству, схема подключения резисторов точно такая же как и у DA3,

Подключение выводов к МК:

  • RO(выход приемника) - подключается к PB11;

  • DI(вход) - подключается к PB10;

  • DE и RE - подключаются PB1.

  • выводы А и B - подключаются к ведущему устройству.

Узел подключения GNSS-приемника

Микроконтроллеры 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В).

Техническая документация ADM3202 [https://www.alldatasheet.com/datasheet-pdf/pdf/48731/AD/ADM3202.html]

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

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

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

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

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

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

Узел преобразователя и стабилизатора напряжения

Микросхема DA1 MP2315 представляет собой синхронный понижающий DC-DC преобразователь с интегрированными силовыми MOSFET-ключами. Высокая частота переключения (до 2.2 МГц), компактный корпус и широкий диапазон входных напряжений (от 4.5 В до 24 В), ссылка на техническую документацию MP2315 [https://www.alldatasheet.com/datasheet-pdf/pdf/1035056/MPS/MP2315.html].

Микросхема DA2 — LP2985 представляет собой малошумящий стабилизатор, предназначен для преобразования входного напряжения +5В в стабильное напряжение +3В, используемое МК и периферийными узлами, ссылка на техническую документацию LP2985 [https://www.alldatasheet.com/datasheet-pdf/pdf/99706/TI/LP2985.html].

Модель печатной платы коммутатора

Слой TOP (верхний слой) и слой BOTTOM(нижний слой) подключены к GND

Прикладываю схему и модель печатной платы cсылка на скачивание Commutator_STM32(.sch/.pcb)https://t.me/ChipCraft В закрепленном сообщении [ #исскуствомк_Схема_Commutator_STM32]

Прикладываю также программу для открытия P-CAD Viewer, да... она очень и очень древняя, но я данную программу по построению схем и печатных плат полюбил очень давно, и в основном проектирую только в ней, cсылка на скачивание P-CAD Viewer [ https://t.me/ChipCraft В закрепленном сообщении [ #исскуствомк_проектирование_sch/pcb — Программа для просмотра схем и печатных плат]

Краткая инструкция по открытию файлов

Для того чтобы открыть схему, необходимо открыть программу SCHView(располагается в папке P-CAD 2006 Viewer) и в ней уже открыть файл, для того чтобы открыть печатную плату, необходимо открыть программу PCBView и в ней уже открыть файл.

Настройка микроконтроллера STM32F030CCTx в CubeIDE (Ведомое устройство)

Настройка RCC и SYS (в RCC выбираю Crystal/Ceramic Resonator, так как у меня внешний кварц)

Настройка выводов узла подключения энкодера

Вывод PA1 предназначен для управления DA3 (MAX14841), он подключен к DE и RE, для приема и передачи данных энкодера, необходимо поднимать и опускать вывод МК, работа схемы:

  • На выводе МК = 1 (высокий уровень, "поднят"):

    • DE = 1 — драйвер включен (выходы A и B активны и передают данные из DI).

    • RE = 1 — приемник выключен (выход RO отключен).

    •  Микросхема находится в режиме передачи.

  • На выводе МК = 0 (низкий уровень, "опущен"):

    • DE = 0 — драйвер выключен (выходы A и B в высокоимпедансном состоянии).

    • RE = 0 — приемник включен (данные с A и B передаются на RO).

    • Микросхема находится в режиме приема.

Настройка USART2

Энкодер передает данные на скорости [230400], в Baud Rate выставляю скорость, остальные параметры без изменений

Заходим во вкладку NVIC Settings и включаем прерывания

Заходим во вкладку DMA Settings и подключаем DMA

Настройка выводов узла приема и передачи данных с ведущим устройством

Вывод PB1 предназначен для управления DA4 (MAX14841), он подключен к DE и RE, для приема и передачи данных с ведущим устройством, необходимо поднимать и опускать вывод МК, работа схемы:

  • На выводе МК = 1 (высокий уровень, "поднят"):

    • DE = 1 — драйвер включен (выходы A и B активны и передают данные из DI).

    • RE = 1 — приемник выключен (выход RO отключен).

    •  Микросхема находится в режиме передачи.

  • На выводе МК = 0 (низкий уровень, "опущен"):

    • DE = 0 — драйвер выключен (выходы A и B в высокоимпедансном состоянии).

    • RE = 0 — приемник включен (данные с A и B передаются на RO).

    • Микросхема находится в режиме приема.

Настройка USART3

Ведущее устройство передает и принимает данные на скорости [230400], в Baud Rate выставляю скорость, остальные параметры без изменений

Заходим во вкладку NVIC Settings и включаем прерывания

Заходим во вкладку DMA Settings и подключаем DMA

Настройка выводов узла подключения приемника-gnss

В данной статье [https://habr.com/ru/articles/936028/] я уже рассказывал про настройку и работу с приемником-gnss, дублирую информацию для удобства.

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

В параметрах USART1 (Parametr Settings) я выбираю:

  • Mode: Asynchronous (асинхронный режим);

  • Baud Rate: 9600 бит/с (в моем примере два модуля-gnss (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/].

Программная реализация ведомого устройства

Ссылка на скачивание исходного кода [ https://t.me/ChipCraft В закрепленном сообщении [ #исскуствомк_исходный_код — Исходный код для STM32F030CCTx_Switch_GPS_DP].

Модуль uart_processing.c

Данный модуль реализует обработку обмена по трем интерфейсам UART, используемым в составе коммутатора:

  • UART1 - прием данных от GNSS-приемника;

  • UART2 - взаимодействие с датчиками перемещения(ДПИ, ДП32, Encoder);

  • UART3 - взаимодействие с ведущим устройством.

Функция void fillBuff_com9(int8_t typeDP)

Формирует ответ на девятую команду протокола, которая служит для запроса или установки типа подключенного датчика перемещения

Режим запроса статуса (typeDP == 1), возвращается текущее значение параметра gParams.type_DP которое отражает активный тип подключенного датчика.

Режим смены конфигурации (typeDP != 1) проверяется, отличается ли новый тип от ранее установленного (gParams.typeDP_fromAB), если произошла смена конфигурации, выполняются переинициализация датчиков.

После выполнения логики, функция формирует ответный пакет в буфере uart_rx_buf_com9:

  • преамбула (0x5A, 0xA5);

  • идентификатор команды (0x09);

  • Тип ДП(retDP);

  • признак активности GPS (gParams.isGPS);

  • статус RTK (isRTK_GPS);

  • конец сообщения (>).

Функция uart_Handler()

Это центральный узел, который предназначается для взаимодействия с ведущим устройством

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

Анализ приема:

  • Проверяется флаг uartRxABDone, сообщающий о завершении приема пакета по интерфейсу USART3;

  • Валидность пакета подтверждается преамбулой (0xAB и 0xBA) и соответствием длины данных (uart_rx_buf_AB[2]).

Декодирование команды:

в зависимости от байта команды uart_rx_buf_AB[3] выполняется одна из ветвей:

  • 'G' (GNSS-данные) - вызывается dpi_getGPS_buffer(), возвращает GNSS буфер и передается ведущему устройству;

  • 0x09(девятая команда) - вызывается fillBuff_com9() и передается ведущему устройству;

  • 'Y' (данные от датчиков перемещения) - вызывается dpi_getDP_buffer(), формирует буфер дистанции и передается ведущему устройству;

  • 0x02 (версия прошивки) - формируется ответ в формате Vx.x.x> и передается ведущему устройству.

Обработка завершения передачи:

  • Если пакет подготовлен, управление передается функции uart_transmitAB();

  • В противном случае запускается повторный прием (uart_startRecievingAB), гарантирует непрерывность работы, после успешной отправки пакета, сбрасывается флаг uartTxIRDone_AB, восстанавливается линия приема и освобождается RS-485 (сброс сигнала DE_RE).

uart_processing.c
#include "./Project/shared.h"
#include "./Project/GNSS/uartProc_GNSS.h"
#include "./Project/GNSS/NMEA.h"
#include "./Project/DP/uart_Proc_dp.h"
#include "./Project/uart_processing.h"
#include "main.h"
#include <stdlib.h>//abs
#include <string.h>//memset
#include <stdio.h>

typedef struct{
	uint8_t code_command;
	uint8_t type_dp;
	uint8_t gps_activity;
}UART_AB_Command9;//ответ на 9 команду
UART_AB_Command9 uart_comm9;
#define STRUCT_SIZE_COMMAND9 (sizeof(struct UART_AB_Command9))

#define SIZEBUF_uart_rx_buf_com9 9 //12
uint8_t uart_rx_buf_com9[SIZEBUF_uart_rx_buf_com9]={0,};
int size_uart_rx_buf_com9=0;

#define SIZEBUF_uart_rx_buf_com2 16 //12
uint8_t uart_rx_buf_com2[SIZEBUF_uart_rx_buf_com2]={0,};
int size_uart_rx_buf_com2=0;

//прерывания от GPS
volatile uint8_t uartRxFullIRDone = 0; //сработало прерывание по полному буферу
volatile uint8_t uartRxHalfIRDone = 0; //сработало прерывание по половине

volatile uint8_t uartRx_DPFullIRDone = 0; //сработало прерывание по полному буферу
volatile uint8_t uartRx_DPHalfIRDone = 0; //сработало прерывание по половине

uint16_t uartDpiRxSize_Done=0;
uint8_t* tmpBuf_test =0;

//прерывания от АБ
volatile uint8_t uartRxABDone = 0;//сработало прерывание на прием от АБ
uint16_t uartRxSize_Done=0;
volatile uint8_t uartTxIRDone_AB = 0; //сработало прерывание на отправку в АБ
//прерывания от ДП
volatile uint8_t uartRxDPDone = 0;//сработало прерывание на прием от ДП
volatile uint8_t uartTxDPDone = 0;//сработало прерывание на отправку в ДП

char enResetUART=1;//разрешить переинициализацию UART

short status_UART=0;//1=startRecieving 2=RxHalf 3=RxCplt //отладка
short status_UART_AB=0;//1-send 2=startRecieving 3=finishRecieving 4=TxCplt

uint8_t* pData_GPS = 0;
uint16_t size_gps = 0;
uint8_t isRTK_GPS =0;
//буфер приема от АБ
#define SIZEBUF_uart_rx_buf_AB 20 // 20
uint8_t uart_rx_buf_AB[SIZEBUF_uart_rx_buf_AB]={0,};

//void buffer_gps(uint8_t* buf_gps,uint16_t size);
//void uart_transmitAB(const uint8_t *pData, uint16_t Size);
char tr_busy=0;

void fillBuff_com9(int8_t typeDP)//typeDPI 1=DPI 2=DP32 3=Encoder
{
	int8_t retDP=-1;
	isRTK_GPS = gParams.isRTK_GPS;

	if(typeDP==-1){//запрос статуса, возвращаем что есть
		retDP=gParams.type_DP;
	}else{//установка подключенного датчика перемещения
		if(gParams.typeDP_fromAB!=typeDP){
			uint8_t typeDP_prev_fromAB=gParams.typeDP_fromAB;
			gParams.typeDP_fromAB=typeDP;

			retDP=typeDP;

			gParams.type_DP=typeDP;
			if(typeDP==DP_DPI){
				if(typeDP_prev_fromAB==DP_DP32){//если были настроены на DP32
					HAL_TIM_Base_Stop_IT(&htim6);
				}
				HAL_UART_DMAStop(&huart2);//?
				HAL_UART_DeInit(&huart2);
				HAL_UART_Init(&huart2);
				uart_startRecieving_DP();//dpi

				gParams.type_DP=-1;//т.к. этот датчик отвечает, то можно выяснить наличи, поому задаём что его нет, как ответит - зададим что есть
				retDP=-1;
			}else if(typeDP==DP_DP32){
				if(typeDP_prev_fromAB==DP_DPI){//если были настроены на DPI
					HAL_UART_DMAStop(&huart2);
					HAL_UART_DeInit(&huart2);
					dp32_enc_init();
				}
			}
			else if(typeDP==DP_ENC){
				if(typeDP_prev_fromAB==DP_DPI){//если были настроены на DPI
					HAL_UART_DMAStop(&huart2);
					HAL_UART_DeInit(&huart2);
					dp32_enc_init();
				}
				if(typeDP_prev_fromAB==DP_DP32){//если были настроены на DP32
					HAL_TIM_Base_Stop_IT(&htim6);
				}

			}
		}else{
			retDP=gParams.type_DP;
		}
		dpi_resetDist();
	}
//-----Модифицированный вариант ответа на команду
	uart_rx_buf_com9[0]=0x5A;
	uart_rx_buf_com9[1]=0xA5;
	//uart_rx_buf_com9[2]=5;
	uart_rx_buf_com9[3]=0x9;
	uart_rx_buf_com9[4]=retDP;
	uart_rx_buf_com9[5]=gParams.isGPS?1:-1;
	uart_rx_buf_com9[6]=isRTK_GPS;
	uart_rx_buf_com9[7]='>';
	size_uart_rx_buf_com9=8;
	uart_rx_buf_com9[2]=size_uart_rx_buf_com9-3;//размер буфера начиная с 3 индекса включительно

	if(size_uart_rx_buf_com9>=53){
		uart_rx_buf_com9[0]=0x5A;
		uart_rx_buf_com9[1]=0xA5;
		//uart_rx_buf_com9[2]=5;
		uart_rx_buf_com9[3]=0x9;
		uart_rx_buf_com9[4]=retDP;
		uart_rx_buf_com9[5]=gParams.isGPS?1:-1;
		uart_rx_buf_com9[6]=isRTK_GPS;
		uart_rx_buf_com9[7]=0;
		uart_rx_buf_com9[8]='>';
		size_uart_rx_buf_com9=9;
		uart_rx_buf_com9[2]=size_uart_rx_buf_com9-4;//размер буфера начиная с 3 индекса включительно
	}
}

void uart_Handler(void){

	if(uartRxABDone){
		uartRxABDone = 0;
		char isTransmit=0;

		if(uart_rx_buf_AB[0] == 0xab && uart_rx_buf_AB[1] == 0xba){
			if(uart_rx_buf_AB[2]+3<=uartRxSize_Done){
				if(uart_rx_buf_AB[3]=='G'){//ответ на GPS
					//f_debug(1);
					int tmpSize;
					uint8_t* tmpBuf=dpi_getGPS_buffer(&tmpSize);
					isTransmit=1;
					uart_transmitAB(tmpBuf, tmpSize);
				}else if(uart_rx_buf_AB[3]==0x9){//ответ на 9 команду
					fillBuff_com9(uart_rx_buf_AB[4]);
					isTransmit=1;
					uart_transmitAB(uart_rx_buf_com9, size_uart_rx_buf_com9);
				}else if(uart_rx_buf_AB[3]=='Y'){//ответ на dp
					int tmpSize;
					uint8_t* tmpBuf=dpi_getDp_buffer(&tmpSize);
					tmpBuf_test = tmpBuf;
					isTransmit=1;
					uart_transmitAB(tmpBuf, tmpSize);
				}else if(uart_rx_buf_AB[3]==0x2){//версия
					int tmpSize;
					uart_rx_buf_com2[0]=0x5A;
					uart_rx_buf_com2[1]=0xA5;
					tmpSize=sprintf((char*)&uart_rx_buf_com2[3],"v%d.%d.%d>",VER_a,VER_b,VER_c);
					uart_rx_buf_com2[2]=tmpSize;
					tmpSize+=3;//размер преамбулы+поле размер
					isTransmit=1;
					uart_transmitAB(uart_rx_buf_com2, tmpSize);
				}
			}
		}

		if(isTransmit==0){
			uart_startRecieving_AB();
		}

	}

	if(uartTxIRDone_AB){
		uartTxIRDone_AB = 0;
//		dist = 0;
		uart_startRecieving_AB();
	}
}

void uart_transmitAB(const uint8_t *pData, uint16_t Size)
{
	if(pData && Size){

		if(tr_busy){
			tr_busy=5;//отладка
		}
		tr_busy=1;
		HAL_GPIO_WritePin(DE_RE_DP_GPIO_Port, DE_RE_DP_Pin, GPIO_PIN_SET);
		HAL_UART_Transmit_DMA(&huart3, pData, Size);

	}
}

void uart_startRecieving_AB(void)
{
	status_UART_AB=2;
	__HAL_UART_CLEAR_OREFLAG(&huart3);//используется для сброса флага ошибки переполнения приёмного буфера UART, который обозначается как ORE (Overrun Error). Нужно сбросить флаг для продолжения приема данных.
	__HAL_UART_CLEAR_IDLEFLAG(&huart3);//используется для сброса флага IDLE (бездействия) в структуре управления UART (huart1). Этот флаг IDLE часто используется для определения окончания передачи данных. Когда флаг IDLE устанавливается, это может означать, что передача данных завершена.
	HAL_UARTEx_ReceiveToIdle_DMA(&huart3, (uint8_t*)uart_rx_buf_AB, SIZEBUF_uart_rx_buf_AB);
	//HAL_UART_Receive_DMA(&huart3, (uint8_t*)uart_rx_buf_AB, 4);
}

void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart)
{
	if(huart == &huart1){
		status_UART=2;//1=startRecieving 2=RxHalf 3=RxCplt //отладка
		uartRxHalfIRDone = 1; //сработало прерывание по половине
	}
	if(huart == &huart2){
		uartRx_DPHalfIRDone =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; //сработало прерывание по полному буферу
	}
	if(huart == &huart2){//DPI
		uartRx_DPFullIRDone = 1;
	}


	if(huart == &huart3)
	{
		HAL_UART_DMAStop(huart);
		uartRxABDone=1;
		//uartRxSize_Done=Size;
		status_UART_AB=3;//1-send 2=startRecieving 3=finishRecieving 4=TxCplt
	}
}

void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) //Callback от UART TX
{
	if(huart == &huart3){//AB
		HAL_GPIO_WritePin(DE_RE_DP_GPIO_Port, DE_RE_DP_Pin, GPIO_PIN_RESET);
		status_UART=4;//1-send 2=startRecieving 3=finishRecieving 4=TxCplt
	    uartTxIRDone_AB=1;
	    tr_busy=0;
	}
	if(huart == &huart2){//dpi
		HAL_GPIO_WritePin(DP_OUT_GPIO_Port, DP_OUT_Pin, GPIO_PIN_RESET);//переключить 485 на приём
		uartTxDPDone = 1;
	}
}
//Callback на получение данных от АБ и ДП
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
	if(huart == &huart3)//AB
	{
		HAL_UART_DMAStop(huart);
		uartRxABDone=1;
		uartRxSize_Done=Size;
		status_UART_AB=3;//1-send 2=startRecieving 3=finishRecieving 4=TxCplt
	}
	if(huart == &huart2){//DPI
		HAL_UART_DMAStop(huart);
		uartRx_DPFullIRDone = 1;
		uartDpiRxSize_Done=Size;
	}
}
// Обработчик ошибок 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(huart->Instance == USART2){//dpi
    	/* Сброс ошибок и восстановление работы */
    	HAL_UART_DeInit(huart);
    	HAL_UART_Init(huart);
    	uartRxDPDone = 0;
    	uartTxDPDone = 0;
    	HAL_GPIO_WritePin(DP_OUT_GPIO_Port, DP_OUT_Pin, GPIO_PIN_RESET);//переключить 485 на приём
    	uart_startRecieving_DP();//dpi
    	uart_startTransmiting_DP();
    }

    if (huart->Instance == USART3) { //AB
		/* Сброс ошибок и восстановление работы */
		HAL_UART_DeInit(huart);
		HAL_UART_Init(huart);
		uartRxABDone = 0;
		uartTxIRDone_AB = 0;
		tr_busy=0;
		memset(uart_rx_buf_AB,0,SIZEBUF_uart_rx_buf_AB);
		HAL_GPIO_WritePin(DE_RE_DP_GPIO_Port, DE_RE_DP_Pin, GPIO_PIN_RESET);
		uart_startRecieving_AB();
	}

}

)

Модуль uart_Proc_dp.c

Краткое описание функций:

  • dpi_getDp_buffer() - получение актуального пакета для передачи;

  • dpi_getDist() - узнать накопленное расстояние;

  • dpi_resetDist() - сбросить счетчик дистанции и внутренние буферы;

  • dpi_getDpi_connect_status() - определение состояния подключения.

Данный модуль предназначен для работы с несколькими типами датчиков перемещения

ДПИ (по интерфейсу RS-485)

  • Команда 0xBA 0xDC используется для запроса дистанции;

  • Ответ от ДПИ передается в 6-байтном формате: 4 байта дистанции + 2 байта контрольной суммы CRC.

  • Функция uartHandlerDp анализирует входящий пакет;

  • CRC вычисляется по алгоритму побайтового суммирования с инверсией результата;

  • Если CRC корректна, обновляется переменная dist, в которой хранится расстояние в импульсах;

  • Результат упаковывается в буфер uart_rx_buf_dpi[];

  • dpi_time_send и dpi_time_status_connect задают цикличность запросов и ограничение времени ожидания;

  • Если ответа нет более 100мс, датчик считается отключенным (gParams.typeDP=-1).

ДП32(работа по генерации импульсов)

подключается напрямую к линии RX микроконтроллера и генерирует импульсы

  • Прерывание по фронту PB3 фиксирует факт прихода импульса;

  • Таймер TIM6 используется для определения направления перемещения

    2 импульса - > движение назад (счетчик уменьшается)

3 импульса - > движение вперед (счетчик увеличивается)

Настройка PB3 и TIM6 в CubeMX
Выбрать GPIO_EXTI(вывод настроен на внешнее прерывание)
Выбрать GPIO_EXTI(вывод настроен на внешнее прерывание)
Выставить в параметрах таймера Counter Period и включить прерывание, остальные параметры без изменений
Выставить в параметрах таймера Counter Period и включить прерывание, остальные параметры без изменений

Encoder (данный тип датчика выдает только импульсы в одном направлении, т.е. дистанция будет всегда положительная)

Каждый фронт на входе PB3 увеличивает счетчик dpEnc_counter, обновление также транслируется с преамбулой (0x5A и 0xA5).

uart_Proc_dp.c
#include "./Project/shared.h"
#include "./Project/GNSS/uartProc_GNSS.h"
#include "./Project/DP/uart_Proc_dp.h"
#include "./Project/uart_processing.h"
#include "main.h"
#include <stdlib.h>//abs
#include <string.h>//memset
#include <stdio.h>
//{0xba,0xdc};
#define size_command_dcba 2
uint8_t command_dcba[size_command_dcba] = {0xba, 0xdc};

//буфер куда будут приходить данные от датчиков
#define SIZEBUF_uart_rx_buf_dp 60//6
uint8_t uart_rx_buf_dp[SIZEBUF_uart_rx_buf_dp]={0,};

#define SIZEBUF_uart_rx_buf_dpi 16 //16
uint8_t uart_rx_buf_dpi[SIZEBUF_uart_rx_buf_dpi]={0,};
int size_uart_rx_buf_dpi=0;

unsigned long dist = 0;//расстояние от начальной точки (в импульсах пути)//dma

extern volatile uint8_t uartRxDPDone;//сработало прерывание на прием 
extern volatile uint8_t uartTxDPDone;//сработало прерывание на отправку

extern volatile uint8_t uartRx_DPFullIRDone; //сработало прерывание по полному буферу
extern volatile uint8_t uartRx_DPHalfIRDone; //сработало прерывание по половине
extern uint16_t uartDpiRxSize_Done;

#define DELAY_DPI_SEND	1//как часто слать запрос в DPI
uint32_t dpi_time_send = 0;

//для проверки что датчик отключили
#define DELAY_DPI_STATUS_CONNECT	100
uint32_t dpi_time_status_connect = 0;
//int8_t dpi_connect_status=-1;//-1=не подключен 1=подключен
//E N D для проверки что датчик отключили

//для DP32
int32_t dp32_counter=0;
int8_t dp32_ImpCounter=0;//2=минус 3=плюс
//E N D для DP32

uint32_t dpEnc_counter=0;

unsigned short calcCRC(unsigned short* pData,int sizeInBytes)
{
	int i;
	unsigned long FCB=0;
	int Len=sizeInBytes>>1;
	if(sizeInBytes&1){
		pData[Len]&=0xFF;
		Len++;
	}
	for (i=0;i<Len;i++) FCB+=(unsigned long)pData[i];
	FCB=(FCB&0xFFFF)+(FCB>>16);
	FCB=(FCB&0xFFFF)+(FCB>>16);
	FCB=~FCB;
	return((unsigned short)(FCB&0xFFFF));
}

void uart_Proc_dp_init(void)
{
	//добавление преамбулы
	uart_rx_buf_dpi[0]=0x5A;
	uart_rx_buf_dpi[1]=0xA5;
	size_uart_rx_buf_dpi = sprintf((char*)&uart_rx_buf_dpi[3],
                                   "%lu>",0lu);
	uart_rx_buf_dpi[2]=size_uart_rx_buf_dpi;
	size_uart_rx_buf_dpi+=3;//размер преамбулы+поле размер
}

void uart_Handler_DP(void)
{

	if(gParams.typeDP_fromAB==DP_DPI){
		uint32_t ms = HAL_GetTick();


		if(uartRx_DPFullIRDone){

			uartRx_DPFullIRDone=0;
			if(uartDpiRxSize_Done==6){
				unsigned long tmp;
				unsigned short tmpCRC;
				unsigned short tmpCalcCRC;
				memcpy(&tmp,&uart_rx_buf_dp[0],4);
				memcpy(&tmpCRC,&uart_rx_buf_dp[4],2);
				tmpCalcCRC=calcCRC((unsigned short*)&tmp, 4);
				if(tmpCalcCRC==tmpCRC){
					dist=tmp;
					//добавление преамбулы
					uart_rx_buf_dpi[0]=0x5A;
					uart_rx_buf_dpi[1]=0xA5;
					size_uart_rx_buf_dpi = sprintf((char*)&uart_rx_buf_dpi[3],
                                                   "%lu>",dist);//добавление преамбулы
					uart_rx_buf_dpi[2]=size_uart_rx_buf_dpi;
					size_uart_rx_buf_dpi+=3;//размер преамбулы+поле размер

					if(size_uart_rx_buf_dpi>=52){
						memset(uart_rx_buf_dpi,0,size_uart_rx_buf_dpi);
						uart_rx_buf_dpi[0]=0x5A;
						uart_rx_buf_dpi[1]=0xA5;
						size_uart_rx_buf_dpi = sprintf((char*)&uart_rx_buf_dpi[3],"%lu%d>",dist,0);//добавление преамбулы
						uart_rx_buf_dpi[2]=size_uart_rx_buf_dpi;
						size_uart_rx_buf_dpi+=3;//размер преамбулы+поле размер
					}
					//if(dpi_connect_status!=1){
					if(gParams.type_DP!=1){//всё норм, ставим статус наличия
						//dpi_connect_status=1;
						gParams.type_DP=1;
						//fillBuff_com9(1, -1);
					}
				}else{
				}
				dpi_time_send=ms;
				dpi_time_status_connect=0;
			}else{
			}
			uart_startRecieving_DP();
		}


		if(dpi_time_send && ((ms - dpi_time_send) >= DELAY_DPI_SEND)){
			dpi_time_send=0;
			uart_startTransmiting_DP();
		}


		if(dpi_time_status_connect && ((ms - dpi_time_status_connect) >= DELAY_DPI_STATUS_CONNECT)){
			dpi_time_status_connect=0;

			if(gParams.type_DP!=-1){
				gParams.type_DP=-1;
				//dpi_connect_status=-1;//-1=не подключен 1=подключен
				//fillBuff_com9(0, -1);
			}

			uart_startTransmiting_DP();
		}
	}
}

int8_t dpi_getDpi_connect_status(void)
{
	//return dpi_connect_status;
	return gParams.type_DP;
}

uint8_t* dpi_getDp_buffer(int* ret_buff_size)
{
	if(ret_buff_size){*ret_buff_size=size_uart_rx_buf_dpi;}
	return uart_rx_buf_dpi;
}

unsigned long dpi_getDist(void)
{
	return dist;
}

void dpi_resetDist(void)
{
	dist=0;
	uart_rx_buf_dpi[0]=0x5A;
	uart_rx_buf_dpi[1]=0xA5;
	size_uart_rx_buf_dpi = sprintf((char*)&uart_rx_buf_dpi[3],
                                   "%lu>", dist);
	uart_rx_buf_dpi[2]=size_uart_rx_buf_dpi;
	size_uart_rx_buf_dpi+=3;//размер преамбулы+поле размер

	if(size_uart_rx_buf_dpi>=52){
		memset(uart_rx_buf_dpi,0,size_uart_rx_buf_dpi);
		dist=0;
		uart_rx_buf_dpi[0]=0x5A;
		uart_rx_buf_dpi[1]=0xA5;
		size_uart_rx_buf_dpi = sprintf((char*)&uart_rx_buf_dpi[3],
                                       "%lu%d>", dist,0);
		uart_rx_buf_dpi[2]=size_uart_rx_buf_dpi;
		size_uart_rx_buf_dpi+=3;//размер преамбулы+поле размер
	}

	dp32_counter=0;
	dp32_ImpCounter=0;//2=минус 3=плюс
	dpEnc_counter=0;
}

void uart_startTransmiting_DP(void)
{
	if(gParams.typeDP_fromAB==DP_DPI){
		HAL_GPIO_WritePin(DP_OUT_GPIO_Port, DP_OUT_Pin, GPIO_PIN_SET);
		HAL_UART_Transmit_DMA(&huart2, (uint8_t *)command_dcba, size_command_dcba);
		dpi_time_status_connect=HAL_GetTick();
	}
}
void uart_startRecieving_DP(void)
{
	if(gParams.typeDP_fromAB==DP_DPI){
		HAL_GPIO_WritePin(DP_OUT_GPIO_Port, DP_OUT_Pin, GPIO_PIN_RESET);//переключить 485 на приём
		memset(uart_rx_buf_dp,0,sizeof(uart_rx_buf_dp));
		HAL_UARTEx_ReceiveToIdle_DMA(&huart2, (uint8_t*)uart_rx_buf_dp, SIZEBUF_uart_rx_buf_dp);
	}
}

void dp32_enc_init(void)
{
	HAL_TIM_Base_Stop_IT(&htim6);
	//настроить ногу RX на прерывание
	GPIO_InitTypeDef GPIO_InitStruct = {0};
	/*Configure GPIO pin : PA3 */
	GPIO_InitStruct.Pin = GPIO_PIN_3;
	GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
	GPIO_InitStruct.Pull = GPIO_NOPULL;
	HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
	//E N D настроить ногу RX на прерывание
	dp32_counter=0;
	dp32_ImpCounter=0;//2=минус 3=плюс
	dpEnc_counter=0;
}

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
	// Обработка прерывания на ноге PA3
	if (GPIO_Pin == GPIO_PIN_3) {
		if(gParams.typeDP_fromAB==DP_DP32){
			if (dp32_ImpCounter==0){
				__HAL_TIM_CLEAR_FLAG(&htim6, TIM_SR_UIF); // очищаем флаг//https://istarik.ru/blog/stm32/118.html
				HAL_TIM_Base_Start_IT(&htim6);
			}
			dp32_ImpCounter++;
		}
		if(gParams.typeDP_fromAB==DP_ENC){
			dpEnc_counter++;
			uart_rx_buf_dpi[0]=0x5A;
			uart_rx_buf_dpi[1]=0xA5;
			size_uart_rx_buf_dpi = sprintf((char*)&uart_rx_buf_dpi[3],
                                           "%lu>",dpEnc_counter);
			uart_rx_buf_dpi[2]=size_uart_rx_buf_dpi;
			size_uart_rx_buf_dpi+=3;//размер преамбулы+поле размер

			if(size_uart_rx_buf_dpi>=52){
				memset(uart_rx_buf_dpi,0,size_uart_rx_buf_dpi);
				dpEnc_counter++;
				uart_rx_buf_dpi[0]=0x5A;
				uart_rx_buf_dpi[1]=0xA5;
				size_uart_rx_buf_dpi = sprintf((char*)&uart_rx_buf_dpi[3],"%lu%d>",dpEnc_counter,0);
				uart_rx_buf_dpi[2]=size_uart_rx_buf_dpi;
				size_uart_rx_buf_dpi+=3;//размер преамбулы+поле размер
			}
		}
	}
}

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
	if (htim->Instance == TIM6) {
		HAL_TIM_Base_Stop_IT(&htim6);
		// Обработка прерывания от таймера TIM6
		if(dp32_ImpCounter<3){//2=минус 3=плюс
			dp32_counter--;
		}else{
			dp32_counter++;
		}
		//добавление преамбулы
		uart_rx_buf_dpi[0]=0x5A;
		uart_rx_buf_dpi[1]=0xA5;
		size_uart_rx_buf_dpi = sprintf((char*)&uart_rx_buf_dpi[3],
                                       "%lu>",dp32_counter);
		uart_rx_buf_dpi[2]=size_uart_rx_buf_dpi;
		size_uart_rx_buf_dpi+=3;//размер преамбулы+поле размер
		dp32_ImpCounter=0;

		if(size_uart_rx_buf_dpi>=52){
			memset(uart_rx_buf_dpi,0,size_uart_rx_buf_dpi);
			uart_rx_buf_dpi[0]=0x5A;
			uart_rx_buf_dpi[1]=0xA5;
			size_uart_rx_buf_dpi = sprintf((char*)&uart_rx_buf_dpi[3],"%lu%d>",dp32_counter,0);
			uart_rx_buf_dpi[2]=size_uart_rx_buf_dpi;
			size_uart_rx_buf_dpi+=3;//размер преамбулы+поле размер
			dp32_ImpCounter=0;
		}
	}
}

Реализацию программного модуля работы с GNSS я уже рассказывал в этой статье [https://habr.com/ru/articles/936028/]

NMEA.c
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;
}
uartProc_GNSS.c
#include "./Project/shared.h"
#include "./Project/GNSS/uartProc_GNSS.h"
#include "./Project/GNSS/NMEA.h"
#include "./Project/DP/uart_Proc_dp.h"
#include "./Project/uart_processing.h"
#include "main.h"
#include <stdlib.h>//abs
#include <string.h>//memset
#include <stdio.h>

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
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>";
                                                       //50.1234567 37.1234567 16:11:33:128 09 2 134.2 34.6
int size_rez_buf_ab=0;

//буфер для сырых данных от GPS
#define SIZEBUF_uart_rx_buf 1000
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_GNSS_init(void)
{
	uart_rezult_buf_out_AB[0]=0x5A;
	uart_rezult_buf_out_AB[1]=0xA5;
//	size_rez_buf_ab=strlen((const char *)uart_rezult_buf_out_AB);
	size_rez_buf_ab=sprintf((char*)&uart_rezult_buf_out_AB[3], "00.0000000 00.0000000 00:00:00.000 00 0 000.0 00.0>");
//	size_rez_buf_ab= uart_rezult_buf_out_AB[3];               //50.1234567 37.1234567 16:11:33:128 09 2 134.2 34.6
	uart_rezult_buf_out_AB[2]=(unsigned char)size_rez_buf_ab;                                                                 //"00.0000000 00.0000000 00:00:00.000 00 0 000.0 00.0>"
	size_rez_buf_ab+=3;//прембула + длина поля
}


//int ttt=0;//отладка

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;
		gps_time_recieve=ms;
	}
	if(uartRxHalfIRDone){
		uartRxHalfIRDone = 0;
		isData=1;
		gps_time_recieve=ms;
	}

	if(isData){
		isData=0;
		//статус о том что gps активен
		gParams.isGPS=1;
//		if(gps_connect_status!=1){
//			gps_connect_status=1;
//			fillBuff_com9(-1, 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);
					}
					if(uart_bufRow[0] == 'R')//Поток данных от RMC
					{
						memcpy(buf_RMC,uart_bufRow,SIZEBUF_buf_RMC);
					}

					//-----------------------------------------Работа с 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;
						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 FORA_ONE
						uart_rezult_buf_out_AB[2]=size_rez_buf_ab;
						size_rez_buf_ab+=3;//размер преамбулы+поле размер

					}
					//переходим в режим поиска шаблона
					uart_rezult_buf_i=0;
					shablonMode=0;
					//memset(uart_rezult_buf_out, 0, SIZEBUF_result_out);
				}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)){
		//dpi_time_status_connect=0;
		gParams.isGPS=0;

		uart_startRecieving_GNSS();
	}
}

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
}

Главный модуль proj_main.c

Инициализационный блок

  • Задержка старта (HAL_Delay(1)), обеспечивает корректность вызова системного таймера, предотвращая возможное чтение нулевого значения;

  • Инициализация параметров (setDefaultParams()), формируются стандартные настройки модуля, включая все протоколы обмена и параметры периферийных интерфейсов;

  • Перезапуск USART2, сначала деинициализирую и потом инициализирую, помогает корректно работать RS-485 перед началом работы;

  • Инициализация функций:

    • uart_GNSS_init() - подготавливает буферы и структуры для приема данных от GNSS -приемника;

    • uart_Proc_dp_init() - настройка логики обработки данных от датчиков перемещения.

    Старт обмена данными:

    • uart_startRecieving_DP() - запуск приема от датчиков;

    • uart_startTransmiting_DP() - формирование и отправка первого запроса к датчику;

    • uart_startRecieving_AB() - начало приема пакетов от ведущего устройства;

    • uart_startRecieving_GNSS() - включение приема от GNSS-приемника.

Основной цикл работы

Внутренний цикл wihle(1) реализует вызов трех основных обработчиков:

  • uart_Handler() - обеспечивает обмен с ведущим устройством по RS-485

  • uart_Handler_GNSS() - выполняет разбор входящего навигационного потока в формате NMEA, а также контроль активности GNSS-приемника;

  • uart_Handler_DP() - реализует обработку данных с датчиков перемещения.

proj_main.c
#include "./Project/shared.h"
#include "./Project/proj_main.h"
#include "./Project/GNSS/uartProc_GNSS.h"
#include "./Project/DP/uart_Proc_dp.h"
#include "./Project/uart_processing.h"

void proj_main()
{
	//volatile const char *ch = ";V-F-BIN;ver: "VER_PROG(VER_a,VER_b,VER_c)";CHIP_ID="STM_CHAR_ID(S_H_I_P_ID)";MODULE_ID="STM_CHAR_ID(M_O_D_U_L_E_ID)";";(void)ch;//0x8008b00
	volatile const char *ch = ";V-F-BIN;ver: "VER_PROG(VER_a,VER_b,VER_c);(void)ch;//0x8008b00


		HAL_Delay(1);//чтобы HAL_GetTick() не выдавал ноль

		setDefaultParams();

		HAL_Delay(500);//отладка
		HAL_UART_DeInit(&huart2);
		HAL_UART_Init(&huart2);

		uart_GNSS_init();
		uart_Proc_dp_init();

		uart_startRecieving_DP();
		uart_startTransmiting_DP();

		uart_startRecieving_AB();
		uart_startRecieving_GNSS();

		while (1){
			//хэндлеры
			uart_Handler();
			uart_Handler_GNSS();
			uart_Handler_DP();

		}//while (1)
}

Просмотр работы приема и передачи данных между ведущим и ведомым устройством, через терминал

С помощью преобразователя USB/RS-485 подключаюсь к DA4 и тестирую

Терминал самодельный, сделан для теста данного проекта
Терминал самодельный, сделан для теста данного проекта

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

Комментарии (6)


  1. Fox_Alex
    08.10.2025 08:11

    RS-485 это обычно про длинные провода. А значит обязательно нужна защита от статики по входам. А если оно еще и на улицу торчит - то и молниезащита.


    1. DM_ChipCraft Автор
      08.10.2025 08:11

      Спасибо большое за комментарий, в качестве соединения ведомого и ведущего устройства я использую кабель UNITRONIC LiYD11Y 4x0,25, очень хорош (экран + витые пары)


  1. hardegor
    08.10.2025 08:11

    При работе с RS485, все обычно забывают его неприятную особенность - он может коротнуть на 250 мА, и тогда стабилизатор LP2985 сделает "пшик", поэтому надо иметь запас.


    1. DM_ChipCraft Автор
      08.10.2025 08:11

      Спасибо большое за совет!! обязательно к нему прислушаюсь!!!


      1. hardegor
        08.10.2025 08:11

        Ну и еще один момент, вы нигде в программе не анализируете ошибки приема - биты FE и ORE, их обязательно надо контролировать.


        1. DM_ChipCraft Автор
          08.10.2025 08:11

          Понял Ваш совет, спасибо, доработаю