下面我将详细说明如何将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, ðernetif_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
八、故障排除
-
连接问题:
- 检查物理连接和LED指示灯
- 验证IP地址配置
- 使用ping测试网络连通性
-
Modbus通信问题:
- 使用Wireshark捕获和分析网络数据包
- 检查FreeMODBUS的返回值
-
性能问题:
- 调整LWIP内存池大小
- 优化FreeRTOS任务优先级
九、扩展功能
- 多连接支持:修改porttcp.c以支持多个并发连接
- 安全性:添加Modbus/TLS支持
- Web配置:添加HTTP服务器用于配置Modbus参数
- OPC UA网关:将Modbus数据映射到OPC UA
这个移植指南提供了完整的FreeMODBUS在STM32F407VGT6 + LAN8720 + LWIP平台上的实现方案。根据你的具体硬件和需求,可能需要进行一些调整。