首页 > 其他分享 >基于STM32的ModBus实现(二)移植FreeMODBUS TCP

基于STM32的ModBus实现(二)移植FreeMODBUS TCP

时间:2024-03-28 14:47:36浏览次数:30  
标签:STM32 MB void TCP ModBus pxPCB include define

一、ModBusTCP

  Modbus TCP是一种基于TCP/IP协议的Modbus通信协议的变种。它允许Modbus协议在以太网上进行通信,提供了一种简单而有效的方式来连接不同类型的设备,如传感器、执行器、PLC等。Modbus TCP使用标准的TCP/IP协议栈,因此可以在现有的以太网基础设施上运行,而无需额外的硬件支持。这使得它在工业自动化和物联网应用中非常流行。 Modbus TCP使用客户端-服务器模型,其中客户端发送请求并接收响应,而服务器则响应请求并提供所需的数据。

二、STM32移植ModBusTCP

  移植文件参考上一节的ModBusRTU移植,需要将ModBusRTU的接口文件修改为支持TCP。

  • 硬件:STM32F407ZGT6、DP83848 PHY
  • 软件:FreeRTOS、LwIP2.1.2、FreeModbus、STM32F4标准库

以下为移植的具体流程:

2.1、STM32 + DP83848 实现MAC

  第一步当然打通硬件的连接,通过以太网ETH外设驱动DP83848实现以太网MAC数据链路层的通信。

  具体流程可以参考基于STM32F407MAC与DP83848实现以太网通讯三(STM32F407MAC配置以及数据收发)

2.2、STM32移植LwIP协议栈

  在完成了数据链路层的通信后,在STM32上要实现UDP/TCP协议需要移植LwIP协议栈,我这里使用的为LwIP2.1.2,移植可以参考基于STM32F407MAC与DP83848实现以太网通讯五(裸机移植LwIP协议栈)

  如果是对LwIP协议栈不熟悉的话就需要通过网上的资料学习了:LwIP应用开发实战指南

2.3、将FreeRTOS移植到STM32并支持LwIP

  在完成了STM32的ETH驱动以及移植LwIP协议栈后,还需要移植FreeRTOS,以及在移植FreeRTOS后修改LwIP的相关配置以支持LwIP的NETCONN和SKCOT编程。

2.4、移植FreeModBusTCP

  上述的移植工作还是比较花时间的,可以在网上找一下模板,或者支持自己开发板的相关例程,只要能够正常连接电脑Ping通就可以。

  在完成了上述工作后就可以移植ModBusTCP了,我是在我自己移植好的LwIP和FreeRTOS的程序上先实现ModBusRTU,确保ModBus除了接口这一块都没问题,参考上一节:基于STM32的ModBus实现(一)移植FreeMODBUS RTU

2.4.1、FreeModBusTCP的Port文件

  在FreeMODBUSV1.6文件中我们找到Demo/MCF5235TCP/port,这个里面存放了MCF5235的LwIP的FreeModBusTCP接口文件,尽管使用到的硬件不一样但是LwIP的TCP层是不基于硬件的,所以可以直接拿过来修改并使用,以下是port文件夹中的文件,我们直接添加到工程,如果是基于之前的ModBusRTU的程序的话,就需要把RTU相关的接口文件从工程删除。

  还需要调整使用的ModBus源文件文件,ModBus ASCII/RTU相关的.c文件就不需要了,将mbtcp.c添加到工程中,还有porttcp.c对应的接口文件port.h文件的路径添加到工程。

01-FreeModBusfile.png

06-inlcude.png

  • porttcp.c: ModBusTCP接口文件
  • portother.c: 实现了打印日志的功能
  • portevent.c: LwIP邮箱机制的操作

2.4.2 porttcp.c

  porttcp.c中的TCP 是由RAW的方式实现的

xMBTCPPortInit(): 创建TCP控制块、绑定IP以及注册回调函数
prvvMBPortReleaseClient(): 关闭TCP链接由vMBTCPPortClose调用
vMBTCPPortClose(): 关闭TCP链接
prvxMBTCPPortAccept(): TCP接收回调函数
prvxMBTCPPortReceive(): 底层接收数据由prvxMBTCPPortAccept调用
xMBTCPPortSendResponse(): ModTCP发送数据

porttcp.c代码如下

portserial.c
/*
 * FreeModbus Libary: lwIP Port
 * Copyright (C) 2006 Christian Walter <[email protected]>
 *
 * 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$
 */

/* ----------------------- System includes ----------------------------------*/
#include <stdio.h>
#include <string.h>

#include "port.h"

/* ----------------------- lwIP includes ------------------------------------*/
#include "lwip/api.h"
#include "lwip/tcp.h"

/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"

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

/* ----------------------- 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 );
void            vMBPortLog( eMBPortLogLevel eLevel, const CHAR * szModule,
                            const CHAR * szFmt, ... );

/* ----------------------- Static variables ---------------------------------*/
static struct tcp_pcb *pxPCBListen;
static struct tcp_pcb *pxPCBClient;

static UCHAR    aucTCPBuf[MB_TCP_BUF_SIZE];
static USHORT   usTCPBufPos;

/* ----------------------- Static functions ---------------------------------*/
static err_t    prvxMBTCPPortAccept( void *pvArg, struct tcp_pcb *pxPCB, err_t xErr );
static err_t    prvxMBTCPPortReceive( void *pvArg, struct tcp_pcb *pxPCB, struct pbuf *p,
                                      err_t xErr );
static void     prvvMBTCPPortError( void *pvArg, err_t xErr );

/* ----------------------- Begin implementation -----------------------------*/
BOOL
xMBTCPPortInit( USHORT usTCPPort )
{
    struct tcp_pcb *pxPCBListenNew, *pxPCBListenOld;
    BOOL            bOkay = FALSE;
    USHORT          usPort;

    if( usTCPPort == 0 )
    {
        usPort = MB_TCP_DEFAULT_PORT;
    }
    else
    {
        usPort = ( USHORT ) usTCPPort;
    }

    if( ( pxPCBListenNew = pxPCBListenOld = tcp_new(  ) ) == NULL )
    {
        /* Can't create TCP socket. */
        bOkay = FALSE;
    }
    else if( tcp_bind( pxPCBListenNew, IP_ADDR_ANY, ( u16_t ) usPort ) != ERR_OK )
    {
        /* Bind failed - Maybe illegal port value or in use. */
        ( void )tcp_close( pxPCBListenOld );
        bOkay = FALSE;
    }
    else if( ( pxPCBListenNew = tcp_listen( pxPCBListenNew ) ) == NULL )
    {
        ( void )tcp_close( pxPCBListenOld );
        bOkay = FALSE;
    }
    else
    {
        /* Register callback function for new clients. */
        tcp_accept( pxPCBListenNew, prvxMBTCPPortAccept );

        /* Everything okay. Set global variable. */
        pxPCBListen = pxPCBListenNew;

#ifdef MB_TCP_DEBUG
        vMBPortLog( MB_LOG_DEBUG, "MBTCP-ACCEPT", "Protocol stack ready.\r\n" );
#endif
    }
    bOkay = TRUE;
    return bOkay;
}

void
prvvMBPortReleaseClient( struct tcp_pcb *pxPCB )
{
    if( pxPCB != NULL )
    {
        if( tcp_close( pxPCB ) != ERR_OK )
        {
            tcp_abort( pxPCB );
        }
        if( pxPCB == pxPCBClient )
        {
#ifdef MB_TCP_DEBUG
            vMBPortLog( MB_LOG_DEBUG, "MBTCP-CLOSE", "Closed connection to %d.%d.%d.%d.\r\n",
                        ip4_addr1( &( pxPCB->remote_ip ) ),
                        ip4_addr2( &( pxPCB->remote_ip ) ),
                        ip4_addr3( &( pxPCB->remote_ip ) ), ip4_addr4( &( pxPCB->remote_ip ) ) );
#endif
            pxPCBClient = NULL;
        }
        if( pxPCB == pxPCBListen )
        {
            pxPCBListen = NULL;
        }
    }
}
void
vMBTCPPortClose(  )
{
    /* Shutdown any open client sockets. */
    prvvMBPortReleaseClient( pxPCBClient );

    /* Shutdown or listening socket. */
    prvvMBPortReleaseClient( pxPCBListen );

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

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

err_t
prvxMBTCPPortAccept( void *pvArg, struct tcp_pcb *pxPCB, err_t xErr )
{
    err_t           error;

    if( xErr != ERR_OK )
    {
        return xErr;
    }

    /* We can handle only one client. */
    if( pxPCBClient == NULL )
    {
        /* Register the client. */
        pxPCBClient = pxPCB;

        /* Set up the receive function prvxMBTCPPortReceive( ) to be called when data
         * arrives.
         */
        tcp_recv( pxPCB, prvxMBTCPPortReceive );

        /* Register error handler. */
        tcp_err( pxPCB, prvvMBTCPPortError );

        /* Set callback argument later used in the error handler. */
        tcp_arg( pxPCB, pxPCB );

        /* Reset the buffers and state variables. */
        usTCPBufPos = 0;

#ifdef MB_TCP_DEBUG
        vMBPortLog( MB_LOG_DEBUG, "MBTCP-ACCEPT", "Accepted new client %d.%d.%d.%d\r\n",
                    ip4_addr1( &( pxPCB->remote_ip ) ),
                    ip4_addr2( &( pxPCB->remote_ip ) ),
                    ip4_addr3( &( pxPCB->remote_ip ) ), ip4_addr4( &( pxPCB->remote_ip ) ) );
#endif

        error = ERR_OK;
    }
    else
    {
        prvvMBPortReleaseClient( pxPCB );
        error = ERR_OK;
    }
    return error;
}

/* Called in case of an unrecoverable error. In any case we drop the client
 * connection. */
void
prvvMBTCPPortError( void *pvArg, err_t xErr )
{
    struct tcp_pcb *pxPCB = pvArg;

    if( pxPCB != NULL )
    {
#ifdef MB_TCP_DEBUG
        vMBPortLog( MB_LOG_DEBUG, "MBTCP-ERROR", "Error with client connection! Droping it.\r\n" );
#endif
        prvvMBPortReleaseClient( pxPCB );
    }
}

err_t
prvxMBTCPPortReceive( void *pvArg, struct tcp_pcb *pxPCB, struct pbuf *p, err_t xErr )
{
    USHORT          usLength;

    err_t           error;

    if( xErr != ERR_OK )
    {
        return xErr;
    }

    /* If pbuf is NULL then remote end has closed connection. */
    if( p == NULL )
    {
        prvvMBPortReleaseClient( pxPCB );
        return ERR_OK;
    }

    /* Acknowledge that we have received the data bytes. */
    tcp_recved( pxPCB, p->len );

    /* Check for internal buffer overflow. In case of an error drop the
     * client. */
    if( ( usTCPBufPos + p->len ) >= MB_TCP_BUF_SIZE )
    {
        prvvMBPortReleaseClient( pxPCB );
        error = ERR_OK;
    }
    else
    {
        memcpy( &aucTCPBuf[usTCPBufPos], p->payload, p->len );
        usTCPBufPos += p->len;

        /* If we have received the MBAP header we can analyze it and calculate
         * the number of bytes left to complete the current request. If complete
         * notify the protocol stack.
         */
        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( pxPCB );
            }
        }
    }
    pbuf_free( p );
    return error;
}

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;

    if( pxPCBClient )
    {
        /* Make sure we can send the packet. */
        assert( tcp_sndbuf( pxPCBClient ) >= usTCPLength );

        if( tcp_write( pxPCBClient, pucMBTCPFrame, ( u16_t ) usTCPLength, NETCONN_COPY ) == ERR_OK )
        {
#ifdef MB_TCP_DEBUG
            prvvMBTCPLogFrame( "MBTCP-SENT", &aucTCPBuf[0], usTCPLength );
#endif
            /* Make sure data gets sent immediately. */
            ( void )tcp_output( pxPCBClient );
            bFrameSent = TRUE;
        }
        else
        {
            /* Drop the connection in case of an write error. */
            prvvMBPortReleaseClient( pxPCBClient );
        }
    }
    return bFrameSent;
}

2.4.3 portevent.c

  portevent.c则主要是使用了LwIP的邮箱机制。

xMBPortEventInit(): 创建ModBusTCP邮箱
vMBPortEventClose(): 删除ModBusTCP邮箱
xMBPortEventPost(): 向ModBusTCP邮箱发送消息
xMBPortEventGet(): 从ModBusTCP邮箱获取消息

  我的LwIP协议栈的支持OS的邮箱机制实现是按照野火的教程实现的,所以在创建邮箱以及使用有一点区别,我的sys_mbox_new()代码实现如下(具体邮箱机制代码参考8.3. sys_arch.c/h文件的编写

/*---------------------------------------------------------------------------*
 * Routine:  sys_mbox_new
 *---------------------------------------------------------------------------*
 * Description:
 *      Creates a new mailbox
 * Inputs:
 *      int size                -- Size of elements in the mailbox
 * Outputs:
 *      sys_mbox_t              -- Handle to new mailbox
 *---------------------------------------------------------------------------*/
err_t sys_mbox_new( sys_mbox_t *pxMailBox, int iSize )
{
err_t xReturn = ERR_MEM;

	*pxMailBox = xQueueCreate( iSize, sizeof( void * ) );

	if( *pxMailBox != NULL )
	{
		xReturn = ERR_OK;
		SYS_STATS_INC_USED( mbox );
	}

	return xReturn;
}

  所以需要修改一下sys_mbox_new()sys_mbox_t的使用,修改如下

...
sys_mbox_new( &xMailBox, 10 );
...
sys_mbox_free( &xMailBox );
...
sys_mbox_post( &xMailBox, &eMailBoxEvent );
...
uiTimeSpent = sys_arch_mbox_fetch( &xMailBox, &peMailBoxEvent, MB_POLL_CYCLETIME );

完整的portevent.c代码如下

portevent.c
/*
 * FreeModbus Libary: lwIP Port
 * Copyright (C) 2006 Christian Walter <[email protected]>
 *
 * 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$
 */

/* ----------------------- System includes ----------------------------------*/
#include "assert.h"

/* ----------------------- lwIP ---------------------------------------------*/
#include "lwip/api.h"
#include "lwip/sys.h"

/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"

/* ----------------------- Defines ------------------------------------------*/
#define MB_POLL_CYCLETIME       100     /* Poll cycle time is 100ms */
/* ----------------------- Static variables ---------------------------------*/
static sys_mbox_t xMailBox = SYS_MBOX_NULL;
static eMBEventType eMailBoxEvent;

/* ----------------------- Start implementation -----------------------------*/
BOOL
xMBPortEventInit( void )
{
    eMailBoxEvent = EV_READY;
    sys_mbox_new( &xMailBox, 10 );
    return xMailBox != SYS_MBOX_NULL ? TRUE : FALSE;
}

void
vMBPortEventClose( void )
{
    if( xMailBox != SYS_MBOX_NULL )
    {
        sys_mbox_free( &xMailBox );
    }
}

BOOL
xMBPortEventPost( eMBEventType eEvent )
{
    eMailBoxEvent = eEvent;
    sys_mbox_post( &xMailBox, &eMailBoxEvent );
    return TRUE;
}

BOOL
xMBPortEventGet( eMBEventType * eEvent )
{
    void           *peMailBoxEvent;
    BOOL            xEventHappend = FALSE;
    u32_t           uiTimeSpent;

    uiTimeSpent = sys_arch_mbox_fetch( &xMailBox, &peMailBoxEvent, MB_POLL_CYCLETIME );
    if( uiTimeSpent != SYS_ARCH_TIMEOUT )
    {
        *eEvent = *( eMBEventType * ) peMailBoxEvent;
        eMailBoxEvent = EV_READY;
        xEventHappend = TRUE;
    }
    return xEventHappend;
}

2.4.4 portevent.c

  portevent.c文件实现了ModBusTCP的DEBUG,使能DEBUG就可以将log、Error信息打印出来方便调试(需要实现fputc函数)

  这个文件不需要修改,只需要删除不必要的头文件就行了,删除了以下内容。

~~~
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
~~~
portother.c
/*
 * FreeModbus Libary: lwIP Port
 * Copyright (C) 2006 Christian Walter <[email protected]>
 *
 * 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$
 */

/* ----------------------- System includes ----------------------------------*/
#include <stdio.h>
#include <stdarg.h>
#include <string.h>

#include "port.h"

/* ----------------------- Defines ------------------------------------------*/
#define MB_FRAME_LOG_BUFSIZE    512

/* ----------------------- Start implementation -----------------------------*/

#ifdef MB_TCP_DEBUG
void
prvvMBTCPLogFrame( UCHAR * pucMsg, UCHAR * pucFrame, USHORT usFrameLen )
{
    int             i;
    int             res;
    int             iBufPos = 0;
    size_t          iBufLeft = MB_FRAME_LOG_BUFSIZE;
    static CHAR     arcBuffer[MB_FRAME_LOG_BUFSIZE];

    assert( pucFrame != NULL );

    for( i = 0; i < usFrameLen; i++ )
    {
        /* Print some additional frame information. */
        switch ( i )
        {
        case 0:
            /* TID = Transaction Identifier. */
            res = snprintf( &arcBuffer[iBufPos], iBufLeft, "| TID = " );
            break;
        case 2:
            /* PID = Protocol Identifier. */
            res = snprintf( &arcBuffer[iBufPos], iBufLeft, " | PID = " );
            break;
        case 4:
            /* Length */
            res = snprintf( &arcBuffer[iBufPos], iBufLeft, " | LEN = " );
            break;
        case 6:
            /* UID = Unit Identifier. */
            res = snprintf( &arcBuffer[iBufPos], iBufLeft, " | UID = " );
            break;
        case 7:
            /* MB Function Code. */
            res = snprintf( &arcBuffer[iBufPos], iBufLeft, "|| FUNC = " );
            break;
        case 8:
            /* MB PDU rest. */
            res = snprintf( &arcBuffer[iBufPos], iBufLeft, " | DATA = " );
            break;
        default:
            res = 0;
            break;
        }
        if( res == -1 )
        {
            break;
        }
        else
        {
            iBufPos += res;
            iBufLeft -= res;
        }

        /* Print the data. */
        res = snprintf( &arcBuffer[iBufPos], iBufLeft, "%02X", pucFrame[i] );
        if( res == -1 )
        {
            break;
        }
        else
        {
            iBufPos += res;
            iBufLeft -= res;
        }
    }

    if( res != -1 )
    {
        /* Append an end of frame string. */
        res = snprintf( &arcBuffer[iBufPos], iBufLeft, " |\r\n" );
        if( res != -1 )
        {
            vMBPortLog( MB_LOG_DEBUG, pucMsg, "%s", arcBuffer );
        }
    }
}
#endif

#ifdef MB_TCP_DEBUG
void
vMBPortLog( eMBPortLogLevel eLevel, const CHAR * szModule, const CHAR * szFmt, ... )
{
    va_list         args;
    static const char *arszLevel2Str[] = { "DEBUG", "INFO", "WARN", "ERROR" };

    ( void )printf( "%s: %s: ", arszLevel2Str[eLevel], szModule );
    va_start( args, szFmt );
    vprintf( szFmt, args );
    va_end( args );
}
#endif

2.4.5 port.h

  由于我们要使用到OS + TCP,因此需要实现临界段的操作需要在port.h中实现ENTER_CRITICAL_SECTION()EXIT_CRITICAL_SECTION(),我们要使用DEBUG所以还要声明相关函数,添加的代码如下

···
#include "FreeRTOS.h"
#include "task.h"
···
#define ENTER_CRITICAL_SECTION() 	taskENTER_CRITICAL();  
#define EXIT_CRITICAL_SECTION()		taskEXIT_CRITICAL();
···
void
prvvMBTCPLogFrame( UCHAR * pucMsg, UCHAR * pucFrame, USHORT usFrameLen );

完整的port.h文件如下

prot.h
 /*
  * FreeModbus Libary: MCF5235 Port
  * Copyright (C) 2006 Christian Walter <[email protected]>
  * Parts of crt0.S Copyright (c) 1995, 1996, 1998 Cygnus Support
  *
  * 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$
  */

#ifndef _PORT_H
#define _PORT_H

#include <assert.h>

#include "FreeRTOS.h"
#include "task.h"
/* ----------------------- Defines ------------------------------------------*/

#define INLINE                  inline
#define PR_BEGIN_EXTERN_C       extern "C" {
#define PR_END_EXTERN_C         }

#ifndef TRUE
#define TRUE                    1
#endif

#ifndef FALSE
#define FALSE                   0
#endif

#ifdef __cplusplus
PR_BEGIN_EXTERN_C
#endif
#define MB_TCP_DEBUG            1       /* Debug output in TCP module. */
/* ----------------------- Type definitions ---------------------------------*/
typedef char    BOOL;

typedef unsigned char UCHAR;
typedef char    CHAR;

typedef unsigned short USHORT;
typedef short   SHORT;

typedef unsigned long ULONG;
typedef long    LONG;

#ifdef MB_TCP_DEBUG
typedef enum
{
    MB_LOG_DEBUG,
    MB_LOG_INFO,
    MB_LOG_WARN,
    MB_LOG_ERROR
} eMBPortLogLevel;
#endif

#define ENTER_CRITICAL_SECTION() 	taskENTER_CRITICAL();  
#define EXIT_CRITICAL_SECTION()		taskEXIT_CRITICAL();

/* ----------------------- Function prototypes ------------------------------*/
#ifdef MB_TCP_DEBUG
void            vMBPortLog( eMBPortLogLevel eLevel, const CHAR * szModule,
                            const CHAR * szFmt, ... );
void
prvvMBTCPLogFrame( UCHAR * pucMsg, UCHAR * pucFrame, USHORT usFrameLen );
#endif

#ifdef __cplusplus
PR_END_EXTERN_C
#endif
#endif

2.5、FreeModBusTCP线程创建

  以上程序不报错的话准备工作就完成了,接下来的是创建TCP线程启动ModBusTCP了,启动ModBusTCP的代码参考Dome/MCF5235TCP/demo.c文件

  vMBServerTask()为demo文件中的ModBus任务,参考了这个函数我们创建一个线程,代码如下

···
void
modbustcp_init(void);
···
static void modbustcp_thread(void *arg)
{	
	LWIP_UNUSED_ARG(arg);
	eMBErrorCode    xStatus;
	for( ;; )
	{
		if( eMBTCPInit( MB_TCP_PORT_USE_DEFAULT ) != MB_ENOERR )
		{
			printf("%s: can't initialize modbus stack!\r\n", PROG );
		}
		else if( eMBEnable(  ) != MB_ENOERR )
		{
			printf("%s: can't enable modbus stack!\r\n", PROG );
		}
		else
		{
			do
			{
				xStatus = eMBPoll(  );
			}
			while( xStatus == MB_ENOERR );
		}
		/* An error occured. Maybe we can restart. */
		( void )eMBDisable(  );
		( void )eMBClose(  );
	}
}

void
modbustcp_init(void)
{
  sys_thread_new("modbustcp_thread", modbustcp_thread, NULL, 512, 4);
}

  到此所有的需要添加和修改的地方都已经完成,完整的main.c文件如下

main.c
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "timer.h"
#include "lwip/timeouts.h"
#include "stm32f4x7_eth.h"
#include "DP83848.h"

/*lwip app*/
#include "lwip_comm.h"
#include "tcpecho.h"

/*FreeModBus*/
#include "mb.h"
#include "mbutils.h"

#if SYSTEM_SUPPORT_OS
#include "FreeRTOS.h"
#include "task.h"
#endif /*SYSTEM_SUPPORT_OS*/

/* ----------------------- Defines ------------------------------------------*/
#define PROG                    "FreeModbus"



//输入寄存器起始地址
#define REG_INPUT_START       0x0000
//输入寄存器数量
#define REG_INPUT_NREGS       8
//保持寄存器起始地址
#define REG_HOLDING_START     0x0000
//保持寄存器数量
#define REG_HOLDING_NREGS     8

//线圈起始地址
#define REG_COILS_START       0x0000
//线圈数量
#define REG_COILS_SIZE        16

//开关寄存器起始地址
#define REG_DISCRETE_START    0x0000
//开关寄存器数量
#define REG_DISCRETE_SIZE     16

/* Private variables ---------------------------------------------------------*/
//输入寄存器内容
uint16_t usRegInputBuf[REG_INPUT_NREGS] = {0x1000,0x1001,0x1002,0x1003,0x1004,0x1005,0x1006,0x1007};
//输入寄存器起始地址
uint16_t usRegInputStart = REG_INPUT_START;

//保持寄存器内容
uint16_t usRegHoldingBuf[REG_HOLDING_NREGS] = {0x147b,0x3f8e,0x147b,0x400e,0x1eb8,0x4055,0x147b,0x408e};
//保持寄存器起始地址
uint16_t usRegHoldingStart = REG_HOLDING_START;

//线圈状态
uint8_t ucRegCoilsBuf[REG_COILS_SIZE / 8] = {0x0f,0x02};
//开关输入状态
uint8_t ucRegDiscreteBuf[REG_DISCRETE_SIZE / 8] = {0x0f,0x02};



/*开始任务*/
TaskHandle_t StartTask_Handler;
void start_task(void *pvParameters);
/*LED任务*/
TaskHandle_t LED0Task_Handler;
void led0_task(void *pvParameters);

/**************************FreeModBus******************************/
// /*ModBus任务*/
// TaskHandle_t ModBus_TASK_STK_Handler;
// void ModBus_task(void *pvParameters);
/*ModBus输入任务*/
TaskHandle_t ModBus_Input_TASK_Handler;
void ModBus_Input_task(void *pvParameters);

void
modbustcp_init(void);
/******************************************************************/


#if SYSTEM_SUPPORT_OS
int main(void)
{
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); // 中断分组配置
    delay_init(168);                                // 初始化延时函数
	LED_Init();   
    uart_init();                              		// 初始化串口
//	printf("Err : %d\n",eMBInit(MB_RTU, 0x01, 0x01, 9600, MB_PAR_NONE));//初始化freemodbus 设置RTU模式和ID等
//	eMBEnable();

                                  // 初始化LED端口
//	TIM3_Int_Init(999, 839);
	/*创建开始任务*/
    xTaskCreate((TaskFunction_t )start_task,            //任务函数
                (const char*    )"start_task",          //任务名称
                (uint16_t       )256,       			//任务堆栈大小
                (void*          )NULL,                  //传递给任务函数的参数
                (UBaseType_t    )1,       				//任务优先级
                (TaskHandle_t*  )&StartTask_Handler);   //任务句柄   
	/*开启任务调度*/
	vTaskStartScheduler();
    while (1)
    {
//		sys_check_timeouts();
    }
}

/*开始任务任务函数*/
void start_task(void *pvParameters)
{
	lwip_comm_init();
//	tcpecho_init();
	modbustcp_init();
    taskENTER_CRITICAL();           //进入临界区
    //创建LED0任务
    xTaskCreate((TaskFunction_t )led0_task,     	
                (const char*    )"led0_task",   	
                (uint16_t       )64, 
                (void*          )NULL,				
                (UBaseType_t    )2,	
                (TaskHandle_t*  )&LED0Task_Handler);      
	// //ModBus任务
  //   xTaskCreate((TaskFunction_t )ModBus_task,     	
  //               (const char*    )"modbus_task",   	
  //               (uint16_t       )256, 
  //               (void*          )NULL,				
  //               (UBaseType_t    )3,	
  //               (TaskHandle_t*  )&ModBus_TASK_STK_Handler);  
	//ModBus输入任务
    xTaskCreate((TaskFunction_t )ModBus_Input_task,     	
                (const char*    )"modbus_input_task",   	
                (uint16_t       )256, 
                (void*          )NULL,				
                (UBaseType_t    )4,	
                (TaskHandle_t*  )&ModBus_Input_TASK_Handler);  
    vTaskDelete(StartTask_Handler); //删除开始任务
    taskEXIT_CRITICAL();            //退出临界区
}

/*LED0任务函数*/
void led0_task(void *pvParameters)
{
    while(1)
    {
        LED0=~LED0;
        vTaskDelay(500);
    }
}

// //ModBus任务
// void ModBus_task(void *pdata)
// {
// 	while(1)
// 	{
// 		eMBPoll();
// 		vTaskDelay(10);
		
// 	}	
// }
//ModBus输入寄存器任务
void ModBus_Input_task(void *pdata)
{
	while(1)
	{
		if(usRegInputBuf[0] > 0x1050)
		{
			usRegInputBuf[0] = 0x1000;
		}
		else
			usRegInputBuf[0] = usRegInputBuf[0] + (u16)1;
		vTaskDelay(1000);
		
	}	
}

static void modbustcp_thread(void *arg)
{	
	LWIP_UNUSED_ARG(arg);
	eMBErrorCode    xStatus;
	for( ;; )
	{
		if( eMBTCPInit( MB_TCP_PORT_USE_DEFAULT ) != MB_ENOERR )
		{
			printf("%s: can't initialize modbus stack!\r\n", PROG );
		}
		else if( eMBEnable(  ) != MB_ENOERR )
		{
			printf("%s: can't enable modbus stack!\r\n", PROG );
		}
		else
		{
			do
			{
				xStatus = eMBPoll(  );
			}
			while( xStatus == MB_ENOERR );
		}
		/* An error occured. Maybe we can restart. */
		( void )eMBDisable(  );
		( void )eMBClose(  );
	}
}

void
modbustcp_init(void)
{
  sys_thread_new("modbustcp_thread", modbustcp_thread, NULL, 512, 4);
}


/****************************************************************************
* 名	  称:eMBRegInputCB 
* 功    能:读取输入寄存器,对应功能码是 04 eMBFuncReadInputRegister
* 入口参数:pucRegBuffer: 数据缓存区,用于响应主机   
*						usAddress: 寄存器地址
*						usNRegs: 要读取的寄存器个数
* 出口参数:
* 注	  意:上位机发来的 帧格式是: SlaveAddr(1 Byte)+FuncCode(1 Byte)
*								+StartAddrHiByte(1 Byte)+StartAddrLoByte(1 Byte)
*								+LenAddrHiByte(1 Byte)+LenAddrLoByte(1 Byte)+
*								+CRCAddrHiByte(1 Byte)+CRCAddrLoByte(1 Byte)
*							3 区
****************************************************************************/
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++ = ( UCHAR )( usRegInputBuf[iRegIndex] >> 8 );
            *pucRegBuffer++ = ( UCHAR )( usRegInputBuf[iRegIndex] & 0xFF );
            iRegIndex++;
            usNRegs--;
        }
    }
    else
    {
        eStatus = MB_ENOREG;
    }

    return eStatus;
}

/****************************************************************************
* 名	  称:eMBRegHoldingCB 
* 功    能:对应功能码有:06 写保持寄存器 eMBFuncWriteHoldingRegister 
*													16 写多个保持寄存器 eMBFuncWriteMultipleHoldingRegister
*													03 读保持寄存器 eMBFuncReadHoldingRegister
*													23 读写多个保持寄存器 eMBFuncReadWriteMultipleHoldingRegister
* 入口参数:pucRegBuffer: 数据缓存区,用于响应主机   
*						usAddress: 寄存器地址
*						usNRegs: 要读写的寄存器个数
*						eMode: 功能码
* 出口参数:
* 注	  意:4 区
****************************************************************************/
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)
		{                                       
			case MB_REG_READ://读 MB_REG_READ = 0
        while(usNRegs > 0)
				{
					*pucRegBuffer++ = (u8)(usRegHoldingBuf[iRegIndex] >> 8);            
					*pucRegBuffer++ = (u8)(usRegHoldingBuf[iRegIndex] & 0xFF); 
          iRegIndex++;
          usNRegs--;					
				}                            
        break;
			case MB_REG_WRITE://写 MB_REG_WRITE = 0
				while(usNRegs > 0)
				{         
					usRegHoldingBuf[iRegIndex] = *pucRegBuffer++ << 8;
          usRegHoldingBuf[iRegIndex] |= *pucRegBuffer++;
          iRegIndex++;
          usNRegs--;
        }				
			}
	}
	else//错误
	{
		eStatus = MB_ENOREG;
	}	
	
	return eStatus;
}

extern void xMBUtilSetBits( UCHAR * ucByteBuf, USHORT usBitOffset, UCHAR ucNBits,
                UCHAR ucValue );
extern UCHAR xMBUtilGetBits( UCHAR * ucByteBuf, USHORT usBitOffset, UCHAR ucNBits );
/****************************************************************************
* 名	  称:eMBRegCoilsCB 
* 功    能:对应功能码有:01 读线圈 eMBFuncReadCoils
*													05 写线圈 eMBFuncWriteCoil
*													15 写多个线圈 eMBFuncWriteMultipleCoils
* 入口参数:pucRegBuffer: 数据缓存区,用于响应主机   
*						usAddress: 线圈地址
*						usNCoils: 要读写的线圈个数
*						eMode: 功能码
* 出口参数:
* 注	  意:如继电器 
*						0 区
****************************************************************************/
eMBErrorCode
eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils,
              eMBRegisterMode eMode )
{
  //错误状态
  eMBErrorCode eStatus = MB_ENOERR;
  //寄存器个数
  int16_t iNCoils = ( int16_t )usNCoils;
  //寄存器偏移量
  int16_t usBitOffset;
  
  //检查寄存器是否在指定范围内
  if( ( (int16_t)usAddress >= REG_COILS_START ) &&
     ( usAddress + usNCoils <= REG_COILS_START + REG_COILS_SIZE ) )
  {
    //计算寄存器偏移量
    usBitOffset = ( int16_t )( usAddress - REG_COILS_START );
    switch ( eMode )
    {
      //读操作
    case MB_REG_READ:
      while( iNCoils > 0 )
      {
        *pucRegBuffer++ = xMBUtilGetBits( ucRegCoilsBuf, usBitOffset,
                                         ( uint8_t )( iNCoils > 8 ? 8 : iNCoils ) );
        iNCoils -= 8;
        usBitOffset += 8;
      }
      break;
      
      //写操作
    case MB_REG_WRITE:
      while( iNCoils > 0 )
      {
        xMBUtilSetBits( ucRegCoilsBuf, usBitOffset,
                       ( uint8_t )( iNCoils > 8 ? 8 : iNCoils ),
                       *pucRegBuffer++ );
        iNCoils -= 8;
      }
      break;
    }
    
  }
  else
  {
    eStatus = MB_ENOREG;
  }
  return eStatus;
}

/****************************************************************************
* 名	  称:eMBRegDiscreteCB 
* 功    能:读取离散寄存器,对应功能码有:02 读离散寄存器 eMBFuncReadDiscreteInputs
* 入口参数:pucRegBuffer: 数据缓存区,用于响应主机   
*						usAddress: 寄存器地址
*						usNDiscrete: 要读取的寄存器个数
* 出口参数:
* 注	  意:1 区
****************************************************************************/
eMBErrorCode
eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
  //错误状态
  eMBErrorCode eStatus = MB_ENOERR;
  //操作寄存器个数
  int16_t iNDiscrete = ( int16_t )usNDiscrete;
  //偏移量
  uint16_t usBitOffset;
  
  //判断寄存器时候再制定范围内
  if( ( (int16_t)usAddress >= REG_DISCRETE_START ) &&
     ( usAddress + usNDiscrete <= REG_DISCRETE_START + REG_DISCRETE_SIZE ) )
  {
    //获得偏移量
    usBitOffset = ( uint16_t )( usAddress - REG_DISCRETE_START );
    
    while( iNDiscrete > 0 )
    {
      *pucRegBuffer++ = xMBUtilGetBits( ucRegDiscreteBuf, usBitOffset,
                                       ( uint8_t)( iNDiscrete > 8 ? 8 : iNDiscrete ) );
      iNDiscrete -= 8;
      usBitOffset += 8;
    }
    
  }
  else
  {
    eStatus = MB_ENOREG;
  }
  return eStatus;
}

/*不使用OS*/
#elif !SYSTEM_SUPPORT_OS
int main(void)
{
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); // 中断分组配置
    delay_init(168);                                // 初始化延时函数
    uart_init();                              // 初始化串口
    LED_Init();                                     // 初始化LED端口
	TIM3_Int_Init(999, 839);
	lwip_comm_init();
	printf("PHY_SR:0x%04x\n",ETH_ReadPHYRegister(0x01, PHY_SR));
    while (1)
    {
		iperf();
		sys_check_timeouts();
    }
}
#endif /* SYSTEM_SUPPORT_OS */



三、ModBusTCP测试

  在程序编译完成无报错,下载程序到开发板,使用网线连接开发板与上位机(电脑),打开ModBusPoll软件,点击connection配置如下。

  • IP Address: LwIP初始化和ModBusTCP绑定的IP
  • Sever Port: ModBusTCP默认的端口号,在porttcp.c中配置
  • 其他默认

02-modbuspollset.png

  选择我们需要读取的功能码与对应的寄存器数量

03-modbusdefinition.png

  选择完成成功读取到数据,串口有相应的DEBUG返回,ModBUSTCP的基本测试完成。

04-modbusout.png

四、总结

  移植ModBusTCP比较简单,但是前提是需要掌握LwIP协议栈的使用。之后将会探讨FreeModBus实现ModBusRTU/TCP和ModBus协议的流程。

标签:STM32,MB,void,TCP,ModBus,pxPCB,include,define
From: https://www.cnblogs.com/fuyunxiansen/p/18101628

相关文章

  • HCIA——三、TCP四次挥手及其wireshark抓包
    ZYHCIA所有内容:TCPTCP四次挥手全过程第一次挥手第二次挥手第三次挥手第四次挥手补充wireshark抓包工具1、搭建拓扑图2、手配ip地址与子网掩码3、服务器启动HTTP服务4、客户端访问HTTP服务器全过程5、TCP数据包数据包信息第一行以及它所包含的信息:数据包信息第二行以及它......
  • TCP与UDP:传输层协议对比
    ......
  • 如何解决Modbus转Profinet网关通信不稳定或数据丢失问题
    接到现场反映,在配置Modbus转Profinet网关时,出现Modbus转Profinet网关(XD-MDPN100)通信不稳定或数据丢失的问题,就这个问题特做出答疑。解决Modbus转Profinet网关(XD-MDPN100)通信不稳定或数据丢失的问题可以从以下几个方面着手:1、检查物理连接与接口:确保所有电缆、接口和连接器都连......
  • 基于STM32的ModBus实现(一)移植FreeMODBUS RTU
    一、FreeMODBUSFreeModbus是一个开源的Modbus通信协议栈实现。它允许开发者在各种平台上轻松地实现Modbus通信功能,包括串口和以太网。FreeMODBUS提供了用于从设备和主站通信的功能,支持ModbusRTU和ModbusTCP协议。在工业控制和自动化领域广泛应用。FreeModBus可通过官......
  • Profinet转ModbusTCP:从站设备转换与集成案例
    本案例旨在探讨如何将ModbusTCP设备数据成功地接入到西门子PROFINET网络中。为了实现这一目标,我们将使用西门子S7-1200型PLC以及Profinet转ModbusTCP网关作为关键设备。为了模拟Modbus从站,我们将使用电脑安装modbuspoll软件。首先需要了解Profinet和ModbusTCP这两种协议的基本概......
  • Proteus8.0仿真应用设计(十七)基于FreeRTOS、STM32F103C8、HAL库、DHT11、LCD12864的温
    一、简介:        DHT11是一款湿、温度一体化的数字传感器。该传感器包括一个电阻式测湿元件和一个NTC测温元件。DHT11与单片机之间能采用简单的单总线进行通信,仅仅需要一个I/O口。通过单片机等微处理器简单的电路连接就能够实时的采集本地湿度和温度。传感器内部......
  • stm32串口使用dma接收数据全为0发送正常
    cubemx版本:keil版本:当使用cubeMX生成代码时,需要调整dma初始化和串口初始化的顺序,在3处那里调整,不然串口接收的数据全是0,未知原因,只找到办法......
  • Qt QTcpSocket 对连接服务器中断的不同情况进行判定
    简述对于一个C/S结构的程序,客户端有些时候需要实时得知与服务器的连接状态。而对于客户端与服务器断开连接的因素很多,现在就目前遇到的情况进行一下总结。分为下面六种不同情况   客户端网线断开   客户端网络断开   客户端通过HTTP代理连接服务器,代理机器断开代......
  • Linux命令:tcpdump - 网络分析
    tcpdump是一个功能强大的命令行网络协议分析器。主要功能:数据包捕获(抓包)数据包过滤数据分析网络故障排除和诊断常用选项-i:指定要监听的网络接口-D:列出可用于抓包的接口-s:设置抓取的数据包长度,超过这个长度的部分会被截断-c:指定要抓取的数据包的数量-w:将抓包数据保存......
  • Profinet转Modbus网关的调试与故障排除教程
    Profinet转Modbus网关(XD-MDPN100)带有网口和串口很大限度地解决了设备接口不统一的问题,支持485和232,可以实现从Modbus通信协议到Profinet通信协议的无缝转换,为不同协议之间的互联互通提供了便利。Profinet转Modbus网关(XD-MDPN100)的调试与故障排除教程通常涉及一系列步骤来确保网......