全部例程

W55MH32-RTThread-TCP通信测试

W55MH32

更新于 2026年9月23日

本篇总结

在 W55MH32 上结合 RT-Thread RTOS 多线程实现 TCP 客户端与服务器双模式并发通信。

🔑 KEIL MDK、UART调试、硬件TOE引擎、TCP、RT-Thread、DHCP

硬件平台:本例程基于 W55MH32L-EVB 开发板运行。

采购推荐:点击前往 WIZnet 官方淘宝店铺 选购 W55MH32L-EVB

本示例演示在 W55MH32 上结合 RT-Thread RTOS 实现 TCP 客户端与服务器双模式并发通信。利用 RT-Thread 多线程特性,创建两个独立任务分别管理 Socket 0(TCP 客户端)和 Socket 1(TCP 服务器),通过注册临界区函数保证多线程操作硬件的线程安全。

前置工具准备

开始本例程前,请确认以下工具已安装:

  • RT-Thread Studio:基于 Eclipse 的 RT-Thread 集成开发环境,用于开发 RT-Thread 应用程序:点击下载
  • WIZ UartTool V1.0:串口调试工具:点击下载
  • SocketTester:网络调试工具:点击下载

RT-Thread 与 TCP 协议简介

RT-Thread 是一款由中国社区主导开发的开源、硬实时嵌入式操作系统(RTOS),诞生于 2006 年。它从最初的小巧内核演变成了组件丰富、生态完善的物联网操作系统平台,负责管理硬件资源、调度任务,并提供网络连接等高级功能。其 Nano 版最小仅需 3KB ROM 和 1KB RAM,采用抢占式调度,中断响应达微秒级。

TCP(Transmission Control Protocol)是一种面向连接的、可靠的传输层协议,用于在网络中可靠地传输数据。TCP 通过三次握手建立连接、四次挥手断开连接,提供可靠的数据传输、流量控制和拥塞控制。

  • 极致轻量:Nano 版最小仅需 3KB ROM 和 1KB RAM,可运行在 Cortex-M0 等资源有限的芯片上
  • 硬实时性:抢占式调度,高优先级任务立即抢占,中断响应达微秒级
  • 丰富生态:内置 TCP/IP 协议栈、文件系统、GUI 引擎、MQTT 等大量软件包
  • 开源友好:Apache License 2.0 协议,可免费用于商业产品,无需公开源码

W55MH32 + RT-Thread 的优势

W55MH32 的硬件 TCP/IP 协议栈(TOE)与 RT-Thread 多线程结合,可实现真正的并发网络通信。每个 Socket 在独立线程中运行状态机,代码结构清晰,且可设置不同优先级。通过注册临界区函数,保证多线程操作硬件寄存器的线程安全。

硬件连接与拓扑

请注意:

测试需要 PC 端和 W55MH32 处于同一网段。

  1. Micro USB 线连接 DEBUG USB 口到 PC(供电 + 调试串口)
  2. 网线连接开发板 RJ45 网口 到路由器 LAN 口
┌─────────────────────────────────┐
│ 路由器(DHCP Server) │
│ 192.168.1.1 │
└────┬──────────────────┬───────────┘
│ │
┌────┴──────┐ ┌────┴──────┐
│ W55MH32 │ │ PC │
│ TCP:8084 │ │SocketTester│
│ IP: DHCP │ │ IP: DHCP │
└───────────┘ └───────────┘
↑ ↑
DEBUG USB ──→ WIZ UartTool
网络拓扑:开发板与 PC 在同一局域网内,通过路由器互通

工程结构

工程位于 SDK 内 1.SDK\ModuleDemo\RT-Thread\TCP\,核心文件如下:

RT-Thread TCP/
├── Project/
│   └── Project.uvprojx       ← Keil 工程文件
└── USER/
    ├── main.c                ← RT-Thread 线程创建 + 网络初始化
    ├── Loopback/
    │   ├── loopback.c        ← TCP 客户端/服务器回环核心
    │   └── loopback.h
    ├── wiz_interface/
    │   ├── wiz_interface.c   ← DHCP / PHY 检测 / 网络初始化
    │   └── wiz_interface.h
    └── uart/
        ├── bsp_uart.c        ← 调试串口 + printf 重定向
        └── bsp_uart.h

步骤1:网络参数与 Socket 分配

文件:USER/main.c。定义 TCP 客户端和服务器各自的 Socket 号、目标 IP/端口以及本地监听端口。

#define SOCKET_TCPC           0  /* TCP客户端Socket号 */
#define SOCKET_TCPS           1  /* TCP服务器Socket号 */
#define ETHERNET_BUF_MAX_SIZE (1024 * 2) /* 网络缓冲区大小(2KB) */

/* TCP客户端:目标服务器IP+端口 */
uint8_t  dest_ip[4] = {192, 168, 1, 120};
uint16_t dest_port  = 8087;
/* TCP服务器:本地监听端口 */
uint16_t local_port = 8084;

W55MH32 的 TOE 引擎支持 8 个独立 Socket(0~7)。本例程将 Socket 0 分配给 TCP 客户端,Socket 1 分配给 TCP 服务器,两者可同时运行。TCP 客户端主动连接 PC 端的 8087 端口,TCP 服务器监听本地 8084 端口等待 PC 连接。

步骤2:RT-Thread 线程配置

文件:USER/main.c。为 TCP 客户端和服务器分别定义线程优先级和栈大小。

/* TCP客户端线程参数 */
#define TASK_TCPC_PRIO     1
#define TASK_TCPC_STK_SIZE 256
static rt_thread_t Task_TCPC_Handler = RT_NULL;

/* TCP服务器线程参数 */
#define TASK_TCPS_PRIO     1
#define TASK_TCPS_STK_SIZE 256
static rt_thread_t Task_TCPS_Handler = RT_NULL;

两个线程优先级均为 1(相同优先级,时间片轮转),栈大小 256(单位为系统字节)。线程栈大小不可过小,否则会导致线程崩溃。

步骤3:系统初始化与线程启动

文件:USER/main.c | 函数:app_init() — 初始化 TOE 引擎、注册临界区函数、检测 PHY 链路、获取网络地址,然后创建并启动双 TCP 线程。

int app_init(void)
{
    rt_kprintf("%s RTThread TCP example\n", _WIZCHIP_ID_);
    wiz_toe_init(); /* W55MH32 TOE引擎初始化 */
    /* 注册RT-Thread临界区函数(线程安全) */
    reg_wizchip_cris_cbfunc(rt_enter_critical, rt_exit_critical);
    wiz_phy_link_check();     /* 检测以太网物理链路 */
    network_init(tcpc_ethernet_buf, &default_net_info);

    /* 开启Keepalive */
    setSn_KPALVTR(SOCKET_TCPC, 6); /* 30s保活 */
    setSn_KPALVTR(SOCKET_TCPS, 6);

    /* 创建并启动双TCP线程 */
    Task_TCPC_Handler = rt_thread_create("Task TCP Client",
        Task_TCPC, RT_NULL, TASK_TCPC_STK_SIZE, TASK_TCPC_PRIO, 20);
    Task_TCPS_Handler = rt_thread_create("Task TCP Server",
        Task_TCPS, RT_NULL, TASK_TCPS_STK_SIZE, TASK_TCPS_PRIO, 20);
    rt_thread_startup(Task_TCPC_Handler);
    rt_thread_startup(Task_TCPS_Handler);
    return 0;
}
INIT_APP_EXPORT(app_init); /* RT-Thread自动初始化 */

reg_wizchip_cris_cbfunc() 注册 RT-Thread 临界区函数(rt_enter_critical/rt_exit_critical),保证多线程操作硬件寄存器的线程安全。INIT_APP_EXPORT 宏将 app_init 注册到 RT-Thread 自动初始化序列,系统启动时自动调用。

RT-Thread 多线程关键点

线程安全:必须通过 reg_wizchip_cris_cbfunc() 注册 RT-Thread 临界区函数,否则多线程操作硬件寄存器会冲突。WIZnet 驱动在访问寄存器前会自动进入临界区,保证同一时间只有一个线程操作硬件。

步骤4:TCP 线程任务函数

文件:USER/Loopback/loopback.c。TCP 客户端线程循环调用 loopback_tcpc() 连接服务器并回环数据,TCP 服务器线程循环调用 loopback_tcps() 监听端口并回环数据。

/* TCP客户端线程:循环连接服务器+数据回环 */
void Task_TCPC(void *parameter)
{
    while (1)
    {
        loopback_tcpc(SOCKET_TCPC, tcpc_ethernet_buf, dest_ip, dest_port);
        rt_thread_mdelay(1); /* 让出CPU给其他线程 */
    }
}

/* TCP服务器线程:循环监听端口+数据回环 */
void Task_TCPS(void *parameter)
{
    while (1)
    {
        loopback_tcps(SOCKET_TCPS, tcps_ethernet_buf, local_port);
        rt_thread_mdelay(1); /* 让出CPU给其他线程 */
    }
}

每个线程循环末尾调用 rt_thread_mdelay(1) 让出 CPU 时间片,使两个线程交替运行。两个线程需使用各自独立的 ethernet_buf(tcpc_ethernet_buf 和 tcps_ethernet_buf),不可共用。

如果你想自定义

  • 改目标 IP/端口 → 修改 main.c 中 dest_ip 和 dest_port
  • 改监听端口 → 修改 local_port 的值
  • 增大吞吐量 → 增大 ETHERNET_BUF_MAX_SIZE 并调整 Socket 收发缓存
  • 线程栈大小 → 建议 ≥ 256,过小会导致线程崩溃

运行验证

烧录例程运行后,首先进行 PHY 链路检测、DHCP 获取 IP,然后 TCP 初始化完成并开始监听端口。打开 WIZ UartTool V1.0,连接 DEBUG USB 对应 COM 口(115200 / 8N1),观察开发板串口输出。

W55MH32 串口输出 — RT-Thread TCP 初始化(PHY 链路 + DHCP + TCP 客户端/服务器启动)
  • TCP 服务器测试(PC 端):打开网络调试工具设为 TCP Server 模式,IP 设为 PC 地址、端口 8087。W55MH32 的 TCP 客户端线程会主动连接该服务器,发送数据后设备原样回发。
  • TCP 客户端测试(PC 端):打开另一个窗口设为 TCP Client 模式,远程地址填 W55MH32 的 IP、端口 8084。连接后发送数据,设备原样回发,验证服务器功能。
SocketTester TCP 服务器/客户端 + WIZ UartTool 串口输出 — RT-Thread TCP 数据回环测试

注意事项

  • PC 端和 W55MH32 需同一网段
  • TCP 客户端连接失败会自动重连
  • 线程栈大小不可过小(建议 ≥ 256),否则线程崩溃
  • 提高吞吐量可增大 ETHERNET_BUF_MAX_SIZE 并调整 Socket 收发缓存


常见问题

Q: 为什么必须注册临界区函数

RT-Thread 多线程环境下,如果两个线程同时操作 W55MH32 的硬件寄存器(如 Socket 状态寄存器),会导致寄存器访问冲突。reg_wizchip_cris_cbfunc() 注册 rt_enter_critical/rt_exit_critical 后,WIZnet 驱动在访问寄存器前会自动进入临界区,保证同一时间只有一个线程操作硬件。

Q: 两个线程能否共用一个 ethernet_buf

不能。TCP 客户端和服务器线程会同时接收和发送数据,如果共用一个缓冲区,一个线程写入的数据可能被另一个线程覆盖,导致数据错乱。必须为每个线程分配独立的缓冲区(如 tcpc_ethernet_buf 和 tcps_ethernet_buf)。

Q: rt_thread_mdelay(1) 的作用是什么

rt_thread_mdelay(1) 让当前线程休眠 1 毫秒,释放 CPU 给其他线程执行。如果不加延时,当前线程会持续占用 CPU(因为 while 循环中没有阻塞操作),导致另一个线程无法运行。RT-Thread 的抢占式调度器在延时期间会切换到其他就绪线程。

Q: 与裸机多 Socket 方案有什么区别

裸机方案通过轮询(socket_sn++)依次处理每个 Socket,宏观上是"伪并发"。RT-Thread 方案通过多线程实现真正的并发处理,每个 Socket 在独立线程中运行,代码结构更清晰,且可设置不同优先级。但需要额外的 RTOS 开销和线程安全保护。

Q: INIT_APP_EXPORT 的作用

INIT_APP_EXPORT(app_init) 是 RT-Thread 的自动初始化宏,它将 app_init 函数注册到 RT-Thread 的自动初始化序列中。系统启动时会自动调用该函数,无需在 main 函数中手动调用。RT-Thread 提供了多个初始化级别(BOARD/DEVICE/COMPONENT/APP),APP 级别在系统启动的最后阶段执行。

总结

本文基于 W55MH32 硬件 TCP/IP 协议栈结合 RT-Thread RTOS 实现了 TCP 客户端与服务器双模式并发通信:

  • 双线程架构:RT-Thread 创建两个独立线程分别管理 TCP 客户端(Socket 0)和服务器(Socket 1)
  • 线程安全:reg_wizchip_cris_cbfunc() 注册 RT-Thread 临界区函数,保证硬件操作安全
  • 独立缓冲区:两个线程需使用各自独立的 ethernet_buf,不可共用
  • 让出 CPU:rt_thread_mdelay(1) 释放时间片,使两个线程交替运行
  • 自动初始化:INIT_APP_EXPORT 宏实现系统启动自动调用,无需手动干预

下载本章例程

我们提供完整的工程文件以及配套开发板,方便你随时测试,快速完成产品开发:

开发环境: Keil MDK5 配套开发板

Administrator

Wiznet 雇员