一、基于freertos下对LAN8720模块进行通信测试

一、硬件环境:

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文件传输功能效果如下:传输的仅为一个小文件,输出文件的传输内容。

相关推荐
sunfove9 小时前
光网络的立交桥:光开关 (Optical Switch) 原理与主流技术解析
网络
张世争10 小时前
windows clion MingW cmake 编译运行 FreeRTOS
windows·freertos·mingw·cmake·clion
Kevin Wang72712 小时前
欧拉系统服务部署注意事项
网络·windows
min18112345612 小时前
深度伪造内容的检测与溯源技术
大数据·网络·人工智能
汤愈韬12 小时前
Full Cone Nat
网络·网络协议·网络安全·security·huawei
zbtlink13 小时前
现在还需要带电池的路由器吗?是用来干嘛的?
网络·智能路由器
桌面运维家13 小时前
vDisk配置漂移怎么办?VOI/IDV架构故障快速修复
网络·架构
dalerkd13 小时前
忙里偷闲叙-谈谈最近两年
网络·安全·web安全
汤愈韬14 小时前
NAT ALG (应用层网关)
网络·网络协议·网络安全·security·huawei
运维栈记15 小时前
虚拟化网络的根基-网络命名空间
网络·docker·容器