新唐MCU技术
直播中

发生的方式

9年用户 1325经验值
擅长:处理器/DSP
私信 关注
[问答]

如何使用 LwIP 在 NuMaker-IoT-M467 上实现 Modbus TCP?

使用 LwIP 在 NuMaker-IoT-M467 上实现 Modbus TCP

回帖(1)

chumowei

2025-9-8 17:52:21

要在 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)测试连接。




关键配置项



  1. lwipopts.h 设置
    #define LWIP_TCP                1   // 启用TCP
    #define TCPIP_THREAD_STACKSIZE  1024
    #define MEM_SIZE                (16*1024) // 调整内存池大小

  2. NuMaker SDK 适配

    • 确保 ethernetif.c 正确驱动板载 PHY(如 IP101)。

    • 检查以太网引脚配置(参考 SYS_Init())。





常见问题排查



  • 连接失败:检查防火墙、IP 冲突。

  • 数据错误:验证 Modbus 字节序(大端)和响应长度。

  • 内存溢出:增大 MEM_SIZE 或减少并发连接数。


通过以上步骤,即可在 NuMaker-IoT-M467 上实现基础的 Modbus TCP 通信。根据实际需求扩展功能码(如 01、05、06 等)和错误处理机制。

举报

更多回帖

发帖
×
20
完善资料,
赚取积分