第二十九章 W55MH32 Modbus_TCP_Server示例

发布于:2025-07-25 ⋅ 阅读:(14) ⋅ 点赞:(0)

目录

1 Modbus TCP简介

2 Modbus TCP的基本原理

3 Modbus TCP的优势

4 注意事项

5 应用场景

6 Modbus TCP报文结构

7 Modbus TCP常用功能码

8 实现过程

9 运行结果

10 总结


本篇文章,我们将详细介绍如何在W55MH32芯片上面实现Modbus TCP协议。并通过实战例程,为大家讲解如何在W55MH32上使用Modbus TCP作为服务器,监听端口,与客户端进行通信。

该例程用到的其他网络协议,如DHCP,请参考相关章节。有关 W55MH32 的初始化过程,请参考 Network Install 章节,这里将不再赘述。

1 Modbus TCP简介

Modbus TCP是一种基于以太网的通信协议,它是经典Modbus协议的扩展。Modbus协议最初由Modicon公司在1979年开发,广泛应用于工业自动化系统,用于实现不同设备之间的数据通信。Modbus TCP结合了Modbus协议的简单性和以太网的高效性,是一种开放、标准化且广泛使用的工业通信协议。

2 Modbus TCP的基本原理

Modbus TCP使用TCP/IP协议栈进行通信,运行在OSI模型的传输层(TCP层)之上。设备之间通过以太网接口连接,数据通过TCP端口(通常是502端口)传输。

主从架构:

主机(Master):主动发起请求的设备(通常是PLC或工业PC)。

从机(Slave):响应主机请求的设备(如传感器、执行器、IO模块)。

数据传输: 主机向从机发送请求,从机解析后执行相应操作,并返回结果。通信过程包括功能码、地址、数据值和错误校验等内容。

3 Modbus TCP的优势

开放性: 不需要支付许可证费用,广泛支持。

简洁性: 数据格式简单易懂,开发和维护成本低。

兼容性: 支持多种工业设备和系统。

实时性: 基于TCP/IP,通信速度快,延迟低。

可扩展性: 能够集成到基于以太网的工业网络中。

4 注意事项

通信可靠性: TCP连接可能存在断开或超时的情况,需要进行异常处理。

数据安全性: Modbus TCP本身不包含加密机制,可使用TLS等协议增加安全性。

设备地址: 每个从机需要唯一的单元标识符(Unit Identifier)。

网络配置: 需正确设置IP地址、子网掩码和网关。

Modbus TCP因其高效性和兼容性,已成为工业物联网(IIoT)和工业4.0中不可或缺的一部分。

5 应用场景

接下来,我们了解下在W55MH32上,可以使用Modbus TCP协议完成哪些操作及应用呢?

1. 工业自动化控制系统(PLC、SCADA等):通过与PLC和SCADA系统集成,W55MH32可以实现设备的实时监控与控制,支持生产线的参数调整、启动和停止等操作,同时能够采集传感器数据并上传至控制系统,用于分布式管理和优化工业流程。

2. 智能楼宇自动化(HVAC系统、能源管理):W55MH32可连接楼宇的HVAC设备,支持远程调节空调和通风系统,并采集电力、水、气等能耗数据,上传至能源管理系统,帮助优化楼宇能效。此外,还可集成智能照明和安防传感器,实现更智能的楼宇管理。

3. 数据采集与监控(远程I/O模块、传感器网络):通过远程I/O模块,W55MH32能够采集温度、压力、湿度等多种传感器数据,并实现远程监控与报警功能。采集的数据还能用于设备健康状态分析,支持预测性维护,提升设备可靠性。

4. 工业设备互联(变频器、伺服驱动器):W55MH32可与变频器、伺服驱动器等设备通信,实现参数调节和运行状态监控,支持高精度的运动控制和状态反馈。同时,作为中转站,它还能实现不同设备之间的互联互通,构建开放的工业互联生态。

6 Modbus TCP报文结构

Modbus TCP报文由以下几个部分组成:

事务处理标识符(2字节):用于标识请求与响应之间的配对。主机在请求中设置一个唯一的标识符,从机在响应中回传相同的值,方便主机识别对应的响应。

协议标识符(2字节):通常为0x0000,表示该报文使用的是Modbus协议。

长度字段(2字节):表示后续数据的字节数(不包括事务处理标识符和协议标识符)。

单元标识符(1字节):标识目标从机设备的地址。在Modbus TCP中,该字段通常用于区分逻辑设备。

功能码(1字节):定义当前操作的类型(如读写寄存器等)。

数据部分(可变长度):包含具体的操作数据,例如寄存器地址、要读取或写入的值等。

7 Modbus TCP常用功能码

功能码 0x01:读取线圈状态,用于读取从机设备中的一组线圈状态(0或1)。

功能码 0x02:读取离散输入,用于读取从机设备中的一组离散输入状态(只读,0或1)。

功能码 0x03:读取保持寄存器,用于读取从机设备中的一组保持寄存器值(通常是可读写的数值)。

功能码 0x04:读取输入寄存器,用于读取从机设备中的一组输入寄存器值(只读数据)。

功能码 0x05:写单个线圈,用于写入从机设备中的一个线圈状态(设置为0或1)。

功能码 0x06:写单个寄存器,用于向从机设备中的一个保持寄存器写入数据。

功能码 0x0F:写多个线圈,用于同时写入从机设备中的多个线圈状态。

功能码 0x10:写多个寄存器,用于同时向从机设备中的多个保持寄存器写入数据。

8 实现过程

接下来,我们在W55MH32上实现Modbus TCP协议服务器模式:

注意:测试实例需要PC端和W55MH32处于同一网段。

步骤一:初始化并注册LED相关函数

    user_led_init();

    user_led_control_init(get_user_led_status, set_user_led_status);

在程序初始化部分添加LED所使用的GPIO外设初始化和注册设置、获取LED状态的函数,用于在接收到特定的Modbus TCP数据时进行状态的显示。

user_led_control_init()函数如下:

void user_led_control_init(int (*get_fun)(void), void (*set_fun)(uint32_t))
{
    if (get_fun != NULL && set_fun != NULL)
    {
        getUserLED_cb = get_fun;
        setUserLED_cb = set_fun;
    }
}

user_led_control_init()为LED控制初始化函数,允许用户注册两个回调函数:一个用于获取LED状态,另一个用于设置LED状态。这些回调函数将在get_led_status()和set_led_status()函数中被调用。函数如下:

int get_led_status(void)
{
    return getUserLED_cb();
}

void set_led_status(int32_t val)
{
    setUserLED_cb(val);
}

get_led_status()和set_led_status()为获取和设置LED状态的函数,get_led_status()函数调用注册的获取LED状态的回调函数,并返回其返回值。set_led_status()函数调用注册的设置LED状态的回调函数,并传入新的状态值。

步骤二:主循环调用do_Modbus()函数

while (1)
{
    do_Modbus(SOCKET_ID);
}

在循环中不断执行do_Modbus()函数的操作,用于处理Modbus TCP通信,根据套接字的不同状态执行相应的操作,包括监听连接请求、处理连接建立事件、接收和处理数据以及关闭连接等。

步骤三:进入do_Modbus()函数,处理接收的报文

void do_Modbus(uint8_t sn)
{
    uint8_t  state = 0;
    uint16_t len;
    getSIPR(lip);
    state = getSn_SR(sn);
    switch (state)
    {
    case SOCK_SYNSENT:
        break;
    case SOCK_INIT:
        listen(sn);
        if (!b_listening_printed)
        {
            b_listening_printed = 1;
            printf("Listening on %d.%d.%d.%d:%d\r\n",
                   lip[0], lip[1], lip[2], lip[3], local_port);
        }
        break;
    case SOCK_LISTEN:
        break;
    case SOCK_ESTABLISHED:
        if (getSn_IR(sn) & Sn_IR_CON)
        {
            setSn_IR(sn, Sn_IR_CON);
            printf("Connected\r\n");
            getSn_DIPR(sn, rip);
            port = getSn_DPORT(sn);
            printf("RemoteIP:%d.%d.%d.%d Port:%d\r\n", rip[0], rip[1], rip[2], rip[3], port);

            if (b_listening_printed)
                b_listening_printed = 0;
        }
        len = getSn_RX_RSR(sn);
        if (len > 0)
        {
            mbTCPtoEVB(sn);
        }
        break;
    case SOCK_CLOSE_WAIT:
        disconnect(sn);
        break;
    case SOCK_CLOSED:
    case SOCK_FIN_WAIT:
        close(sn);
        socket(sn, Sn_MR_TCP, local_port, Sn_MR_ND); // Sn_MR_ND
        break;
    default:
        break;
    }
}

首先,程序会获取本地IP地址和指定socket的状态,根据socket的状态,执行相应的操作。例如,如果socket处于监听状态,则开始监听;如果处于已建立连接状态,则处理接收到的数据。在处理已建立连接状态时,检查是否有新的连接请求,并打印连接信息(包括远程IP地址和端口号)。如果有接收到的数据,则调用mbTCPtoEVB()函数检查Modbus TCP数据并执行对应操作。mbTCPtoEVB()函数内容如下:

void mbTCPtoEVB(uint8_t sn)
{
    if (MBtcp2evbFrame() != 0)                         // 检查是否接收到完整的Modbus TCP帧
    {
        if (pucASCIIBufferCur[0] == 0x01)              // 验证设备地址是否为0x01(Modbus从站地址)
        {
            if ((uint8_t)pucASCIIBufferCur[1] == 0x05) // 处理单线圈写入命令(0x05)
            {
                if ((uint8_t)pucASCIIBufferCur[4] == 0xff) // 写入值为0xFF00(表示线圈接通)
                {
                    set_led_status(0);                  // 关闭LED(此处逻辑可能与实际硬件相关,需注意高低电平定义)
                    printf("LED ON\r\n");
                }
                else if ((uint8_t)pucASCIIBufferCur[4] == 0x00) // 写入值为0x0000(表示线圈断开)
                {
                    set_led_status(1);                  // 开启LED
                    printf("LED OFF\r\n");
                }
                send(sn, recv_data, recv_len);          // 回发响应帧(通常为原请求帧的确认)
            }
            else if ((uint8_t)pucASCIIBufferCur[1] == 0x01) // 处理读线圈状态命令(0x01)
            {
                if (recv_data[recv_len - 1] != 0x01)    // 检查接收数据长度是否符合预期(此处判断可能需根据实际协议调整)
                {
                    printf("len error!%x\r\n", recv_data[recv_len - 1]);
                }
                else
                {
                    printf("Read OK!\r\n");
                    // 构造Modbus响应帧(包含事务处理标识、协议标识、长度等头部信息)
                    send_data[0] = recv_data[0];        // 复制事务处理标识(高位)
                    send_data[1] = recv_data[1];        // 复制事务处理标识(低位)
                    send_data[2] = recv_data[2];        // 复制协议标识(高位,通常为0x00)
                    send_data[3] = recv_data[3];        // 复制协议标识(低位,通常为0x00)
                    send_data[4] = 0x00;                // 响应长度(高位)
                    send_data[5] = 0x04;                // 响应长度(低位,标识后续数据字节数)
                    send_data[6] = 0x01;                // 从站地址(与请求一致)
                    send_data[7] = 0x01;                // 功能码(与请求一致,读线圈状态)
                    send_data[8] = 0x01;                // 数据长度(1字节)
                    send_data[9] = ~get_led_status();   // 线圈状态(取LED状态的反值,需注意与实际状态的对应关系)
                    send_len     = 10;                   // 响应帧总长度
                    send(sn, send_data, send_len);       // 发送响应帧
                    memset(send_data, 0, send_len);      // 清空发送缓冲区
                }
            }
            else
            {
                printf("error code!\r\n");               // 不支持的功能码
            }
        }
        else
        {
            printf("address error!\r\n");                // 设备地址不匹配
        }
    }
}

mbTCPtoEVB()函数用于处理从Modbus TCP接收到的数据,根据数据内容执行相应的操作,包括写单个设备和读单个设备操作,并根据操作结果发送响应数据。比如在接收到一段Modbus TCP数据之后,首先会进行检查、比对,符合预设的值时,对LED进行开或关的状态设置,并打印相应的信息,再调用send()函数将接收到的数据原样发送回去。如果接收到的数据不符合预期,会打印相应的错误信息。

9 运行结果

烧录例程运行后,首先可以看到进行了PHY链路检测,然后打印了设置的网络地址信息,然后开始监听地址和端口号,信息如下图所示:

我们使用网络调试助手进行连接:

然后发送“00 01 00 00 00 06 01 01 00 00 00 01”报文后回复“0 01 00 00 00 04 01 01 01 FE”如下图所示:

接着我们发送“ 01 00 00 00 06 01 05 00 00 ff 00 00 00”令,LED就被打开了,且指令被进行了回传:

发送“0 01 00 00 00 06 01 05 00 00 00 00 00 00”可以关闭LED:

10 总结

本文讲解了如何在 W55MH32 芯片上实现 Modbus TCP 协议的服务器模式,通过实战例程展示了从初始化 LED 相关函数、主循环调用处理函数到解析处理接收到的报文的完整过程。文章详细介绍了 Modbus TCP 的概念、基本原理、优势、注意事项、应用场景、报文结构和常用功能码,帮助读者理解其在工业通信中的实际应用价值。

下一篇文章将讲解在 W55MH32 芯片上实现 HTTP_Server 与 NetBIOS 功能,解析如何通过 NetBIOS 名称访问 HTTP 服务器的网页内容,同时通过实战例程讲解具体实现步骤与要点,敬请期待!


网站公告

今日签到

点亮在社区的每一天
去签到