2025-03-31 2:12 AM - last edited on 2025-04-07 2:48 AM by mƎALLEm
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.
Solved! Go to Solution.
2026-03-16 10:22 AM
从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最大的性能,以上是我的调试心得,希望能帮到你,如有谬误之处,望批评指正,共勉之。