一、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文件的路径添加到工程。
- 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中配置
- 其他默认
选择我们需要读取的功能码与对应的寄存器数量
选择完成成功读取到数据,串口有相应的DEBUG返回,ModBUSTCP的基本测试完成。
四、总结
移植ModBusTCP比较简单,但是前提是需要掌握LwIP协议栈的使用。之后将会探讨FreeModBus实现ModBusRTU/TCP和ModBus协议的流程。
标签:STM32,MB,void,TCP,ModBus,pxPCB,include,define From: https://www.cnblogs.com/fuyunxiansen/p/18101628