FreeMODBUS在STM32F407VGT6 + LAN8720 + LWIP基础上的移植指南

[复制链接]
2128|0
keer_zu 发表于 2025-9-3 01:14 | 显示全部楼层 |阅读模式

下面我将详细说明如何将FreeMODBUS移植到STM32F407VGT6 + LAN8720 + LWIP平台上,并提供完整的Demo程序。

一、准备工作

1. 获取FreeMODBUS源码

从官方仓库克隆代码:

git clone https://github.com/cwalter-at/freemodbus.git

2. 工程结构

建议的工程目录结构:

Project/
├── Core/
├── Drivers/
├── LWIP/
├── FreeMODBUS/
│   ├── modbus/
│   │   ├── include/
│   │   ├── rtu/
│   │   ├── tcp/
│   │   └── functions/
│   └── port/
│       ├── portevent.c
│       ├── portserial.c
│       ├── porttcp.c
│       └── porttimer.c
└── Middlewares/

二、FreeMODBUS移植步骤

1. 添加FreeMODBUS到工程

将FreeMODBUS源码添加到你的STM32工程中,并设置正确的包含路径。

2. 端口文件实现

portevent.c - 事件处理

#include "port.h"
#include "mbport.h"

static QueueHandle_t xQueueHnd;

BOOL xMBPortEventInit( void )
{
    xQueueHnd = xQueueCreate(1, sizeof(eMBEventType));
    return xQueueHnd != NULL;
}

BOOL xMBPortEventPost( eMBEventType eEvent )
{
    eMBEventType eSentEvent = eEvent;
    return xQueueSend(xQueueHnd, &eSentEvent, 0) == pdPASS;
}

BOOL xMBPortEventGet( eMBEventType * eEvent )
{
    return xQueueReceive(xQueueHnd, eEvent, portMAX_DELAY) == pdPASS;
}

porttcp.c - TCP端口实现

#include "port.h"
#include "mbport.h"
#include "lwip/tcp.h"
#include "lwip/api.h"

#define MB_TCP_PORT    502     // Modbus TCP默认端口

static struct tcp_pcb *modbus_tcp_pcb = NULL;
static struct tcp_pcb *client_pcb = NULL;

// TCP接受回调
static err_t modbus_tcp_accept(void *arg, struct tcp_pcb *newpcb, err_t err)
{
    LWIP_UNUSED_ARG(arg);
    LWIP_UNUSED_ARG(err);
  
    if (client_pcb != NULL) {
        // 只允许一个连接
        tcp_abort(newpcb);
        return ERR_ABRT;
    }
  
    // 设置接收回调
    tcp_recv(newpcb, modbus_tcp_recv);
  
    // 设置错误回调
    tcp_err(newpcb, modbus_tcp_err);
  
    client_pcb = newpcb;
  
    return ERR_OK;
}

// TCP接收回调
static err_t modbus_tcp_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
{
    LWIP_UNUSED_ARG(arg);
  
    if (err != ERR_OK) {
        if (p != NULL) {
            pbuf_free(p);
        }
        return err;
    }
  
    if (p == NULL) {
        // 连接关闭
        client_pcb = NULL;
        tcp_close(tpcb);
        return ERR_OK;
    }
  
    // 处理接收到的数据
    if (tpcb == client_pcb) {
        // 将数据传递给Modbus协议栈
        pbuf_copy_partial(p, (void*)xMBTCPPortGetRequest(), p->tot_len, 0);
        (void)xMBPortEventPost(EV_FRAME_RECEIVED);
    }
  
    tcp_recved(tpcb, p->tot_len);
    pbuf_free(p);
  
    return ERR_OK;
}

// TCP错误回调
static void modbus_tcp_err(void *arg, err_t err)
{
    LWIP_UNUSED_ARG(arg);
    LWIP_UNUSED_ARG(err);
  
    client_pcb = NULL;
}

// FreeMODBUS TCP端口初始化
BOOL xMBTCPPortInit(USHORT usTCPPort)
{
    err_t err;
  
    modbus_tcp_pcb = tcp_new();
    if (modbus_tcp_pcb == NULL) {
        return FALSE;
    }
  
    err = tcp_bind(modbus_tcp_pcb, IP_ADDR_ANY, usTCPPort);
    if (err != ERR_OK) {
        memp_free(MEMP_TCP_PCB, modbus_tcp_pcb);
        return FALSE;
    }
  
    modbus_tcp_pcb = tcp_listen(modbus_tcp_pcb);
    if (modbus_tcp_pcb == NULL) {
        return FALSE;
    }
  
    tcp_accept(modbus_tcp_pcb, modbus_tcp_accept);
  
    return TRUE;
}

// 关闭TCP端口
void vMBTCPPortClose(void)
{
    if (client_pcb != NULL) {
        tcp_arg(client_pcb, NULL);
        tcp_recv(client_pcb, NULL);
        tcp_err(client_pcb, NULL);
        tcp_close(client_pcb);
        client_pcb = NULL;
    }
  
    if (modbus_tcp_pcb != NULL) {
        tcp_close(modbus_tcp_pcb);
        memp_free(MEMP_TCP_PCB, modbus_tcp_pcb);
        modbus_tcp_pcb = NULL;
    }
}

// 发送响应
void vMBTCPPortSendResponse(void)
{
    if (client_pcb != NULL) {
        tcp_write(client_pcb, (void*)xMBTCPPortGetResponse(), usMBTCPLength, 1);
        tcp_output(client_pcb);
    }
}

porttimer.c - 定时器实现(TCP模式下不需要)

#include "port.h"
#include "mbport.h"

BOOL xMBPortTimersInit(USHORT usTimeOut50us)
{
    // TCP模式下不需要定时器
    return TRUE;
}

void vMBPortTimersEnable(void)
{
    // TCP模式下不需要定时器
}

void vMBPortTimersDisable(void)
{
    // TCP模式下不需要定时器
}

portserial.c - 串口实现(TCP模式下不需要)

#include "port.h"
#include "mbport.h"

BOOL xMBPortSerialInit(UCHAR ucPort, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity)
{
    // TCP模式下不需要串口
    return TRUE;
}

void vMBPortSerialEnable(BOOL xRxEnable, BOOL xTxEnable)
{
    // TCP模式下不需要串口
}

void vMBPortSerialClose(void)
{
    // TCP模式下不需要串口
}

3. 端口头文件 port.h

#ifndef _PORT_H
#define _PORT_H

#include <stdint.h>
#include <stdlib.h>

// FreeRTOS包含
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"

// 类型定义
typedef uint8_t BOOL;

typedef unsigned char UCHAR;
typedef char    CHAR;

typedef uint16_t USHORT;
typedef int16_t  SHORT;

typedef uint32_t ULONG;
typedef int32_t  LONG;

#ifndef TRUE
#define TRUE            1
#endif

#ifndef FALSE
#define FALSE           0
#endif

// 临界区保护
#define ENTER_CRITICAL_SECTION( ) vTaskSuspendAll()
#define EXIT_CRITICAL_SECTION( ) xTaskResumeAll()

#endif

三、应用层实现

1. Modbus数据区定义

// modbus_app.c
#include "mb.h"
#include "mbtcp.h"

// 定义Modbus数据区大小
#define REG_HOLDING_NREGS  100
#define REG_INPUT_NREGS    50
#define COIL_NREGS         100
#define DISCRETE_NREGS     50

// Modbus数据存储
static USHORT usRegHoldingBuf[REG_HOLDING_NREGS];
static USHORT usRegInputBuf[REG_INPUT_NREGS];
static UCHAR ucCoilBuf[(COIL_NREGS + 7) / 8];
static UCHAR ucDiscreteBuf[(DISCRETE_NREGS + 7) / 8];

// 初始化Modbus数据区
void modbus_data_init(void)
{
    int i;
  
    // 初始化保持寄存器
    for (i = 0; i < REG_HOLDING_NREGS; i++) {
        usRegHoldingBuf[i] = 0;
    }
  
    // 初始化输入寄存器
    for (i = 0; i < REG_INPUT_NREGS; i++) {
        usRegInputBuf[i] = i * 10; // 示例数据
    }
  
    // 初始化线圈
    for (i = 0; i < (COIL_NREGS + 7) / 8; i++) {
        ucCoilBuf[i] = 0;
    }
  
    // 初始化离散输入
    for (i = 0; i < (DISCRETE_NREGS + 7) / 8; i++) {
        ucDiscreteBuf[i] = 0;
    }
}

// 保持寄存器回调函数
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 - REG_HOLDING_START);
    
        switch (eMode) {
            case MB_REG_READ:
                while (usNRegs > 0) {
                    *pucRegBuffer++ = (UCHAR)(usRegHoldingBuf[iRegIndex] >> 8);
                    *pucRegBuffer++ = (UCHAR)(usRegHoldingBuf[iRegIndex] & 0xFF);
                    iRegIndex++;
                    usNRegs--;
                }
                break;
            
            case MB_REG_WRITE:
                while (usNRegs > 0) {
                    usRegHoldingBuf[iRegIndex] = *pucRegBuffer++ << 8;
                    usRegHoldingBuf[iRegIndex] |= *pucRegBuffer++;
                    iRegIndex++;
                    usNRegs--;
                }
                break;
        }
    } else {
        eStatus = MB_ENOREG;
    }
  
    return eStatus;
}

// 输入寄存器回调函数
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 - REG_INPUT_START);
    
        while (usNRegs > 0) {
            *pucRegBuffer++ = (UCHAR)(usRegInputBuf[iRegIndex] >> 8);
            *pucRegBuffer++ = (UCHAR)(usRegInputBuf[iRegIndex] & 0xFF);
            iRegIndex++;
            usNRegs--;
        }
    } else {
        eStatus = MB_ENOREG;
    }
  
    return eStatus;
}

// 线圈回调函数
eMBErrorCode eMBRegCoilsCB(UCHAR * pucRegBuffer, USHORT usAddress, 
                          USHORT usNCoils, eMBRegisterMode eMode)
{
    eMBErrorCode eStatus = MB_ENOERR;
    USHORT usBitOffset;
    USHORT usNByte;
    UCHAR ucBitIndex;
    UCHAR ucTemp;
  
    usBitOffset = (USHORT)(usAddress - COIL_START);
    usNByte = (USHORT)((usNCoils + 7) / 8);
    ucBitIndex = 0;
  
    if ((usAddress >= COIL_START) && 
        (usAddress + usNCoils <= COIL_START + COIL_NREGS)) {
        switch (eMode) {
            case MB_REG_READ:
                while (usNCoils > 0) {
                    if (ucBitIndex == 0) {
                        *pucRegBuffer = 0;
                    }
                
                    if (xMBUtilGetBits(ucCoilBuf, usBitOffset, 1)) {
                        *pucRegBuffer |= (UCHAR)(1 << ucBitIndex);
                    }
                
                    ucBitIndex++;
                    usBitOffset++;
                    usNCoils--;
                
                    if ((ucBitIndex >= 8) || (usNCoils == 0)) {
                        pucRegBuffer++;
                        ucBitIndex = 0;
                    }
                }
                break;
            
            case MB_REG_WRITE:
                while (usNCoils > 0) {
                    if (ucBitIndex == 0) {
                        ucTemp = *pucRegBuffer++;
                    }
                
                    xMBUtilSetBits(ucCoilBuf, usBitOffset, 1, 
                                  (ucTemp >> ucBitIndex) & 0x01);
                
                    ucBitIndex++;
                    usBitOffset++;
                    usNCoils--;
                
                    if (ucBitIndex >= 8) {
                        ucBitIndex = 0;
                    }
                }
                break;
        }
    } else {
        eStatus = MB_ENOREG;
    }
  
    return eStatus;
}

// 离散输入回调函数
eMBErrorCode eMBRegDiscreteCB(UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete)
{
    eMBErrorCode eStatus = MB_ENOERR;
    USHORT usBitOffset;
    USHORT usNByte;
    UCHAR ucBitIndex;
  
    usBitOffset = (USHORT)(usAddress - DISCRETE_START);
    usNByte = (USHORT)((usNDiscrete + 7) / 8);
    ucBitIndex = 0;
  
    if ((usAddress >= DISCRETE_START) && 
        (usAddress + usNDiscrete <= DISCRETE_START + DISCRETE_NREGS)) {
        while (usNDiscrete > 0) {
            if (ucBitIndex == 0) {
                *pucRegBuffer = 0;
            }
        
            if (xMBUtilGetBits(ucDiscreteBuf, usBitOffset, 1)) {
                *pucRegBuffer |= (UCHAR)(1 << ucBitIndex);
            }
        
            ucBitIndex++;
            usBitOffset++;
            usNDiscrete--;
        
            if ((ucBitIndex >= 8) || (usNDiscrete == 0)) {
                pucRegBuffer++;
                ucBitIndex = 0;
            }
        }
    } else {
        eStatus = MB_ENOREG;
    }
  
    return eStatus;
}

2. Modbus任务创建

// modbus_task.c
#include "main.h"
#include "cmsis_os.h"
#include "lwip.h"
#include "mb.h"
#include "mbtcp.h"

extern void modbus_data_init(void);

// Modbus任务函数
void modbus_task(void const *argument)
{
    eMBErrorCode eStatus;
  
    // 初始化Modbus数据区
    modbus_data_init();
  
    // 初始化Modbus TCP协议栈
    eStatus = eMBInit(MB_TCP, 1, 502, MB_PAR_NONE);
    if (eStatus != MB_ENOERR) {
        printf("Modbus TCP initialization failed: %d\n", eStatus);
        vTaskDelete(NULL);
        return;
    }
  
    // 注册回调函数
    eMBRegisterCB(MB_REG_HOLDING, eMBRegHoldingCB);
    eMBRegisterCB(MB_REG_INPUT, eMBRegInputCB);
    eMBRegisterCB(MB_REG_COILS, eMBRegCoilsCB);
    eMBRegisterCB(MB_REG_DISCRETE, eMBRegDiscreteCB);
  
    // 启用Modbus协议栈
    eStatus = eMBEnable();
    if (eStatus != MB_ENOERR) {
        printf("Modbus TCP enable failed: %d\n", eStatus);
        vTaskDelete(NULL);
        return;
    }
  
    printf("Modbus TCP server started on port 502\n");
  
    // Modbus主循环
    while (1) {
        // 处理Modbus事件
        eMBPoll();
    
        // 短暂延时,让出CPU
        osDelay(5);
    }
}

// 创建Modbus任务
void modbus_task_init(void)
{
    osThreadDef(MODBUS, modbus_task, osPriorityNormal, 0, 512);
    osThreadCreate(osThread(MODBUS), NULL);
}

四、主函数和系统初始化

main.c

#include "main.h"
#include "cmsis_os.h"
#include "lwip.h"
#include "netif.h"

// 全局网络接口
struct netif gnetif;

// 外设初始化函数
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_ETH_Init(void);
static void MX_LWIP_Init(void);
void modbus_task_init(void);

int main(void)
{
    // HAL库初始化
    HAL_Init();
  
    // 系统时钟配置
    SystemClock_Config();
  
    // GPIO初始化
    MX_GPIO_Init();
  
    // 以太网初始化
    MX_ETH_Init();
  
    // LWIP初始化
    MX_LWIP_Init();
  
    // 创建Modbus任务
    modbus_task_init();
  
    // 启动FreeRTOS调度器
    osKernelStart();
  
    while (1) {
        // 不应该执行到这里
    }
}

// LWIP初始化函数
void MX_LWIP_Init(void)
{
    ip_addr_t ipaddr, netmask, gw;
  
#if LWIP_DHCP
    ip_addr_set_zero_ip4(&ipaddr);
    ip_addr_set_zero_ip4(&netmask);
    ip_addr_set_zero_ip4(&gw);
#else
    // 静态IP配置
    IP4_ADDR(&ipaddr, 192, 168, 1, 10);
    IP4_ADDR(&netmask, 255, 255, 255, 0);
    IP4_ADDR(&gw, 192, 168, 1, 1);
#endif
  
    // 添加网络接口
    netif_add(&gnetif, &ipaddr, &netmask, &gw, NULL, &ethernetif_init, &tcpip_input);
  
    // 注册默认网络接口
    netif_set_default(&gnetif);
  
    // 启用网络接口
    netif_set_up(&gnetif);
  
#if LWIP_DHCP
    // 启动DHCP客户端
    dhcp_start(&gnetif);
#endif
  
    // 设置主机名
    netif_set_hostname(&gnetif, "stm32-modbus");
}

// 以太网处理任务(由LWIP创建)
void ethernetif_input(void *argument)
{
    while (1) {
        // 处理以太网输入
        ethernetif_input(&gnetif);
        osDelay(1);
    }
}

五、LWIP配置

lwipopts.h中添加以下配置:

// 启用TCP
#define LWIP_TCP                1

// TCP配置
#define TCP_MSS                 1460
#define TCP_SND_BUF             (4 * TCP_MSS)
#define TCP_WND                 (2 * TCP_MSS)
#define TCP_SND_QUEUELEN        (4 * TCP_SND_BUF/TCP_MSS)

// 内存配置
#define MEM_SIZE                (16000)
#define MEMP_NUM_PBUF           16
#define MEMP_NUM_UDP_PCB        4
#define MEMP_NUM_TCP_PCB        5
#define MEMP_NUM_TCP_PCB_LISTEN 3
#define MEMP_NUM_TCP_SEG        16
#define MEMP_NUM_SYS_TIMEOUT    8

// PBUF配置
#define PBUF_POOL_SIZE          16
#define PBUF_POOL_BUFSIZE       1524
#define PBUF_LINK_HLEN          16

// ARP配置
#define LWIP_ARP                1
#define ARP_TABLE_SIZE          10

// DHCP配置(可选)
#define LWIP_DHCP               0

// 统计信息(调试用)
#define LWIP_STATS              0
#define LWIP_PROVIDE_ERRNO      1

六、编译配置

1. 包含路径

确保在编译器中添加以下包含路径:

  • FreeMODBUS/modbus/include
  • FreeMODBUS/port
  • LWIP/include

2. 预处理器定义

添加以下预处理器定义:

LWIP_TIMEVAL_PRIVATE=0
USE_HAL_DRIVER
STM32F407xx

七、测试和验证

1. 使用Modbus测试工具

可以使用以下工具测试Modbus TCP服务器:

  • Modbus Poll (Windows)
  • qModMaster (Linux/Windows)
  • mbpoll (命令行工具)

2. 测试命令示例

# 读取保持寄存器
mbpoll -t 3 -r 0 -c 10 192.168.1.10

# 写入保持寄存器
mbpoll -t 3 -r 0 -c 1 192.168.1.10 -0 1234

# 读取输入寄存器
mbpoll -t 4 -r 0 -c 10 192.168.1.10

八、故障排除

  1. 连接问题

    • 检查物理连接和LED指示灯
    • 验证IP地址配置
    • 使用ping测试网络连通性
  2. Modbus通信问题

    • 使用Wireshark捕获和分析网络数据包
    • 检查FreeMODBUS的返回值
  3. 性能问题

    • 调整LWIP内存池大小
    • 优化FreeRTOS任务优先级

九、扩展功能

  1. 多连接支持:修改porttcp.c以支持多个并发连接
  2. 安全性:添加Modbus/TLS支持
  3. Web配置:添加HTTP服务器用于配置Modbus参数
  4. OPC UA网关:将Modbus数据映射到OPC UA

这个移植指南提供了完整的FreeMODBUS在STM32F407VGT6 + LAN8720 + LWIP平台上的实现方案。根据你的具体硬件和需求,可能需要进行一些调整。

您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:qq群:49734243 Email:zukeqiang@gmail.com

1488

主题

12949

帖子

55

粉丝
快速回复 在线客服 返回列表 返回顶部