公司动态
RT-Thread网络驱动移植实战:在iCore3双核心板实现STM32F407与DM9161以太网通信
1. 项目背景与核心价值最近在折腾一块银杏科技的iCore3双核心板这板子挺有意思ARM和FPGA集成在一块板子上STM32F407作为主控旁边还配了个EP4CE10的FPGA。我手头有个项目需要设备具备稳定的网络通信能力同时还要处理一些实时性要求高的逻辑比如数据采集和预处理。裸机开发网络协议栈太费劲了LWIP配置起来也一堆坑所以就想到了RT-Thread这个国产的实时操作系统。它内置了SAL套接字抽象层和丰富的网络组件理论上能省不少事。但问题来了iCore3的官方资料和例程基本都是基于裸机或者FreeRTOS的关于RT-Thread的移植特别是网络部分的几乎找不到现成的参考。网上的信息也零零散散要么是纯STM32F4的移植要么是讲RT-Thread网络框架但没具体到硬件。所以这次移植更像是一次“摸着石头过河”的探索。我的目标很明确在iCore3的ARM核心STM32F407上成功运行RT-Thread并驱动板载的DM9161以太网PHY芯片实现基础的Ping、TCP客户端/服务器等网络功能为后续更复杂的应用比如通过TCP传输FPGA处理后的数据打下基础。这个过程里硬件初始化、驱动适配、网络协议栈配置每一步都得自己趟一遍。2. iCore3硬件平台与RT-Thread选型分析2.1 iCore3双核心板资源梳理在动手写代码之前必须把家底摸清楚。银杏科技的iCore3双核心板其核心资源分布决定了我们软件架构的设计。ARM核心 (STM32F407VGT6)这是本次移植的主战场。CPU: Cortex-M4内核带FPU主频168MHz性能应对RT-Thread和轻量级网络应用绰绰有余。内存: 192KB的SRAM其中64KB是CCM速度更快1MB的Flash。RT-Thread内核本身不大但网络协议栈和应用程序会占用不少RAM需要合理规划内存池。外设关键点: 重点关注以太网控制器ETH。STM32F407内置了MAC层我们需要通过RMII接口连接外部的PHY芯片。根据原理图确认了PHY是DM9161这是一个很常见的10/100M芯片。RMII的引脚如ETH_MII_TXD0/1, ETH_MII_RXD0/1, ETH_MII_CRS_DV, ETH_MII_REF_CLK等需要与PHY正确连接。此外用于PHY复位和地址选择的GPIO也要确认。时钟: 需要为ETH提供50MHz的参考时钟REF_CLK。在iCore3上这个时钟通常由外部有源晶振或通过STM32的MCO输出提供必须核对原理图确认来源这关系到PHY能否正常工作。FPGA核心 (Altera EP4CE10): 在本阶段ARM移植RT-Thread网络它暂时是“旁观者”但架构设计上要为后续联动留好接口。FPGA与ARM通过FSMCFlexible Static Memory Controller并行总线通信这为高速数据交换提供了硬件基础。我们的长远目标是让FPGA处理AD7606这类高速ADC的数据预处理后通过FSMC丢给ARM再由ARM通过RT-Thread的网络接口发送出去。因此在RT-Thread中我们需要规划一个高效的、基于DMA或中断的FSMC驱动线程。2.2 为什么选择RT-Thread而不是其他RTOS面对FreeRTOS、μC/OS等选择我最终选了RT-Thread主要是看中它在嵌入式网络生态上的“全家桶”优势。内置网络协议栈与SAL抽象层这是最吸引我的点。RT-Thread原生集成了LwIP一个轻量级TCP/IP协议栈并且通过SALSocket Abstraction Layer做了封装。这意味着我的应用程序可以直接调用标准的BSD Socket API如socket,bind,connect,send,recv底层无论是用LwIP还是后续切换为AT Socket、WIZnet硬件协议栈应用层代码几乎不用改。这对于需要稳定网络功能的产品来说大大降低了开发和维护成本。丰富的软件包生态通过RT-Thread的包管理工具pkgs --upgrade和menuconfig可以像搭积木一样添加功能。比如我可以轻松引入cJSON包来处理网络传输中的JSON数据引入webclient或mongoose来实现HTTP客户端/服务器甚至添加文件系统、GUI等组件。这对于iCore3这种资源相对丰富的平台能快速实现功能扩展。设备驱动框架RT-Thread的设备驱动框架rt_device统一了外设的访问接口open/close/read/write/control。一旦以太网设备驱动适配完成它就会像“一个网络设备文件”一样被系统管理上层应用通过设备名即可访问非常清晰。对国产芯片与社区的友好支持RT-Thread在国内嵌入式社区非常活跃对STM32系列芯片的支持已经相当成熟有大量的BSP板级支持包可以参考。虽然iCore3是特定厂商的板子但核心是STM32F4这为我们移植提供了坚实的基础。基于以上几点RT-Thread能让我更专注于iCore3上网络应用逻辑的实现而不是陷在协议栈的调试泥潭里。3. 构建基础工程与内核移植3.1 获取RT-Thread源码与创建工程我使用的是RT-Thread的源码开发方式这样灵活性最高。首先从GitHub克隆官方仓库git clone https://github.com/RT-Thread/rt-thread.git。不建议直接使用最新master分支可能会遇到不稳定问题我选择了一个长期支持版本LTS的分支比如v4.1.x。对于STM32F4系列RT-Thread已经提供了丰富的BSP。我们可以在rt-thread/bsp/stm32/stm32f407-atk-explorer这个目录下找到一份最接近的BSP。我们的策略是复制一份这个BSP并修改为iCore3的配置。复制BSP模板在bsp/stm32目录下新建一个文件夹命名为stm32f407-icore3然后将stm32f407-atk-explorer目录下的所有文件拷贝过来。修改工程基础配置进入新目录首要修改的是board/SConscript和board/Kconfig文件。SConscript决定了哪些源文件会被编译进工程。我们需要根据iCore3的实际硬件调整芯片型号、启动文件、链接脚本等。关键是要确认链接脚本board/linker_scripts/link.lds中的内存布局是否与STM32F407VGT6匹配Flash起始0x08000000长度1MRAM起始0x20000000长度192KB注意CCM内存的地址0x10000000通常需要特殊配置初期可先不使用。使用Env工具与menuconfigRT-Thread推荐使用其Env工具和scons进行构建。在stm32f407-icore3目录下打开Env首先执行menuconfig命令。这里要进行关键配置硬件配置在Hardware Drivers Config - On-chip Peripheral Drivers中使能需要用到的硬件如UART用于串口调试输出但先不要使能ETH等基础内核跑通再说。内核配置在Kernel Config中设置系统时钟频率RT_TICK_PER_SECOND通常为1000即1ms一个tick配置线程栈大小、优先级数量等。对于网络应用建议将默认线程栈调大一些比如从512调到1024。组件配置在RT-Thread Components - Network中勾选Enable network stack和Enable SAL (socket abstraction layer)。在SAL下层选择Enable lwIP stack。同时为了调试网络建议使能Ping command和NetIO command。软件包暂时不添加复杂软件包保持工程简洁。配置完成后保存退出。在Env中执行scons --targetmdk5或iar、vscode生成MDK工程文件。用Keil MDK打开生成的project.uvprojx编译下载到iCore3如果能看到串口输出RT-Thread的版本信息和shell提示符msh 那么恭喜你RT-Thread内核已经在iCore3上成功运行了这是万里长征第一步。3.2 时钟树与引脚复用配置网络功能依赖精确的时钟。STM32F407的ETH外设需要几个特定的时钟SYSCLK: 系统时钟我们配置为168MHz。HCLK: AHB总线时钟通常与SYSCLK同频168MHz。PCLK1: APB1外设时钟通常为42MHz。PCLK2: APB2外设时钟通常为84MHz。ETH时钟: 这是关键。RMII接口需要一个50MHz的REF_CLK提供给PHYDM9161。这个时钟可以来自外部有源晶振最稳定可靠的方式。检查iCore3原理图看是否有独立的50MHz晶振连接到PHY的REF_CLK引脚。STM32的MCO输出如果板子没有外部晶振则需要配置STM32的PA8引脚MCO1输出50MHz时钟。这需要通过PLL配置来实现稳定性稍逊于专用晶振。在board.c文件的rt_hw_board_init()函数中或在单独的drv_clk.c文件中我们需要仔细配置RCC确保上述时钟正确产生。特别是如果使用MCO输出配置代码如下所示需根据实际PLL分频系数调整// 示例配置MCO1输出PLL时钟需计算分频得到50MHz void SystemClock_Config(void) { // ... 其他PLL、系统时钟配置 ... // 假设PLL时钟为168MHz RCC_PLLConfig(RCC_PLLSource_HSE, ...); // 配置PLL源和倍频 // 配置MCO1输出PLL时钟并2分频得到84MHz不对我们需要50MHz。 // 更常见的做法是如果PLL时钟是168MHz可以通过PLL的Q分频器产生一个50MHz的时钟供给ETH然后通过MCO输出检查。 // 实际上STM32F4有一个专用的PLLPLL48M用于产生USB和RNG的48MHz时钟ETH的50MHz通常由外部提供或另一个PLLPLLI2S产生。 // 因此强烈建议查阅iCore3原理图确认50MHz REF_CLK的来源。如果是外部晶振则只需在RCC中使能ETH时钟即可。 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_ETH_MAC, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_ETH_MAC_Tx, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_ETH_MAC_Rx, ENABLE); }引脚复用则在drv_gpio.c或board.h中配置。我们需要将ETH相关的RMII引脚PG13, PG14, PC1, PC4, PC5, PA1, PA2, PA7等具体查数据手册设置为复用功能AF11ETH。同时配置连接PHY的复位引脚比如PE2和地址引脚如果有多片PHY为普通推挽输出。注意STM32F4的引脚复用功能编号AF11是固定的但具体哪个引脚对应哪个信号必须严格参照芯片数据手册和板子原理图。配置错误会导致PHY无法链接。4. DM9161 PHY驱动适配与LwIP集成4.1 理解RT-Thread的网络设备驱动模型RT-Thread中一个网络接口被抽象为一个“网络设备”netdev它建立在更基础的“设备”rt_device框架之上。对于以太网我们需要完成两部分驱动MAC层驱动即STM32F4内置ETH外设的驱动。这部分RT-Thread的BSP通常已经实现好了在drivers/drv_eth.c中它负责DMA描述符配置、数据包收发中断处理、与LwIP的接口ethernetif对接。PHY层驱动即外部芯片DM9161的驱动。我们需要实现PHY的初始化、状态读取链接、速度、双工模式、软复位等操作。这部分需要我们自己根据DM9161的数据手册来编写。驱动的工作流程是上电后MAC驱动初始化ETH外设然后调用PHY驱动初始化DM9161。PHY驱动会通过SMIMDC/MDIO总线不断轮询或中断检测链接状态。一旦检测到链接建立PHY驱动会将链接参数速度、双工配置给MAC层然后MAC层驱动会调用LwIP的netif_set_link_up函数通知网络接口就绪。随后应用层就可以进行Socket通信了。4.2 编写DM9161 PHY驱动在drivers目录下新建或修改一个drv_phy.c和drv_phy.h。首先实现最基本的SMI总线读写函数。SMI是两线制MDC时钟MDIO数据的同步串行接口。我们可以利用STM32的ETH外设自带的SMI控制器也可以通过GPIO模拟。为了可靠性和效率强烈建议使用硬件SMI。RT-Thread的STM32 BSP中通常已有SMI的底层读写函数eth_device_ready,eth_device_read等我们需要封装针对DM9161的读写。// drv_phy.h #define PHY_ADDRESS 0x0 // DM9161的地址根据板子硬件连接确定通常为0或1 // drv_phy.c #include drv_eth.h static rt_err_t dm9161_init(rt_uint32_t phy_addr) { rt_uint16_t reg_value; rt_err_t result RT_EOK; // 1. 软件复位PHY phy_write(phy_addr, PHY_REG_BMCR, PHY_BMCR_RESET); rt_thread_delay(RT_TICK_PER_SECOND / 50); // 等待复位完成至少1ms // 读取复位位直到它自动清零 do { reg_value phy_read(phy_addr, PHY_REG_BMCR); } while (reg_value PHY_BMCR_RESET); // 2. 配置PHY工作模式可选也可使用自动协商 // 例如强制设置为100M全双工 // phy_write(phy_addr, PHY_REG_BMCR, PHY_BMCR_SPEED_100 | PHY_BMCR_FULLDUPLEX | PHY_BMCR_AUTONEG_DISABLE); // 更推荐使用自动协商 phy_write(phy_addr, PHY_REG_BMCR, PHY_BMCR_AUTONEG_ENABLE); // 重启自动协商 phy_write(phy_addr, PHY_REG_BMCR, phy_read(phy_addr, PHY_REG_BMCR) | PHY_BMCR_RESTART_AUTONEG); // 3. 等待自动协商完成 rt_uint32_t timeout 0; while (timeout 500) { // 等待约5秒 reg_value phy_read(phy_addr, PHY_REG_BMSR); if (reg_value PHY_BMSR_AUTONEG_COMPLETE) { break; } rt_thread_delay(RT_TICK_PER_SECOND / 100); // 延时10ms } if (timeout 500) { rt_kprintf(PHY auto-negotiation timeout!\n); result -RT_ETIMEOUT; } return result; } // PHY状态监测线程或定时器函数 static void phy_monitor_thread_entry(void *parameter) { rt_uint16_t status; struct rt_eth_device *eth_dev (struct rt_eth_device *)parameter; while (1) { status phy_read(PHY_ADDRESS, PHY_REG_BMSR); if (status PHY_BMSR_LINK_STATUS) { // 链接已建立 if (!(eth_dev-parent.user_data PHY_LINK_UP)) { // 之前是断开状态 rt_kprintf(PHY Link Up.\n); // 读取速度双工信息 rt_uint16_t ctrl phy_read(PHY_ADDRESS, PHY_REG_PHYSR); rt_uint32_t speed (ctrl PHY_PHYSR_SPEED_MASK) ? ETH_SPEED_100M : ETH_SPEED_10M; rt_uint32_t duplex (ctrl PHY_PHYSR_DUPLEX) ? ETH_MODE_FULLDUPLEX : ETH_MODE_HALFDUPLEX; // 通知MAC驱动更新状态 eth_device_linkchange(eth_dev-parent, RT_TRUE); // 这里可以将speed和duplex信息传递给MAC驱动通过eth_dev-parent.user_data或其他方式 eth_dev-parent.user_data | PHY_LINK_UP; } } else { // 链接断开 if (eth_dev-parent.user_data PHY_LINK_UP) { rt_kprintf(PHY Link Down.\n); eth_device_linkchange(eth_dev-parent, RT_FALSE); eth_dev-parent.user_data ~PHY_LINK_UP; } } rt_thread_delay(RT_TICK_PER_SECOND * 2); // 每2秒检查一次 } }这个驱动提供了初始化和一个简单的轮询线程来监测链接状态。更优雅的方式是使用PHY的中断引脚如果有连接来触发状态检查。4.3 配置LwIP并绑定网络设备接下来需要在menuconfig中仔细配置LwIP。关键配置项位于RT-Thread Components - Network - light weight IP stack。内存池大小lwIP heap size和lwIP pbuf pool size需要根据应用调整。对于简单的Ping和TCP回显默认值可能够用。但如果要传输大量数据比如从FPGA过来的图像数据需要调大。我建议初始设置为lwIP heap size: 16384(16KB) 和pbuf pool size: 512。协议使能确保Enable IPv4、Enable UDP、Enable TCP、Enable ICMP用于Ping是开启的。DHCP如果希望iCore3从路由器自动获取IP使能Enable DHCP。调试阶段我建议先使用静态IP避免DHCP失败导致网络不可用。Socket数量Max number of open sockets和Max number of listening TCP sockets根据你的应用需求设置。配置保存后在board.h或rtconfig.h中定义静态IP地址如果不用DHCP#define LWIP_DHCP 0 // 关闭DHCP #define RT_LWIP_IPADDR 192.168.1.100 #define RT_LWIP_GWADDR 192.168.1.1 #define RT_LWIP_MSKADDR 255.255.255.0最后我们需要在应用初始化阶段显式地注册和初始化网络设备。通常在main.c或一个专门的app_network.c中#include rtthread.h #include netif/ethernetif.h #include drv_eth.h int phy_init(void) { // 初始化PHY if (dm9161_init(PHY_ADDRESS) ! RT_EOK) { rt_kprintf(DM9161 PHY init failed!\n); return -1; } rt_kprintf(DM9161 PHY init OK.\n); return 0; } INIT_APP_EXPORT(phy_init); // 使用自动初始化机制 int eth_init(void) { // 注册以太网设备设备名为 e0 eth_system_device_init(); // 查找名为e0的设备并初始化 rt_device_t dev rt_device_find(e0); if (dev) { rt_device_init(dev); rt_device_open(dev, RT_DEVICE_FLAG_RDWR); } return 0; } INIT_DEVICE_EXPORT(eth_init); // 在设备初始化阶段执行编译下载后如果一切顺利在串口shell中使用ifconfig命令应该能看到网络接口e0并且有IP地址如果是静态配置。此时插上网线PHY监测线程应该能打印出“Link Up”的信息。5. 网络功能测试与排坑实战5.1 基础连通性测试Ping与TCP Echo网络驱动起来后第一步是进行最基础的连通性测试。Ping测试在RT-Thread的shellmsh中使用ping命令。ping命令在menuconfig的Network - Utilities - Enable ping utility中启用。msh /ping 192.168.1.1如果看到来自网关的回复说明IP层和链路层基本正常。如果失败首先检查物理连接网线是否插好路由器/交换机对应端口灯是否亮IP配置iCore3的IP、网关、掩码是否与测试PC在同一网段PC的防火墙是否暂时关闭驱动状态ifconfig命令显示接口是UP状态吗PHY驱动打印“Link Up”了吗TCP Echo服务器测试RT-Thread的netutils组件中有一个简单的tcp_echo_server示例。在menuconfig中使能它RT-Thread online packages - IoT - internet of things - netutils: Network utilities for RT-Thread - Enable TCP echo server然后重启。 在msh中启动服务器msh /tcp_echo_server服务器默认监听端口5001。在PC上使用网络调试助手如NetAssist创建一个TCP客户端连接到iCore3的IP如192.168.1.100和端口5001。发送任意字符串服务器应该会原样返回。这个测试验证了TCP协议栈、Socket API和网络数据收发路径的完整性。5.2 常见问题与深度排坑在实际操作中我遇到了几个典型问题这里把排查思路分享出来问题一PHY始终无法链接Link Down现象ifconfig显示接口DOWNPHY监测线程一直打印“Link Down”。排查步骤查时钟这是最高频的坑。用示波器测量RMII_REF_CLK引脚通常是PA1看是否有稳定的50MHz方波。如果没有检查STM32的RCC配置确认MCO输出或外部晶振是否启用并正确分频。查复位测量PHY的复位引脚如PE2电平。上电后应该有一个从低到高的跳变低电平复位。确保驱动中的复位时序正确复位释放后给了足够的稳定时间DM9161要求至少1ms。查SMI通信在PHY初始化函数中尝试读取PHY的ID寄存器地址1和2。DM9161的ID通常是0x0181。如果读不出来或读错说明SMIMDC/MDIO通信失败。检查这两根线的上拉电阻通常需要4.7K上拉检查GPIO的复用功能配置是否正确AF11检查MDC时钟频率是否合适不能太高初期可设低速如1MHz。查RMII线序核对原理图确认TXD0/1, RXD0/1, CRS_DV, REF_CLK这7根线是否与STM32和PHY正确连接没有错位。问题二可以Ping通但TCP连接不稳定或速度极慢现象Ping延迟正常且稳定但TCP连接时断时续或者传输大文件时速度只有几十Kbps。排查步骤检查MAC驱动DMA描述符STM32的ETH驱动使用DMA搬运数据。描述符内存必须是非缓存Non-Cacheable的或者需要正确维护缓存一致性。在drv_eth.c中用于DMA描述符和缓冲区的大数组通常需要用__attribute__((section(.RxDecripSection)))和__attribute__((section(.TxDecripSection)))等方式放到特定的段section并在链接脚本中将这些段定位到SRAM而非CCM中并设置其属性为NOLOAD。这是最容易出错的地方之一。调整LwIP内存参数默认的MEMP_NUM_PBUF,MEMP_NUM_TCP_PCB等可能不够。在lwipopts.h或通过menuconfig中适当增加这些池的大小。特别是TCP_WNDTCP窗口大小和TCP_MSS最大报文段长度对于提高吞吐量很重要。可以尝试将TCP_WND增加到4*TCP_MSSTCP_MSS通常为1460。优化中断处理确保ETH的接收和发送中断优先级设置合理中断服务函数ISR中处理要快进快出只做必要的标记将耗时的处理如将数据包递交给LwIP放到线程中完成。检查是否因为中断处理太慢导致丢包。使用Wireshark抓包分析在PC端用Wireshark抓取与iCore3通信的包。观察TCP三次握手是否正常是否有大量的重传Retransmission或校验和错误Checksum Error。这能精确定位是网络层、传输层还是应用层的问题。问题三长时间运行后死机或内存泄漏现象设备运行几小时或几天后网络中断或系统卡死。排查步骤检查线程栈溢出网络相关线程如tcpip线程、phy_monitor线程、应用线程的栈空间是否足够可以在msh中使用list_thread命令查看各线程的栈使用情况max used列。如果使用率超过90%就需要在menuconfig中调大对应线程的栈大小。检查内存池耗尽使用list_mempool命令查看LwIP的各个内存池如PBUF_POOL,TCP_PCB等的使用情况。如果池被耗尽会导致网络操作失败。需要根据应用压力调整menuconfig中的相关配置。检查Socket资源释放应用程序中是否确保每个socket在使用后都正确调用了close特别是在出错处理路径上。资源未释放会逐渐耗尽Socket描述符。5.3 进阶测试实现一个简单的TCP数据转发服务为了模拟未来与FPGA协同工作的场景我编写了一个简单的TCP服务器线程。它监听一个端口接收客户端发来的数据并原样发回Echo同时统计接收到的字节数。这个例子包含了Socket编程的基本框架和常见错误处理。#include rtthread.h #include sys/socket.h #include netdb.h #include string.h #define TCP_ECHO_PORT 8080 #define TCP_BUFF_SIZE 2048 static void tcp_echo_server_entry(void *parameter) { int sock -1, connected -1; struct sockaddr_in server_addr, client_addr; socklen_t client_len; char *recv_data; int recv_len; recv_data (char *)rt_malloc(TCP_BUFF_SIZE); if (recv_data RT_NULL) { rt_kprintf(No memory for TCP buffer!\n); return; } // 创建Socket sock socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (sock 0) { rt_kprintf(TCP echo server: socket create failed.\n); goto __exit; } // 绑定地址和端口 server_addr.sin_family AF_INET; server_addr.sin_port htons(TCP_ECHO_PORT); server_addr.sin_addr.s_addr INADDR_ANY; // 监听所有网络接口 if (bind(sock, (struct sockaddr *)server_addr, sizeof(server_addr)) 0) { rt_kprintf(TCP echo server: bind failed.\n); goto __exit; } // 开始监听 if (listen(sock, 5) 0) { rt_kprintf(TCP echo server: listen failed.\n); goto __exit; } rt_kprintf(TCP echo server started, listen on port %d.\n, TCP_ECHO_PORT); while (1) { client_len sizeof(client_addr); // 等待客户端连接 connected accept(sock, (struct sockaddr *)client_addr, client_len); if (connected 0) { rt_kprintf(TCP echo server: accept failed.\n); continue; } rt_kprintf(A client connected, ip: %s, port: %d\n, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); // 与客户端通信 do { // 接收数据 recv_len recv(connected, recv_data, TCP_BUFF_SIZE - 1, 0); if (recv_len 0) { // 连接关闭或出错 break; } recv_data[recv_len] \0; // 添加字符串结束符方便打印 rt_kprintf(Received %d bytes: %s\n, recv_len, recv_data); // 回显数据 if (send(connected, recv_data, recv_len, 0) 0) { rt_kprintf(TCP echo server: send failed.\n); break; } } while (1); // 关闭客户端连接 closesocket(connected); rt_kprintf(Client disconnected.\n); } __exit: if (sock 0) closesocket(sock); if (recv_data) rt_free(recv_data); } int tcp_echo_server_start(void) { rt_thread_t tid; tid rt_thread_create(tcp_echo, tcp_echo_server_entry, RT_NULL, 2048, // 栈空间 10, // 优先级 10); // 时间片 if (tid ! RT_NULL) { rt_thread_startup(tid); return 0; } return -1; } MSH_CMD_EXPORT(tcp_echo_server_start, start a TCP echo server);将这个文件加入工程编译运行。在msh中执行tcp_echo_server_start就可以用网络调试工具连接测试了。这个简单的例子验证了RT-Thread网络栈的稳定性和基本功能为后续集成FPGA数据流打下了坚实基础。