RTX5(CMSISI-RTOS V2)的FreeModbus-TCP移植


硬件平台

stm32f407vgt6 + LAN8720

RL-TCPnet V7.X网协议栈移植

主要使用RTE环境创建,基于安富莱_STM32-V5开发板的工程模板修改
,具体步骤可参考安富莱的文档:安富莱_STM32-V5开发板_RL-TCPnet V7.X网络教程(V0.4)

有几点需要注意:

1、我使用的PHY芯片为 LAN8720, 在RTE环境可以直接选择此款芯片,但是仍旧未使用RTE环境提供的驱动,使用的为 armfly 提供的驱动:修正版RL-TCPnet V7.X和LwIP的LAN8720驱动.差异不大,主要添加了软件复位。

2、实际上添加软件复位后依旧不好用,基于我板子的硬件,在初始化网络前添加了硬件复位。即复位连接到 LAN8720 reset引脚的 GPIO.

3、我使用的板子上 LAN8720 的时钟源由单片机的 MCO 给出,所以还需要额外配置 MCO1 输出 25MHz 的时钟,如果使用独立晶振,则不需要配置。

4、同时测试了 FreeRTOS 和 RTX5 的底层,发现 FreeRTOS 在测试中确实不稳定,连续 ping 一会都会出现失败甚至短连的情况,建议使用 RTX5.

5、其它详细步骤参考安富莱的文档。

安富莱提供的模板中已经实现了以太网口插拔情况的检测,实际测试效果很好,完成移植后基本可以直接使用,打开 RL-net 的 DEBUG 模式后可以看到相关的信息。NetBIOS Name Service这项功能也非常实用,可以在 DHCP 开启的情况的使用 host name 替代 IP 地址。

FreeModbus-TCP移植

源码

github上的freemodbus,直接 clone 到本地或下载压缩包即可。

移植

官网未给出详细的移植步骤,只给出了 Porting for RTU/ASCII的说明。但是建议先看一下前面部分的说明。

移植前需要确保网络协议栈可正常工作,保证 TCP 数据可以正常收发,modbus-tcp 主要依赖于 TCP 协议,通常从机作为 TCP-server ,主机作为TCP- client 区轮询从机。

将源码包下的modbus文件夹加入主程序,注意如果只移植 TCP 模式的话只需要加入下图所示的文件,全部加入的话 RTU 和 ASCII 的部分可能报错。

这部分文件基本不作修改,只修改一处,即mbconfig.h中的模式使能定义,仅使能 TCP 模式:

然后自行新建modbus_port文件夹,主要需要4个文件:
prot.h, 移植有关的宏定义,从源码文件夹下的demo/BRAE/下复制即可,只使用 TCP 模式此文件不需要修改,使用 RTU 还需要添加宏定义的实现,可不修改此文件。

portevent.c,从同样的文件夹下复制,添加如下函数的定义,在porttcp.c中会用到。

void
vMBPortEventClose( void )
{
    xEventInQueue = FALSE;
}

porttcp.c,tcp 相关接口的移植,也是移植 freemodbus-TCP 主要需要修改的地方。在demo/MCF5235TCP/port下复制porttcp.c作为模板,据此来修改 tcp 接口实现。这部分主要参考RL-TCPnet的 API 说明,可以在 RTE 管理器中打开说明文档。

贴出修改后的内容:

/*
 * @Author: JunQiLiu
 * @Date: 2021-01-10 09:13:48
 * @LastEditTime: 2021-01-10 16:55:29
 * @Description: 
 * @FilePath: \USE_Node\freemodbus_port\porttcp.c
 * @ 
 */
/*
 * Copyright (C) 2021 JunQi Liu
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * File: $Id$
 */

//includes
#include <stdio.h>
#include "port.h"
#include "rl_net.h"
#include "mb.h"
#include "mbport.h"

//MBAP Header
#define MB_TCP_UID          6
#define MB_TCP_LEN          4
#define MB_TCP_FUNC         7

//MB_TCP defines
#define MB_TCP_DEFAULT_PORT 502 /* TCP listening port. */
#define MB_TCP_BUF_SIZE     ( 256 + 7 ) /* Must hold a complete Modbus TCP frame. */

//Prototypes 
void            vMBPortEventClose( void );
#ifdef MB_TCP_DEBUG
void            vMBPortLog( eMBPortLogLevel eLevel, const CHAR * szModule,
                            const CHAR * szFmt, ... );
#endif
//variables
static int32_t mb_tcp_socket;

static UCHAR    aucTCPBuf[MB_TCP_BUF_SIZE];
static USHORT   usTCPBufPos;

//functions
void prvvMBPortReleaseClient( int32_t mb_tcp_socket);
/**
 * @description: callback function of tcp server
 * @param 
 *  [in]    socket  TCP socket handle.
 *  [in]    event   Event type as shown in the table below (netTCP_Event).
 *  [in]    addr    Pointer to the structure containing the remote IP address and port number.
 *  [in]    buf Pointer to buffer containing the received data.
 *  [in]    len Number of bytes in the received packet.
 * @return 
 * 1 - incoming connection accepted.
 * 0 - incoming connection rejected.
 * @note: 
 * @Author: JunQiLiu
 */
uint32_t mb_tcp_cb_server(int32_t socket, netTCP_Event event, \
const NET_ADDR *addr, const uint8_t *buf, uint32_t len)
{
    USHORT          usLength;
    switch(event)
    {
        case netTCP_EventConnect:
            if(addr->addr_type ==   NET_ADDR_IP4)
            {
                printf("远程客户端请求连接 IP: %d.%d.%d.%d 端口号:%d\r\n",
                addr->addr[0], 
                addr->addr[1], 
                addr->addr[2], 
                addr->addr[3],
                    addr->port);
                return(1);
            }
            return (0);
        case netTCP_EventEstablished:
            break;
        case netTCP_EventClosed:
            break;
        case netTCP_EventAborted:
            break;
        case netTCP_EventACK:
            break;
        case netTCP_EventData:
            if( ( usTCPBufPos + len ) >= MB_TCP_BUF_SIZE )
            {
                prvvMBPortReleaseClient( mb_tcp_socket );
            }
            else
            {
                memcpy( &aucTCPBuf[usTCPBufPos], buf, len );
                usTCPBufPos += len;
                if( usTCPBufPos >= MB_TCP_FUNC )
                {
                    /* Length is a byte count of Modbus PDU (function code + data) and the
                    * unit identifier. */
                    usLength = aucTCPBuf[MB_TCP_LEN] << 8U;
                    usLength |= aucTCPBuf[MB_TCP_LEN + 1];

                    /* Is the frame already complete. */
                    if( usTCPBufPos < ( MB_TCP_UID + usLength ) )
                    {
                    }
                    else if( usTCPBufPos == ( MB_TCP_UID + usLength ) )
                    {
                        #ifdef MB_TCP_DEBUG
                                        prvvMBTCPLogFrame( "MBTCP-RECV", &aucTCPBuf[0], usTCPBufPos );
                        #endif
                        ( void )xMBPortEventPost( EV_FRAME_RECEIVED );
                    }
                    else
                    {
                        #ifdef MB_TCP_DEBUG
                                        vMBPortLog( MB_LOG_DEBUG, "MBTCP-ERROR",
                                                    "Received to many bytes! Droping client.\r\n" );
                        #endif
                        /* This should not happen. We can't deal with such a client and
                        * drop the connection for security reasons.
                        */
                        prvvMBPortReleaseClient( mb_tcp_socket );
                    }
                }
            }            
            break;
    }
    return (0);
}


BOOL
xMBTCPPortInit( USHORT usTCPPort )
{   
    BOOL            bOkay = FALSE;
    USHORT          usPort;

    if( usTCPPort == 0 )
    {
        usPort = MB_TCP_DEFAULT_PORT;
    }
    else
    {
        usPort = ( USHORT ) usTCPPort;
    }
    mb_tcp_socket = netTCP_GetSocket(mb_tcp_cb_server);
    if(mb_tcp_socket >= 0)
    {
        if(netTCP_Listen(mb_tcp_socket,usPort) == netOK)
        {
            netTCP_SetOption (mb_tcp_socket, netTCP_OptionKeepAlive, 1);
            usTCPBufPos = 0;
            bOkay = TRUE;
            return bOkay;
        }
        else
        {
            printf("netTCP_Listen failed!\n");
            bOkay = FALSE;
            return bOkay; 
        }

    }
    else
    {
        printf("GetSocket failed!\n");
        bOkay = FALSE;
        return bOkay;
    }   
}

void
prvvMBPortReleaseClient( int32_t mb_tcp_socket)
{
    #ifdef MB_TCP_DEBUG
                    vMBPortLog( MB_LOG_DEBUG, "MBTCP-ERROR",
                                "Droping client.\r\n" );
    #endif
    if(mb_tcp_socket != NULL)
    {
        if(netTCP_Close(mb_tcp_socket) != netOK)
        {
            netTCP_Abort(mb_tcp_socket);
        }
        if(netTCP_GetState(mb_tcp_socket) != netTCP_StateCLOSED)
        {
            if(netTCP_Close(mb_tcp_socket) != netOK)
            {
                netTCP_Abort(mb_tcp_socket);
            }
        }
        netTCP_ReleaseSocket(mb_tcp_socket);
    }   
}
void
vMBTCPPortClose(  )
{
    /* Shutdown any open client sockets. */
    prvvMBPortReleaseClient( mb_tcp_socket );

    /* Release resources for the event queue. */
    vMBPortEventClose(  );
}

void
vMBTCPPortDisable( void )
{
    prvvMBPortReleaseClient( mb_tcp_socket );
}



BOOL

xMBTCPPortGetRequest( UCHAR ** ppucMBTCPFrame, USHORT * usTCPLength )
{
    *ppucMBTCPFrame = &aucTCPBuf[0];
    *usTCPLength = usTCPBufPos;

    /* Reset the buffer. */
    usTCPBufPos = 0;
    return TRUE;
}

BOOL
xMBTCPPortSendResponse( const UCHAR * pucMBTCPFrame, USHORT usTCPLength )
{
    BOOL            bFrameSent = FALSE;
    uint8_t *sendbuf;

    if( mb_tcp_socket >= 0 )
    {
        if(netTCP_GetState(mb_tcp_socket) == netTCP_StateESTABLISHED)
        {
            if(netTCP_SendReady(mb_tcp_socket) == TRUE)
            {
                if(netTCP_GetMaxSegmentSize (mb_tcp_socket) >= usTCPLength)
                {
                    sendbuf = netTCP_GetBuffer(usTCPLength);
                    memcpy(sendbuf,pucMBTCPFrame,usTCPLength);
                    netTCP_Send(mb_tcp_socket,sendbuf,usTCPLength);
                    bFrameSent = TRUE;
                    return bFrameSent;
                }
            }
        }
    }
    return bFrameSent;
}

最后是寄存器操作的相关实现,在这部分可以自定义对 modbus 寄存器的具体操作,根据官网的描述:

Whenever the protocol stack requires a value it calls one of the callback function with the register address and the number of registers to read as an argument. The application should then read the actual register values (for example the ADC voltage) and should store the result in the supplied buffer.
If the protocol stack wants to update a register value because a write register function was received a buffer with the new register values is passed to the callback function. The function should then use these values to update the application register values.
...
Examples:
AT91SAM7X_ROWLEY/demo.c, AVR/demo.c, LINUX/demo.c, MCF5235/demo.c, MCF5235TCP/demo.c, MSP430/demo.c, STR71X/simple2.c, STR71XTCP/demo.c, WIN32/demo.cpp, and WIN32TCP/demo.cpp.

直接从 demo 中复制一份,可自行命名,如mb_registers.c,贴出完整内容:

/*
 * @Author: JunQiLiu
 * @Date: 2021-01-10 18:38:31
 * @LastEditTime: 2021-01-10 18:42:28
 * @Description: 
 * @FilePath: \USE_Node\User\mb_registers.c
 * @ 
 */
#include "mb.h"

#define REG_INPUT_START         1000
#define REG_INPUT_NREGS         4
#define REG_HOLDING_START       2000
#define REG_HOLDING_NREGS       130



static USHORT   usRegInputStart = REG_INPUT_START;
static USHORT   usRegInputBuf[REG_INPUT_NREGS];
static USHORT   usRegHoldingStart = REG_HOLDING_START;
static USHORT   usRegHoldingBuf[REG_HOLDING_NREGS];

eMBErrorCode
eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
{
    eMBErrorCode    eStatus = MB_ENOERR;
    int             iRegIndex;

    if( ( usAddress >= REG_INPUT_START )
        && ( usAddress + usNRegs <= REG_INPUT_START + REG_INPUT_NREGS ) )
    {
        iRegIndex = ( int )( usAddress - usRegInputStart );
        while( usNRegs > 0 )
        {
            *pucRegBuffer++ = ( unsigned char )( usRegInputBuf[iRegIndex] >> 8 );
            *pucRegBuffer++ = ( unsigned char )( usRegInputBuf[iRegIndex] & 0xFF );
            iRegIndex++;
            usNRegs--;
        }
    }
    else
    {
        eStatus = MB_ENOREG;
    }
    return eStatus;
}


eMBErrorCode
eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode )
{
    eMBErrorCode    eStatus = MB_ENOERR;
    int             iRegIndex;

    if( ( usAddress >= REG_HOLDING_START ) &&
        ( usAddress + usNRegs <= REG_HOLDING_START + REG_HOLDING_NREGS ) )
    {
        iRegIndex = ( int )( usAddress - usRegHoldingStart );
        switch ( eMode )
        {
            /* Pass current register values to the protocol stack. */
        case MB_REG_READ:
            while( usNRegs > 0 )
            {
                *pucRegBuffer++ = ( UCHAR ) ( usRegHoldingBuf[iRegIndex] >> 8 );
                *pucRegBuffer++ = ( UCHAR ) ( usRegHoldingBuf[iRegIndex] & 0xFF );
                iRegIndex++;
                usNRegs--;
            }
            break;

            /* Update current register values with new values from the
             * protocol stack. */
        case MB_REG_WRITE:
            while( usNRegs > 0 )
            {
                usRegHoldingBuf[iRegIndex] = *pucRegBuffer++ << 8;
                usRegHoldingBuf[iRegIndex] |= *pucRegBuffer++;
                iRegIndex++;
                usNRegs--;
            }
        }
    }
    else
    {
        eStatus = MB_ENOREG;
    }
    return eStatus;
}


eMBErrorCode
eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode )
{
    return MB_ENOREG;
}

eMBErrorCode
eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
    return MB_ENOREG;
}

完成这4个文件的修改后,在初始化网络后加入 freemodbus 的初始化:

eMBTCPInit(502);    //监听端口
eMBEnable();

最后将eMBPoll()加入任务的主循环:

测试

以 1ms 为间隔持续轮询从机,同时持续发送ping操作,长时间测试未发生错误

修改寄存器:

通过 wireshark 抓包查看数据也均正常:

总结

相比 modbus-RTU , modbus-TCP 具有更高的传输速率和更好的稳定性,扩展性也更强,只需要有具体的 IP 地址,可以实现远距离的传输,可玩性很高。但是网上关于 modbus-TCP 在单片机上移植的参考内容还是比较少的,又找到相关的博客写的都不是特别清楚,而且大多使用的是 lwip 协议栈,希望能让看到这篇文章的同学少走弯路。

参考

FreeModbus 说明
安富莱_STM32-V5开发板_RL-TCPnet V7.X网络教程
CMSIS-RTOS2 API

  • 分享:
评论
还没有评论
    发表评论 说点什么