Sep . 2025
Для эффективного и надёжного управления DMR858M рекомендуется использовать объектно-ориентированный подход, создав класс драйвера для инкапсуляции всех взаимодействий с модулем. Эта архитектура аналогична библиотекам, разработанным для других модулей AT-команд (например, GSM или Wi-Fi), и обеспечивает хорошую модульность и возможность повторного использования.
Мы разработаем класс C++ под названием DMR858M_Controller. Этот класс будет отвечать за управление коммуникациями по UART, формирование и анализ кадров данных, обработку команд и ответов, а также управление состоянием модуля.
// DMR858M_Controller.h #include <Arduino.h> class DMR858M_Controller { public: DMR858M_Controller(HardwareSerial& serial, int pttPin, int csPin); void begin(long speed); bool setFrequency(uint32_t txFreq, uint32_t rxFreq); bool setPowerLevel(bool highPower); bool getFirmwareVersion(String& version); void setPTT(bool active); // ... другие прототипы функций private: HardwareSerial& _serial; int _pttPin; int _csPin; void sendCommand(uint8_t cmd, uint8_t rw, const uint8_t* data, uint16_t len); bool waitForResponse(uint8_t* буфер, uint16_t& len, uint32_t таймаут = 1000); uint16_t calculateChecksum(const uint8_t* данные, size_t len); };
sendCommand — это ядро всех операций записи. Он отвечает за сборку полного двоичного пакета, вычисление контрольной суммы и отправку его через UART.
// DMR858M_Controller.cpp void DMR858M_Controller::sendCommand(uint8_t cmd, uint8_t rw, const uint8_t* data, uint16_t len) { const uint16_t totalFrameLen = 9 + len; uint8_t frame[totalFrameLen]; frame[0] = 0x68; // Head frame[1] = cmd; // CMD frame[2] = rw; // R/W frame[3] = 0x01; // S/R (Запрос) frame[4] = 0x00; // CKSUM_HI (временный) frame[5] = 0x00; // CKSUM_LO (временный) frame[6] = (len >> 8) & 0xFF; // LEN_HI frame[7] = len & 0xFF; // LEN_LO если (data && len > 0) { memcpy(&frame[8], data, len); } frame[8 + len] = 0x10; // Хвост // Вычислить контрольную сумму от CMD до конца DATA uint16_t checksum = calculateChecksum(&frame[1], 7 + len); frame[4] = (checksum >> 8) & 0xFF; // CKSUM_HI frame[5] = checksum & 0xFF; // CKSUM_LO _serial.write(frame, totalFrameLen); } uint16_t DMR858M_Controller::calculateChecksum(const uint8_t* buf, size_t len) { uint32_t sum = 0; const uint8_t* current_buf = buf; size_t current_len = len; while (current_len > 1) { сумма += (uint16_t)((*current_buf << 8) | *(current_buf + 1)); current_buf += 2; current_len -= 2; } if (current_len > 0) { сумма += (uint16_t)(*current_buf << 8); } while (sum >> 16) { сумма = (sum & 0xFFFF) + (sum >> 16); } return (uint16_t)(sum ^ 0xFFFF); }
Во встраиваемых системах блокирующее ожидание — это шаблон программирования, которого следует избегать. Простая функция waitForResponse, использующая цикл типа while(!_serial.available()){}, заморозит весь основной цикл, не давая микроконтроллеру выполнять другие задачи, такие как обновление дисплея или реагирование на нажатия кнопок, что приведёт к зависанию системы.
Более надёжная конструкция должна быть неблокируемой . В основном цикле программа должна постоянно проверять последовательный порт на наличие данных и использовать конечный автомат для обработки входящего кадра данных. Такой подход гарантирует, что система сможет обрабатывать другие события реального времени, ожидая ответа от модуля. Для такой платформы, как ESP32, поддерживающей FreeRTOS, лучшим решением будет создание отдельной задачи RTOS для управления взаимодействием с модулем DMR. Эта задача может блокироваться при отсутствии данных, не влияя на выполнение других задач.
Вот упрощенный пример неблокирующей логики чтения, подходящей для функции Arduino loop():
// Упрощенная неблокирующая логика обработки ответа void loop() { // ... другие задачи... if (_serial.available()) { // Считывание байта и помещение его в буфер // Использование конечного автомата для анализа кадра данных (поиск заголовка 0x68, чтение указанной длины, проверка контрольной суммы и хвоста 0x10) // После успешного анализа обработать данные ответа } }
Ниже приведен полный пример Arduino/PlatformIO, демонстрирующий, как инициализировать модуль, управлять PTT с помощью кнопки и отправлять SMS через последовательный монитор.
#include <Arduino.h> #include "DMR858M_Controller.h" #define PTT_BUTTON_PIN 25 #define PTT_MODULE_PIN 26 #define LED_PIN 2 HardwareSerial SerialTwo(2); DMR858M_Controller dmr(SerialTwo, PTT_MODULE_PIN, -1); void setup() { Serial.begin(115200); pinMode(PTT_BUTTON_PIN, INPUT_PULLUP); pinMode(LED_PIN, OUTPUT); dmr.begin(57600); delay(500); String fwVersion; if (dmr.getFirmwareVersion(fwVersion)) { Serial.println("Прошивка DMR858M: " + fwVersion); } else { Serial.println("Не удалось связаться с модулем DMR858M."); } // Пример: Установить частоту для канала 1 на 433,500 МГц dmr.setFrequency(433500000, 433500000); } void loop() { // Логика управления PTT if (digitalRead(PTT_BUTTON_PIN) == LOW) { dmr.setPTT(true); digitalWrite(LED_PIN, HIGH); // Индикатор передачи } else { dmr.setPTT(false); digitalWrite(LED_PIN, LOW); } // ... здесь можно добавить неблокирующую логику обработки последовательного ответа... // Пример: Отправить SMS через последовательный монитор if (Serial.available()) { String cmd = Serial.readStringUntil('\n'); if (cmd.startsWith("sms")) { // Анализ содержимого SMS и идентификатора цели // Вызов dmr.sendSMS(...) Serial.println("Команда SMS получена."); } } }
Часть 1: Углубленный анализ модуля DMR858M
Часть 2: Интеграция оборудования и эталонный дизайн
Часть 3: Разбор последовательного протокола управления
Часть 4: Разработка прошивки и проектирование драйверов
Часть 5: Изучение расширенных функций и заключение
+86-755-23080616
sales@nicerf.com
Сайт: https://www.nicerf.com/ .
Адрес: 309-314, 3/F, корпус A, деловое здание Хунду, зона 43, район Баоань, Шэньчжэнь, Китай
политика конфиденциальности
· Политика конфиденциальности
В настоящее время нет доступного контента
Электронная почта: sales@nicerf.com
Тел:+86-755-23080616