发 帖  
原厂入驻New
[资料] 【正点原子FPGA连载】第三十六章基于TCP协议的远程更新QSPI Flash实验-领航者 ZYNQ 之嵌入式开发
2020-9-8 11:08:32  106 正点原子FPGA
分享

1)实验平台:正点原子领航者ZYNQ开发板
2)平台购买地址:https://item.taobao.com/item.htm?&id=606160108761
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/FPGA/zdyz_linhanz.html
4)对正点原子FPGA感兴趣的同学可以加群讨论:876744900
5)关注正点原子公众号,获取最新资料



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

在《程序固化实验》中,我们了解了如何通过SDK软件将BOOT.bin文件固化到QSPI中,这种现场通过SDK软件固化的方式很常用,重新固化也很方便。然而在实际应用中,通过SDK软件固化或重新固化QSPI并不一定可行,如产品量产发布后进入维护升级阶段,若需要修改、更新QSPI中的BOOT.bin文件,遇到产品安放在高危环境中或产品整合到大型机械内部,或产品生产时没有预留JTAG口,而是预先将程序固化到QSPI中等情况,使用SDK软件现场重新固化就不可行。此时通过网络远程更新QSPI的方式将显得极其重要和方便。本章我们将介绍如何使用TCP协议实现远程更新QSPI。本章包括以下几个部分:
3636.1简介
36.2实验任务
36.3硬件设计
36.4软件设计
36.5下载验证
36.1简介
在《程序固化实验》中我们可以看到,将生成的BOOT.bin文件烧写到QSPI中就完成了程序固化,其实质是将BOOT.bin文件的数据写入到QSPI中。将数据写入到QSPI中的方式有多种,通过SDK软件工具使用JTAG接口写入是一种常用的方式。除此之外,我们在《QSPI读写实验》通过调用相关函数操作QSPI向QSPI中写入数据也是一种常用的方式。显然,远程更新QSPI使用的是后一种方式。
远程更新QSPI就是将BOOT.bin文件通过网络协议如常用的TCP、UDP协议传给远端联网的文件接收端即领航者开发板。接收端将文件暂存在ddr3中,当文件传输完成后,接收端接收到更新命令后将调用相关函数将文件数据写入到QSPI中,写入完成后为了防止写入出错,需要将写入到QSPI中的数据读出以进行校验。校验成功后就可以重新以QSPI启动的方式启动,完成远程更新。
从上述可以看出,接收端的领航者开发板作为服务端,发送端作为客户端将BOOT.bin文件数据上传给服务端是一个较好的客户/服务器模型。有一个特别需要注意的地方是,当客户端上传完文件后,作为服务端的领航者开发板如何知道文件传输完成并启动更新呢。
有两种方式可以解决。一是客户端传输完成后,关闭连接,服务端知道客户端关闭连接后知道文件传输完成,更新QSPI。此种方式弊端很多,如不能知道后续的更新情况,若发生写入到QSPI错误,不能及时修复,以及不能避免因环境问题导致的网络误关闭。另一种是当客户端传输文件完成后,向服务端发送更新命令,服务端接收到更新命令后启动更新。为了防止传错文件等意外情况,也可以添加清除命令,使之前传送的数据无效。
由于TCP协议的稳定可靠,本章我们选择TCP协议作为网络传输协议。领航者开发板利用lwip协议栈开启TCP服务作为服务端,可以写一个TCP客户端的上位机或使用网络调试助手开启TCP客户端传送BOOT.bin文件。
最后我们比较下通过SDK软件更新(使用JTAG接口方式)和网络更新方式的优缺点。
表 36.1.1 更新方式比较


36.2实验任务
本章的实验任务是使用LWIP协议栈的tcp协议实现远程更新QSPI的功能,当输入“update”命令时更新QSPI并反馈信息,当输入“clear”命令时之前传输的数据无效。
36.3硬件设计
根据实验任务我们可以画出本次实验的系统框图,如下图所示:

图 36.3.1 系统框图

在图 5.3.1中,UART用于打印程序相关的信息,LWIP通过以太网传输文件数据,传输的BOOT.bin文件数据写入到QSPI中。
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 Processing System模块修改其配置,即使能QSPI,如下图所示:

图 36.3.2 使能QSPI Flash控制器

2-3 配置完成后点击“OK”。然后在Diagram窗口空白处右击,然后选择“Validate Design”验证设计。验证完成后弹出对话框提示“Validation Successful”表明设计无误,点击“OK”确认。最后按快捷键“Ctrl + S”保存设计。
step3:生成顶层HDL
在Source面板中,右键点击Block Design设计文件“system.bd”,然后执行“Generate Output Products”。
step4:生成Bitstream文件并导出到SDK
由于本实验未用到PL部分,所以无需生成Bitstream文件,只需导出到SDK即可。如果使用到PL,则需要添加引脚约束以及对该系统进行综合、实现并生成Bitstream文件。
4-1 导出硬件。
在菜单栏中选择 File > Export > Export hardware。
并在弹出的对话框中,取消勾选“Include bitstream”,直接点击“OK”按钮。
因为是在前一工程的基础上建立的,还保留着前一工程的结果,所以会弹出“Module Already Exported”对话框,我们点击“Yes”按钮。
4-2 硬件导出完成后,选择菜单File->Launch SDK,启动SDK开发环境。
36.4软件设计
本次实验的软件设计与《LWIP echo server》实验无本质差别,程序框架保持不变,主要是将《LWIP echo server》实验的echo.c文件实现的功能改写成需求的远程更新QSPI功能,可在《LWIP echo server》实验的基础上修改,但为了方便程序的管理,此处我们删除《LWIP echo server》实验的应用工程,保留bsp工程。下面我们开始第五步——创建应用工程。
step5:在SDK中创建应用工程
5-1 在菜单栏中选择File->New->Application Project, 新建一个SDK空应用工程。
在弹出的界面中,输入工程名“qspi_update_tcp”,注意Board Support Package选择“Use existing”,然后选择“Next >”,如下图所示,在下一界面选择“Empty Application”。

图 36.4.1 新建SDK应用工程

5-2 大家可以从提供的例程中拷贝SDK的源文件,需拷贝的文件如下:

图 36.4.2 源文件

main.c文件和平台相关文件platform.h、platform_config.h、platform_zynq.c与《LWIP echo server》实验中的相同,是使用lwip的通用源码文件,剩下的三个源文件是我们本实验的主要功能文件。其中qspi_driver.c是QSPI的驱动文件,主要包括QSPI的初始化和更新QSPI功能、qspi_remote_update.c是程序的核心文件,实现TCP服务器功能并接收客户端发送来的文件以及响应客户端的命令、qspi_remote_update.h是联系qspi_remote_update.c与qspi_driver.c的头文件。下面我们对主要内容进行讲解。
5-2 主要内容讲解
首先我们来看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文件的最大字节数。虽然我们领航者开发板使用的QSPI为32MB,但一般使用时不会超过16MB,因为BOOT.bin文件一般只有4MB左右,此处我们定义为16MB(16*1024*1024),可以根据实际需求进行修改,但不能低于需要传送的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读写实验》。
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实现,代码如下:
  • 116 //接收回调函数
  • 117 static err_t recv_callback(void *arg, struct tcp_PCB *tpcb, struct pbuf *p, err_t err)
  • 118 {
  • 119 struct pbuf *q;
  • 120
  • 121 if (!p) {
  • 122 tcp_close(tpcb);
  • 123 tcp_recv(tpcb, NULL);
  • 124 xil_printf("tcp connection closed\r\n");
  • 125 return ERR_OK;
  • 126 }
  • 127 q = p;
  • 128
  • 129 if (q->tot_len == 6 && !(memcmp("update", p->payload, 6))) {
  • 130 start_update_flag = 1;
  • 131 sent_msg("\r\nStart QSPI Update\r\n");
  • 132 } else if (q->tot_len == 5 && !(memcmp("clear", p->payload, 5))) {
  • 133 start_update_flag = 0;
  • 134 total_bytes = 0;
  • 135 sent_msg("Clear received data\r\n");
  • 136 xil_printf("Clear received data\r\n");
  • 137 } else {
  • 138 while (q->tot_len != q->len) {
  • 139 memcpy(&rxbuffer[total_bytes], q->payload, q->len);
  • 140 total_bytes += q->len;
  • 141 q = q->next;
  • 142 }
  • 143 memcpy(&rxbuffer[total_bytes], q->payload, q->len);
  • 144 total_bytes += q->len;
  • 145 }
  • 146
  • 147 tcp_recved(tpcb, p->tot_len);
  • 148 pbuf_free(p);
  • 149
  • 150 return ERR_OK;
  • 151 }


代码第126~145行是我们实现的功能。当接收到的数据长度为6且内容为“update”时,表明发送方发送完数据且准备更新QSPI,此时程序将开始更新QSPI标志位start_update_flag置1并向发送方发送“Start QSPI Update”信息。当接收到的数据长度为5且内容为“clear”时,表明发送方想清除先前发送的数据,此时将统计接收数据总字节数变量total_bytes置为0。对于除此之外接收到的信息,将写入到rxbuffer中,rxbuffer是一个大小为MAX_FLASH_LEN的数组,用于存放发送方发送的BOOT.bin文件数据。
这里涉及到一个重要的结构体,数据包结构体pbuf,其定义如下:
  • struct pbuf {
  • struct pbuf *next;
  • void *payload;
  • u16_t tot_len;
  • u16_t len;
  • u8_t type;
  • u8_t flags;
  • u16_t ref;
  • };


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字段表示pbuf的类型。
flags字段在源代码中并未被使用到,在初始化一个pbuf的时候,该字段的值通常被设为0,而在其他地方也未使用到该字段。
最后ref字段表示该pbuf被引用的次数。引用表示有其他指针指向当前pbuf,这里的指针可以是其他pbuf的next指针,也可以是其他任何形式的指针。初始化一个pbuf的时候,ref字段值被设置为1(因为该pbuf的地址一定会被返回给一个指针变量)。
  • 70 //将接收到的BOOT.bin文件写入到QSPI中
  • 71 int transfer_data()
  • 72 {
  • 73 char msg[60];
  • 74 if (start_update_flag) {
  • 75 xil_printf("Start QSPI Update!\r\n");
  • 76 xil_printf("file size of BOOT.bin is %lu Bytes\r\n", total_bytes);
  • 77 sprintf(msg, "file size of BOOT.bin is %lu Bytes\r\n",total_bytes);
  • 78 sent_msg(msg);
  • 79 if (qspi_update(total_bytes, rxbuffer) != XST_SUCCESS){
  • 80 sent_msg("Update Qspi Error!\r\n");
  • 81 xil_printf("Update Qspi Error!\r\n");
  • 82 }
  • 83 else
  • 84 total_bytes = 0;
  • 85 }
  • 86
  • 87 start_update_flag = 0;
  • 88
  • 89 return 0;
  • 90 }


在《LWIP echo server》实验的echo.c文件中我们并没有用上transfer_data()函数,此处我们将transfer_data()函数用做更新QSPI的起始函数。当发送方发送“update”更新命令时,程序将开始更新标志start_update_flag置1,从而使transfer_data()函数得以调用qspi_update()函数更新QSPI。transfer_data()函数在main函数的while(1)循环中被调用。
5-11 lwip设置
为了提高数据传送的效率,我们对lwip进行相应设置。右键点击bsp工程lwip_server_bsp,在弹出的菜单中选择“Board Support Package Settings”,如下图所示:

图 36.4.3 打开BSP设置

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

图 36.4.4 设置lwip_memory_options选项

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

图 36.4.5 设置pbuf_options选项

设置tcp_options选项,将tcp_snd_buf和tcp_wnd设为65535,增大tcp发送缓冲空间和窗口大小,如下图所示:


图 36.4.6 设置tcp_options选项

设置temac_adapter_options选项,将n_rx_descriptors 和n_tx_descriptors设置为512,以提高系统性能,如下图所示

图 36.4.7 设置temac_adapter_options选项

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

图 36.5.1 显示打印结果

如果接到路由器,因为有DHCP服务器,可自动获取IP 给开发板;如果没有DHCP 服务器,则当领航者开发板DHCP超时时使用默认IP 地址:192.168.1.10,端口号为设置的6789。图 36.5.1中红框圈起来的,表示QSPI初始化成功。
6-3 远程更新QSPI
打开网络调试助手,在网络调试助手发送区设置里选择“启用文件数据源”,选择需要发送的BOOT.bin 文件,这里我们选择《程序固化实验》生成的BOOT.bin 文件,然后点击发送,如下图所示:

图 36.5.2 加载BOOT.bin 文件

传输完成后,输入更新QSPI命令“update”,如下图所示:

图 36.5.3 输入更新QSPI命令“update”

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

图 36.5.4 实时反馈的更新进度信息

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

图 36.5.5 接收方通过串口实时输出更新信息

校验成功后,关闭电源开关。将领航者核心板上的启动模式开关左边拨到上面(置为1),右边拨到下面(置为0),即设置为由QSPI Flash启动,然后再次打开电源开关。
电源开关打开后,核心板上PL配置完成的指示灯点亮。然后每次按下底板上PL_KEY0,可以改变核心板上LED2的显示状态,说明远程更新QSPI成功,本次实验在领航者ZYNQ开发板上面下载验证成功。



0
分享淘帖 显示全部楼层

评论

高级模式
您需要登录后才可以回帖 登录 | 注册

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容图片侵权或者其他问题,请联系本站作侵删。 侵权投诉
发资料
关闭

站长推荐 上一条 /8 下一条

快速回复 返回顶部 返回列表