一、硬件环境:
1.LAN8720模块:

引脚说明:

STM32,
开发环境,cubeide,cubemx。
2、界面端配置:

默认即可,当mac地址冲突时,修改mac地址。

LAN8720和LAN8742无特别大的差异,仅是功能的优化与提升的差别。

开启常用的配置,TCP与UDP相关的。
二、代码部分
lwipopts.h
c
// 基础配置
#define NO_SYS 0 // 启用RTOS(必须)
#define LWIP_SOCKET 1 // 启用Socket API(核心)
#define LWIP_TCP 1 // 启用TCP
#define LWIP_UDP 1 // 启用UDP
#define LWIP_IPV4 1 // 必须=1,否则IPv4相关宏被禁用
#define LWIP_IPV6 0 // 若不用IPv6,直接禁用(减少宏冲突)
#define LWIP_NETIF_LINK_CALLBACK 1 // 链路状态回调
// 内存配置(根据需求调整)
#define MEM_SIZE 16384 // 内存池大小
#define MEMP_NUM_TCP_PCB 5 // TCP连接数
#define TCP_MSS 1460 // TCP最大分段
#define TCP_WND 8192 // TCP窗口大小
// Socket相关
#define LWIP_SO_REUSE 1 // 依赖项:启用SO_REUSE系列选项
#define LWIP_SOCKET 1 // 确保Socket API已启用
#define LWIP_SO_RCVTIMEO 1 // 接收超时
#define LWIP_SO_SNDTIMEO 1 // 发送超时
#define MEMP_NUM_UDP_PCB 5 // UDP连接控制块(至少1,建议5)
#define LWIP_SO_RCVTIMEO 1 // 必须启用接收超时(否则setsockopt无效)
添加函数
lwip.c
c
#include "lwip/ip_addr.h" // 必须包含,否则宏无法展开
#include "lwip/ip4_addr.h" // 若用IPv4专用宏,需包含
#include "lwip/sys.h"
void MX_LWIP_Process(void)
{
// LwIP核心事件处理(标准实现)
sys_check_timeouts();
}
连接网络相关的信息
c
#include "lwip/sockets.h"
#include "lwip/netdb.h"
#define TCP_SERVER_PORT 8080 // 监听端口
#define TCP_BUF_SIZE 1024
#define TCP_SERVER_IP "192.168.1.5" // PC的实际IP
#define TCP_SERVER_PORT 8080 // PC服务端端口
#define RECV_BUF_SIZE 1024 // 接收缓冲区大小
#define CONNECT_RETRY_CNT 5 // 连接失败重试次数
#define UDP_TEST_PORT 8081
#define UDP_TARGET_IP "192.168.1.5" // PC 端的IP 地址
#define UDP_SEND_INTERVAL 1000 // 发送间隔(ms)
LAN8720 初始化 任务
c
void StartDefaultTask(void *argument)
{
/* init code for LWIP */
MX_LWIP_Init();
/* USER CODE BEGIN StartDefaultTask */
osDelay(1000);
uint32_t dhcp_timeout = 0;
ip4_addr_t ipaddr, netmask, gw;
printf("=== Network Init Start ===\r\n");
// DHCP自动获取IP(超时5秒切换静态IP)
#ifdef LWIP_DHCP
printf("Waiting for DHCP IP...\r\n");
while (netif_default->ip_addr.addr == 0)
{
MX_LWIP_Process(); // 处理LwIP事件
osDelay(100);
dhcp_timeout++;
if (dhcp_timeout > 50) // 5秒超时
{
printf("DHCP Timeout! Use Static IP\r\n");
// 配置静态IP(根据你的网段修改)
IP4_ADDR(&ipaddr, 192, 168, 1, 7);
IP4_ADDR(&netmask, 255, 255, 255, 0);
IP4_ADDR(&gw, 192, 168, 1, 1);
netif_set_addr(&netif_default, &ipaddr, &netmask, &gw);
break;
}
}
#else
// 直接用静态IP(CubeMX配置后无需此段)
IP4_ADDR(&ipaddr, 192, 168, 1, 100);
IP4_ADDR(&netmask, 255, 255, 255, 0);
IP4_ADDR(&gw, 192, 168, 1, 1);
netif_set_addr(&netif_default, &ipaddr, &netmask, &gw);
#endif
// 打印网络信息
printf("IP Address: %s\r\n", ip4addr_ntoa(&netif_default->ip_addr));
printf("Netmask : %s\r\n", ip4addr_ntoa(&netif_default->netmask));
printf("Gateway : %s\r\n", ip4addr_ntoa(&netif_default->gw));
printf("=== Network Init Success ===\r\n");
// 初始化完成,删除自身任务
osThreadTerminate(defaultTaskHandle);
}
TCP SERVER 任务
c
void StartTask02(void *argument)
{
int server_fd, client_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_addr_len = sizeof(client_addr);
char recv_buf[1024];
int recv_len;
// 1. 创建TCP Socket(增加错误日志)
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0)
{
printf("TCP Socket Create Failed! Err: %d\r\n", server_fd); // 打印具体错误码
osThreadTerminate(myTask02Handle);
return;
}
printf("TCP Socket Create Success! fd: %d\r\n", server_fd);
// 3. 绑定IP和端口(增加错误日志)
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY; // 监听所有网卡
server_addr.sin_port = htons(TCP_SERVER_PORT);
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
{
printf("TCP Bind Port %d Failed! Err: %d\r\n", TCP_SERVER_PORT, errno); // 打印绑定错误
close(server_fd);
osThreadTerminate(myTask02Handle);
return;
}
printf("TCP Bind Port %d Success!\r\n", TCP_SERVER_PORT);
// 4. 开始监听(增加错误日志,backlog设为1,减少资源占用)
if (listen(server_fd, 1) < 0)
{
printf("TCP Listen Failed! Err: %d\r\n", errno);
close(server_fd);
osThreadTerminate(myTask02Handle);
return;
}
printf("TCP Server: Listen on Port %d\r\n", TCP_SERVER_PORT);
// 5. 循环处理客户端连接
while (1)
{
// 等待客户端连接(阻塞)
client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_addr_len);
if (client_fd < 0)
{
printf("TCP Accept Failed!\r\n");
osDelay(100);
continue;
}
// 打印客户端信息
printf("TCP Client Connected: IP=%s, Port=%d\r\n",
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
// 6. 循环接收并回显数据
while (1)
{
// 接收客户端数据(阻塞,超时由LWIP_SO_RCVTIMEO控制)
recv_len = recv(client_fd, recv_buf, sizeof(recv_buf)-1, 0);
if (recv_len > 0)
{
recv_buf[recv_len] = '\0'; // 字符串结束符
printf("TCP Recv: %s (Len: %d)\r\n", recv_buf, recv_len);
// 回显数据给客户端
send(client_fd, recv_buf, recv_len, 0);
}
else if (recv_len == 0)
{
// 客户端主动关闭连接
printf("TCP Client Disconnected\r\n");
break;
}
else
{
// 接收错误
printf("TCP Recv Error! Err: %d\r\n", recv_len);
break;
}
}
// 关闭客户端连接
close(client_fd);
printf("TCP Connection Closed\r\n");
// 处理LwIP事件
MX_LWIP_Process();
osDelay(10);
}
}
UDP 任务
c
void StartTask03(void *argument)
{
int udp_fd;
struct sockaddr_in target_addr, local_addr;
char send_buf[64];
uint32_t send_count = 0;
struct timeval timeout; // 接收超时配置
// 1. 创建UDP Socket
udp_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (udp_fd < 0)
{
printf("UDP Socket Create Failed!\r\n");
osThreadTerminate(myTask03Handle);
return;
}
// 2. 设置UDP接收超时(关键:500ms超时,避免阻塞)
timeout.tv_sec = 0;
timeout.tv_usec = 500000; // 500毫秒
setsockopt(udp_fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
// 3. 绑定STM32本地端口(必须绑定,否则无法接收)
memset(&local_addr, 0, sizeof(local_addr));
local_addr.sin_family = AF_INET;
local_addr.sin_addr.s_addr = INADDR_ANY; // 监听所有网卡
local_addr.sin_port = htons(UDP_TEST_PORT); // 绑定8081端口
if (bind(udp_fd, (struct sockaddr *)&local_addr, sizeof(local_addr)) < 0)
{
printf("UDP Bind Local Port Failed! Err: %d\r\n", errno);
close(udp_fd);
osThreadTerminate(myTask03Handle);
return;
}
// 4. 配置PC的目标IP和端口
memset(&target_addr, 0, sizeof(target_addr));
target_addr.sin_family = AF_INET;
target_addr.sin_port = htons(UDP_TEST_PORT);
if (inet_pton(AF_INET, UDP_TARGET_IP, &target_addr.sin_addr) <= 0)
{
printf("UDP Target IP Invalid!\r\n");
close(udp_fd);
osThreadTerminate(myTask03Handle);
return;
}
printf("UDP Init OK! Target IP: %s:%d\r\n", UDP_TARGET_IP, UDP_TEST_PORT);
// 5. 循环发送+非阻塞接收
while (1)
{
// ---- 发送UDP包到PC ----
snprintf(send_buf, sizeof(send_buf), "UDP Socket Test %lu", send_count++);
int send_len = sendto(udp_fd, send_buf, strlen(send_buf), 0,
(struct sockaddr *)&target_addr, sizeof(target_addr));
if (send_len > 0)
{
printf("UDP Send: %s (Len: %d)\r\n", send_buf, send_len);
}
else
{
printf("UDP Send Failed! Err: %d\r\n", send_len);
}
// ---- 接收PC的UDP数据(非阻塞,超时500ms)----
char recv_buf[64];
struct sockaddr_in recv_addr;
socklen_t recv_addr_len = sizeof(recv_addr);
int recv_len = recvfrom(udp_fd, recv_buf, sizeof(recv_buf)-1, 0,
(struct sockaddr *)&recv_addr, &recv_addr_len);
// 处理接收结果(区分"超时"和"错误")
if (recv_len > 0)
{
recv_buf[recv_len] = '\0'; // 字符串结束符
printf("✅ UDP Recv from PC(%s:%d): %s\r\n",
inet_ntoa(recv_addr.sin_addr), ntohs(recv_addr.sin_port), recv_buf);
}
else if (recv_len == 0)
{
printf("UDP Recv: Peer closed\r\n");
}
else
{
// 超时属于正常情况(不是错误),无需打印
if (errno != EAGAIN && errno != EWOULDBLOCK)
{
printf("UDP Recv Error! Err: %d\r\n", errno);
}
}
// LwIP事件处理
sys_check_timeouts();
osDelay(1000); // 1秒循环一次
}
}
TCP CLIENT 任务
c
void StartTask04(void *argument)
{
int tcp_fd = -1;
struct sockaddr_in server_addr;
char recv_buf[RECV_BUF_SIZE];
char send_buf[64];
uint32_t send_count = 0;
int retry_cnt = 0;
struct timeval timeout; // 接收超时配置
// 等待网络就绪(IP非0)
while (netif_default->ip_addr.addr == 0)
{
osDelay(100);
}
printf("Network Ready, Try Connect to PC TCP Server...\r\n");
// 循环尝试连接PC服务端(失败重试)
while (1)
{
// ---- 1. 创建TCP Socket ----
tcp_fd = socket(AF_INET, SOCK_STREAM, 0);
if (tcp_fd < 0)
{
printf("TCP Socket Create Failed! Err: %d\r\n", tcp_fd);
osDelay(1000);
continue;
}
// ---- 2. 设置接收超时(避免阻塞)----
timeout.tv_sec = 1; // 1秒超时
timeout.tv_usec = 0;
setsockopt(tcp_fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
// ---- 3. 配置PC服务端地址 ----
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(TCP_SERVER_PORT);
// 转换PC IP为网络字节序
if (inet_pton(AF_INET, TCP_SERVER_IP, &server_addr.sin_addr) <= 0)
{
printf("TCP Server IP(%s) Invalid!\r\n", TCP_SERVER_IP);
close(tcp_fd);
osDelay(1000);
continue;
}
// ---- 4. 主动连接PC服务端 ----
printf("Try Connect to %s:%d (Retry: %d/%d)...\r\n",
TCP_SERVER_IP, TCP_SERVER_PORT, retry_cnt+1, CONNECT_RETRY_CNT);
if (connect(tcp_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == 0)
{
printf("✅ TCP Connect to PC Server Success!\r\n");
retry_cnt = 0; // 连接成功,重置重试计数
break; // 退出重试循环,进入数据收发
}
else
{
printf("❌ TCP Connect Failed! Err: %d\r\n", errno);
close(tcp_fd);
retry_cnt++;
if (retry_cnt >= CONNECT_RETRY_CNT)
{
printf("Connect Retry Max, Wait 5s to Retry...\r\n");
retry_cnt = 0;
osDelay(5000);
}
else
{
osDelay(1000);
}
}
}
// ---- 5. 连接成功,循环收发数据 ----
while (1)
{
// ---- 发送数据到PC服务端 ----
snprintf(send_buf, sizeof(send_buf), "STM32 Client Data %lu", send_count++);
int send_len = send(tcp_fd, send_buf, strlen(send_buf), 0);
if (send_len > 0)
{
printf("TCP Send: %s (Len: %d)\r\n", send_buf, send_len);
}
else if (send_len == 0)
{
printf("TCP Send: PC Server Closed\r\n");
break; // 连接断开,退出收发循环
}
else
{
printf("TCP Send Failed! Err: %d\r\n", send_len);
break;
}
// ---- 接收PC服务端数据(超时1秒)----
int recv_len = recv(tcp_fd, recv_buf, RECV_BUF_SIZE-1, 0);
if (recv_len > 0)
{
recv_buf[recv_len] = '\0'; // 字符串结束符
printf("TCP Recv from PC: %s (Len: %d)\r\n", recv_buf, recv_len);
// 示例:响应PC指令(如PC发"LED_ON",STM32回复确认)
if (strstr(recv_buf, "LED_ON") != NULL)
{
char ack_buf[] = "STM32: LED_ON OK!\r\n";
send(tcp_fd, ack_buf, strlen(ack_buf), 0);
printf("Send ACK: %s", ack_buf);
}
else if (strstr(recv_buf, "LED_OFF") != NULL)
{
char ack_buf[] = "STM32: LED_OFF OK!\r\n";
send(tcp_fd, ack_buf, strlen(ack_buf), 0);
printf("Send ACK: %s", ack_buf);
}
}
else if (recv_len == 0)
{
printf("TCP Recv: PC Server Closed Connection\r\n");
break; // 连接断开
}
else
{
// 超时属于正常情况,无需打印(非错误)
if (errno != EAGAIN && errno != EWOULDBLOCK)
{
printf("TCP Recv Error! Err: %d\r\n", errno);
}
}
// ---- LwIP事件处理 + 延时 ----
sys_check_timeouts();
osDelay(1000); // 1秒收发一次
}
// ---- 6. 连接断开,关闭Socket并重试连接 ----
printf("TCP Connection Closed, Reconnect After 5s...\r\n");
close(tcp_fd);
osDelay(5000);
}
TFTP传输任务
c
#define TFTP_PORT 69 // TFTP默认端口
#define TFTP_BLOCK_SIZE 512 // TFTP数据块大小(协议规定)
#define TFTP_OP_RRQ 1 // 读请求(客户端下载,暂不支持)
#define TFTP_OP_WRQ 2 // 写请求(客户端上传,核心支持)
#define TFTP_OP_DATA 3 // 数据块
#define TFTP_OP_ACK 4 // 确认块
#define TFTP_OP_ERROR 5 // 错误
/* TFTP错误码 */
#define TFTP_ERR_NOT_DEFINED 0
#define TFTP_ERR_UNSUPPORT_OP 1 // 不支持的操作
#define TFTP_ERR_INVALID_BLOCK 2 // 无效块号
#define TFTP_ERR_DISK_FULL 3
/* 存储配置(示例:SD卡,路径为"/tftp/") */
#define TFTP_STORAGE_PATH "/tftp/"
/* TFTP会话信息(仅保留必要字段,无文件存储) */
typedef struct {
struct sockaddr_in client_addr; // 客户端地址
// FIL file; // 打开的文件对象
uint16_t block_num; // 当前数据块号
uint8_t is_active; // 会话是否激活
} tftp_session_t;
static tftp_session_t tftp_session = {0};
/**
* @brief 发送TFTP ACK包(确认数据块)
*/
static err_t tftp_send_ack(int udp_fd, struct sockaddr_in *client_addr, uint16_t block_num)
{
uint8_t ack_pkt[4];
// 操作码(4=ACK) + 块号(网络字节序)
ack_pkt[0] = 0;
ack_pkt[1] = TFTP_OP_ACK;
ack_pkt[2] = (block_num >> 8) & 0xFF;
ack_pkt[3] = block_num & 0xFF;
// 发送ACK到客户端
int send_len = sendto(udp_fd, ack_pkt, sizeof(ack_pkt), 0,
(struct sockaddr *)client_addr, sizeof(*client_addr));
return (send_len == sizeof(ack_pkt)) ? ERR_OK : ERR_VAL;
}
/**
* @brief 发送TFTP ERROR包(错误通知)
*/
static err_t tftp_send_error(int udp_fd, struct sockaddr_in *client_addr, uint16_t err_code, const char *err_msg) {
uint8_t err_pkt[512];
uint16_t len = 0;
// 操作码(5=ERROR) + 错误码(网络字节序)
err_pkt[len++] = 0;
err_pkt[len++] = TFTP_OP_ERROR;
err_pkt[len++] = (err_code >> 8) & 0xFF;
err_pkt[len++] = err_code & 0xFF;
// 错误信息(以0结尾)
strncpy((char*)&err_pkt[len], err_msg, sizeof(err_pkt)-len-1);
len += strlen(err_msg);
err_pkt[len++] = 0;
// 发送错误包到客户端
sendto(udp_fd, err_pkt, len, 0, (struct sockaddr *)client_addr, sizeof(*client_addr));
return ERR_OK;
}
/**
* @brief 解析TFTP WRQ包(写请求:客户端要上传文件)
*/
static err_t tftp_parse_wrq(uint8_t *pkt, uint16_t pkt_len, char *filename, uint16_t filename_max_len) {
if (pkt[1] != TFTP_OP_WRQ) {
return ERR_VAL; // 不是WRQ包
}
// 解析文件名(以0结尾的字符串)
uint16_t idx = 2; // 跳过操作码(前2字节)
uint16_t fn_len = 0;
while (idx < pkt_len && pkt[idx] != 0 && fn_len < filename_max_len-1) {
filename[fn_len++] = pkt[idx++];
}
filename[fn_len] = 0; // 字符串结束符
// 跳过模式(如"octet"),无需处理
while (idx < pkt_len && pkt[idx] != 0) idx++;
return (fn_len > 0) ? ERR_OK : ERR_VAL;
}
/**
* @brief 打印TFTP数据块内容(ASCII+十六进制双格式)
*/
static void tftp_print_data_block(uint16_t block_num, uint8_t *data, uint16_t data_len) {
printf("\n=====================================\n");
printf("TFTP Block %d (Len: %d Bytes)\n", block_num, data_len);
printf("ASCII Content:\n");
// 打印ASCII(不可见字符用.替代)
for (uint16_t i=0; i<data_len; i++) {
if (data[i] >= 0x20 && data[i] <= 0x7E) { // 可打印ASCII
printf("%c", data[i]);
} else {
printf(".");
}
if ((i+1) % 16 == 0) printf("\n"); // 每16字节换行
}
printf("\n\nHex Content:\n");
// 打印十六进制(每行16字节,带地址)
for (uint16_t i=0; i<data_len; i++) {
if (i % 16 == 0) printf("0x%04X: ", i);
printf("%02X ", data[i]);
if ((i+1) % 8 == 0) printf(" "); // 每8字节空格分隔
if ((i+1) % 16 == 0) printf("\n"); // 每16字节换行
}
printf("\n=====================================\n");
}
用于观察TFTP 接收文件的内容任务
void StartTask05(void *argument)
{
int udp_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_addr_len = sizeof(client_addr);
uint8_t recv_buf[TFTP_BLOCK_SIZE + 4]; // 数据块+协议头(最大516字节)
int recv_len;
char filename[64];
// 等待网络就绪
while (netif_default->ip_addr.addr == 0) {
osDelay(100);
}
printf("=== TFTP Server Start (Port: %d) ===\r\n", TFTP_PORT);
// 1. 创建UDP Socket
udp_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (udp_fd < 0) {
printf("TFTP UDP Socket Create Failed! Err: %d\r\n", udp_fd);
osThreadTerminate(myTask05Handle);
return;
}
// 2. 绑定TFTP默认端口(69)
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(TFTP_PORT);
if (bind(udp_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
printf("TFTP Bind Port %d Failed! Err: %d\r\n", TFTP_PORT, errno);
close(udp_fd);
osThreadTerminate(myTask05Handle);
return;
}
// 3. 循环处理TFTP请求
while (1) {
// 接收客户端数据包
recv_len = recvfrom(udp_fd, recv_buf, sizeof(recv_buf)-1, 0,
(struct sockaddr *)&client_addr, &client_addr_len);
if (recv_len < 2) {
osDelay(100);
continue; // 数据包过短,忽略
}
// 解析操作码
switch (recv_buf[1]) {
case TFTP_OP_WRQ: {
// 处理写请求:客户端要上传文件
if (tftp_parse_wrq(recv_buf, recv_len, filename, sizeof(filename)) != ERR_OK) {
tftp_send_error(udp_fd, &client_addr, TFTP_ERR_NOT_DEFINED, "Invalid WRQ");
break;
}
// char file_path[128];
// snprintf(file_path, sizeof(file_path), "%s%s", TFTP_STORAGE_PATH, filename);
printf("\n📢 TFTP WRQ Received: Client %s:%d Upload File -> %s\r\n",
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), filename);
// f_res = f_open(&tftp_session.file, file_path, FA_CREATE_ALWAYS | FA_WRITE);
// if (f_res != FR_OK) {
// printf("TFTP File Open Failed! Err: %d\r\n", f_res);
// tftp_send_error(udp_fd, &client_addr, TFTP_ERR_ACCESS_VIOLATION, "File Open Error");
// break;
// }
//
// 初始化会话:记录客户端地址、块号(从1开始)
tftp_session.client_addr = client_addr;
tftp_session.block_num = 1;
tftp_session.is_active = 1;
// 回复ACK 0(WRQ的确认)
tftp_send_ack(udp_fd, &client_addr, 0);
printf("✅ TFTP Ready to Receive File Content (Only Print, No Save)\r\n");
break;
}
case TFTP_OP_DATA: {
// 处理数据块:客户端发送的文件数据
if (!tftp_session.is_active) {
tftp_send_error(udp_fd, &client_addr, TFTP_ERR_NOT_DEFINED, "No Active Session");
break;
}
// 验证块号(网络字节序转主机序)
uint16_t recv_block = (recv_buf[2] << 8) | recv_buf[3];
if (recv_block != tftp_session.block_num) {
// 块号不匹配,重发上一个ACK
tftp_send_ack(udp_fd, &tftp_session.client_addr, tftp_session.block_num - 1);
break;
}
// 提取数据(跳过前4字节协议头)
uint16_t data_len = recv_len - 4;
if (data_len > 0) {
// 核心修改:打印数据块内容(不存储)
tftp_print_data_block(recv_block, &recv_buf[4], data_len);
// 写入文件
// UINT bytes_written;
// f_res = f_write(&tftp_session.file, &recv_buf[4], data_len, &bytes_written);
// if (f_res != FR_OK || bytes_written != data_len)
// {
// tftp_send_error(udp_fd, &tftp_session.client_addr, TFTP_ERR_DISK_FULL, "File Write Error");
// f_close(&tftp_session.file);
// tftp_session.is_active = 0;
// break;
// }
// printf("TFTP Recv Block %d: %d Bytes\r\n", recv_block, data_len);
}
// 回复当前块号的ACK
tftp_send_ack(udp_fd, &tftp_session.client_addr, recv_block);
// 检查是否接收完成(数据块小于512字节)
if (data_len < TFTP_BLOCK_SIZE) {
printf("\n🎉 TFTP File Content Receive Complete! Total Blocks: %d\r\n", recv_block);
tftp_session.is_active = 0;
} else {
// 块号递增,准备接收下一块
tftp_session.block_num++;
}
break;
}
default: {
// 不支持的操作码
tftp_send_error(udp_fd, &client_addr, TFTP_ERR_UNSUPPORT_OP, "Only WRQ/DATA Supported");
break;
}
}
sys_check_timeouts();
osDelay(10);
}
// 关闭Socket(理论上不会执行)
close(udp_fd);
}
三、效果
stm32 做客户端,pc服务端测试效果如图:

PC做客户端:

UDP测试,需要配设置好ip地址进行测试。

测试TFTP文件传输功能效果如下:传输的仅为一个小文件,输出文件的传输内容。
