Skip to main content
Senior
Mar 31, 2025 9:12 AM UTC
Solved

STM32H7 TFTP Server

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.

jowakar122Best answer
Senior
Apr 7, 2025 9:30 AM UTC

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

 

20 replies

Visitor II
Mar 16, 2026 5:22 PM UTC

从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最大的性能,以上是我的调试心得,希望能帮到你,如有谬误之处,望批评指正,共勉之。