
Привет, Хабр!
В данной статье будет рассматриваться разработка коммутатора, для приема, обработки и передачи потока данных от GNSS-приемника и энкодера, осуществляться передача ведущему устройству будет по интерфейсу RS-485.
Интерфейс RS-485 - представляет собой промышленный стандарт физического уровня передачи данных, широко применяемый в распределенных системах управления, телеметрии и автоматизации, его ключевая особенность - использование дифференциального сигнала, что обеспечивает высокую помехоустойчивость и надежность передачи данных на значительные расстояния (до 1200 метров).
В основе работы лежит двухпроводная линия (выводы А и В), по которой передается информация в виде разности потенциалов:
Логическая "1" фиксируется при условии, что напряжение на линии А ниже, чем на линии B;
Логический "0" соответствует ситуации, когда потенциал линии А выше потенциала линии В.
Такой метод передачи данных позволяет минимизировать влияние электромагнитных помех, так как внешние наводки одинаково взаимодействуют на обе линии и компенсируются при дифференциальном приеме.
В стандартных трансиверах интерфейса RS-485 передатчик и приемник интегрированы в одном корпусе и подключены к общей двухпроводной линии, направление выбирается выводом микроконтроллера, уровень на которой определяет в каком режиме микросхема, в приеме или в передаче.

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

Принцип взаимодействия устройств
Данная схема реализует архитектуру «ведущий-ведомый» (Master-Slave). Процесс взаимодействия можно разделить на два ключевых этапа:
-
Этап опроса и обнаружения устройств
При инициализации системы ведущее устройство отправляет ведомому устройству (коммутатору) запрос на получение списка активных подключенных устройств. В данном примере запрашивается статус энкодера и приемника GNSS.Ведомое устройство, формирует и возвращает ведущему буфер данных, содержащий информацию о наличии активности или отсутствии связи с каждым из опрошенных устройств.
-
Этап сбора данных
После успешного обнаружения активных устройств ведущее устройство инициирует цикл опроса для сбора данных. Последовательно отправляются запросы на получение данных: дистанции с энкодера и навигационной информации с приемника 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) до преобразования = delta [ 10.6V ], нельзя подключать к микроконтроллеру STM32.
Вид осциллограммы передаваемых данных модуля-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).
все остальные параметры без изменений.

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

При работе с GPS-модулями, которые передают NMEA-сообщения раз в секунду (1Hz), важно правильно организовать прием данных, чтобы не пропустить ни одного пакета. Необходимо настроить DMA в режиме Circular данный режим минимизирует нагрузку на процессор и гарантирует надежный прием.
Конфигурация DMA Settings
Захожу в параметр DMA Settings и выполняю следующие настройки:
Выбор потока/канала: USART1_RX (прием данных);
Mode: Circular;
Increment Memory Address: Enabled (автоинкремент памяти);
Data Width: Byte (8 бит, соответствует формату NMEA).

Коротко о NMEA 0183
Это текстовый протокол, используемый для передачи данных между морским и авиационным навигационным оборудованием, включая GPS-приемники. Большинство современных GPS-модулей выводят информацию именно в этом формате.
Основные особенности:
Текстовый формат – данные передаются в виде ASCII-строк;
Структура сообщений – каждая строка начинается с
$
, содержит идентификатор типа данных и заканчивается контрольной суммой;Скорость передачи – обычно 9600 бод (но может быть и выше для высокочастотных модулей);
Частота обновления – чаще всего 1 раз в секунду (1Hz), но бывают 5Hz, 10Hz и более.
Пример строки (GGA – Global Positioning System Fix Data):
№ |
Поле |
Значение |
Описание |
1 |
UTC-время |
112530.000 |
11:25:30 UTC |
2 |
Широта |
6012.3456 |
60° 12.3456′ |
3 |
Полушарие широты |
N/S |
Север/ЮГ |
4 |
Долгота |
03015.6789 |
30° 15.6789′ |
5 |
Полушарие долготы |
E/W |
Восток/Запад |
6 |
Fix Quality |
1 |
GPS фикс (автономный) |
7 |
Спутники |
10 |
10 спутников |
8 |
HDOP |
0.95 |
Горизонтальная точность |
9 |
Высота |
45.3 |
Высота над уровнем моря |
10 |
Ед. высоты |
M |
Метры |
11 |
Геоид. высота |
12.5 |
Высота геоида |
12 |
Ед. геоида |
M |
Метры |
13 |
Дифф. коррекция |
пусто |
Нет данных |
14 |
CRC |
*65 |
Контрольная сумма |
После обработки GGA:
Время: 11:25:30
Широта: 60.20576° N
Долгота: 30.261315° E
Качество фикса: 1
Спутников: 10
Высота: 45.3 м
Пример строки (RMC – Recommended Minimum Navigation Information):
№ |
Поле |
Значение |
Описание |
1 |
UTC-время |
112530.000 |
11:25:30 UTC |
2 |
Статус |
А |
Данные действительные |
3 |
Широта |
6012.3456 |
60° 12.3456′ |
4 |
Полушарие широты |
N/S |
Север/ЮГ |
5 |
Долгота |
03015.6789 |
30° 15.6789′ |
6 |
Полушарие долготы |
E/W |
Восток/Запад |
7 |
Скорость |
5.12 |
5.12 узла |
8 |
Курс |
87.45 |
87.45° |
9 |
Дата |
110825 |
11 августа 2025 |
10 |
Маг. отклонение |
пусто |
- |
11 |
Ед. маг. откл |
пусто |
- |
12 |
Режим |
A |
Автономный GPS |
13 |
CRC |
*6C |
Контрольная сумма |
После обработки RMC:
Время: 11:25:30
Статус: Данные действительные
Широта: 60.20576° N
Долгота: 30.261315° E
Скорость: 5.12 узла (~9.48 км/ч)
Курс: 87.45°
Дата: 11.08.2025
Также прикрепляю еще одну ссылку, где в детальности продемонстрирована расшифровка протокола NMEA0183 [https://wiki.iarduino.ru/page/NMEA-0183/].
Программная реализация ведомого устройства
Ссылка на скачивание исходного кода [ 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


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)
hardegor
08.10.2025 08:11При работе с RS485, все обычно забывают его неприятную особенность - он может коротнуть на 250 мА, и тогда стабилизатор LP2985 сделает "пшик", поэтому надо иметь запас.
DM_ChipCraft Автор
08.10.2025 08:11Спасибо большое за совет!! обязательно к нему прислушаюсь!!!
hardegor
08.10.2025 08:11Ну и еще один момент, вы нигде в программе не анализируете ошибки приема - биты FE и ORE, их обязательно надо контролировать.
Fox_Alex
RS-485 это обычно про длинные провода. А значит обязательно нужна защита от статики по входам. А если оно еще и на улицу торчит - то и молниезащита.
DM_ChipCraft Автор
Спасибо большое за комментарий, в качестве соединения ведомого и ведущего устройства я использую кабель UNITRONIC LiYD11Y 4x0,25, очень хорош (экран + витые пары)