在STM32F407VGT6 + LAN8720 + LWIP上实现Modbus TCP的实施方案
下面是一个完整的、可实施的Modbus TCP实现方案,基于STM32F407VGT6微控制器、LAN8720以太网PHY和LWIP协议栈。
1. 系统架构设计
1.1 硬件连接
- STM32F407VGT6通过RMII接口连接LAN8720
- 正确配置时钟和引脚(参考STM32CubeMX配置)
- 确保LAN8720的nINT/REFCLKO引脚正确连接
1.2 软件架构
应用层: Modbus TCP服务器
|
协议层: Modbus协议处理
|
传输层: TCP Socket (LWIP)
|
网络层: IP协议 (LWIP)
|
链路层: Ethernet (LWIP + LAN8720驱动)
|
硬件层: STM32F407 + LAN8720
2. 代码实现
2.1 必要的头文件定义
// modbus_tcp.h
#ifndef __MODBUS_TCP_H
#define __MODBUS_TCP_H
#include "lwip/api.h"
#include "lwip/opt.h"
#include "lwip/tcp.h"
#include "lwip/tcpip.h"
#include "lwip/netif.h"
#include "lwip/dhcp.h"
#include "netif/etharp.h"
// Modbus功能码定义
#define MB_FUNC_READ_COILS 0x01
#define MB_FUNC_READ_DISCRETE_INPUTS 0x02
#define MB_FUNC_READ_HOLDING_REGISTERS 0x03
#define MB_FUNC_READ_INPUT_REGISTERS 0x04
#define MB_FUNC_WRITE_SINGLE_COIL 0x05
#define MB_FUNC_WRITE_SINGLE_REGISTER 0x06
#define MB_FUNC_WRITE_MULTIPLE_COILS 0x0F
#define MB_FUNC_WRITE_MULTIPLE_REGISTERS 0x10
// Modbus异常码定义
#define MB_EX_NONE 0x00
#define MB_EX_ILLEGAL_FUNCTION 0x01
#define MB_EX_ILLEGAL_DATA_ADDRESS 0x02
#define MB_EX_ILLEGAL_DATA_VALUE 0x03
#define MB_EX_SLAVE_DEVICE_FAILURE 0x04
#define MB_EX_ACKNOWLEDGE 0x05
#define MB_EX_SLAVE_DEVICE_BUSY 0x06
#define MB_EX_MEMORY_PARITY_ERROR 0x08
#define MB_EX_GATEWAY_PATH_UNAVAILABLE 0x0A
#define MB_EX_DEVICE_FAILED_TO_RESPOND 0x0B
// Modbus TCP头部结构
typedef struct {
uint16_t transaction_id;
uint16_t protocol_id;
uint16_t length;
uint8_t unit_id;
} modbus_tcp_header_t;
// Modbus PDU结构
typedef struct {
uint8_t function_code;
uint8_t data[1];
} modbus_pdu_t;
// 寄存器读取请求结构
typedef struct {
uint16_t start_addr;
uint16_t reg_count;
} read_registers_req_t;
// 寄存器读取响应结构
typedef struct {
uint8_t byte_count;
uint16_t registers[1];
} read_registers_resp_t;
// 线圈读取响应结构
typedef struct {
uint8_t byte_count;
uint8_t coils[1];
} read_coils_resp_t;
// 单寄存器写入请求结构
typedef struct {
uint16_t reg_addr;
uint16_t reg_value;
} write_single_register_req_t;
// 多寄存器写入请求结构
typedef struct {
uint16_t start_addr;
uint16_t reg_count;
uint8_t byte_count;
uint16_t registers[1];
} write_multiple_registers_req_t;
// Modbus连接上下文
typedef struct {
struct tcp_pcb *pcb;
modbus_tcp_header_t current_header;
uint8_t exception_code;
uint8_t is_connected;
} modbus_connection_t;
// 函数声明
void modbus_tcp_init(void);
err_t modbus_tcp_accept(void *arg, struct tcp_pcb *newpcb, err_t err);
err_t modbus_tcp_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err);
void modbus_tcp_error(void *arg, err_t err);
err_t modbus_tcp_poll(void *arg, struct tcp_pcb *tpcb);
err_t modbus_tcp_sent(void *arg, struct tcp_pcb *tpcb, u16_t len);
// 应用回调函数
uint16_t get_holding_register(uint16_t addr);
uint8_t get_coil(uint16_t addr);
uint16_t get_input_register(uint16_t addr);
uint8_t get_discrete_input(uint16_t addr);
void set_holding_register(uint16_t addr, uint16_t value);
void set_coil(uint16_t addr, uint8_t value);
#endif /* __MODBUS_TCP_H */
2.2 Modbus TCP服务器实现
// modbus_tcp.c
#include "modbus_tcp.h"
#include "string.h"
#define MODBUS_TCP_PORT 502
#define MODBUS_MAX_CONNECTIONS 3
static struct tcp_pcb *modbus_pcb = NULL;
static modbus_connection_t connections[MODBUS_MAX_CONNECTIONS];
// 初始化Modbus TCP服务器
void modbus_tcp_init(void)
{
err_t err;
// 创建新的TCP控制块
modbus_pcb = tcp_new();
if (!modbus_pcb) {
printf("Error creating PCB\n");
return;
}
// 绑定到Modbus TCP端口
err = tcp_bind(modbus_pcb, IP_ADDR_ANY, MODBUS_TCP_PORT);
if (err != ERR_OK) {
printf("Error binding to port %d: %d\n", MODBUS_TCP_PORT, err);
memp_free(MEMP_TCP_PCB, modbus_pcb);
return;
}
// 开始监听
modbus_pcb = tcp_listen(modbus_pcb);
if (!modbus_pcb) {
printf("Error listening\n");
return;
}
// 设置接受连接回调
tcp_accept(modbus_pcb, modbus_tcp_accept);
// 初始化连接数组
memset(connections, 0, sizeof(connections));
printf("Modbus TCP server started on port %d\n", MODBUS_TCP_PORT);
}
// 接受新连接回调
err_t modbus_tcp_accept(void *arg, struct tcp_pcb *newpcb, err_t err)
{
int i;
static int connection_count = 0;
LWIP_UNUSED_ARG(arg);
LWIP_UNUSED_ARG(err);
// 查找空闲连接槽
for (i = 0; i < MODBUS_MAX_CONNECTIONS; i++) {
if (connections[i].pcb == NULL) {
break;
}
}
if (i >= MODBUS_MAX_CONNECTIONS) {
printf("Max connections reached, rejecting\n");
tcp_abort(newpcb);
return ERR_ABRT;
}
// 设置接收回调
tcp_recv(newpcb, modbus_tcp_recv);
// 设置错误回调
tcp_err(newpcb, modbus_tcp_error);
// 设置轮询回调
tcp_poll(newpcb, modbus_tcp_poll, 4);
// 设置发送完成回调
tcp_sent(newpcb, modbus_tcp_sent);
// 初始化连接上下文
connections[i].pcb = newpcb;
connections[i].is_connected = 1;
connections[i].exception_code = MB_EX_NONE;
connection_count++;
printf("New Modbus TCP connection (%d active)\n", connection_count);
return ERR_OK;
}
// 接收数据回调
err_t modbus_tcp_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
{
modbus_connection_t *conn = (modbus_connection_t *)arg;
modbus_tcp_header_t *header;
modbus_pdu_t *pdu;
uint16_t pdu_length;
if (!p) {
// 连接关闭
printf("Connection closed\n");
modbus_tcp_connection_close(conn, tpcb);
return ERR_OK;
}
if (err != ERR_OK) {
printf("Receive error: %d\n", err);
pbuf_free(p);
return err;
}
// 确认我们收到了数据
tcp_recved(tpcb, p->tot_len);
// 检查数据长度是否足够
if (p->tot_len < sizeof(modbus_tcp_header_t)) {
printf("Packet too short: %d bytes\n", p->tot_len);
pbuf_free(p);
return ERR_OK;
}
// 获取Modbus TCP头部
header = (modbus_tcp_header_t *)p->payload;
// 检查协议标识符
if (header->protocol_id != 0) {
printf("Invalid protocol ID: %d\n", ntohs(header->protocol_id));
pbuf_free(p);
return ERR_OK;
}
// 保存当前头部信息
conn->current_header.transaction_id = header->transaction_id;
conn->current_header.protocol_id = header->protocol_id;
conn->current_header.unit_id = header->unit_id;
// 计算PDU长度
pdu_length = ntohs(header->length) - 1; // 减去单元标识符
// 获取PDU
pdu = (modbus_pdu_t *)((uint8_t *)p->payload + sizeof(modbus_tcp_header_t));
// 处理Modbus请求
process_modbus_request(conn, pdu, pdu_length);
pbuf_free(p);
return ERR_OK;
}
// 处理Modbus请求
void process_modbus_request(modbus_connection_t *conn, modbus_pdu_t *pdu, uint16_t pdu_length)
{
// 根据功能码处理请求
switch (pdu->function_code) {
case MB_FUNC_READ_HOLDING_REGISTERS:
handle_read_holding_registers(conn, pdu, pdu_length);
break;
case MB_FUNC_READ_COILS:
handle_read_coils(conn, pdu, pdu_length);
break;
case MB_FUNC_READ_DISCRETE_INPUTS:
handle_read_discrete_inputs(conn, pdu, pdu_length);
break;
case MB_FUNC_READ_INPUT_REGISTERS:
handle_read_input_registers(conn, pdu, pdu_length);
break;
case MB_FUNC_WRITE_SINGLE_REGISTER:
handle_write_single_register(conn, pdu, pdu_length);
break;
case MB_FUNC_WRITE_SINGLE_COIL:
handle_write_single_coil(conn, pdu, pdu_length);
break;
case MB_FUNC_WRITE_MULTIPLE_REGISTERS:
handle_write_multiple_registers(conn, pdu, pdu_length);
break;
case MB_FUNC_WRITE_MULTIPLE_COILS:
handle_write_multiple_coils(conn, pdu, pdu_length);
break;
default:
// 不支持的功能码
printf("Unsupported function code: 0x%02X\n", pdu->function_code);
send_exception_response(conn, pdu->function_code, MB_EX_ILLEGAL_FUNCTION);
break;
}
}
// 处理读取保持寄存器请求
void handle_read_holding_registers(modbus_connection_t *conn, modbus_pdu_t *pdu, uint16_t pdu_length)
{
read_registers_req_t *req = (read_registers_req_t *)pdu->data;
read_registers_resp_t *resp;
modbus_tcp_header_t *resp_header;
struct pbuf *p_resp;
uint16_t start_addr, reg_count;
uint16_t length;
uint8_t *payload;
int i;
// 检查请求长度
if (pdu_length < sizeof(read_registers_req_t)) {
printf("Invalid read holding registers request length: %d\n", pdu_length);
send_exception_response(conn, pdu->function_code, MB_EX_ILLEGAL_DATA_VALUE);
return;
}
// 获取起始地址和寄存器数量
start_addr = ntohs(req->start_addr);
reg_count = ntohs(req->reg_count);
// 验证请求参数
if (reg_count < 1 || reg_count > 125) {
printf("Invalid register count: %d\n", reg_count);
send_exception_response(conn, pdu->function_code, MB_EX_ILLEGAL_DATA_VALUE);
return;
}
// 检查地址范围
if ((start_addr + reg_count) > MODBUS_HOLDING_REGISTERS_MAX) {
printf("Address out of range: %d + %d > %d\n",
start_addr, reg_count, MODBUS_HOLDING_REGISTERS_MAX);
send_exception_response(conn, pdu->function_code, MB_EX_ILLEGAL_DATA_ADDRESS);
return;
}
// 计算响应长度
length = sizeof(modbus_tcp_header_t) + sizeof(modbus_pdu_t) + 1 + (reg_count * 2);
// 分配响应缓冲区
p_resp = pbuf_alloc(PBUF_TRANSPORT, length, PBUF_RAM);
if (!p_resp) {
printf("Failed to allocate response buffer\n");
return;
}
payload = (uint8_t *)p_resp->payload;
// 填充响应头
resp_header = (modbus_tcp_header_t *)payload;
resp_header->transaction_id = conn->current_header.transaction_id;
resp_header->protocol_id = 0;
resp_header->length = htons(sizeof(modbus_pdu_t) + 1 + (reg_count * 2));
resp_header->unit_id = conn->current_header.unit_id;
// 填充PDU
resp = (read_registers_resp_t *)(payload + sizeof(modbus_tcp_header_t));
resp->function_code = pdu->function_code;
resp->byte_count = reg_count * 2;
// 填充寄存器值
for (i = 0; i < reg_count; i++) {
uint16_t reg_value = get_holding_register(start_addr + i);
resp->registers[i] = htons(reg_value);
}
// 发送响应
tcp_write(conn->pcb, p_resp->payload, p_resp->len, 1);
tcp_output(conn->pcb);
// 释放缓冲区
pbuf_free(p_resp);
}
// 发送异常响应
void send_exception_response(modbus_connection_t *conn, uint8_t function_code, uint8_t exception_code)
{
modbus_tcp_header_t *header;
modbus_pdu_t *pdu;
struct pbuf *p;
uint8_t payload[sizeof(modbus_tcp_header_t) + sizeof(modbus_pdu_t) + 1];
// 准备异常响应
header = (modbus_tcp_header_t *)payload;
header->transaction_id = conn->current_header.transaction_id;
header->protocol_id = 0;
header->length = htons(3); // 单元ID + 功能码 + 异常码
header->unit_id = conn->current_header.unit_id;
pdu = (modbus_pdu_t *)(payload + sizeof(modbus_tcp_header_t));
pdu->function_code = function_code | 0x80; // 设置异常标志
pdu->data[0] = exception_code;
// 分配pbuf
p = pbuf_alloc(PBUF_TRANSPORT, sizeof(payload), PBUF_RAM);
if (!p) {
printf("Failed to allocate exception response buffer\n");
return;
}
// 复制数据到pbuf
pbuf_take(p, payload, sizeof(payload));
// 发送响应
tcp_write(conn->pcb, p->payload, p->len, 1);
tcp_output(conn->pcb);
// 释放pbuf
pbuf_free(p);
}
// 关闭连接
void modbus_tcp_connection_close(modbus_connection_t *conn, struct tcp_pcb *pcb)
{
int i;
// 查找连接并标记为断开
for (i = 0; i < MODBUS_MAX_CONNECTIONS; i++) {
if (connections[i].pcb == pcb) {
connections[i].pcb = NULL;
connections[i].is_connected = 0;
break;
}
}
// 关闭TCP连接
tcp_arg(pcb, NULL);
tcp_sent(pcb, NULL);
tcp_recv(pcb, NULL);
tcp_err(pcb, NULL);
tcp_poll(pcb, NULL, 0);
if (tcp_close(pcb) != ERR_OK) {
tcp_abort(pcb);
}
printf("Modbus TCP connection closed\n");
}
// 错误回调
void modbus_tcp_error(void *arg, err_t err)
{
modbus_connection_t *conn = (modbus_connection_t *)arg;
LWIP_UNUSED_ARG(err);
if (conn) {
printf("Modbus TCP connection error: %d\n", err);
conn->pcb = NULL;
conn->is_connected = 0;
}
}
// 轮询回调
err_t modbus_tcp_poll(void *arg, struct tcp_pcb *tpcb)
{
modbus_connection_t *conn = (modbus_connection_t *)arg;
if (!conn) {
tcp_abort(tpcb);
return ERR_ABRT;
}
// 可以在这里实现超时检测
return ERR_OK;
}
// 发送完成回调
err_t modbus_tcp_sent(void *arg, struct tcp_pcb *tpcb, u16_t len)
{
LWIP_UNUSED_ARG(arg);
LWIP_UNUSED_ARG(tpcb);
LWIP_UNUSED_ARG(len);
return ERR_OK;
}
2.3 应用回调函数实现
// modbus_app.c
#include "modbus_tcp.h"
// 定义Modbus数据区大小
#define MODBUS_HOLDING_REGISTERS_MAX 100
#define MODBUS_INPUT_REGISTERS_MAX 50
#define MODBUS_COILS_MAX 100
#define MODBUS_DISCRETE_INPUTS_MAX 50
// Modbus数据存储
static uint16_t holding_registers[MODBUS_HOLDING_REGISTERS_MAX];
static uint16_t input_registers[MODBUS_INPUT_REGISTERS_MAX];
static uint8_t coils[MODBUS_COILS_MAX];
static uint8_t discrete_inputs[MODBUS_DISCRETE_INPUTS_MAX];
// 初始化Modbus数据区
void modbus_data_init(void)
{
int i;
// 初始化保持寄存器
for (i = 0; i < MODBUS_HOLDING_REGISTERS_MAX; i++) {
holding_registers[i] = 0;
}
// 初始化输入寄存器
for (i = 0; i < MODBUS_INPUT_REGISTERS_MAX; i++) {
input_registers[i] = 0;
}
// 初始化线圈
for (i = 0; i < MODBUS_COILS_MAX; i++) {
coils[i] = 0;
}
// 初始化离散输入
for (i = 0; i < MODBUS_DISCRETE_INPUTS_MAX; i++) {
discrete_inputs[i] = 0;
}
}
// 获取保持寄存器值
uint16_t get_holding_register(uint16_t addr)
{
if (addr < MODBUS_HOLDING_REGISTERS_MAX) {
return holding_registers[addr];
}
return 0;
}
// 设置保持寄存器值
void set_holding_register(uint16_t addr, uint16_t value)
{
if (addr < MODBUS_HOLDING_REGISTERS_MAX) {
holding_registers[addr] = value;
}
}
// 获取输入寄存器值
uint16_t get_input_register(uint16_t addr)
{
if (addr < MODBUS_INPUT_REGISTERS_MAX) {
return input_registers[addr];
}
return 0;
}
// 获取线圈状态
uint8_t get_coil(uint16_t addr)
{
if (addr < MODBUS_COILS_MAX) {
return coils[addr];
}
return 0;
}
// 设置线圈状态
void set_coil(uint16_t addr, uint8_t value)
{
if (addr < MODBUS_COILS_MAX) {
coils[addr] = value ? 1 : 0;
}
}
// 获取离散输入状态
uint8_t get_discrete_input(uint16_t addr)
{
if (addr < MODBUS_DISCRETE_INPUTS_MAX) {
return discrete_inputs[addr];
}
return 0;
}
2.4 主函数和初始化
// main.c
#include "main.h"
#include "lwip.h"
#include "modbus_tcp.h"
// 全局网络接口
struct netif gnetif;
int main(void)
{
// HAL初始化
HAL_Init();
// 系统时钟配置
SystemClock_Config();
// 初始化GPIO、ETH等外设
MX_GPIO_Init();
MX_ETH_Init();
// 初始化LWIP
MX_LWIP_Init();
// 初始化Modbus数据区
modbus_data_init();
// 初始化Modbus TCP服务器
modbus_tcp_init();
// 主循环
while (1) {
// 处理以太网输入
ethernetif_input(&gnetif);
// 处理超时
sys_check_timeouts();
// 其他应用任务
// ...
// 短暂延时
HAL_Delay(1);
}
}
// 以太网初始化函数(由STM32CubeMX生成)
void MX_ETH_Init(void)
{
// 以太网外设初始化代码
// ...
}
// LWIP初始化函数
void MX_LWIP_Init(void)
{
ip_addr_t ipaddr, netmask, gw;
// 初始化IP地址(可以根据需要配置为静态或DHCP)
#if LWIP_DHCP
ip_addr_set_zero_ip4(&ipaddr);
ip_addr_set_zero_ip4(&netmask);
ip_addr_set_zero_ip4(&gw);
#else
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
}
3. 配置和优化建议
3.1 LWIP配置 (lwipopts.h)
// 重要配置选项
#define LWIP_TCP 1
#define TCP_MSS 1460
#define TCP_SND_BUF (4 * TCP_MSS)
#define TCP_WND (4 * TCP_MSS)
#define LWIP_NETIF_HOSTNAME 1
#define LWIP_NETCONN 0
#define LWIP_SOCKET 0
#define MEM_SIZE (16000)
#define PBUF_POOL_SIZE (24)
#define PBUF_POOL_BUFSIZE (1524)
#define LWIP_STATS 0
#define LWIP_PROVIDE_ERRNO 1
3.2 性能优化建议
-
内存管理:
- 根据实际连接数调整
MEM_SIZE
和 PBUF_POOL_SIZE
- 使用内存池管理Modbus响应缓冲区
-
连接管理:
- 实现连接超时机制,自动关闭空闲连接
- 限制最大连接数,防止资源耗尽
-
数据处理:
- 使用DMA进行以太网数据传输
- 优化Modbus数据处理函数,减少内存拷贝
-
错误处理:
- 实现完整的异常处理机制
- 添加日志记录功能,便于调试
4. 测试和验证
4.1 测试工具
- 使用Modbus TCP客户端工具(如ModScan32、qModMaster等)
- 使用网络调试工具(如Wireshark)捕获和分析数据包
4.2 测试步骤
- 验证网络连接和IP配置
- 测试基本Modbus功能(读取保持寄存器)
- 测试所有支持的功能码
- 测试异常情况处理(非法地址、非法值等)
- 测试多连接并发处理能力
- 测试长时间运行的稳定性
5. 部署注意事项
-
安全性:
- 考虑添加访问控制列表(ACL)
- 实现连接速率限制,防止DoS攻击
-
可靠性:
- 添加看门狗机制,确保系统稳定性
- 实现连接异常恢复机制
-
可维护性:
- 添加诊断功能,便于故障排查
- 实现远程配置和管理接口
这个实施方案提供了一个完整的Modbus TCP服务器框架,可以根据具体应用需求进行扩展和优化。代码已经考虑了STM32F407的资源限制和LWIP协议栈的特性,可以直接在目标硬件上运行。