【正点原子FPGA连载】 第三十四章基于TCP协议的远程更新QSPI Flash实验 摘自【正点原子】DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南

第三十四章基于TCP协议的远程更新QSPI Flash实验

在《程序固化实验》中,我们了解了如何通过Vitis软件将BOOT.bin文件固化到QSPI Flash中,这种现场通过Vitis软件固化的方式很常用,重新固化也很方便。然而在实际应用中,通过Vitis软件固化或重新固化Flash并不一定可行,如产品量产发布后进入维护升级阶段,若需要修改、更新Flash中的BOOT.bin文件,遇到产品安放在高危环境中或产品整合到大型机械内部,或产品生产时没有预留JTAG口,而是预先将程序固化到Flash中等情况,使用Vitis软件现场重新固化就不可行。此时通过网络远程更新Flash的方式将显得极其重要和方便。本章我们将介绍如何使用TCP协议实现远程更新QSPI Flash。本章包括以下几个部分:
3434.1简介
34.2实验任务
34.3硬件设计
34.4软件设计
34.5下载验证

34.1简介
在《程序固化实验》中我们可以看到,将生成的BOOT.bin文件烧写到QSPI Flash中就完成了程序固化,其实质是将BOOT.bin文件的数据写入到QSPI Flash中。将数据写入到QSPI Flash中的方式有多种,通过Vitis软件工具使用JTAG接口写入是一种常用的方式。除此之外,我们在《QSPI Flash读写实验》通过调用相关函数操作QSPI向Flash中写入数据也是一种常用的方式。显然,远程更新QSPI Flash使用的是后一种方式。
远程更新QSPI Flash就是将BOOT.bin文件通过网络协议如常用的TCP、UDP协议传给远端联网的文件接收端即MPSOC开发板。接收端将文件暂存在DDR中,当文件传输完成后,接收端接收到更新命令后将调用相关函数将文件数据写入到QSPI Flash中,写入完成后为了防止写入出错,需要将写入到Flash中的数据读出以进行校验。校验成功后就可以重新以QSPI Flash启动的方式启动,完成远程更新。
从上述可以看出,接收端的MPSOC开发板作为服务端,发送端作为客户端将BOOT.bin文件数据上传给服务端是一个较好的客户/服务器模型。有一个特别需要注意的地方是,当客户端上传完文件后,作为服务端的MPSOC开发板如何知道文件传输完成并启动更新呢。
有两种方式可以解决。一是客户端传输完成后,关闭连接,服务端知道客户端关闭连接后知道文件传输完成,更新QSPI Flash。此种方式弊端很多,如不能知道后续的更新情况,若发生写入到QSPI错误,不能及时修复,以及不能避免因环境问题导致的网络误关闭。另一种是当客户端传输文件完成后,向服务端发送更新命令,服务端接收到更新命令后启动更新。为了防止传错文件等意外情况,也可以添加清除命令,使之前传送的数据无效。
由于TCP协议的稳定可靠,本章我们选择TCP协议作为网络传输协议。MPSOC开发板利用lwip协议栈开启TCP服务作为服务端,可以写一个TCP客户端的上位机或使用网络调试助手开启TCP客户端传送BOOT.bin文件。
最后我们比较下通过Vitis软件更新(使用JTAG接口方式)和网络更新方式的优缺点。
表 35.1.1 更新方式比较
在这里插入图片描述

34.2实验任务
本章的实验任务是使用LWIP协议栈的tcp协议实现远程更新QSPI Flash的功能,当输入“update”命令时更新QSPI并反馈信息,当输入“clear”命令时之前传输的数据无效,当输入“erase”命令时对Flash进行擦除,如果此时Flash中有程序则会被擦除掉。
34.3硬件设计
根据实验任务我们可以画出本次实验的系统框图,如下图所示:
在这里插入图片描述

图 35.3.1 系统框图
在图 35.3.1中,UART用于打印程序相关的信息,LWIP通过以太网传输文件数据,传输的BOOT.bin文件数据写入到QSPI Flash中。
step1:创建Vivado工程
本次实验的硬件设计只需在《LWIP echo server》实验的基础上添加QSPI即可。
1-1 我们先打开《LWIP echo server》实验的Vivado工程,打开后将工程另存为 “qspi_update_tcp”工程。
step2:使用IP Integrator创建Processing System
2-1 在Vivado界面左侧的Flow Navigator中,点击IP INTEGRATOR下的Open Block Design以打开Diagram窗口。然后在右侧打开的Diagram界面中双击Zynq Ultrascale+ MPSOC模块修改其配置,即使能QSPI,如下图所示:
在这里插入图片描述

图 35.3.2 使能QSPI Flash控制器
2-3 配置完成后点击“OK”。然后在Diagram窗口空白处右击,然后选择“Validate Design”验证设计。验证完成后弹出对话框提示“Validation Successful”表明设计无误,点击“OK”确认。最后按快捷键“Ctrl + S”保存设计。
step3:生成顶层HDL
在Source面板中,右键点击Block Design设计文件“design_1.bd”,然后执行“Generate Output Products”。
step4:生成Bitstream文件并导出到Vitis
由于本实验未用到PL部分,所以无需生成Bitstream文件,只需导出Hardware平台文件即可。如果使用到PL,则需要添加引脚约束以及对该系统进行综合、实现并生成Bitstream文件。
4-1 导出硬件。
在菜单栏中选择 File > Export > Export hardware。
并在弹出的对话框中,取消勾选“Include bitstream”,并确认导出路径是否正确,然后点击“OK”按钮。建立vitis文件夹,将导出的平台文件移动到该文件夹下。
4-2 硬件导出完成后,选择菜单Tools->Launch Vitis,将工作空间的路径指定到上一步中vitis文件夹下,启动Vitis开发环境。
34.4软件设计
本次实验的软件设计与《LWIP echo server》实验无本质差别,程序框架保持不变,主要是将《LWIP echo server》实验的echo.c文件实现的功能改写成本实验需求的远程更新QSPI Flash功能,可在《LWIP echo server》实验的基础上修改,但为了方便程序的管理,可以直接从例程中拷贝源文件。下面我们开始第五步——创建应用工程。
step5:在Vitis中创建应用工程
5-1 在菜单栏中选择File->New->Application Project, 新建一个Vitis空应用工程,如下图所示,点击“Next >”,添加应用平台文件,添加完成后,接下来依次点击“Next>”,直到弹出选择模板界面,选择“Empty Application”空应用工程,然后点击“Finish”,如图35.4.2所示。
在这里插入图片描述

图 35.4.1 新建VITIS应用工程
在这里插入图片描述

图35.4.2 建立空应用工程
5-2 大家可以从提供的例程中拷贝Vitis应用工程的源文件,需拷贝的文件如下:
在这里插入图片描述

图35.4.3 源文件
main.c文件和平台相关文件platform.h、platform_config.h、platform_zynqmp.c与《LWIP echo server》实验中的相同,是使用lwip的通用源码文件,剩下的三个源文件是我们本实验的主要功能文件。其中qspips.c是QSPI的驱动文件,主要包括QSPI的初始化和更新QSPI功能;qspi_remote_update.c是程序的核心文件,实现TCP服务器功能并接收客户端发送来的文件以及响应客户端的命令;qspi_remote_update.h是联系qspi_remote_update.c与qspips.c的头文件。下面我们对主要内容进行讲解。
5-3 主要内容讲解
首先我们来看qspi_remote_update.h头文件,其内容如下:

1  #ifndef SRC_QSPI_REMOTE_UPDATE_H_
2  #define SRC_QSPI_REMOTE_UPDATE_H_
3  
4  #include "xparameters.h"
5  #include "xtime_l.h"
6  #include "xstatus.h"
7  #include <stdio.h>
8  
9  //服务器端口
10 #define SER_PORT            6789
11 //接收的最大文件大小16MB
12 #define MAX_FLASH_LEN       16*1024*1024
13 
14 int qspi_init();
15 int qspi_update(u32 total_bytes, const u8 *flash_data);
16 void process_print(u8 percent);
17 void sent_msg(const char *msg);
18 float  get_time_s();
19 
20 #endif

代码第10行的宏定义的SER_PORT为TCP服务器端口号,可以看到我们使用的TCP服务器端口号为6789,可以根据需要进行修改。第12行宏定义的MAX_FLASH_LEN表示写入QSPI文件的最大字节数。虽然我们MPSOC开发板使用的QSPI为32MB,但一般使用时不会超过16MB,且本实验使用的BOOT.bin文件大小只有5MB多,此处我们定义为16MB(1610241024),可以根据实际需求进行修改,但不能低于需要传送的BOOT.bin文件的大小。
代码第14行起为函数声明,其中qspi_init()为QSPI初始化函数,在创建TCP服务的start_application()函数中调用。 qspi_update(u32 total_bytes, const u8 *flash_data)是QSPI更新函数,形参flash_data为TCP服务器接收的BOOT.bin文件数据,total_bytes为BOOT.bin文件的大小,该函数主要实现的功能如下:
调用FlashErase函数擦除FLASH(QSPI),并反馈擦除进度及花费的时间
调用FlashWrite函数向FLASH中写入BOOT.bin文件数据,并反馈写入进度及花费的时间
调用FlashRead函数从FLASH中读出数据并与flash_data进行校验,反馈校验进度及花费的时间
QSPI的初始化以及上述函数的使用可参考《QSPI Flash读写实验》。
process_print(u8 percent)为进度打印函数,打印QSPI更新时擦除、写入和校验的进度信息。
sent_msg(const char *msg)函数用于向发送方发送信息,以实时反馈更新进度。
get_time_s()为获取系统当前时间(单位秒sec)的函数,用于计时QSPI更新时擦除、写入和校验所花费的时间。
现在我们来看程序的核心文件qspi_remote_update.c。由于该源文件较长,我们取两个重要函数进行讲解。相比较于《LWIP echo server》实验中的echo.c文件只是实现了echo功能,将客户端发送给服务端的数据原封不动的发送回去,qspi_remote_update.c实现了接收客户端发送来的文件以及响应客户端的命令的功能。该功能由接收回调函数recv_callback实现,代码如下:

128 //接收回调函数
129 static err_t recv_callback(void *arg, struct tcp_pcb *tpcb,
130                                       struct pbuf *p, err_t err)
131 {
    
    
132     struct pbuf *q;
133 
134     if (!p) {
    
    
135         tcp_close(tpcb);
136         tcp_recv(tpcb, NULL);
137         xil_printf("tcp connection closed\r\n");
138         return ERR_OK;
139     }
140     q = p;
141 
142     if (q->tot_len == 6 && !(memcmp("update", p->payload, 6))) {
    
    
143         start_update_flag = 1;
144         sent_msg("\r\nStart QSPI Update\r\n");
145     } else if (q->tot_len == 5 && !(memcmp("clear", p->payload, 5))) {
    
    
146         start_update_flag = 0;
147         total_bytes = 0;
148         sent_msg("Clear received data\r\n");
149         xil_printf("Clear received data\r\n");
150     } else if(q->tot_len == 5 && !(memcmp("erase", p->payload, 5))){
    
    
151         start_erase_flag = 1;
152         sent_msg("\r\nQspi Erase\r\n");
153         xil_printf("\r\nQspi Erase\r\n");
154     } else {
    
    
155         while (q->tot_len != q->len) {
    
    
156             memcpy(&rxbuffer[total_bytes], q->payload, q->len);
157             total_bytes += q->len;
158             q = q->next;
159         }
160         memcpy(&rxbuffer[total_bytes], q->payload, q->len);
161         total_bytes += q->len;
162     }
163 
164     tcp_recved(tpcb, p->tot_len);
165     pbuf_free(p);
166 
167     return ERR_OK;
168 }

代码第140~162行是我们实现的功能。当接收到的数据长度为6且内容为“update”时,表明发送方发送完数据且准备更新QSPI,此时程序将开始更新QSPI标志位start_update_flag置1并向发送方发送“Start QSPI Update”信息。当接收到的数据长度为5且内容为“clear”时,表明发送方想清除先前发送的数据,此时将统计接收数据总字节数变量total_bytes置为0。对于除此之外接收到的信息,将写入到rxbuffer中,rxbuffer是一个大小为MAX_FLASH_LEN的数组,用于存放发送方发送的BOOT.bin文件数据。当接收到的数据长度为5且内容为“erase”时,程序将擦除flash标志位start_erase_flag置1并向发送方发送“Qspi Erase”信息,此操作可以用来单独擦除Flash,方便在开发板上调试程序。
这里涉及到一个重要的结构体,数据包结构体pbuf,其定义如下:

struct pbuf {
    
    
  struct  pbuf *next;
  void    *payload;
  u16_t   tot_len;
  u16_t   len;
  u8_t    type_internal;
  u8_t    flags;
  LWIP_PBUF_REF_T ref;
  u8_t    if_idx;
};

next指针指向下一个pbuf结构,因为实际发送或接收的数据包可能很大,而每个pbuf能够管理的数据有限,所以,存在需要多个pbuf结构才能完全描述一个数据包的情况。此时,所有描述同一个数据包的pbuf需要连接在一个链表上,称之为pbuf链表,这一点用next实现。
payload是数据指针,指向该pbuf管理的数据起始地址,这里,数据起始地址可以是紧跟在pbuf结构之后的RAM空间中,也可能处在ROM中的某个地址上,而决定这点的是当前pbuf的类型,即type字段的值。
len字段表示当前pbuf中的有效数据长度,而tot_len表示当前pbuf和其后所有pbuf的有效数据的总长度。显然,tot_len字段是len字段与pbuf链表中下一个pbuf的tot_len字段之和;pbuf链表中第一个pbuf的tot len字段表示整个数据包的长度,而最后一个pbuf的tot_len字段必同len字段相等(只有在很特殊的情况下,才可能存在一条pbuf链表上保存多个数据包的情况)。
type_internal字段表示pbuf的类型。
flags字段在源代码中并未被使用到,在初始化一个pbuf的时候,该字段的值通常被设为0,而在其他地方也未使用到该字段。
最后ref字段表示该pbuf被引用的次数。引用表示有其他指针指向当前pbuf,这里的指针可以是其他pbuf的next指针,也可以是其他任何形式的指针。初始化一个pbuf的时候,ref字段值被设置为1(因为该pbuf的地址一定会被返回给一个指针变量)。

71  //将接收到的BOOT.bin文件写入到QSPI中
72  int transfer_data()
73  {
    
    
74      char msg[60];
75      if (start_update_flag) {
    
    
76          xil_printf("\r\nStart QSPI Update!\r\n");
77          xil_printf("file size of BOOT.bin is %lu Bytes\r\n", total_bytes);
78          sprintf(msg, "file size of BOOT.bin is %lu Bytes\r\n",total_bytes);
79          sent_msg(msg);
80          if (qspi_update(total_bytes, rxbuffer) != XST_SUCCESS){
    
    
81              sent_msg("Update Qspi Error!\r\n");
82              xil_printf("Update Qspi Error!\r\n");
83          }
84          else
85              total_bytes = 0;
86      }
87  
88      if(start_erase_flag){
    
    
89          sent_msg("\r\nStart QSPI Erase!\r\n");
90          xil_printf("\r\nStart QSPI Erase!\r\n");
91  
92          if(qspi_erase() != XST_SUCCESS){
    
    
93              sent_msg("\r\nQSPI Erase Error!\r\n");
94              xil_printf("\r\nQSPI Erase Error!\r\n");
95          }
96      }
97  
98      start_update_flag = 0;
99      start_erase_flag  = 0;
100 
101     return 0;
102 }

在《LWIP echo server》实验的echo.c文件中我们并没有用上transfer_data()函数,此处我们将transfer_data()函数用做更新QSPI的起始函数。当发送方发送“update”更新命令时,程序将开始更新标志start_update_flag置1,从而使transfer_data()函数得以调用qspi_update()函数更新QSPI。当发送方发送“erase”擦除命令时,程序将开始擦除标志start_erase_flag置1,然后调用qspi_erase()函数擦除QSPI Flash。transfer_data()函数在main函数的while(1)循环中被调用。
5-4 lwip设置
由于本次工程没有使用lwip工程模板,所以需要手动在板级支持包(BSP)界面中勾选lwip211库。具体操作参考《基于lwip的echo server实验》。
为了提高数据传送的效率,我们对lwip进行相应设置。打开板级支持包界面,如下图所示:
在这里插入图片描述

图35.2.4 打开BSP设置
在打开的界面中,点击standalone下的lwip211,设置右侧界面的选项。主要设置的选项如下:
设置lwip_memory_options选项。将mem_size设置为524288,增加可得到的总的堆空间;将memp_n_pbuf设置为1024,增加pbuf数;将memp_n_tcp_seg设置为1024,提高同时排队的TCP段数。如下图所示。
在这里插入图片描述

图35.4.5 设置lwip_memory_options选项
设置pbuf_options选项。将pbuf_pool_size为pbuf池中的缓冲区数量。对于高性能系统,可以考虑将pbuf池大小增加到一个较高的值,此处设为16384,如下图所示。
在这里插入图片描述

图35.4.6 设置pbuf_options选项
设置tcp_options选项,将tcp_snd_buf和tcp_wnd设为65535,增大tcp发送缓冲空间和窗口大小,如下图所示:
在这里插入图片描述

图35.4.7 设置tcp_options选项
设置temac_adapter_options选项,将n_rx_descriptors 和n_tx_descriptors设置为512,以提高系统性能,如下图所示
在这里插入图片描述

图35.4.8 设置temac_adapter_options选项
其余选项保持默认即可,无需修改。
34.5下载验证
首先我们将下载器与MPSOC开发板上的JTAG接口连接,下载器另外一端与电脑连接。然后使用USB连接线将USB UART接口与电脑连接,用于串口通信。使用网线一端连接MPSOC开发板的以太网接口,另一端与电脑或路由器连接。最后连接开发板的电源,并打开电源开关。
现在进入最后一步。
step6:板级验证
6-1 在VITIS软件的下方的VITIS Terminal窗口中点击右上角的加号连接串口。
6-2 下载程序。下载完成后,可以看到串口打印的结果如下:
在这里插入图片描述

图 35.5.1 显示打印结果
如果开发板通过网线连接到路由器,因为有DHCP服务器,可自动获取IP 给开发板;如果没有DHCP 服务器(开发板和电脑通过网线直连),则会等待DHCP获取IP,在等待一段时间仍无法获取IP,串口会打印“ERROR:DHCP request timed out”,接下来会使用默认IP 地址:192.168.1.10,端口号为设置的6789。如果采用开发板和电脑直连的方式,想跳过等待的话,则可以将DHCP关闭,关闭的方法参考“基于lwip的echo server实验”。图 35.5.1中红框圈起来的,表示QSPI初始化成功。
6-3 远程更新QSPI
打开网络调试助手,在网络调试助手发送区设置里选择“启用文件数据源”,选择需要发送的BOOT.bin 文件,这里我们选择《程序固化实验》生成的BOOT.bin 文件,然后点击发送,如下图所示:
在这里插入图片描述

图 35.5.2 加载BOOT.bin 文件
传输完成后,输入更新QSPI命令“update”,如下图所示:
在这里插入图片描述

图 35.5.3 输入更新QSPI命令“update”
输入更新QSPI命令“update”后,启动QSPI更新,更新信息实时通过网络传送回发送方,显示在网络调试助手中,如下图所示:
在这里插入图片描述

图 35.5.4 实时反馈的更新进度信息
通过传送回的文件大小,可以了解到传送过程中有没有丢包。更新进度信息中的Elapsed time表明每个操作(擦除、写入、校验)所花费的时间。
此时,接收方也会通过串口实时输出更新信息,如下图所示:
在这里插入图片描述

图 35.5.5 接收方通过串口实时输出更新信息
校验成功后,关闭电源开关。将MPSOC开发板上的启动模式开关拨到ON_ON_OFF_ON位置(参考《程序固化实验》),即设置为由QSPI Flash启动,然后再次打开电源开关。
电源开关打开后,开发板上PL配置完成的指示灯点亮。然后每次按下开发板上PL_KEY1,可以改变PS_LED1的显示状态,说明远程更新QSPI Flash成功,本次实验在MPSOC开发板上面下载验证成功。

猜你喜欢

转载自blog.csdn.net/weixin_55796564/article/details/129588514
今日推荐