要在 NuMaker-IoT-M467 上使用 LwIP 实现 Modbus TCP,需依次完成硬件初始化、网络配置、Modbus TCP 服务器集成和数据处理。以下是步骤详解和代码示例:
1. 初始化硬件和 LwIP 协议栈
- 硬件初始化:启用以太网外设、配置时钟和 GPIO。
- LwIP 初始化:创建内存池、网卡接口、设置 IP 地址(DHCP/静态)。
#include "lwip/opt.h"
#include "lwip/init.h"
#include "netif/ethernet.h"
#include "lwip/netif.h"
#include "NuNetDev.h"
void lwip_init_system(void) {
// 初始化LwIP内核
tcpip_init(NULL, NULL);
// 添加网卡接口 (NuNetDev 为新唐提供的驱动)
struct netif netif;
ip_addr_t ip, netmask, gw;
IP4_ADDR(&ip, 192, 168, 1, 100); // 静态IP
IP4_ADDR(&netmask, 255, 255, 255, 0);
IP4_ADDR(&gw, 192, 168, 1, 1);
netif_add(&netif, &ip, &netmask, &gw, NULL, ethernetif_init, tcpip_input);
netif_set_default(&netif);
netif_set_up(&netif);
}
2. 创建 Modbus TCP 服务器
- 监听端口 502:使用 LwIP 的 TCP API 设置服务器。
- 接受客户端连接:为每个连接单独处理。
#include "lwip/sockets.h"
#include "sys_arch.h"
#define MODBUS_PORT 502
void modbus_tcp_task(void *arg) {
int server_sock, client_sock;
struct sockaddr_in server_addr, client_addr;
socklen_t len = sizeof(client_addr);
// 创建套接字
server_sock = lwip_socket(AF_INET, SOCK_STREAM, 0);
// 绑定端口
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(MODBUS_PORT);
server_addr.sin_addr.s_addr = INADDR_ANY;
lwip_bind(server_sock, (struct sockaddr*)&server_addr, sizeof(server_addr));
// 开始监听
lwip_listen(server_sock, 5);
while (1) {
// 接受客户端连接
client_sock = lwip_accept(server_sock, (struct sockaddr*)&client_addr, &len);
// 在新线程中处理客户端请求
sys_thread_new("modbus_client", handle_client, (void*)client_sock, 1024, 3);
}
}
3. 实现 Modbus 请求处理
- 解析请求:识别 Modbus 功能码和地址。
- 生成响应:根据功能码读取/写入寄存器数据。
void handle_client(void *arg) {
int sock = (int)arg;
uint8_t req_buf[256], resp_buf[256];
int bytes_received;
while (1) {
// 接收数据
bytes_received = lwip_recv(sock, req_buf, sizeof(req_buf), 0);
if (bytes_received <= 0) break; // 连接断开
// 解析Modbus头:事务ID(2B)|协议ID(2B)|长度(2B)|单元ID(1B)|功能码(1B)
uint16_t trans_id = (req_buf[0] << 8) | req_buf[1];
uint16_t length = (req_buf[4] << 8) | req_buf[5];
uint8_t func_code = req_buf[7];
// 构建响应头(保留部分字段)
memcpy(resp_buf, req_buf, 7); // 复制事务ID、协议ID、长度、单元ID
resp_buf[4] = 0; resp_buf[5] = 3; // 新长度(功能码+数据长度,示例)
// 处理功能码(示例:03读保持寄存器)
if (func_code == 0x03) {
uint16_t start_addr = (req_buf[8] << 8) | req_buf[9];
uint16_t reg_count = (req_buf[10] << 8) | req_buf[11];
// 填充响应数据:功能码 + 字节数 + 寄存器值
resp_buf[7] = 0x03; // 功能码
resp_buf[8] = reg_count * 2; // 字节数
read_registers(start_addr, reg_count, &resp_buf[9]); // 读取数据到resp_buf[9]之后
// 更新响应长度
uint16_t resp_len = 9 + reg_count * 2;
resp_buf[5] = resp_len - 6; // 长度字段(排除前6字节头)
// 发送响应
lwip_send(sock, resp_buf, resp_len, 0);
}
}
lwip_close(sock); // 关闭连接
}
4. 配置寄存器数据存储
在内存中定义虚拟寄存器数组,模拟设备数据:
#define REG_COUNT 100
uint16_t holding_registers[REG_COUNT]; // 保持寄存器
void read_registers(uint16_t addr, uint16_t count, uint8_t *out) {
for (int i = 0; i < count; i++) {
out[2*i] = holding_registers[addr + i] >> 8; // 高位
out[2*i + 1] = holding_registers[addr + i]; // 低位
}
}
5. 启动任务和调试
在 main() 中初始化并启动 Modbus 任务:
int main(void) {
hardware_init(); // 初始化系统时钟、以太网PHY等
lwip_init_system(); // 启动LwIP
// 创建Modbus主线程
sys_thread_new("modbus_server", modbus_tcp_task, NULL, 2048, 4);
while (1) {
sys_check_timeouts(); // 处理LwIP超时事件
}
}
- 调试工具:使用 Modbus 客户端(如 Modbus Poll、qModMaster)测试连接。
关键配置项
- lwipopts.h 设置:
#define LWIP_TCP 1 // 启用TCP
#define TCPIP_THREAD_STACKSIZE 1024
#define MEM_SIZE (16*1024) // 调整内存池大小
- NuMaker SDK 适配:
- 确保
ethernetif.c 正确驱动板载 PHY(如 IP101)。
- 检查以太网引脚配置(参考
SYS_Init())。
常见问题排查
- 连接失败:检查防火墙、IP 冲突。
- 数据错误:验证 Modbus 字节序(大端)和响应长度。
- 内存溢出:增大
MEM_SIZE 或减少并发连接数。
通过以上步骤,即可在 NuMaker-IoT-M467 上实现基础的 Modbus TCP 通信。根据实际需求扩展功能码(如 01、05、06 等)和错误处理机制。