Implementation of Modbus RTU (Remote Terminal Unit) protocol on microcontroller (RS485, UART)

Table of Contents

1.ModbusRTU code

2. About the microcontroller receiving a complete frame of ModbusRTU data from the host computer

3. About ModbusRTU test


1.ModbusRTU code

ModbusRTU protocol.c file:

#include "ModbusRTU.h"
#include "mcu_config.h"

ModbusState ModbusStateRXD = MODBUS_RXD_WAIT;
ModbusRTUBuffStruct ModbusRTUBuffRXD = { 0 };
ModbusRTUBuffStruct ModbusRTUBuffTXD = { 0 };

uint8_t ModbusTick = 0;

uint8_t ReadRegMpa(uint16_t Adder, uint16_t *regData);
uint8_t WriteRegMap(uint16_t Adder, uint16_t Data);


/**
 * @brief Modbus receive interrupt callback function
 * @param RxData
 */
void ModbusRTU_Receive(uint8_t RxData)
{
    ModbusTick = 0;
    if (ModbusStateRXD == MODBUS_RXD_WAIT) {
        ModbusStateRXD = MODBUS_RXD_ON;
        ModbusRTUBuffRXD.DataLen = 0;
        ModbusRTUBuffRXD.DataBuff[ModbusRTUBuffRXD.DataLen] = RxData;
        ModbusRTUBuffRXD.DataLen + + ;
    }
    else if (ModbusStateRXD == MODBUS_RXD_ON) {
        if (ModbusRTUBuffRXD.DataLen >= MODBUSRTU_BUFF_MAX)
            ModbusStateRXD = MODBUS_RXD_FINISH;
        else {
            ModbusRTUBuffRXD.DataBuff[ModbusRTUBuffRXD.DataLen] = RxData;
            ModbusRTUBuffRXD.DataLen + + ;
        }
    }
}


/**
 * @brief Modbus reception completion judgment is based on the interval of serial communication data frames (this function is executed in the 1ms interrupt)
 * @note In the receiving state, if the time interval between data received by the serial port is greater than the maximum timeout, it is deemed that one frame of data reception is completed.
 * @param
 */
void ModbusRTU_RXDCompleteJudge(void)
{
    if (ModbusStateRXD == MODBUS_RXD_ON) {
        ModbusTick = (ModbusTick < UINT8_MAX) ? (ModbusTick + 1) : UINT8_MAX;
        if (ModbusTick > MODBUSRTU_TIMEOUT_MAX)
            ModbusStateRXD = MODBUS_RXD_FINISH;
    }
}


/**
 * @brief Modbus frame data length check
 * @param pData receiving buffer address
 * @param Len data length
 * @return verification result
 * @retval MODBUS_OK: Verification successful
 * @retval MODBUS_ERR: Verification failed
 */
static ModbusState ModbusRTU_FrameLenCheck(uint8_t *pData, uint8_t Len)
{
    uint16_t lenData = 0;
    switch (pData[1])
    {
    case MODBUS_FUNCTION_03: //Read multi-channel save register data
        lenData = (uint16_t)(pData[4] << 8) | pData[5];
        if (lenData > MODBUSRTU_REG_NUM_MAX) //The length is out of range
            return MODBUS_ERR;
        if (Len != 8)
            return MODBUS_ERR;
        return MODBUS_OK;
        break;
    case MODBUS_FUNCTION_06: //Write single-channel register
        if (Len != 8) // Write single-channel register instruction, the data length must be 8
            return MODBUS_ERR;
        else
            return MODBUS_OK;
        break;
    case MODBUS_FUNCTION_10: //Write multiple register data
        lenData = (uint16_t)(pData[4] << 8) | pData[5];
        if (lenData > MODBUSRTU_REG_NUM_MAX || Len != (pData[6] + 9) || ((lenData << 1) != pData[6]))
            return MODBUS_ERR;
        else
            return MODBUS_OK;
        break;
    default:
        return MODBUS_ERR;
        break;
    }
}

/**
 * @brief send buffer initialization
 * @param None
 */
static void ModbusRTU_TxDataInit(void)
{
    ModbusRTUBuffTXD.DataLen = 0;
}

/**
 * @brief Write data to the send buffer (but when you need to send data to the host, you do not need to consider sending the check code operation)
 * @param Data data
 * @return status
 * @retval MODBUS_ERR: Insufficient send buffer
 * @retval MODBUS_OK: Operation successful
 */
static ModbusState ModbusRTU_TxDataPush(uint8_t Data)
{
    if (ModbusRTUBuffTXD.DataLen >= (MODBUSRTU_BUFF_MAX - 2))
        return MODBUS_ERR;
    ModbusRTUBuffTXD.DataBuff[ModbusRTUBuffTXD.DataLen] = Data;
    ModbusRTUBuffTXD.DataLen + + ;
    return MODBUS_OK;
}

/**
 * @brief Data sending, this function will automatically calculate the check code of the sending buffer data (crc16 verification, and write the check code data to the sending buffer and send it)
 * @param None
 */
static void ModbusRTU_SendData(void)
{
    if (ModbusRTUBuffTXD.DataLen) {
        // Calculate check code
        uint16_t crc16Check = CRC_16((uint8_t *) & amp;ModbusRTUBuffTXD.DataBuff[0], ModbusRTUBuffTXD.DataLen, 0xFFFF);
        ModbusRTUBuffTXD.DataBuff[ModbusRTUBuffTXD.DataLen + 1] = (uint8_t)(crc16Check >> 8);
        ModbusRTUBuffTXD.DataBuff[ModbusRTUBuffTXD.DataLen] = (uint8_t)(crc16Check & amp; 0xFF);
        ModbusRTUBuffTXD.DataLen + = 2;
        // data sending
        // for (uint8_t index = 0;index < ModbusRTUBuffTXD.DataLen;index + + ) {

        // }
        API_UartSendData((uint8_t *) & amp;ModbusRTUBuffTXD.DataBuff[0], ModbusRTUBuffTXD.DataLen);
        //Data sending is completed, clear the sending buffer
        ModbusRTU_TxDataInit();
    }
}

/**
 * @brief Send error information to the host
 * @param Function function code
 * @param Error error message
 */
static void ModbusRTU_SendError(ModbusFunction Function, ModbusError Error)
{
    ModbusRTU_TxDataInit();
    ModbusRTU_TxDataPush(MODBUSRTU_ADDER); //Write the Modbus slave address
    ModbusRTU_TxDataPush((Funtion + 0x80));
    ModbusRTU_TxDataPush(Error);
}

/**
 * @brief Modbus initialization
 * @param
 */
void ModbusRTU_Init(void)
{

}



/**
 * @brief Modbus read register command, corresponding to the 03 command
 * @param pData receives buffer data
 */
static void ModbusRTU_ReadRegFunction(const uint8_t *pData)
{
    // 03 command instruction
    uint16_t adderStart = (uint16_t)(pData[2] << 8) + (uint16_t)pData[3]; // Register starting address
    uint16_t lenData = (uint16_t)(pData[4] << 8) + (uint16_t)pData[5]; // Number of registers to be read
    ModbusRTU_TxDataPush(MODBUSRTU_ADDER); //Reply slave address
    ModbusRTU_TxDataPush(pData[1]); //Reply function code 03
    ModbusRTU_TxDataPush((lenData << 1)); //The length of data to be replied
    for (uint16_t index = 0;index < lenData;index + + ) {
        uint16_t regData = 0;
        if (ReadRegMpa((adderStart + index), & amp;regData)) {
            ModbusRTU_TxDataPush((regData >> 8)); // Put data into the send buffer, high end first
            ModbusRTU_TxDataPush((regData & amp; 0xFF));
        }
        else { // illegal access
            ModbusRTU_SendError((ModbusFunction)pData[1], MODBUS_ERR_UNADD);
            return;
        }
    }
}

/**
 * @brief 06 instruction: write a single register instruction
 * @param pData receives buffer data
 */
static void ModbusRTU_WriteRegFunction(const uint8_t *pData)
{
    //06 command
    uint16_t adderStart = (uint16_t)(pData[2] << 8) + (uint16_t)pData[3]; // Register starting address
    uint16_t regData = (uint16_t)(pData[4] << 8) + (uint16_t)pData[5]; // Data to be written
    //Data sent: slave address, function code, register address high bit, register address low bit, data high bit, data low bit, check code high bit, check code low bit
    for (uint8_t index = 0;index < 6;index + + ) {
        ModbusRTU_TxDataPush(pData[index]);
    }
    if (WriteRegMap(adderStart, regData)) { // Data written successfully
        return;
    }
    else { // Data writing is illegal
        ModbusRTU_SendError((ModbusFunction)pData[1], MODBUS_ERR_UNADD);
        return;
    }
}
/**
 * @brief 10 instructions: write multiple registers
 * @param pData
 */
static void ModbusRTU_WriteMultiRegFunction(const uint8_t *pData)
{
    //0x10 command
    uint16_t adderStart = (uint16_t)(pData[2] << 8) + (uint16_t)pData[3]; // Register starting address
    uint16_t lenData = (uint16_t)(pData[4] << 8) + (uint16_t)pData[5]; // Number of registers to be read
    //Data sent: slave address, function code, register address high bit, register address low bit, check code high bit, check code low bit
    for (uint8_t index = 0;index < 6;index + + ) {
        ModbusRTU_TxDataPush(pData[index]);
    }
    for (uint8_t index = 0;index < lenData;index + + ) {
        uint16_t regData = (uint16_t)(pData[7 + index * 2] << 8) + (uint16_t)pData[7 + index * 2 + 1]; // Data to be written
        if (WriteRegMap(adderStart + index, regData) == 0) { // Data writing failed
            ModbusRTU_SendError((ModbusFunction)pData[1], MODBUS_ERR_UNADD);
            return;
        }
    }
}

/**
 * @brief MobusRTU protocol analysis
 * @param None
 * @return
 */
ModbusState ModbusRTU_Handle(void)
{

    if (ModbusStateRXD == MODBUS_RXD_FINISH) { // Host data reception is completed and analysis begins

        if (ModbusRTUBuffRXD.DataLen < 3 || ModbusRTUBuffRXD.DataBuff[0] != MODBUSRTU_ADDER) { // Data length exception or address error
            ModbusRTUBuffRXD.DataLen = 0;
            ModbusStateRXD = MODBUS_RXD_WAIT;
            return MODBUS_ERR;
        }
        //Modbus data check code calculation
uint16_t modbusRXDCrc16 = 0;
        uint16_t modbusCrc16 = CRC_16((uint8_t *) & amp;ModbusRTUBuffRXD.DataBuff[0], (ModbusRTUBuffRXD.DataLen - 2), 0xFFFF);
        modbusRXDCrc16 = (uint16_t)(ModbusRTUBuffRXD.DataBuff[ModbusRTUBuffRXD.DataLen - 1] << 8) +
            (uint16_t)ModbusRTUBuffRXD.DataBuff[ModbusRTUBuffRXD.DataLen - 2];
        if (modbusCrc16 != modbusRXDCrc16) { // Verification failed
            ModbusRTUBuffRXD.DataLen = 0;
            ModbusStateRXD = MODBUS_RXD_WAIT;
            return MODBUS_ERR;
        }
        ModbusState state = ModbusRTU_FrameLenCheck((uint8_t *) & amp;ModbusRTUBuffRXD.DataBuff[0], ModbusRTUBuffRXD.DataLen); // Frame data length check
        if (state != MODBUS_OK) {
            ModbusRTU_SendError((ModbusFunction)ModbusRTUBuffRXD.DataBuff[1], state); //Send error code status
            ModbusRTU_SendData(); // Data sending
            ModbusStateRXD = MODBUS_RXD_WAIT;
            return MODBUS_ERR;
        }
        switch ((ModbusFunction)ModbusRTUBuffRXD.DataBuff[1])
        {
        case MODBUS_FUNCTION_03: // Read multiple registers
            ModbusRTU_ReadRegFunction((uint8_t *) & amp;ModbusRTUBuffRXD.DataBuff[0]);
            break;
        case MODBUS_FUNCTION_06: //Write single-channel register
            ModbusRTU_WriteRegFunction((uint8_t *) & amp;ModbusRTUBuffRXD.DataBuff[0]);
            break;
        case MODBUS_FUNCTION_10:
            ModbusRTU_WriteMultiRegFunction((uint8_t *) & amp;ModbusRTUBuffRXD.DataBuff[0]);
            break;
        default: //Exception reply, no function code exists
            ModbusRTU_SendError((ModbusFunction)ModbusRTUBuffRXD.DataBuff[1], MODBUS_ERR_NUFUN);
            break;
        }
        ModbusRTU_SendData(); // Data sending
        ModbusRTUBuffRXD.DataLen = 0; //receive buffer reset
        ModbusStateRXD = MODBUS_RXD_WAIT; //Waiting for receiving status
        return MODBUS_OK;
    }
    else
        return MODBUS_OK;
}


///The following functions need to be written according to the actual situation/


uint16_t SaveData[40] = {
    0x0001,
    0x0002,
    0x00F0,
    0x00F1,
    0xFF00,
    0x1234,
    0x4321,
    0x4567,
    0x9876,
    0x0000
};

/**
 * @brief read register operation
 * @param Adder data address
 * @param regData read data pointer
 * @return 0: Failed to read register operation
 * 1: The register read operation failed and succeeded
 */
uint8_t ReadRegMpa(uint16_t Adder, uint16_t *regData)
{
    // Allow reading, return 1,
    // Reading is not allowed, returns 0
    if (Adder < 10) {
        *regData = SaveData[Adder];
        return 1;
    }
    else
        return 0;
}

/**
 * @brief write register operation
 * @param Adder data address
 * @param Data Data to be written
 * @return 0: The register writing operation failed
 * 1: The register read operation failed and succeeded
 */
uint8_t WriteRegMap(uint16_t Adder, uint16_t Data)
{
    if (Adder < 10) {
        SaveData[Adder] = Data;
        return 1;
    }
    else
        return 0;
}

ModbusRTU protocol.h file: 2

#ifndef _MODBUSRTU_H_
#define _MODBUSRTU_H_

#include <stdint.h>


#define MODBUSRTU_BUFF_MAX (200) // ModbusRTU buffer size
#define MODBUSRTU_ADDER (0x01) // Modbus slave address, fixed address
#define MODBUSRTU_REG_NUM_MAX (125) // No greater than 125 is allowed
#define MODBUSRTU_TIMEOUT_MAX (20) // Maximum receive timeout

typedef enum {
    MODBUS_OK,
    MODBUS_ERR,
    MODBUS_RXD_WAIT, //Waiting for data reception
    MODBUS_RXD_ON, // Data receiving
    MODBUS_RXD_FINISH, //Data reception completed
    MODBUS_RXD_BUFF_FULL, // Buffer overflow
}ModbusState;

typedef enum {
    MODBUS_FUNCTION_03 = 0x03, // Read holding register function code
    MODBUS_FUNCTION_06 = 0x06, //Write single-channel register
    MODBUS_FUNCTION_10 = 0x10, //Write multiple data
}ModbusFunction;

typedef enum {
    MODBUS_ERR_NUFUN = 1, // Illegal function
    MODBUS_ERR_UNADD = 2, // Illegal data address
    MODBUS_ERR_UNDATA = 3, //Illegal data pointer
    MODBUS_ERR_FAULT = 4, // Slave device failure
    MODBUS_ERR_ENSURE = 5, // Confirm
    MODBUS_ERR_BUY = 7, // The slave device is busy
    MODBUS_ERR_OECHECK = 8, // Store parity error
    MODBUS_ERR_UNPATH = 0x0A, // Unavailable gateway path
    MODBUS_ERR_RESFAIL = 0x0B, // Response failed
}ModbusError;

typedef struct ModbusRTU
{
    uint8_t DataLen; // Buffer data length
    uint8_t DataBuff[MODBUSRTU_BUFF_MAX];
}ModbusRTUBuffStruct;



void ModbusRTU_Receive(uint8_t RxData);
void ModbusRTU_RXDCompleteJudge(void);
void ModbusRTU_Init(void);
ModbusState ModbusRTU_Handle(void);

#endif

2. About the microcontroller receiving a complete frame of ModbusRTU data from the host computer

(1) The microcontroller has UART idle interrupt (STM32), which can be used to determine the completion of a frame of data reception;

(2) The microcontroller has no idle interrupt and uses timing to realize UART idle judgment. Please pay attention to the idle time setting. The above code uses a timer to determine the completion of one frame of data reception.

3. About ModbusRTU test

Host software: Modbus Poll;

Slave software: Modbus Slave;

Usage tutorial and download link:

Modbus simulator Modbus Poll and Modbus Slave detailed graphic tutorials

The knowledge points of the article match the official knowledge archives, and you can further learn relevant knowledge. Network skill treeProtocols that support applicationsThe role of the application layer 42,006 people are learning the system