基于STM32F407的 TFTP Server

前言

这篇文章记录了一个基于 STM32F407TFTP Server 项目从模板工程到最终可用版本的完整实现过程。项目的目标并不只是"把 TFTP 跑起来",而是做成一个真正能用于现场调试和文件管理的小系统:

  • 板端基于 STM32CubeMX 生成模板
  • 网络协议栈使用 LwIP
  • 文件系统使用 FatFs
  • 任务调度使用 FreeRTOS
  • 上位机使用 Python + Tkinter,提供图形化界面
  • 支持文件列表、上传、下载、删除、取消传输、设备重启

最终效果是:STM32F407 插上网线和存储介质后,可以像一个轻量文件服务器一样,通过上位机对设备文件进行管理。


一、项目目标

整个项目主要解决下面几个需求:

  1. STM32F407 上实现一个稳定的 TFTP Server
  2. 通过 FatFs 将 TFTP 读写直接映射到本地文件系统
  3. 使用 FreeRTOS 管理系统初始化顺序和后台任务
  4. 提供一个易用的上位机工具,而不是只靠命令行
  5. 解决大文件传输、取消传输、会话残留、栈溢出、超时等实际工程问题

二、系统架构

1. 板端软件栈

板端使用如下软件组件:

  • STM32CubeMX:生成外设初始化代码
  • HAL:底层外设驱动
  • FreeRTOS:任务调度
  • LwIP:网络协议栈
  • FatFs:文件系统
  • TFTP Server:基于 lwIP 官方 tftp_server.c

整体逻辑如下:

text 复制代码
STM32F407
 ├─ SDIO / 存储设备
 │   └─ FatFs
 ├─ ETH
 │   └─ LwIP
 │       └─ TFTP Server
 └─ FreeRTOS
     ├─ 文件系统初始化任务
     ├─ 网络初始化任务
     └─ TFTP 服务任务

2. 上位机软件栈

上位机使用纯 Python 标准库实现,无需安装额外第三方依赖:

  • socket:实现 TFTP 客户端
  • tkinter:图形化界面
  • threading:异步上传下载
  • queue:线程间状态和进度通信

三、板端实现思路

1. FreeRTOS 启动顺序控制

在带文件系统和网络协议栈的嵌入式项目里,启动顺序非常关键。如果顺序混乱,很容易出现:

  • 网络先启动,但配置文件还没读
  • TFTP 服务先注册,但 FatFs 还没挂载
  • 上位机请求过来时,底层资源还没就绪

因此项目里增加了一套基于 EventGroup 的启动同步机制,大致顺序如下:

  1. 初始化 FatFs
  2. 加载 config.json
  3. 按配置初始化 LwIP
  4. 启动 TFTP Server

这样可以保证 TFTP 服务启动时:

  • 存储已经挂载
  • 网络参数已经生效
  • 配置文件已经完成读取或恢复

2. FatFs 文件系统接入

文件系统部分的核心是把 TFTP 的文件操作回调映射到 FatFs

  • 打开文件:f_open
  • 读取文件:f_read
  • 写入文件:f_write
  • 关闭文件:f_close

这使得 TFTP 可以直接对设备内的文件进行上传和下载,而不需要额外再写一层"缓存数据库"或"中间抽象层"。

3. TFTP Server 接入

项目没有手写完整 TFTP 协议栈,而是复用了 lwIP 官方提供的 tftp_server.c,只实现它要求的 4 个回调:

  • open
  • close
  • read
  • write

这是一种很适合嵌入式项目的做法:

  • 协议逻辑交给成熟库
  • 设备逻辑只关注文件读写
  • 调试范围更清晰

4. 虚拟控制文件设计

为了让上位机不仅能传文件,还能做控制操作,项目设计了几个"虚拟文件"接口:

  • filelist.txt
    用于获取设备上的文件列表
  • _tftp_delete/<filename>
    用于删除指定文件
  • _tftp_reboot.req
    用于请求设备重启
  • config.json
    用于配置文件上传和替换

这种设计有个很大的优点:

  • 仍然保持 TFTP 的标准文件读写模型
  • 上位机实现简单
  • 设备端逻辑清晰

四、配置文件机制

项目中引入了 config.json 来配置网络和业务参数,例如:

  • IP 地址
  • 网关
  • 子网掩码
  • TFTP 开关
  • 串口记录参数
  • Modbus TCP 参数

为了避免配置文件损坏后设备无法启动,还做了三层保护:

  1. 优先加载 config.json
  2. 如果失败,则尝试恢复 _config_backup.json
  3. 如果备份也无效,则创建一份内置默认配置

这样即使用户上传了错误配置,也不会轻易把设备"搞死"。


五、上位机 GUI 设计

1. 为什么不只做命令行

命令行当然可以完成上传下载,但实际使用中会有几个问题:

  • 普通用户不愿意记命令
  • 没有传输状态提示
  • 出错时不够直观
  • 不适合频繁点选设备文件

所以最终把上位机做成了一个带界面的工具。

2. GUI 功能

图形界面支持:

  • 输入设备 IP、端口、超时、重试次数
  • 刷新远程文件列表
  • 选择远程文件下载
  • 选择本地文件上传
  • 删除远程文件
  • 请求设备重启
  • 显示活动日志
  • 显示上传下载进度条
  • 支持取消正在进行的传输

3. 上传文件名逻辑

早期版本里,上传时可能误用 GUI 中的 Remote name 文本框内容,导致:

  • 用户明明选择了本地文件 modbus_144.csv
  • 实际上传到设备的却是另一个旧文件名

最终修正为:

  • 上传时默认始终使用本地文件名
  • 选中文件后自动同步更新 Remote name

这样更符合用户直觉,也更不容易误操作。


六、工程中遇到的核心问题

这个项目真正有价值的地方,不只是功能实现,而是踩过并解决了一系列典型工程问题。

1. tcpip_thread 栈溢出

最开始点击刷新文件列表时,系统直接进了:

c 复制代码
vApplicationStackOverflowHook()

问题根因是:

  • lwIPtftp_server.c 回调运行在 tcpip_thread
  • 默认 TCPIP_THREAD_STACKSIZE 太小
  • 文件列表生成和格式化输出又增加了额外栈占用

最终处理:

  • 增大 TCPIP_THREAD_STACKSIZE
  • 增大 DEFAULT_THREAD_STACKSIZE
  • 避免在热点路径里使用较重的格式化输出

2. 文件名过长

一开始删除文件会报:

text 复制代码
Filename too long/not NULL terminated

原因是 lwIP 默认:

c 复制代码
#define TFTP_MAX_FILENAME_LEN 20

但像 _tftp_delete/xxx.bin 这种虚拟文件名很容易超过 20。

最终通过在 lwipopts.h 中覆盖该宏解决。

3. 只允许一个连接

取消传输或请求超时后,马上再发起下一次请求,经常会碰到:

text 复制代码
Only one connection at a time is supported

这是 lwIP 官方 TFTP 服务器的行为:同一时间只允许一个会话。

解决方式有两层:

  • 缩短设备端 TFTP 会话超时
  • GUI 端在取消后增加"释放等待期"

这样用户不会在旧会话尚未释放时立即触发新请求。

4. 下载时报 Wrong block number

这个问题出现在早期 PC 端下载重传逻辑中:

  • 客户端在等待下一包 DATA 时错误地重发了旧的 ACK
  • 而设备端官方服务器只接受当前块号对应的 ACK

最终将客户端下载逻辑改为:

  • 等待服务器自己重发
  • 不主动乱发旧 ACK

之后问题消失。

5. 大文件下载首包超时

现象是:

  • 小文件和删除操作都正常
  • 大文件下载时直接 download request timed out

根因是 lwIP 内存池不足,首个 DATA 包分配失败后没有明显错误输出。

最终通过增大以下参数解决:

  • MEM_SIZE
  • MEMP_NUM_PBUF
  • PBUF_POOL_SIZE

这也是嵌入式网络项目里非常典型的问题。

6. 没有进度条,用户误以为卡死

上传下载大文件时,如果只有一个"等待"状态,用户通常会觉得程序卡住了。

因此最终把上传和下载都做成了真实进度条:

  • 按实际传输字节推进
  • 已知总大小时显示百分比
  • 未知总大小时显示动态状态

7. 用户中途不想等,需要取消

大文件传输时,必须允许用户中断操作。

最终实现:

  • GUI 提供 Cancel Transfer
  • 后台线程轮询取消信号
  • socket 传输循环主动退出
  • 取消后进入短暂释放等待期,避免旧会话残留影响下一次操作

七、最终功能列表

当前版本已经支持以下能力:

板端

  • STM32F407 + LwIP + FatFs + FreeRTOS
  • TFTP 文件上传
  • TFTP 文件下载
  • TFTP 删除文件
  • TFTP 获取文件列表
  • TFTP 上传配置文件
  • TFTP 请求设备重启
  • 启动顺序同步
  • 配置文件恢复与默认配置创建

上位机

  • 图形化界面
  • 刷新文件列表
  • 双击下载
  • 选择本地文件上传
  • 真实进度条
  • 取消传输
  • 活动日志显示
  • 取消后的设备释放等待机制

八、项目中的经验总结

这个项目有几个很值得记录的经验。

1. 嵌入式网络项目,功能实现只是第一步

真正麻烦的往往不是"能不能跑起来",而是:

  • 会不会偶发超时
  • 会不会因资源不足而静默失败
  • 用户误操作时会不会进入异常状态

2. lwIP 默认配置不适合直接上生产

默认参数更偏向演示和轻量场景:

  • 栈小
  • 堆小
  • pbuf 少

一旦进入真实文件传输场景,几乎一定要调。

3. GUI 不是"锦上添花",而是稳定性的一部分

良好的 GUI 不只是好看,它还能减少误操作:

  • 进度条减少焦虑
  • 取消按钮提高可控性
  • 冷却等待减少协议冲突
  • 文件名自动同步减少错误上传

4. 设计"虚拟文件接口"非常适合 TFTP

相比额外定义一套私有命令协议,直接用虚拟文件做控制动作:

  • 简单
  • 稳定
  • 易调试
  • 和标准 TFTP 客户端也兼容

九、后续可继续优化的方向

虽然当前版本已经可用,但还有一些可继续优化的方向:

  • 上传取消后自动删除设备端半截文件
  • 下载取消后自动删除本地未完成文件
  • GUI 中文文案和编码统一
  • 增加速度显示和剩余时间估算
  • 增加文件覆盖确认
  • 增加配置文件在线编辑器
  • 增加多设备管理能力

十、结语

这个 STM32F407 TFTP Server 项目本质上是一次非常典型的嵌入式系统工程实践:

  • 底层有外设和文件系统
  • 中间有 RTOS 和网络协议栈
  • 上层有真实用户界面
  • 中间穿插着超时、栈、内存、协议状态机等工程问题

如果只看"实现一个 TFTP",这件事并不复杂;但如果目标是"做一个用户敢用、现场能跑、出问题还能定位"的系统,那就必须把这些细节都一一补齐。

这也是这个项目最有价值的地方。


附:项目关键点一览

板端关键模块

  • FreeRTOS 启动同步
  • FatFs 文件系统挂载
  • config.json 加载与恢复
  • LwIP 网络初始化
  • TFTP 回调文件映射

上位机关键模块

  • TFTP 客户端协议实现
  • Tkinter 图形界面
  • 异步传输线程
  • 进度条更新
  • 取消传输
  • 会话释放等待

如果你也在做类似项目,希望这篇记录能帮你少踩几个坑。

相关推荐
飞凌嵌入式2 小时前
如何用JishuShell在RK3588核心板上快速部署OpenClaw?
arm开发·人工智能·嵌入式硬件·openclaw
余生皆假期-2 小时前
永磁同步电机的星形 (Y) 和三角形 (Δ) 有何不同?
单片机·嵌入式硬件
点灯小铭2 小时前
基于单片机的空气质量检测仪系统设计
单片机·嵌入式硬件·毕业设计·课程设计·期末大作业
鸟电波2 小时前
硬件笔记——Allegro绘制PCB(未完待续)
笔记·嵌入式硬件·智能硬件
ai产品老杨3 小时前
异构计算时代的安防底座:基于 x86/ARM 双架构与多芯片适配的 AI 视频云平台架构解析
arm开发·人工智能·架构
悠哉悠哉愿意3 小时前
【单片机复习笔记】十三届国赛复盘2
笔记·单片机·嵌入式硬件
清风6666663 小时前
基于单片机的矿井温度、烟雾与甲烷检测通风报警系统设计
单片机·嵌入式硬件·毕业设计·课程设计·期末大作业
水云桐程序员3 小时前
单片机如何工作|单片机工作原理
单片机·嵌入式硬件
xingzhemengyou13 小时前
STM32 CAN总线设置多个滤波器
stm32