简介:这份资源面向需要在STM32F4平台上实现跨系统文件传输的嵌入式开发者,尤其适合已具备一定RTOS与网络编程基础、正在做双机通信项目的工程师。工程将FreeRTOS实时操作系统、Fatfs文件系统与LWIP网络协议栈整合到STM32F4芯片上,并基于操作系统提供的socket接口建立FTP连接,从而与Linux或Windows主机互发文件,解决嵌入式设备与PC之间可靠传输数据的问题。压缩包共322个文件,以150个h头文件与130个c源文件为主体,另含少量txt说明、htm/html文档及工程配置与说明文件,整体约1.87MB,代码结构完整、便于按模块查阅。目前已有3195人学习下载。读者可从中获得一套可直接参考的移植与联调范例,理解FreeRTOS任务调度下LWIP与Fatfs的协同方式,掌握socket方式搭建FTP服务端的实现思路,并借鉴其目录组织与配置细节,用于自身项目的快速搭建与排错。1. 拿到这套 STM32F4 网络文件系统工程,我第一反应是翻到 FTP 任务那层有个做工业数据采集的朋友跟我吐槽,说现场设备要把日志文件通过网口丢给上位机,用 TFTP 嫌不可靠,用 SMB 又太重,最后选来选去还是 FTP 最省事。他手上正好有一块 STM32F4 的板子,外挂 SD 卡,跑着 FreeRTOS,想直接在上面搭一个 FTP 服务器。我听完就笑了,这套组合其实早就是嵌入式网络存储的经典搭配:STM32F4 负责硬件和协议栈,FreeRTOS 管任务调度,FATFS 管文件系统,LWIP 管 TCP/IP,FTP 服务器把这几层串起来,让上位机像访问普通服务器一样读写板子上的文件。这份工程就是把这四样东西揉在一起,目标很明确:板子插上网线、插上 SD 卡,PC 端用 FileZilla 或命令行 ftp 就能登录,上传下载文件。适合谁?适合正在做数据记录仪、远程文件管理、设备日志回传的嵌入式开发者,尤其是那些已经跑通 LWIP 裸机、但还没把文件系统和网络任务整合进 RTOS 的人。下面我按“先看结构、再跑起来、最后改得动”的顺序,把这份工程拆一遍。2. 工程架构与任务划分:从 main 到 FTP 会话的完整链路2.1 目录结构里藏着模块边界拿到源码包,先别急着编译。我一般会先扫一眼顶层目录,因为目录划分直接反映了作者对模块解耦的理解。这套工程常见的结构是这样的:project/ ├── Core/ │ ├── Inc/ │ │ ├── main.h │ │ ├── FreeRTOSConfig.h │ │ └── lwipopts.h │ └── Src/ │ ├── main.c │ ├── freertos.c │ ├── gpio.c │ ├── spi.c │ └── ethernetif.c ├── FATFS/ │ ├── App/ │ │ └── fatfs.c │ └── Target/ │ ├── sd_diskio.c │ └── ffconf.h ├── LWIP/ │ ├── App/ │ │ └── lwip.c │ └── Target/ │ └── ethernetif.c ├── FTP/ │ ├── ftp_server.c │ └── ftp_server.h └── Middlewares/ ├── FreeRTOS/ ├── FATFS/ └── LWIP/Core放的是 CubeMX 生成的初始化代码和 RTOS 配置,FATFS/Target里是 SD 卡底层驱动,LWIP/Target里是以太网 MAC 和 PHY 的对接,FTP目录才是业务核心。这种分法好处是:换 PHY 芯片只动LWIP/Target,换存储介质只动FATFS/Target,FTP 协议层几乎不用改。2.2 任务划分:为什么 FTP 要单独一个任务FreeRTOS 下跑 LWIP,通常有两种模式:NO_SYS=0 带操作系统,或者 NO_SYS=1 裸机。这套工程用的是前者,因为 FTP 需要阻塞等待、需要文件 IO,裸机轮询写起来太痛苦。典型任务划分如下:任务名优先级栈大小职责defaultTaskosPriorityNormal512 words系统心跳、状态灯lwipTaskosPriorityHigh1024 words调用MX_LWIP_Process或信号量驱动ftpTaskosPriorityAboveNormal2048 words监听 21 端口、处理 FTP 命令fileIOTaskosPriorityNormal1024 words可选的异步写卡,避免阻塞网络FTP 任务栈要给大一点,因为f_open、f_write内部有局部变量,加上 LWIP 的 pbuf 操作,栈溢出是新手最常见的翻车点。我一般会在FreeRTOSConfig.h里把configCHECK_FOR_STACK_OVERFLOW设为 2,先跑起来看有没有触发钩子。2.3 网络初始化:从 PHY 到 IP 的必经步骤在lwip.c里,初始化顺序不能乱。常见做法是:void MX_LWIP_Init(void) { /* 1. 初始化内存池和 pbuf 池 */ lwip_init(); /* 2. 添加网络接口,ethernetif_init 里会配置 MAC */ netif_add(&gnetif, &ipaddr, &netmask, &gw, NULL, ðernetif_init, &tcpip_input); /* 3. 设置为默认网卡并启动 */ netif_set_default(&gnetif); if (netif_is_link_up(&gnetif)) { netif_set_up(&gnetif); } else { netif_set_down(&gnetif); } /* 4. 启动 DHCP 或使用静态 IP */ dhcp_start(&gnetif); // 若用静态 IP 则注释掉 }参数说明:ipaddr、netmask、gw在lwipopts.h或main.h里定义。如果现场没有 DHCP 服务器,一定要改成静态 IP,否则dhcp_start会一直重试,FTP 客户端连不上。ethernetif_init里会调用low_level_init,那里配置 MAC 地址和 PHY 的自动协商。注意 PHY 地址要跟硬件原理图一致,常见是 0 或 1,填错就 ping 不通。2.4 FTP 协议层:命令解析与数据连接FTP 协议有两个连接:控制连接(21 端口)和数据连接(主动模式 20 端口,被动模式随机端口)。嵌入式端一般只实现被动模式,因为主动模式需要服务器主动连客户端,NAT 环境下很麻烦。核心逻辑在ftp_server.c:void ftp_task(void *argument) { struct netconn *conn, *newconn; err_t err; /* 创建 TCP 控制连接,监听 21 端口 */ conn = netconn_new(NETCONN_TCP); netconn_bind(conn, IP_ADDR_ANY, 21); netconn_listen(conn); for (;;) { /* 阻塞等待客户端连接 */ err = netconn_accept(conn, &newconn); if (err == ERR_OK) { /* 为每个客户端创建一个会话处理 */ ftp_session(newconn); netconn_close(newconn); netconn_delete(newconn); } } }ftp_session里是一个状态机:先发送220欢迎码,然后循环netconn_recv读取命令行,解析USER、PASS、PASV、LIST、RETR、STOR等命令。PASV命令要创建一个新的netconn绑定随机端口,把 IP 和端口拼成227 Entering Passive Mode (h1,h2,h3,h4,p1,p2)返回给客户端。数据连接建立后,RETR用f_read读文件,STOR用f_write写文件,每次读写 1024 字节左右,通过netconn_write或netconn_send发出去。注意:netconn_recv返回的 pbuf 必须用netbuf_delete释放,否则内存池很快耗尽,表现为连几次之后就无法登录。3. 跑通工程:从编译到 FileZilla 登录的每一步3.1 编译前必须检查的三个配置第一,FreeRTOSConfig.h里的configTOTAL_HEAP_SIZE。LWIP 的mem和pbuf如果使用 FreeRTOS 堆,这个值要够大,建议至少 30KB。第二,lwipopts.h里的MEM_SIZE和PBUF_POOL_SIZE。FTP 传输大文件时,PBUF_POOL_SIZE太小会导致丢包,我一般设 16 以上。第三,ffconf.h里的_FS_TINY和_USE_LFN。如果不需要长文件名,关掉_USE_LFN可以省不少 ROM。/* lwipopts.h 关键片段 */ #define MEM_SIZE (16 * 1024) #define PBUF_POOL_SIZE 16 #define TCP_SND_BUF (4 * TCP_MSS) #define TCP_WND (4 * TCP_MSS)TCP_SND_BUF和TCP_WND决定了发送窗口,太小会限制吞吐量。STM32F4 的 RAM 有 192KB,可以适当放大。3.2 硬件连接与上电顺序SD 卡通过 SPI 或 SDIO 连接,以太网通过 RMII 接口接 PHY。上电顺序建议:先插 SD 卡,再插网线,最后上电。因为MX_FATFS_Init里会调用f_mount,如果卡没插好,挂载失败,后面 FTP 的LIST命令会返回空目录。网线要在MX_LWIP_Init之前插好,否则 PHY 链路没起来,netif_is_link_up返回 false,IP 不会生效。3.3 用命令行先验证,再上图形客户端很多人一上来就用 FileZilla,结果连不上也不知道是哪层的问题。我习惯先用命令行:ftp 192.168.1.100 # 输入用户名和密码,工程里通常硬编码为 admin/admin # 登录成功后: ls put test.bin get test.bin bye如果ls能列出 SD 卡根目录,说明 FATFS 和 FTP 控制连接都正常。如果put失败,看返回码:550一般是文件系统写保护或路径不对,425是数据连接没建起来,检查PASV返回的 IP 是不是板子的实际 IP。有些路由器会给板子分配不同网段,PASV里返回的 IP 如果还是旧地址,客户端就连不上数据端口。3.4 常见连接失败排查表现象可能原因解决ping 不通PHY 地址错、网线没插、IP 冲突读 PHY ID 寄存器,确认地址;换 IP能 ping 通但 21 端口拒绝ftp_task 没创建或栈溢出看任务是否运行,加大栈登录后 ls 卡死SD 卡挂载失败或 SPI 速率太高降低 SPI 分频,重新 f_mount上传大文件断连pbuf 耗尽或 TCP 窗口太小增大 PBUF_POOL_SIZE 和 TCP_SND_BUF中文文件名乱码未启用 UTF-8 或 LFN启用 _USE_LFN 并设置代码页4. 避坑与常见问题:那几次让我熬夜的翻车现场4.1 栈溢出导致硬件错误现象:FTP 登录后执行ls,板子直接死机,串口打印 HardFault。原因:ftp_session里用了局部数组存文件名,加上f_readdir内部调用,栈需求超过默认的 128 words。解决:把ftpTask栈调到 2048 words,并在FreeRTOSConfig.h里打开栈溢出检测,钩子函数里打印任务名。4.2 pbuf 泄漏,连三次就再也连不上现象:连续登录三次后,第四次netconn_accept返回ERR_MEM。原因:ftp_session里收到netbuf后只用了netbuf_data,忘了netbuf_delete。解决:每个netconn_recv成功返回后,处理完必须释放。我一般会写一个ftp_free_netbuf辅助函数,确保每条分支都走到。4.3 SD 卡热插拔后文件系统崩溃现象:运行中拔卡再插,f_open返回FR_NO_FILESYSTEM。原因:FATFS 的工作区还在引用旧卡的数据,没有重新挂载。解决:检测到卡状态变化时,先f_mount(NULL, "", 0)卸载,再重新f_mount。如果用的是 SDIO,还要重新初始化外设。4.4 被动模式返回的 IP 是 0.0.0.0现象:FileZilla 登录成功,但LIST超时。原因:PASV命令里拼 IP 时用了gnetif.ip_addr,但 DHCP 还没完成,地址是 0。解决:在ftp_session里判断ip_addr_isany(&gnetif.ip_addr),如果是就返回425并提示稍后重试。或者干脆用静态 IP,省掉这个玄学问题。4.5 同时读写导致 FATFS 重入现象:一个客户端在下载,另一个客户端在列目录,板子卡死。原因:FATFS 默认不是线程安全的,f_read和f_opendir同时操作同一逻辑驱动器会破坏内部状态。解决:加互斥锁,或者限制同时只有一个 FTP 会话。简单做法是在ftp_task里用xSemaphoreTake保护所有文件操作。5. 二次开发:把 FTP 服务器改成你要的样子5.1 加权限控制:从硬编码到查表工程里默认用户名密码是写死的,二次开发时肯定要改。我一般会建一个用户表:typedef struct { const char *user; const char *pass; const char *root; // 该用户可见的根目录 uint8_t writable; } ftp_user_t; static const ftp_user_t users[] = { {"admin", "admin123", "/", 1}, {"guest", "guest", "/public", 0}, };在USER和PASS命令处理里遍历这个表,匹配成功后把root和writable存到会话上下文。后续RETR、STOR、LIST都基于root做路径拼接,并且检查writable决定是否允许写。这样不用改协议层,只加一个查表逻辑。5.2 提高传输速度:调整 TCP 参数和缓冲区STM32F4 的 RMII 接口在 100Mbps 下,实际 FTP 吞吐量通常能到 3~5MB/s。如果只有几百 KB/s,先查TCP_SND_BUF和TCP_WND,再查 SD 卡读写速度。SD 卡用 SPI 模式时,分频太高会丢数据,建议先降到 10MHz 以下测试,稳定后再逐步提高。另外,f_write每次写 512 字节的倍数效率最高,因为 FATFS 底层扇区是 512 字节。5.3 增加断点续传:REST 命令的实现FTP 的REST命令可以指定偏移量,配合RETR实现断点续传。在ftp_session里加一个rest_offset变量,收到REST时解析数字存起来,RETR时用f_lseek跳到该偏移再读。注意REST之后必须紧跟RETR,否则偏移量要清零。这个功能对现场大日志文件回传很实用,网络抖动断了不用从头传。5.4 验证方法:用脚本压测而不是手动点改完之后别只手动传一个小文件就完事。我一般会写一个简单的 Python 脚本,循环上传下载不同大小的文件,跑半小时看有没有内存泄漏或死机:from ftplib import FTP import os ftp = FTP() ftp.connect('192.168.1.100', 21) ftp.login('admin', 'admin123') for i in range(100): with open('test.bin', 'rb') as f: ftp.storbinary(f'STOR test_{i}.bin', f) with open(f'download_{i}.bin', 'wb') as f: ftp.retrbinary(f'RETR test_{i}.bin', f.write) ftp.quit()跑完之后看板子还剩多少堆,如果xPortGetFreeHeapSize持续下降,说明有泄漏,重点查netbuf和pbuf的释放路径。从那以后我每次改完网络相关的代码,都强制走一遍“命令行登录 → 小文件传输 → 大文件传输 → 并发连接 → 压测脚本”这五步,少一步都可能在上线后翻车。希望帮到你。本文还有配套的精品资源,点击获取
简介:这份资源面向需要在STM32F4平台上实现跨系统文件传输的嵌入式开发者,尤其适合已具备一定RTOS与网络编程基础、正在做双机通信项目的工程师。工程将FreeRTOS实时操作系统、Fatfs文件系统与LWIP网络协议栈整合到STM32F4芯片上,并基于操作系统提供的socket接口建立FTP连接,从而与Linux或Windows主机互发文件,解决嵌入式设备与PC之间可靠传输数据的问题。压缩包共322个文件,以150个h头文件与130个c源文件为主体,另含少量txt说明、htm/html文档及工程配置与说明文件,整体约1.87MB,代码结构完整、便于按模块查阅。目前已有3195人学习下载。读者可从中获得一套可直接参考的移植与联调范例,理解FreeRTOS任务调度下LWIP与Fatfs的协同方式,掌握socket方式搭建FTP服务端的实现思路,并借鉴其目录组织与配置细节,用于自身项目的快速搭建与排错。1. 拿到这套 STM32F4 网络文件系统工程,我第一反应是翻到 FTP 任务那层有个做工业数据采集的朋友跟我吐槽,说现场设备要把日志文件通过网口丢给上位机,用 TFTP 嫌不可靠,用 SMB 又太重,最后选来选去还是 FTP 最省事。他手上正好有一块 STM32F4 的板子,外挂 SD 卡,跑着 FreeRTOS,想直接在上面搭一个 FTP 服务器。我听完就笑了,这套组合其实早就是嵌入式网络存储的经典搭配:STM32F4 负责硬件和协议栈,FreeRTOS 管任务调度,FATFS 管文件系统,LWIP 管 TCP/IP,FTP 服务器把这几层串起来,让上位机像访问普通服务器一样读写板子上的文件。这份工程就是把这四样东西揉在一起,目标很明确:板子插上网线、插上 SD 卡,PC 端用 FileZilla 或命令行 ftp 就能登录,上传下载文件。适合谁?适合正在做数据记录仪、远程文件管理、设备日志回传的嵌入式开发者,尤其是那些已经跑通 LWIP 裸机、但还没把文件系统和网络任务整合进 RTOS 的人。下面我按“先看结构、再跑起来、最后改得动”的顺序,把这份工程拆一遍。2. 工程架构与任务划分:从 main 到 FTP 会话的完整链路2.1 目录结构里藏着模块边界拿到源码包,先别急着编译。我一般会先扫一眼顶层目录,因为目录划分直接反映了作者对模块解耦的理解。这套工程常见的结构是这样的:project/ ├── Core/ │ ├── Inc/ │ │ ├── main.h │ │ ├── FreeRTOSConfig.h │ │ └── lwipopts.h │ └── Src/ │ ├── main.c │ ├── freertos.c │ ├── gpio.c │ ├── spi.c │ └── ethernetif.c ├── FATFS/ │ ├── App/ │ │ └── fatfs.c │ └── Target/ │ ├── sd_diskio.c │ └── ffconf.h ├── LWIP/ │ ├── App/ │ │ └── lwip.c │ └── Target/ │ └── ethernetif.c ├── FTP/ │ ├── ftp_server.c │ └── ftp_server.h └── Middlewares/ ├── FreeRTOS/ ├── FATFS/ └── LWIP/Core放的是 CubeMX 生成的初始化代码和 RTOS 配置,FATFS/Target里是 SD 卡底层驱动,LWIP/Target里是以太网 MAC 和 PHY 的对接,FTP目录才是业务核心。这种分法好处是:换 PHY 芯片只动LWIP/Target,换存储介质只动FATFS/Target,FTP 协议层几乎不用改。2.2 任务划分:为什么 FTP 要单独一个任务FreeRTOS 下跑 LWIP,通常有两种模式:NO_SYS=0 带操作系统,或者 NO_SYS=1 裸机。这套工程用的是前者,因为 FTP 需要阻塞等待、需要文件 IO,裸机轮询写起来太痛苦。典型任务划分如下:任务名优先级栈大小职责defaultTaskosPriorityNormal512 words系统心跳、状态灯lwipTaskosPriorityHigh1024 words调用MX_LWIP_Process或信号量驱动ftpTaskosPriorityAboveNormal2048 words监听 21 端口、处理 FTP 命令fileIOTaskosPriorityNormal1024 words可选的异步写卡,避免阻塞网络FTP 任务栈要给大一点,因为f_open、f_write内部有局部变量,加上 LWIP 的 pbuf 操作,栈溢出是新手最常见的翻车点。我一般会在FreeRTOSConfig.h里把configCHECK_FOR_STACK_OVERFLOW设为 2,先跑起来看有没有触发钩子。2.3 网络初始化:从 PHY 到 IP 的必经步骤在lwip.c里,初始化顺序不能乱。常见做法是:void MX_LWIP_Init(void) { /* 1. 初始化内存池和 pbuf 池 */ lwip_init(); /* 2. 添加网络接口,ethernetif_init 里会配置 MAC */ netif_add(&gnetif, &ipaddr, &netmask, &gw, NULL, ðernetif_init, &tcpip_input); /* 3. 设置为默认网卡并启动 */ netif_set_default(&gnetif); if (netif_is_link_up(&gnetif)) { netif_set_up(&gnetif); } else { netif_set_down(&gnetif); } /* 4. 启动 DHCP 或使用静态 IP */ dhcp_start(&gnetif); // 若用静态 IP 则注释掉 }参数说明:ipaddr、netmask、gw在lwipopts.h或main.h里定义。如果现场没有 DHCP 服务器,一定要改成静态 IP,否则dhcp_start会一直重试,FTP 客户端连不上。ethernetif_init里会调用low_level_init,那里配置 MAC 地址和 PHY 的自动协商。注意 PHY 地址要跟硬件原理图一致,常见是 0 或 1,填错就 ping 不通。2.4 FTP 协议层:命令解析与数据连接FTP 协议有两个连接:控制连接(21 端口)和数据连接(主动模式 20 端口,被动模式随机端口)。嵌入式端一般只实现被动模式,因为主动模式需要服务器主动连客户端,NAT 环境下很麻烦。核心逻辑在ftp_server.c:void ftp_task(void *argument) { struct netconn *conn, *newconn; err_t err; /* 创建 TCP 控制连接,监听 21 端口 */ conn = netconn_new(NETCONN_TCP); netconn_bind(conn, IP_ADDR_ANY, 21); netconn_listen(conn); for (;;) { /* 阻塞等待客户端连接 */ err = netconn_accept(conn, &newconn); if (err == ERR_OK) { /* 为每个客户端创建一个会话处理 */ ftp_session(newconn); netconn_close(newconn); netconn_delete(newconn); } } }ftp_session里是一个状态机:先发送220欢迎码,然后循环netconn_recv读取命令行,解析USER、PASS、PASV、LIST、RETR、STOR等命令。PASV命令要创建一个新的netconn绑定随机端口,把 IP 和端口拼成227 Entering Passive Mode (h1,h2,h3,h4,p1,p2)返回给客户端。数据连接建立后,RETR用f_read读文件,STOR用f_write写文件,每次读写 1024 字节左右,通过netconn_write或netconn_send发出去。注意:netconn_recv返回的 pbuf 必须用netbuf_delete释放,否则内存池很快耗尽,表现为连几次之后就无法登录。3. 跑通工程:从编译到 FileZilla 登录的每一步3.1 编译前必须检查的三个配置第一,FreeRTOSConfig.h里的configTOTAL_HEAP_SIZE。LWIP 的mem和pbuf如果使用 FreeRTOS 堆,这个值要够大,建议至少 30KB。第二,lwipopts.h里的MEM_SIZE和PBUF_POOL_SIZE。FTP 传输大文件时,PBUF_POOL_SIZE太小会导致丢包,我一般设 16 以上。第三,ffconf.h里的_FS_TINY和_USE_LFN。如果不需要长文件名,关掉_USE_LFN可以省不少 ROM。/* lwipopts.h 关键片段 */ #define MEM_SIZE (16 * 1024) #define PBUF_POOL_SIZE 16 #define TCP_SND_BUF (4 * TCP_MSS) #define TCP_WND (4 * TCP_MSS)TCP_SND_BUF和TCP_WND决定了发送窗口,太小会限制吞吐量。STM32F4 的 RAM 有 192KB,可以适当放大。3.2 硬件连接与上电顺序SD 卡通过 SPI 或 SDIO 连接,以太网通过 RMII 接口接 PHY。上电顺序建议:先插 SD 卡,再插网线,最后上电。因为MX_FATFS_Init里会调用f_mount,如果卡没插好,挂载失败,后面 FTP 的LIST命令会返回空目录。网线要在MX_LWIP_Init之前插好,否则 PHY 链路没起来,netif_is_link_up返回 false,IP 不会生效。3.3 用命令行先验证,再上图形客户端很多人一上来就用 FileZilla,结果连不上也不知道是哪层的问题。我习惯先用命令行:ftp 192.168.1.100 # 输入用户名和密码,工程里通常硬编码为 admin/admin # 登录成功后: ls put test.bin get test.bin bye如果ls能列出 SD 卡根目录,说明 FATFS 和 FTP 控制连接都正常。如果put失败,看返回码:550一般是文件系统写保护或路径不对,425是数据连接没建起来,检查PASV返回的 IP 是不是板子的实际 IP。有些路由器会给板子分配不同网段,PASV里返回的 IP 如果还是旧地址,客户端就连不上数据端口。3.4 常见连接失败排查表现象可能原因解决ping 不通PHY 地址错、网线没插、IP 冲突读 PHY ID 寄存器,确认地址;换 IP能 ping 通但 21 端口拒绝ftp_task 没创建或栈溢出看任务是否运行,加大栈登录后 ls 卡死SD 卡挂载失败或 SPI 速率太高降低 SPI 分频,重新 f_mount上传大文件断连pbuf 耗尽或 TCP 窗口太小增大 PBUF_POOL_SIZE 和 TCP_SND_BUF中文文件名乱码未启用 UTF-8 或 LFN启用 _USE_LFN 并设置代码页4. 避坑与常见问题:那几次让我熬夜的翻车现场4.1 栈溢出导致硬件错误现象:FTP 登录后执行ls,板子直接死机,串口打印 HardFault。原因:ftp_session里用了局部数组存文件名,加上f_readdir内部调用,栈需求超过默认的 128 words。解决:把ftpTask栈调到 2048 words,并在FreeRTOSConfig.h里打开栈溢出检测,钩子函数里打印任务名。4.2 pbuf 泄漏,连三次就再也连不上现象:连续登录三次后,第四次netconn_accept返回ERR_MEM。原因:ftp_session里收到netbuf后只用了netbuf_data,忘了netbuf_delete。解决:每个netconn_recv成功返回后,处理完必须释放。我一般会写一个ftp_free_netbuf辅助函数,确保每条分支都走到。4.3 SD 卡热插拔后文件系统崩溃现象:运行中拔卡再插,f_open返回FR_NO_FILESYSTEM。原因:FATFS 的工作区还在引用旧卡的数据,没有重新挂载。解决:检测到卡状态变化时,先f_mount(NULL, "", 0)卸载,再重新f_mount。如果用的是 SDIO,还要重新初始化外设。4.4 被动模式返回的 IP 是 0.0.0.0现象:FileZilla 登录成功,但LIST超时。原因:PASV命令里拼 IP 时用了gnetif.ip_addr,但 DHCP 还没完成,地址是 0。解决:在ftp_session里判断ip_addr_isany(&gnetif.ip_addr),如果是就返回425并提示稍后重试。或者干脆用静态 IP,省掉这个玄学问题。4.5 同时读写导致 FATFS 重入现象:一个客户端在下载,另一个客户端在列目录,板子卡死。原因:FATFS 默认不是线程安全的,f_read和f_opendir同时操作同一逻辑驱动器会破坏内部状态。解决:加互斥锁,或者限制同时只有一个 FTP 会话。简单做法是在ftp_task里用xSemaphoreTake保护所有文件操作。5. 二次开发:把 FTP 服务器改成你要的样子5.1 加权限控制:从硬编码到查表工程里默认用户名密码是写死的,二次开发时肯定要改。我一般会建一个用户表:typedef struct { const char *user; const char *pass; const char *root; // 该用户可见的根目录 uint8_t writable; } ftp_user_t; static const ftp_user_t users[] = { {"admin", "admin123", "/", 1}, {"guest", "guest", "/public", 0}, };在USER和PASS命令处理里遍历这个表,匹配成功后把root和writable存到会话上下文。后续RETR、STOR、LIST都基于root做路径拼接,并且检查writable决定是否允许写。这样不用改协议层,只加一个查表逻辑。5.2 提高传输速度:调整 TCP 参数和缓冲区STM32F4 的 RMII 接口在 100Mbps 下,实际 FTP 吞吐量通常能到 3~5MB/s。如果只有几百 KB/s,先查TCP_SND_BUF和TCP_WND,再查 SD 卡读写速度。SD 卡用 SPI 模式时,分频太高会丢数据,建议先降到 10MHz 以下测试,稳定后再逐步提高。另外,f_write每次写 512 字节的倍数效率最高,因为 FATFS 底层扇区是 512 字节。5.3 增加断点续传:REST 命令的实现FTP 的REST命令可以指定偏移量,配合RETR实现断点续传。在ftp_session里加一个rest_offset变量,收到REST时解析数字存起来,RETR时用f_lseek跳到该偏移再读。注意REST之后必须紧跟RETR,否则偏移量要清零。这个功能对现场大日志文件回传很实用,网络抖动断了不用从头传。5.4 验证方法:用脚本压测而不是手动点改完之后别只手动传一个小文件就完事。我一般会写一个简单的 Python 脚本,循环上传下载不同大小的文件,跑半小时看有没有内存泄漏或死机:from ftplib import FTP import os ftp = FTP() ftp.connect('192.168.1.100', 21) ftp.login('admin', 'admin123') for i in range(100): with open('test.bin', 'rb') as f: ftp.storbinary(f'STOR test_{i}.bin', f) with open(f'download_{i}.bin', 'wb') as f: ftp.retrbinary(f'RETR test_{i}.bin', f.write) ftp.quit()跑完之后看板子还剩多少堆,如果xPortGetFreeHeapSize持续下降,说明有泄漏,重点查netbuf和pbuf的释放路径。从那以后我每次改完网络相关的代码,都强制走一遍“命令行登录 → 小文件传输 → 大文件传输 → 并发连接 → 压测脚本”这五步,少一步都可能在上线后翻车。希望帮到你。本文还有配套的精品资源,点击获取