Skip to main content
Senior
March 31, 2025
Solved

STM32H7 TFTP Server

  • March 31, 2025
  • 11 replies
  • 1737 views

I am trying to implement TFTP file transfer on my STM32 module (Nucleo-H723ZG). I have taken reference from:
https://github.com/STMicroelectronics/STM32CubeF4/tree/master/Projects/STM324xG_EVAL/Applications/LwIP/LwIP_IAP
https://github.com/JoeMerten/Stm32-Tools-Evaluation/tree/master/STM32Cube_FW_F4_V1.9.0/Projects/STM324xG_EVAL/Applications/LwIP/LwIP_TFTP_Server

For file transfer I am using tftpd64 software. I used the software for PC-PC file transfer which were on the same local network and it was successful. But when I am trying to send file from PC-STM32 module I am only receiving write request from the client (PC). After that  I am suppose the get a block 0 acknowledgement from the server (STM32 module) which I am not receiving. 

I tried debugging the code but in the code the acknowledgement is being sent successfully. For more information:
- my ethernet is up

- I have successfully ping the IP address that I have assigned or configured for the module.

- Moreover, I am using UDP so I am able to send messages using Hercules software.

Until the block 0 acknowledgement is received by the client (PC), it will not send the data packets.

Best answer by jowakar122

For TFTP server to work properly it is recommended to use the tftp_server source and header files in Middleware. Earlier I was referring to the GitHub code in which a different file is created for initializing TFTP server, which came with many problems like mentioned above. After creating new project and by using this autogenerated files by STM32CubeIDE, I accomplished creating a TFTP server on stm module and transfer a file. 

Some points to consider when using this file:
- To initialize TFTP server we have to define the function pointers for handling file I/O operations. This structure is used by the TFTP server to manage file transfers, including opening, writing, and closing files. 

jowakar122_0-1744017902059.png

- This functions are called from the recv function in the tftp_server.c file.

jowakar122_1-1744018125776.png

 

11 replies

Visitor II
March 16, 2026

从F4移植到H7的时候尤其需要注意内存问题,low_level_output接口里使用的HAL_ETH_Transmit这个函数是会通过DMA来数据发送的,ETH的DMA是无法访问DTCM-RAM和ITCM-RAM这俩个内存的?解决办法如下:
1、检查你的icf文件里配置的RAM起始地址是不是在这两区域,如果是,这将导致这个HAL_ETH_Transmit接口报总线错误;

2、如果你cf文件里的RAM起始地址不在DTCM-RAM和ITCM-RAM,比如在AXI SRAM,需要注意缓存一致性问题;否则,也会导致ETH无法发送数据,且底层和上层发送接口不会报错;

/**
 * @brief Sends TFTP ACK packet 
 * @PAram upcb: pointer on udp_pcb structure
 * @PAram to: pointer on the receive IP address structure
 * @PAram to_port: receive port number
 * @PAram block: block number
 * @retval: err_t: error code 
 */
static err_t IAP_tftp_send_ack_packet(struct udp_pcb *upcb, const ip_addr_t *to, int to_port, int block)
{
 err_t err;
 struct pbuf *pkt_buf; /* Chain of pbuf's to be sent */

 /* create the maximum possible size packet that a TFTP ACK packet can be */
 char packet[TFTP_ACK_PKT_LEN];
	
	memset(packet, 0, TFTP_ACK_PKT_LEN *sizeof(char));

 /* define the first two bytes of the packet */
 IAP_tftp_set_opcode(packet, TFTP_ACK);

 /* Specify the block number being ACK'd.
 * If we are ACK'ing a DATA pkt then the block number echoes that of the DATA pkt being ACK'd (duh)
 * If we are ACK'ing a WRQ pkt then the block number is always 0
 * RRQ packets are never sent ACK pkts by the server, instead the server sends DATA pkts to the
 * host which are, obviously, used as the "acknowledgement". This saves from having to sEndTransferboth
 * an ACK packet and a DATA packet for RRQs - see RFC1350 for more info. */
 IAP_tftp_set_block(packet, block);

 /* PBUF_TRANSPORT - specifies the transport layer */
 //pkt_buf = pbuf_alloc(PBUF_TRANSPORT, TFTP_ACK_PKT_LEN, PBUF_POOL);//,PBUF_RAM);
 pkt_buf = pbuf_alloc(PBUF_TRANSPORT, TFTP_ACK_PKT_LEN, PBUF_RAM);

 if (!pkt_buf) /*if the packet pbuf == NULL exit and EndTransfertransmission */
 {
#ifdef USE_LCD
 LCD_ErrTrace("Can not allocate pbuf\n");
#endif
 return ERR_MEM;
 }

 /* Copy the original data buffer over to the packet buffer's payload */
 memcpy(pkt_buf->payload, packet, TFTP_ACK_PKT_LEN);

 /* Sending packet by UDP protocol */
 err = udp_sendto(upcb, pkt_buf, to, to_port);

 /* free the buffer pbuf */
 pbuf_free(pkt_buf);

 return err;
}

这个示例里的tftpserver.c的IAP_tftp_send_ack_packet函数里使用的pkt_buf是从PBUF_POOL申请的,默认的lwip配置下,内存池是在静态RAM区域的也就是icf文件里指定的RAM起始地址区域;如果其位于DTCM-RAM和ITCM-RAM则HAL_ETH_Transmit会报总线错误,导致无法回复客户端应答;如果其位于AXI SRAM,而这个示例里并未维护缓冲一致的操作,所以也会导致low_level_output这个接口无法成功发送应答;你会发现,如果pkt_buf改是从PBUF_RAM申请,则可以正常发送应答包,那是因为这个示例里,PBUF_RAM这种申请方式申请的内存位于SRAM3这个区域在MPU配置的时候有配置成不可缓存、不可缓冲 且SRAM位于D2域,ETH的DMA可访问该区域;故可发送成功;
那难道就不能从PBUF_POOL申请pbuf吗?当然是可以的,但需要自行维护缓存一致性;

/**
 * @brief This function should do the actual transmission of the packet. The packet is
 * contained in the pbuf that is passed to the function. This pbuf
 * might be chained.
 *
 * @PAram netif the lwip network interface structure for this ethernetif
 * @PAram p the MAC packet to send (e.g. IP packet including MAC addresses and type)
 * @return ERR_OK if the packet could be sent
 * an err_t value if the packet couldn't be sent
 *
 * @note Returning ERR_MEM here if a DMA queue of your MAC is full can lead to
 * strange results. You might consider waiting for space in the DMA queue
 * to become available since the stack doesn't retry to send a packet
 * dropped because of memory failure (except for the TCP timers).
 */
static err_t low_level_output(struct netif *netif, struct pbuf *p)
{
 uint32_t i = 0;
 struct pbuf *q;
 err_t errval = ERR_OK;
 ETH_BufferTypeDef Txbuffer[ETH_TX_DESC_CNT];

 memset(Txbuffer, 0, ETH_TX_DESC_CNT * sizeof(ETH_BufferTypeDef));

 for (q = p; q != NULL; q = q->next)
 {
 if (i >= ETH_TX_DESC_CNT)
 return ERR_IF;

 SCB_CleanInvalidateDCache_by_Addr((uint32_t *)q->payload, q->len);
 Txbuffer[i].buffer = q->payload;
 Txbuffer[i].len = q->len;

 if (i > 0)
 {
 Txbuffer[i - 1].next = &Txbuffer[i];
 }

 if (q->next == NULL)
 {
 Txbuffer[i].next = NULL;
 }

 i++;
 }

 TxConfig.Length = p->tot_len;
 TxConfig.TxBuffer = Txbuffer;

 HAL_StatusTypeDef ret = HAL_ETH_Transmit(&EthHandle, &TxConfig, ETH_DMA_TRANSMIT_TIMEOUT);
 if (ret != 0)
 {
 __NOP();
 }

 return errval;
}

关键在205行的清除并使cache失效从而保证,dma取到了正确的数据;这里不建议直接不开启D-chche,因为这样无法发挥FTM32H7最大的性能,以上是我的调试心得,希望能帮到你,如有谬误之处,望批评指正,共勉之。