HOMEABOUT USPRODUCT INFORMATION A PRACTICAL GUIDE TO CUSTOM ESP32 DIGITAL WALKIE-T...

A Practical Guide to Custom ESP32 Digital Walkie-Talkies (Part 4): Firmware Development and Driver Design

11

Sep . 2025

By sdga:

To control the DMR858M efficiently and reliably, an object-oriented approach is recommended, creating a driver class to encapsulate all interactions with the module. This architecture is similar to libraries designed for other AT command modules (such as GSM or Wi-Fi modules) and offers good modularity and reusability.

Architecture Method: DMR858M_Controller Class

We will design a C++ class named DMR858M_Controller. This class will be responsible for managing UART communication, building and parsing data frames, handling commands and responses, and managing the module's state.

// 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);
    // ... other function prototypes

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* buffer, uint16_t& len, uint32_t timeout = 1000);
    uint16_t calculateChecksum(const uint8_t* data, size_t len);
};

Core Implementation Details (Code Example)

Packet Construction and Transmission

sendCommand is the core of all write operations. It is responsible for assembling the complete binary packet, calculating the checksum, and sending it via 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 (Request)
   
    frame[4] = 0x00;    // CKSUM_HI (temporary)
    frame[5] = 0x00;    // CKSUM_LO (temporary)
   
    frame[6] = (len >> 8) & 0xFF; // LEN_HI
    frame[7] = len & 0xFF;        // LEN_LO
   
    if (data && len > 0) {
        memcpy(&frame[8], data, len);
    }
   
    frame[8 + len] = 0x10; // Tail
   
    // Calculate checksum from CMD to the end of 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) {
        sum += (uint16_t)((*current_buf << 8) | *(current_buf + 1));
        current_buf += 2;
        current_len -= 2;
    }
    if (current_len > 0) {
        sum += (uint16_t)(*current_buf << 8);
    }
    while (sum >> 16) {
        sum = (sum & 0xFFFF) + (sum >> 16);
    }
    return (uint16_t)(sum ^ 0xFFFF);
}

Importance of Response Handling and Asynchronous Operations

In embedded systems, blocking waits are a programming pattern to be avoided. A simple waitForResponse function using a loop like while(!_serial.available()){} will freeze the entire main loop, preventing the MCU from performing other tasks such as updating a display or responding to button presses, leading to an unresponsive system.

A more robust design should be non-blocking. In the main loop, the program should continuously check the serial port for data and use a state machine to process the incoming data frame. This approach ensures that the system can still handle other real-time events while waiting for a response from the module. For a platform like the ESP32 that supports FreeRTOS, a better solution is to create a dedicated RTOS task to handle communication with the DMR module. This task can block when there is no data without affecting the execution of other tasks.

Here is a simplified example of non-blocking read logic suitable for an Arduino loop() function:

// Simplified non-blocking response handling logic
void loop() {
    // ... other tasks...

    if (_serial.available()) {
        // Read byte and place it into a buffer
        // Use a state machine to parse the data frame (look for header 0x68, read specified length, verify checksum and tail 0x10)
        // Once successfully parsed, process the response data
    }
}

Comprehensive Example: A Proof-of-Concept Program

The following is a complete Arduino/PlatformIO example that demonstrates how to initialize the module, control PTT with a button, and send an SMS via the serial monitor.

#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 Firmware: " + fwVersion);
    } else {
        Serial.println("Failed to communicate with DMR858M module.");
    }

    // Example: Set frequency for channel 1 to 433.500 MHz
    dmr.setFrequency(433500000, 433500000);
}

void loop() {
    // PTT control logic
    if (digitalRead(PTT_BUTTON_PIN) == LOW) {
        dmr.setPTT(true);
        digitalWrite(LED_PIN, HIGH); // Transmit indicator
    } else {
        dmr.setPTT(false);
        digitalWrite(LED_PIN, LOW);
    }

    // ... non-blocking serial response handling logic can be added here...

    // Example: Send SMS via serial monitor
    if (Serial.available()) {
        String cmd = Serial.readStringUntil('\n');
        if (cmd.startsWith("sms")) {
            // Parse SMS content and target ID
            // Call dmr.sendSMS(...)
            Serial.println("SMS command received.");
        }
    }
}

A Practical Guide to Custom ESP32 Digital Walkie-Talkies Series


Part 1: In-depth Analysis of the DMR858M Module

Part 2: Hardware Integration and Reference Design

Part 3: Deconstructing the Serial Control Protocol

Part 4: Firmware Development and Driver Design

Part 5: Exploring Advanced Features and Conclusion



    Contact Us

     +86-755-23080616

     sales@nicerf.com

    Website: https://www.nicerf.com/

    Address: 309-315, 3/F, Bldg A, Hongdu business building, Zone 43, Baoan Dist, Shenzhen, China

    Contact us
    Privacy Policy

    Privacy Policy

    · Privacy Policy

    There is currently no content available


               

    Email:sales@nicerf.com

    Tel:+86-755-23080616

    Address:309-315, 3/F, Bldg A, Hongdu business building, Zone 43, Baoan Dist, Shenzhen, China


    ×