本篇总结
在 W55MH32 上结合 RT-Thread RTOS 多线程实现 UDP 客户端与服务器双模式并发通信。
🔑 KEIL MDK、UART调试、硬件TOE引擎、UDP、RT-Thread、DHCP
硬件平台:本例程基于 W55MH32L-EVB 开发板运行。
采购推荐:点击前往 WIZnet 官方淘宝店铺 选购 W55MH32L-EVB
本示例演示在 W55MH32 上结合 RT-Thread RTOS 实现 UDP 客户端与服务器双模式并发通信。利用 RT-Thread 多线程特性,创建两个独立任务分别管理 Socket 0(UDP 客户端)和 Socket 1(UDP 服务器),通过注册临界区函数保证多线程操作硬件的线程安全。UDP 无连接特性使数据收发更简洁高效。
前置工具准备
开始本例程前,请确认以下工具已安装:
- RT-Thread Studio:基于 Eclipse 的 RT-Thread 集成开发环境,用于开发 RT-Thread 应用程序:点击下载
- WIZ UartTool V1.0:串口调试工具:点击下载
- SocketTester:网络调试工具:点击下载
RT-Thread 与 UDP 协议简介
RT-Thread 是一款由中国社区主导开发的开源、硬实时嵌入式操作系统(RTOS),诞生于 2006 年。它从最初的小巧内核演变成了组件丰富、生态完善的物联网操作系统平台,负责管理硬件资源、调度任务,并提供网络连接等高级功能。其 Nano 版最小仅需 3KB ROM 和 1KB RAM,采用抢占式调度,中断响应达微秒级。
UDP(User Datagram Protocol)是一种无连接的、不可靠的传输层协议。与 TCP 不同,UDP 在发送数据前不需要建立连接,发送方直接将数据报发送给接收方,但不保证数据的可靠到达。UDP 适用于对实时性要求高、可容忍少量丢包的场景,如视频流、DNS 查询、IoT 传感器数据上报等。
- 极致轻量:Nano 版最小仅需 3KB ROM 和 1KB RAM,可运行在 Cortex-M0 等资源有限的芯片上
- 硬实时性:抢占式调度,高优先级任务立即抢占,中断响应达微秒级
- 丰富生态:内置 TCP/IP 协议栈、文件系统、GUI 引擎、MQTT 等大量软件包
- 开源友好:Apache License 2.0 协议,可免费用于商业产品,无需公开源码
W55MH32 + RT-Thread 的优势
W55MH32 的硬件 TCP/IP 协议栈(TOE)与 RT-Thread 多线程结合,可实现真正的并发网络通信。UDP 无需建立/断开连接,数据收发更简洁高效。每个 Socket 在独立线程中运行状态机,代码结构清晰,且可设置不同优先级。
硬件连接与拓扑
请注意:
测试需要 PC 端和 W55MH32 处于同一网段。
- Micro USB 线连接 DEBUG USB 口到 PC(供电 + 调试串口)
- 网线连接开发板 RJ45 网口 到路由器 LAN 口
┌─────────────────────────────────┐
│ 路由器(DHCP Server) │
│ 192.168.1.1 │
└────┬──────────────────┬───────────┘
│ │
┌────┴──────┐ ┌────┴──────┐
│ W55MH32 │ │ PC │
│ UDP:8080 │ │SocketTester│
│ IP: DHCP │ │ IP: DHCP │
└───────────┘ └───────────┘
↑ ↑
DEBUG USB ──→ WIZ UartTool
网络拓扑:开发板与 PC 在同一局域网内,通过路由器互通
工程结构
工程位于 SDK 内 1.SDK\ModuleDemo\RT-Thread\UDP\,核心文件如下:
RT-Thread UDP/
├── Project/
│ └── Project.uvprojx ← Keil 工程文件
└── USER/
├── main.c ← RT-Thread 线程创建 + 网络初始化
├── Loopback/
│ ├── loopback.c ← UDP 客户端/服务器回环核心
│ └── 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。定义 UDP 客户端和服务器各自的 Socket 号、目标 IP/端口以及本地监听端口。
#define SOCKET_UDPC 0 /* UDP客户端Socket号 */
#define SOCKET_UDPS 1 /* UDP服务器Socket号 */
#define ETHERNET_BUF_MAX_SIZE (1024 * 2) /* 网络缓冲区大小(2KB) */
/* UDP客户端:目标服务器IP+端口 */
uint8_t dest_ip[4] = {192, 168, 1, 147};
uint16_t dest_port = 8080;
/* UDP服务器:本地监听端口 */
uint16_t local_port = 8080;
W55MH32 的 TOE 引擎支持 8 个独立 Socket(0~7)。本例程将 Socket 0 分配给 UDP 客户端,Socket 1 分配给 UDP 服务器,两者可同时运行。UDP 客户端向 PC 端的 8080 端口发送数据,UDP 服务器监听本地 8080 端口等待 PC 发送数据。
步骤2:RT-Thread 线程配置
文件:USER/main.c。为 UDP 客户端和服务器分别定义线程优先级和栈大小。
/* UDP客户端线程参数 */
#define TASK_UDPC_PRIO 1
#define TASK_UDPC_STK_SIZE 256
static rt_thread_t Task_UDPC_Handler = RT_NULL;
/* UDP服务器线程参数 */
#define TASK_UDPS_PRIO 1
#define TASK_UDPS_STK_SIZE 256
static rt_thread_t Task_UDPS_Handler = RT_NULL;
两个线程优先级均为 1(相同优先级,时间片轮转),栈大小 256(单位为系统字节)。线程栈大小不可过小,否则会导致线程崩溃。
步骤3:系统初始化与线程启动
文件:USER/main.c | 函数:app_init() — 初始化 TOE 引擎、注册临界区函数、检测 PHY 链路、获取网络地址,然后创建并启动双 UDP 线程。
int app_init(void)
{
rt_kprintf("%s RTThread UDP 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(udpc_ethernet_buf, &default_net_info);
/* 创建并启动双UDP线程 */
Task_UDPC_Handler = rt_thread_create("Task UDP Client",
Task_UDPC, RT_NULL, TASK_UDPC_STK_SIZE, TASK_UDPC_PRIO, 20);
Task_UDPS_Handler = rt_thread_create("Task UDP Server",
Task_UDPS, RT_NULL, TASK_UDPS_STK_SIZE, TASK_UDPS_PRIO, 20);
rt_thread_startup(Task_UDPC_Handler);
rt_thread_startup(Task_UDPS_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:UDP 线程任务函数
文件:USER/Loopback/loopback.c。UDP 客户端线程循环调用 loopback_udpc() 发送数据并接收回环,UDP 服务器线程循环调用 loopback_udps() 监听端口并回环数据。
/* UDP客户端线程:循环向服务器发送数据+接收回环 */
void Task_UDPC(void *parameter)
{
while (1)
{
loopback_udpc(SOCKET_UDPC, udpc_ethernet_buf, dest_ip, dest_port);
rt_thread_mdelay(1); /* 让出CPU给其他线程 */
}
}
/* UDP服务器线程:循环监听端口+回环数据 */
void Task_UDPS(void *parameter)
{
while (1)
{
loopback_udps(SOCKET_UDPS, udps_ethernet_buf, local_port);
rt_thread_mdelay(1); /* 让出CPU给其他线程 */
}
}
每个线程循环末尾调用 rt_thread_mdelay(1) 让出 CPU 时间片,使两个线程交替运行。两个线程需使用各自独立的 ethernet_buf(udpc_ethernet_buf 和 udps_ethernet_buf),不可共用。UDP 无连接,sendto() 直接指定目标地址,recvfrom() 获取来源地址。
如果你想自定义
- 改目标 IP/端口 → 修改
main.c 中 dest_ip 和 dest_port
- 改监听端口 → 修改
local_port 的值
- 增大吞吐量 → 增大
ETHERNET_BUF_MAX_SIZE 并调整 Socket 收发缓存
- 线程栈大小 → 建议 ≥ 256,过小会导致线程崩溃
运行验证
烧录例程运行后,首先进行 PHY 链路检测、DHCP 获取 IP,然后 UDP 初始化完成并开始监听端口。打开 WIZ UartTool V1.0,连接 DEBUG USB 对应 COM 口(115200 / 8N1),观察开发板串口输出。
W55MH32 串口输出 — RT-Thread UDP 初始化(PHY 链路 + DHCP + UDP 客户端/服务器启动)
- UDP 服务器测试(PC 端):打开网络调试工具设为 UDP 模式,远程地址填 W55MH32 的 IP、端口 8080。发送数据后设备原样回发,验证服务器回环功能。
- UDP 客户端测试(PC 端):打开另一个窗口设为 UDP 模式,本地端口设为 8080。W55MH32 的 UDP 客户端线程会向该端口发送数据,PC 端接收后可回复,设备会打印接收到的数据。
SocketTester UDP 客户端 + WIZ UartTool 串口输出 — RT-Thread UDP 数据回环测试
注意事项
- PC 端和 W55MH32 需同一网段
- UDP 不保证可靠传输,少量丢包属正常现象
- 线程栈大小不可过小(建议 ≥ 256),否则线程崩溃
- 两个线程需使用各自独立的 ethernet_buf,不可共用
常见问题
Q: 为什么必须注册临界区函数
RT-Thread 多线程环境下,如果两个线程同时操作 W55MH32 的硬件寄存器(如 Socket 状态寄存器),会导致寄存器访问冲突。reg_wizchip_cris_cbfunc() 注册 rt_enter_critical/rt_exit_critical 后,WIZnet 驱动在访问寄存器前会自动进入临界区,保证同一时间只有一个线程操作硬件。
Q: 两个线程能否共用一个 ethernet_buf
不能。UDP 客户端和服务器线程会同时接收和发送数据,如果共用一个缓冲区,一个线程写入的数据可能被另一个线程覆盖,导致数据错乱。必须为每个线程分配独立的缓冲区(如 udpc_ethernet_buf 和 udps_ethernet_buf)。
Q: UDP 和 TCP 的区别是什么
UDP 是无连接协议,无需建立/断开连接,发送时直接指定目标地址(sendto),接收时获取来源地址(recvfrom)。TCP 是面向连接的协议,需三次握手建立连接、四次挥手断开连接。UDP 更轻量高效但不保证可靠传输,TCP 保证可靠传输但开销更大。UDP 适用于实时性要求高、可容忍丢包的场景。
Q: rt_thread_mdelay(1) 的作用是什么
rt_thread_mdelay(1) 让当前线程休眠 1 毫秒,释放 CPU 给其他线程执行。如果不加延时,当前线程会持续占用 CPU(因为 while 循环中没有阻塞操作),导致另一个线程无法运行。RT-Thread 的抢占式调度器在延时期间会切换到其他就绪线程。
Q: 与裸机多 Socket 方案有什么区别
裸机方案通过轮询(socket_sn++)依次处理每个 Socket,宏观上是"伪并发"。RT-Thread 方案通过多线程实现真正的并发处理,每个 Socket 在独立线程中运行,代码结构更清晰,且可设置不同优先级。但需要额外的 RTOS 开销和线程安全保护。
总结
本文基于 W55MH32 硬件 TCP/IP 协议栈结合 RT-Thread RTOS 实现了 UDP 客户端与服务器双模式并发通信:
- 双线程架构:RT-Thread 创建两个独立线程分别管理 UDP 客户端(Socket 0)和服务器(Socket 1)
- 线程安全:reg_wizchip_cris_cbfunc() 注册 RT-Thread 临界区函数,保证硬件操作安全
- 无连接特性:UDP 无需建立/断开连接,sendto() 直接发送,recvfrom() 获取来源地址
- 独立缓冲区:两个线程需使用各自独立的 ethernet_buf,不可共用
- 自动初始化:INIT_APP_EXPORT 宏实现系统启动自动调用,无需手动干预