硬件平台:
芯片 STM32F429IGT6
IDE:MDK5.23
连接方式:开发板直连电脑网络端口
1:首先利用FreeRtos的函数创建一个线程:IAP_tftpd_init
功能在于:创建一个基于端口69的UDP服务器
void tftp_init(void)
{
myConnectUDPHandler = sys_thread_new(“IAP_tftpd_init”, IAP_tftpd_init, NULL, 256,OSUDPECHO_THREAD_PRIO );
}
2:对IAP_tftpd_init函数实现一下功能:
a:创建UDP控制块,绑定自行设置的ip地址以及69端口,这样用来监听的服务器就准备完成了
b:接下来执行while语句,利用netconn_recv函数接收网络端口数据,若没有接收到来自网络发给端口69的数据,会阻塞在这里(当然在OS当中会跳转到其他任务执行)。
c:当收到数据后,判断buf是否存在数据,若满足则再次创建UDP控制块,端口绑定0,用于接收TFTP数据。此为协议规定,此处不做讲解
d:对数据的操作码进行解析,是否为WRQ请求,不是则直接删除端口0的UDP控制块,重新netconn_recv,若是则执行IAP_tftp_process_write函数,后续对IAP_tftp_process_write进行讲解。里面会新创建线程用于接收TFTP文件数据
e:IAP_tftp_process_write执行完成之后,由于在IAP_tftp_process_write里面也有netconn_recv函数,所以我们需要将原先的netconn_recv函数进行屏蔽,故需要挂起IAP_tftpd_init任务,不在执行。恢复myTransfUDPHandler线程(该线程在后续函数讲解中创立)至此IAP_tftpd_init讲解完毕。
static void IAP_tftpd_init(void *arg)
{
static struct netbuf *buf;
err_t err;
unsigned port = 69; /* 69 is the port used for TFTP protocol initial transaction */
tftp_opcode op;
struct netconn *p_conn;//用于连接之后的UDP控制块
/* create a new UDP PCB structure */
printf("创建新的UDP控制块\r\n");
p_conn = netconn_new(NETCONN_UDP);
if (!p_conn)
{
/* Error creating PCB. Out of Memory */
return;
}
printf("绑定69号端口\r\n");
/* Bind this PCB to port 69 */
err = netconn_bind(p_conn, &ipaddr, port);
if (err == ERR_OK)
{
//准备接收数据
while (1)
{
netconn_recv(p_conn, &buf);//接收数据
if(buf!=NULL)
{
printf("收到数据,创建新的UDP线程\r\n");
conn = netconn_new(NETCONN_UDP);
if (conn!=NULL)
{
printf("绑定0号端口\r\n");
err = netconn_bind(conn, &ipaddr, 0);
op = IAP_tftp_decode_op(buf->p->payload);
if (op != TFTP_WRQ)
{
printf("非TFTP_WRQ命令\r\n");
netconn_delete(conn);
}
else
{
printf("TFTP_WRQ命令\r\n");
IAP_tftp_process_write(conn,&(buf->addr), buf->port);
}
netbuf_delete(buf);
printf("开始挂起连接任务。等待传输完成之后回复\r\n");
osThreadResume(myTransfUDPHandler);
osThreadSuspend(myConnectUDPHandler);//挂起自己 ,不在执行
}
else
{
/* Error creating PCB. Out of Memory */
printf("创建新的UDP线程失败\r\n");
}
}
}
}
else
{
;
}
}
3:IAP_tftp_process_write函数说明:
形参:struct netconn *upcb, 端口为0的UDP控制块
const ip_addr_t *to,发送方的ip地址,存在于buf->addr里面
int to_port,发送方的端口,后续会原路返回,故此作为参数进行传递
新建args参数进行os传输传递,主要记录发送方ip地址,端口,包块数,操作码等信息
b:创建新线程IAP_wrq_recv_callback,用于接收TFTP发来的文件数据,新建后挂起,等待应答TFTP第一个ACK之后在恢复
c:通过调用IAP_tftp_send_ack_packet函数应答TFTP。
/**
* @brief Processes TFTP write request
* @param to: pointer on the receive IP address
* @param to_port: receive port number
* @retval None
*/
static int IAP_tftp_process_write(struct netconn *upcb, const ip_addr_t *to, int to_port)
{
tftp_connection_args *args = NULL;
/* This function is called from a callback,
* therefore interrupts are disabled,
* therefore we can use regular malloc */
args = mem_malloc(sizeof *args);
if (!args)
{
IAP_tftp_cleanup_wr(upcb, args);
return 0;
}
args->op = TFTP_WRQ;
args->to_ip.addr = to->addr;
args->to_port = to_port;
/* the block # used as a positive response to a WRQ is _always_ 0!!! (see RFC1350) */
args->block = 0;
args->tot_bytes = 0;
printf("创建IAP_wrq_recv_callback线程,处于挂起状态\r\n");
myTransfUDPHandler = sys_thread_new("IAP_wrq_recv_callback", IAP_wrq_recv_callback, args, 512,OSUDPECHO_THREAD_PRIO );
osThreadSuspend(myTransfUDPHandler);//刚申请,暂时不用执行
total_count =0;
printf("初始化flash配置\r\n");
/* init flash */
FLASH_If_Init();
printf("开始擦除flash\r\n");
/* erase user flash area */
//FLASH_If_Erase(USER_FLASH_FIRST_PAGE_ADDRESS);
printf("擦除完毕\r\n");
Flash_Write_Address = USER_FLASH_FIRST_PAGE_ADDRESS;
/* 通过应答第一个ACK来启动写事务
之后再次收到数据会进入IAP_wrq_recv_callback回调函数*/
printf("通过应答第一个ACK来启动写事务\r\n");
IAP_tftp_send_ack_packet(upcb, to, to_port, args->block);
return 0;
}
4:IAP_tftp_send_ack_packet函数说明
在TFTP源码里面,通过RAW接口对其应答包进行发送,而采用OS之后,需要将其发送接口全部修改为NETCON,故此处不多做讲解
/**
* @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 netconn *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 */
struct netbuf *pkt_buf = NULL;
/* 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 = netbuf_new();
netbuf_alloc(pkt_buf,TFTP_ACK_PKT_LEN);
/* Copy the original data buffer over to the packet buffer's payload */
memcpy(pkt_buf->p->payload, packet, TFTP_ACK_PKT_LEN);
/* Sending packet by UDP protocol */
err = netconn_sendto(upcb, pkt_buf, to, to_port);
/* free the buffer pbuf */
netbuf_delete(pkt_buf);
return err;
}
当应答第一个ACK之后,TFTP开始传输文件数据,每包长度516字节,2字节操作码+2字节Block数+512Data,故需要利用到上述中说明的新线程进行处理IAP_wrq_recv_callback。
5:IAP_wrq_recv_callback函数讲解
a:执行netconn_recv
b:若收到数据后,开始进行解析。
长度是否大于4且包数是否连续,
c:条件满足的情况下开始拷贝到缓存,记录包数block+1,接收包数总长度累加,写入flash等操作
d:若存在一包数据小于512字节,视为最后一包数据,删除buf及控制块,恢复连接任务线程IAP_tftpd_init,删除自身线程
e:删除自身线程后,在再次执行到IAP_tftpd_init任务中,以此循环。
/**
* @brief Processes data transfers after a TFTP write request
* @param _args: used as pointer on TFTP connection args
* @param upcb: pointer on udp_pcb structure
* @param pkt_buf: pointer on a pbuf stucture
* @param ip_addr: pointer on the receive IP_address structure
* @param port: receive port address
* @retval None
*/
static void IAP_wrq_recv_callback(void *arg)
{
static struct netbuf *buf;
tftp_connection_args *args = (tftp_connection_args *)arg;
uint32_t data_buffer[128];//存储flash数据
uint16_t count=0;//每次写入的数据长度 Word型
printf("创建了一个用来接收数据包的线程\r\n");
for(;;)
{
netconn_recv(conn, &buf);//接收数据,若无数据会阻塞在这里
if(buf!=NULL)
{
if (buf->p->len != buf->p->tot_len)
{
return;
}
/*TFTP中在传输数据时通过这里的数据块序号来判断数据是否有丢失*/
/*如果接收方得到的DATA包中的序号不是之前一个DATA包中序号值加1,
那么就判断为接收到的数据包有误,返回ERROR包让发送方重新发送。
数据块序号从最开始的1开始累加;
*/
if ((buf->p->len > TFTP_DATA_PKT_HDR_LEN)//>TFTP_DATA_PKT_HDR_LEN说明存在数据包
&&//判断所接受的数据包是否是连续的下一包数据
(IAP_tftp_extract_block(buf->p->payload) == (args->block + 1)))
{
/* 复制包数据到data_buffer */
printf(" 复制包数据到data_buffer\r\n");
printf(" 包长:%d\r\n",buf->p->len);
printf(" 包块:%d\r\n",args->block);
pbuf_copy_partial(buf->p, data_buffer, buf->p->len - TFTP_DATA_PKT_HDR_LEN,
TFTP_DATA_PKT_HDR_LEN);
total_count += buf->p->len - TFTP_DATA_PKT_HDR_LEN;//累积所接受数据长度
printf(" 总长度:%d\r\n",total_count);
count = (buf->p->len - TFTP_DATA_PKT_HDR_LEN)/4;//因为是按照字进行写入flash 所以/4
if (((buf->p->len - TFTP_DATA_PKT_HDR_LEN)%4)!=0)
count++;
printf(" count:%d\r\n",count);
printf(" 将数据写到flash\r\n");
/* 将数据写到flash 每次增加512字节的偏移地址 */
//FLASH_If_Write(Flash_Write_Address+(args->block<<9), data_buffer ,count);
/* update our block number to match the block number just received */
args->block++;//每写完一块,之后+1
/* update total bytes */
(args->tot_bytes) += (buf->p->len - TFTP_DATA_PKT_HDR_LEN);//更新总长度
/* This is a valid pkt but it has no data. This would occur if the file being
written is an exact multiple of 512 bytes. In this case, the args->block
value must still be updated, but we can skip everything else. */
}
else if (IAP_tftp_extract_block(buf->p->payload) == (args->block + 1))
{
/* update our block number to match the block number just received */
args->block++;
}
/*收完一包数据之后发送ACK*/
/*应答DATA时数据块由1开始依次往上累加以确认数据包是否有漏传。*/
IAP_tftp_send_ack_packet(conn,&(buf->addr), buf->port,args->block);
/* 如果最后一次写入返回的值小于最大tftp数据pkt长度,
然后我们收到了整个文件,所以我们可以退出(这就是tftp的方式
发出传输结束的信号!
*/
if (buf->p->len < TFTP_DATA_PKT_LEN_MAX)
{
IAP_tftp_cleanup_wr(conn, args);
netbuf_delete(buf);
printf("删除传输任务线程IAP_wrq_recv_callback\r\n");
printf("恢复连接任务线程IAP_tftpd_init\r\n");
osThreadResume(myConnectUDPHandler);//恢复连接任务
vTaskDelete(myTransfUDPHandler);
}
else
{
netbuf_delete(buf);
}
}
}
上述代码流程就是如此,笔者没有flash的具体擦除写入,仅实现通过TFTP收到数据,相信后续的工作对于大家来说也比较简单。
调试信息如下:
以上就是该方式的实现方式,由于本人第一次写博客,经验不足,有误之处还请指正。