Modbus RTU(Remote Terminal Unit)协议在单片机上的实现(RS485、UART)

本文档详细介绍了如何在单片机中实现ModbusRTU协议的编码与解码,包括数据接收的中断处理、帧数据校验以及不同功能码的处理。还讨论了在没有空闲中断的情况下使用定时器进行数据帧判断的方法。最后提到了用于测试的上位机软件ModbusPoll和从机软件ModbusSlave,以及相关的教程资源。
摘要由CSDN通过智能技术生成

目录

1.ModbusRTU代码

2.关于单片机接收上位机完整的一帧ModbusRTU数据

3.关于ModbusRTU测试


1.ModbusRTU代码

ModbusRTU协议.c文件:

#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接收中断回调函数
 * @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接收完成判断,基于串口通信数据帧的间隔(该函数在1ms中断中执行)
 * @note 在接收状态下,如果串口接收的数据之间时间间隔大于最大超时时间,即认定一帧数据接收完成
 * @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帧数据长度校验
 * @param pData 接收缓冲区地址
 * @param Len 数据长度
 * @return 校验结果
 *    @retval MODBUS_OK:校验成功
 *    @retval MODBUS_ERR:校验失败
 */
static ModbusState ModbusRTU_FrameLenCheck(uint8_t *pData, uint8_t Len)
{
    uint16_t lenData = 0;
    switch (pData[1])
    {
    case MODBUS_FUNCTION_03:   // 读多路保存寄存器数据
        lenData = (uint16_t)(pData[4] << 8) | pData[5];
        if (lenData > MODBUSRTU_REG_NUM_MAX)  // 长度超出范围
            return MODBUS_ERR;
        if (Len != 8)
            return MODBUS_ERR;
        return MODBUS_OK;
        break;
    case MODBUS_FUNCTION_06:   // 写单路寄存器
        if (Len != 8)  // 写单路寄存器指令,数据长度必须为8
            return MODBUS_ERR;
        else
            return MODBUS_OK;
        break;
    case MODBUS_FUNCTION_10:    // 写多个寄存器数据
        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 发送缓冲区初始化
 * @param  None
 */
static void ModbusRTU_TxDataInit(void)
{
    ModbusRTUBuffTXD.DataLen = 0;
}

/**
 * @brief 向发送缓冲区写入数据(但需要向主机发送数据时,不用考虑发送校验码操作)
 * @param Data 数据
 * @return 状态
 *   @retval MODBUS_ERR:发送缓冲区不足
 *   @retval MODBUS_OK:操作成功
 */
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 数据发送,该函数将自动计算发送缓冲区数据的校验码(crc16校验,并将校验码数据写入到发送缓冲并进行发送)
 * @param  None
 */
static void ModbusRTU_SendData(void)
{
    if (ModbusRTUBuffTXD.DataLen) {
        // 计算校验码
        uint16_t crc16Check = CRC_16((uint8_t *)&ModbusRTUBuffTXD.DataBuff[0], ModbusRTUBuffTXD.DataLen, 0xFFFF);
        ModbusRTUBuffTXD.DataBuff[ModbusRTUBuffTXD.DataLen + 1] = (uint8_t)(crc16Check >> 8);
        ModbusRTUBuffTXD.DataBuff[ModbusRTUBuffTXD.DataLen] = (uint8_t)(crc16Check & 0xFF);
        ModbusRTUBuffTXD.DataLen += 2;
        // 数据发送
        // for (uint8_t index = 0;index < ModbusRTUBuffTXD.DataLen;index++) {

        // }
        API_UartSendData((uint8_t *)&ModbusRTUBuffTXD.DataBuff[0], ModbusRTUBuffTXD.DataLen);
        // 数据发送完成,清除发送缓冲区
        ModbusRTU_TxDataInit();
    }
}

/**
 * @brief 向主机发送错误信息
 * @param Funtion 功能码
 * @param Error 错误信息
 */
static void ModbusRTU_SendError(ModbusFunction Funtion, ModbusError Error)
{
    ModbusRTU_TxDataInit();
    ModbusRTU_TxDataPush(MODBUSRTU_ADDER);  // 写入Modbus从机地址
    ModbusRTU_TxDataPush((Funtion + 0x80));
    ModbusRTU_TxDataPush(Error);
}

/**
 * @brief Modbus初始化
 * @param
 */
void ModbusRTU_Init(void)
{

}



/**
 * @brief Modbus读寄存器指令,对应03指令
 * @param pData 接收缓冲区数据
 */
static void ModbusRTU_ReadRegFunction(const uint8_t *pData)
{
    // 03命令指令
    uint16_t adderStart = (uint16_t)(pData[2] << 8) + (uint16_t)pData[3];   // 寄存器起始地址
    uint16_t lenData = (uint16_t)(pData[4] << 8) + (uint16_t)pData[5];  // 需要读取的寄存器个数
    ModbusRTU_TxDataPush(MODBUSRTU_ADDER);  // 回复从机地址
    ModbusRTU_TxDataPush(pData[1]);  // 回复功能码 03
    ModbusRTU_TxDataPush((lenData << 1));  // 待回复的数据长度
    for (uint16_t index = 0;index < lenData;index++) {
        uint16_t regData = 0;
        if (ReadRegMpa((adderStart + index), &regData)) {
            ModbusRTU_TxDataPush((regData >> 8));     // 将数据放入发送缓冲区,高位优先
            ModbusRTU_TxDataPush((regData & 0xFF));
        }
        else {   // 非法访问
            ModbusRTU_SendError((ModbusFunction)pData[1], MODBUS_ERR_UNADD);
            return;
        }
    }
}

/**
 * @brief 06指令:写单个寄存器指令
 * @param pData 接收缓冲区数据
 */
static void ModbusRTU_WriteRegFunction(const uint8_t *pData)
{
    // 06指令
    uint16_t adderStart = (uint16_t)(pData[2] << 8) + (uint16_t)pData[3];   // 寄存器起始地址
    uint16_t regData = (uint16_t)(pData[4] << 8) + (uint16_t)pData[5];  // 待写入的数据
    // 发送的数据:从机地址、功能码、寄存器地址高位、寄存器地址低位、数据高位、数据低位、校验码高位、校验码低位
    for (uint8_t index = 0;index < 6;index++) {
        ModbusRTU_TxDataPush(pData[index]);
    }
    if (WriteRegMap(adderStart, regData)) {  // 数据写入成功
        return;
    }
    else {  // 数据写入非法
        ModbusRTU_SendError((ModbusFunction)pData[1], MODBUS_ERR_UNADD);
        return;
    }
}
/**
 * @brief 10指令:写多个寄存器
 * @param pData
 */
static void ModbusRTU_WriteMultiRegFunction(const uint8_t *pData)
{
    // 0x10指令
    uint16_t adderStart = (uint16_t)(pData[2] << 8) + (uint16_t)pData[3];   // 寄存器起始地址
    uint16_t lenData = (uint16_t)(pData[4] << 8) + (uint16_t)pData[5];  // 需要读取的寄存器个数
    // 发送的数据:从机地址、功能码、寄存器地址高位、寄存器地址低位、校验码高位、校验码低位
    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];  // 待写入的数据
        if (WriteRegMap(adderStart + index, regData) == 0) {  // 数据写入失败
            ModbusRTU_SendError((ModbusFunction)pData[1], MODBUS_ERR_UNADD);
            return;
        }
    }
}

/**
 * @brief MobusRTU协议解析
 * @param  None
 * @return
 */
ModbusState ModbusRTU_Handle(void)
{

    if (ModbusStateRXD == MODBUS_RXD_FINISH) {   // 主机数据接收完成,开始解析

        if (ModbusRTUBuffRXD.DataLen < 3 || ModbusRTUBuffRXD.DataBuff[0] != MODBUSRTU_ADDER) {  // 数据长度异常或地址错误
            ModbusRTUBuffRXD.DataLen = 0;
            ModbusStateRXD = MODBUS_RXD_WAIT;
            return MODBUS_ERR;
        }
        // Modbus数据校验码计算
				uint16_t modbusRXDCrc16 = 0;
        uint16_t modbusCrc16 = CRC_16((uint8_t *)&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) {  // 校验失败
            ModbusRTUBuffRXD.DataLen = 0;
            ModbusStateRXD = MODBUS_RXD_WAIT;
            return MODBUS_ERR;
        }
        ModbusState state = ModbusRTU_FrameLenCheck((uint8_t *)&ModbusRTUBuffRXD.DataBuff[0], ModbusRTUBuffRXD.DataLen);   // 帧数据长度校验
        if (state != MODBUS_OK) {
            ModbusRTU_SendError((ModbusFunction)ModbusRTUBuffRXD.DataBuff[1], state);   // 发送错误故障码状态
            ModbusRTU_SendData();  // 数据发送
            ModbusStateRXD = MODBUS_RXD_WAIT;
            return MODBUS_ERR;
        }
        switch ((ModbusFunction)ModbusRTUBuffRXD.DataBuff[1])
        {
        case MODBUS_FUNCTION_03:  // 读取多个寄存器
            ModbusRTU_ReadRegFunction((uint8_t *)&ModbusRTUBuffRXD.DataBuff[0]);
            break;
        case MODBUS_FUNCTION_06:  // 写单路寄存器
            ModbusRTU_WriteRegFunction((uint8_t *)&ModbusRTUBuffRXD.DataBuff[0]);
            break;
        case MODBUS_FUNCTION_10:
            ModbusRTU_WriteMultiRegFunction((uint8_t *)&ModbusRTUBuffRXD.DataBuff[0]);
            break;
        default:  // 异常回复,不存在功能码
            ModbusRTU_SendError((ModbusFunction)ModbusRTUBuffRXD.DataBuff[1], MODBUS_ERR_NUFUN);
            break;
        }
        ModbusRTU_SendData();  // 数据发送
        ModbusRTUBuffRXD.DataLen = 0;  // 接收缓冲区复位
        ModbusStateRXD = MODBUS_RXD_WAIT;  // 等待接收状态
        return MODBUS_OK;
    }
    else
        return MODBUS_OK;
}


///以下函数需要根据实际情况编写/


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

/**
 * @brief 读寄存器操作
 * @param Adder 数据地址
 * @param regData 读数据指针
 * @return 0:读寄存器操作失败
 *         1:读寄存器操作失败成功
 */
uint8_t ReadRegMpa(uint16_t Adder, uint16_t *regData)
{
    // 允许读取,返回1,
    // 不允许读取,返回0
    if (Adder < 10) {
        *regData = SaveData[Adder];
        return 1;
    }
    else
        return 0;
}

/**
 * @brief 写寄存器操作
 * @param Adder 数据地址
 * @param Data 待写入的数据
 * @return 0:写寄存器操作失败
 *         1:读寄存器操作失败成功
 */
uint8_t WriteRegMap(uint16_t Adder, uint16_t Data)
{
    if (Adder < 10) {
        SaveData[Adder] = Data;
        return 1;
    }
    else
        return 0;
}

ModbusRTU协议.h文件:2

#ifndef _MODBUSRTU_H_
#define _MODBUSRTU_H_

#include <stdint.h>


#define MODBUSRTU_BUFF_MAX (200)   // ModbusRTU缓冲区大小
#define MODBUSRTU_ADDER (0x01)     // Modbus从机地址,固定地址
#define MODBUSRTU_REG_NUM_MAX (125)  // 不允许大于125
#define MODBUSRTU_TIMEOUT_MAX (20 )    // 接收超时最大值

typedef enum {
    MODBUS_OK,
    MODBUS_ERR,
    MODBUS_RXD_WAIT,  // 数据接收等待
    MODBUS_RXD_ON,  // 数据接收中
    MODBUS_RXD_FINISH,  // 数据接收完成
    MODBUS_RXD_BUFF_FULL,  // 缓冲溢出
}ModbusState;

typedef enum {
    MODBUS_FUNCTION_03 = 0x03,   // 读保持寄存器功能码
    MODBUS_FUNCTION_06 = 0x06,   // 写单路寄存器
    MODBUS_FUNCTION_10 = 0x10,   // 写多个数据
}ModbusFunction;

typedef enum {
    MODBUS_ERR_NUFUN = 1,    // 非法功能
    MODBUS_ERR_UNADD = 2,    // 非法数据地址
    MODBUS_ERR_UNDATA = 3,   // 非法数据指
    MODBUS_ERR_FAULT = 4,    // 从站设备故障
    MODBUS_ERR_ENSURE = 5,   // 确认
    MODBUS_ERR_BUY = 7,      // 从属设备忙碌
    MODBUS_ERR_OECHECK = 8,  // 存储奇偶性差错
    MODBUS_ERR_UNPATH = 0x0A,  // 不可用网关路径
    MODBUS_ERR_RESFAIL = 0x0B,  // 响应失败
}ModbusError;

typedef struct ModbusRTU
{
    uint8_t DataLen;  // 缓冲区数据长度
    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.关于单片机接收上位机完整的一帧ModbusRTU数据

(1) 单片机具备UART空闲中断(STM32),使用空闲中断确定一帧数据接收完成;

(2) 单片机无空闲中断,使用定时实现UART空闲判断。需注意空闲时间设置,上述代码使用定时器实现一帧数据接收完成的判断。

3.关于ModbusRTU测试

主机软件:Modbus Poll;

从机软件:Modbus Slave;

使用教程与下载链接:

Modbus仿真器 Modbus Poll 和Modbus Slave详细图文教程

  • 4
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
### 回答1: Modbus RTU通信协议是一种基于串行通信的通信协议,常用于工业自动化领域。STM32是一款由STMicroelectronics推出的系列32位微控制器,具有高性能和丰富的外设功能。 要使用Modbus RTU通信协议在STM32上进行通信,可以采取以下步骤: 1. 配置串口:选择一个合适的串口并进行相应的初始化配置,包括波特率、数据位、停止位和奇偶校验位等设置。这些配置应该与Modbus RTU设备(如PLC或传感器)的设置匹配。 2. 实现Modbus RTU协议:在STM32上实现Modbus RTU协议,需要编写相关的代码来处理通信协议的帧结构、数据解析和响应等。这包括解析从Modbus主设备接收到的命令帧,执行相应的功能,并将响应数据打包为响应帧发送回Modbus主设备。 3. 与外部设备通信:通过STM32的串口与外部Modbus RTU设备进行通信。可以使用适当的USART或UART外设,使用串口驱动程序传输和接收消息。 4. 调试和测试:实现后,需要进行调试和测试以确保通信的正确性和稳定性。可以使用调试工具或虚拟串口软件模拟Modbus从设备,验证STM32的通信功能。 总结来说,要在STM32上使用Modbus RTU通信协议,需要配置串口、实现Modbus RTU协议、进行外部设备的通信和进行调试和测试。这样可以实现STM32与其他Modbus RTU设备之间的可靠通信。 ### 回答2: Modbus RTU是一种常用的串行通信协议,适用于STM32等微控制器的通信应用。该协议使用简单的二进制编码方式进行数据传输,支持点对点和多点通信。 在STM32上实现Modbus RTU通信协议,首先要了解协议的基本结构和传输规则。Modbus RTU使用了RS-485物理层接口和UART串口通信协议进行数据传输。通过对UART和GPIO的配置,可以轻松实现RS-485的硬件连接,并使用UART进行数据的发送和接收。 在软件层面上,可以使用STM32的库函数或者第三方库来实现Modbus协议的解析和封装。首先需要对接收到的数据进行解析,提取出地址、功能码、寄存器地址和数据等字段。然后根据功能码进行相应的操作,如读取或写入寄存器的数据。 对于Modbus RTU通信协议,需要注意以下几点: 1. 通信速率:根据具体需求选择合适的通信速率,常见的有9600、19200等。 2. 地址设置:每个设备都有独一无二的地址,通信时需要根据地址进行寻址。 3. 异常处理:在通信过程中,可能会发生通信错误或者设备故障,需要合适地处理异常情况。 在使用Modbus RTU通信协议时,可以根据具体应用需求选择合适的通信方式,如点对点通信或者多点通信。可以使用STM32提供的硬件资源和软件编程能力,来实现Modbus RTU通信协议实现设备之间的数据交换。 ### 回答3: Modbus RTU是一种串行通信协议,常用于实现设备之间的通信。STM32是一系列基于ARM Cortex-M内核的微控制器产品。 Modbus RTU协议通过串行通信方式实现设备之间的数据传输。它是一种基于从站/主站的通信方式,通常需要一台主机设备(主站)和多台从机设备(从站)。主站通过发送指令来读取或写入从站设备中的寄存器值。Modbus RTU协议使用二进制编码,可以在串行通信支持的较低速率下进行通信,具有较高的实时性和稳定性。 STM32是一家STMicroelectronics推出的微控制器产品系列,使用ARM Cortex-M内核。它具有低功耗、高计算性能和丰富的外设资源,常用于工业自动化、智能家居、安全控制等领域。STM32系列微控制器支持Modbus RTU协议,通过串口或UART接口与其他设备进行通信。 在STM32微控制器中,可以通过配置串口或UART模块来实现Modbus RTU通信。首先,需要在STM32的引脚配置和时钟设置中进行相应的初始化。然后,通过编程设置串口或UART的参数,如波特率、数据位数、停止位数等。接下来,可以使用STM32的通信接口库函数来发送和接收Modbus RTU协议的数据帧。 总而言之,Modbus RTU是一种常用的串行通信协议,需要通过合适的硬件设备和适当的编程来实现。在STM32微控制器中,可以通过串口或UART模块来支持Modbus RTU通信。这种通信方式在工业自动化和其他领域中具有广泛的应用。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值