OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 5558|回复: 0

[XILINX] 【正点原子FPGA连载】第三十四章 基于TCP协议的远程更新QSPI Flash实验--摘自【正点原子】领航者ZYNQ之嵌入式开发指南_...

[复制链接]

1107

主题

1118

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
4615
金钱
4615
注册时间
2019-5-8
在线时间
1218 小时
发表于 2020-11-17 18:19:41 | 显示全部楼层 |阅读模式
本帖最后由 正点原子运营 于 2020-11-19 10:29 编辑

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

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

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

1.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接口方式)和网络更新方式的优缺点。
表 31.1.1 更新方式比较
C4D0E847-3E66-4b93-BE3F-E9256708084E.png
1.2 实验任务
本章的实验任务是使用LWIP协议栈的tcp协议实现远程更新QSPI的功能,当输入“update”命令时更新QSPI并反馈信息,当输入“clear”命令时之前传输的数据无效。
1.3 硬件设计
根据实验任务我们可以画出本次实验的系统框图,如下图所示:
image002.png

31.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,如下图所示:
image004.png

31.3.2 使能QSPI Flash控制器
2-3 配置完成后点击“OK”。然后在Diagram窗口空白处右击,然后选择“Validate Design”验证设计。验证完成后弹出对话框提示“ValidationSuccessful”表明设计无误,点击“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。
并在弹出的对话框中,取消勾选“Includebitstream”,直接点击“OK”按钮。
因为是在前一工程的基础上建立的,还保留着前一工程的结果,所以会弹出“Module Already Exported”对话框,我们点击“Yes”按钮。
4-2 硬件导出完成后,选择菜单File->Launch SDK,启动SDK开发环境。
1.4 软件设计
本次实验的软件设计与《LWIPecho server》实验无本质差别,程序框架保持不变,主要是将《LWIP echo server》实验的echo.c文件实现的功能改写成需求的远程更新QSPI功能,可在《LWIP echo server》实验的基础上修改,但为了方便程序的管理,此处我们删除《LWIPecho server》实验的应用工程,保留bsp工程。下面我们开始第五步——创建应用工程。
step5:在SDK中创建应用工程
5-1 在菜单栏中选择File->New->Application Project, 新建一个SDK空应用工程。
在弹出的界面中,输入工程名“qspi_update_tcp”,注意Board Support Package选择“Use existing”,然后选择“Next >”,如下图所示,在下一界面选择“Empty Application”。

image006.jpg
31.4.1 新建SDK应用工程
5-2 大家可以从提供的例程中拷贝SDK的源文件,需拷贝的文件如下:
image008.jpg

31.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. #include "xparameters.h"
  4. #include "xtime_l.h"
  5. #include "xstatus.h"
  6. #include <stdio.h>

  7. //服务器端口
  8. #define SER_PORT            6789
  9. //接收的最大文件大小16MB
  10. #define MAX_FLASH_LEN       16*1024*1024

  11. int qspi_init();
  12. int qspi_update(u32 total_bytes, const u8 *flash_data);
  13. void process_print(u8 percent);
  14. void sent_msg(const char *msg);
  15. float  get_time_s();

  16. #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文件的大小,该函数主要实现的功能如下:
&#216;  调用FlashErase函数擦除FLASH(QSPI),并反馈擦除进度及花费的时间
&#216; 调用FlashWrite函数向FLASH中写入BOOT.bin文件数据,并反馈写入进度及花费的时间
&#216; 调用FlashRead函数从FLASH中读出数据并与flash_data进行校验,反馈校验进度及花费的时间
QSPI的初始化以及上述函数的使用可参考《QSPI读写实验》。
process_print(u8percent)为进度打印函数,打印QSPI更新时擦除、写入和校验的进度信息。
sent_msg(constchar *msg)函数用于向发送方发送信息,以实时反馈更新进度。
get_time_s()为获取系统当前时间(单位秒sec)的函数,用于计时QSPI更新时擦除、写入和校验所花费的时间。
现在我们来看程序的核心文件qspi_remote_update.c。由于该源文件较长,我们取两个重要函数进行讲解。相比较于《LWIP echo server》实验中的echo.c文件只是实现了echo功能,将客户端发送给服务端的数据原封不动的发送回去,qspi_remote_update.c实现了接收客户端发送来的文件以及响应客户端的命令的功能。该功能由接收回调函数recv_callback实现,代码如下:
  1. //接收回调函数
  2. static err_t recv_callback(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
  3. {
  4.      struct pbuf *q;

  5.      if (!p) {
  6.          tcp_close(tpcb);
  7.          tcp_recv(tpcb, NULL);
  8.          xil_printf("tcpconnection closed\rundefined");
  9.          return ERR_OK;
  10.      }
  11.      q = p;

  12.      if (q->tot_len == 6 && !(memcmp("update", p->payload, 6))) {
  13.          start_update_flag = 1;
  14.          sent_msg("\rundefinedStartQSPI Update\rundefined");
  15.      } else if (q->tot_len == 5 && !(memcmp("clear", p->payload, 5))) {
  16.          start_update_flag = 0;
  17.          total_bytes = 0;
  18.          sent_msg("Clearreceived data\rundefined");
  19.          xil_printf("Clearreceived data\rundefined");
  20.      } else {
  21.          while (q->tot_len != q->len) {
  22.              memcpy(&rxbuffer[total_bytes], q->payload, q->len);
  23.              total_bytes += q->len;
  24.              q = q->next;
  25.          }
  26.          memcpy(&rxbuffer[total_bytes], q->payload, q->len);
  27.          total_bytes += q->len;
  28.      }

  29.      tcp_recved(tpcb, p->tot_len);
  30.      pbuf_free(p);

  31.      return ERR_OK;
  32. }
复制代码
代码第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,其定义如下:
  1. struct pbuf {
  2.   struct pbuf *next;
  3.   void *payload;
  4.   u16_t tot_len;
  5.   u16_t len;
  6.   u8_t type;
  7.   u8_t flags;
  8.   u16_t ref;
  9. };
复制代码
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的totlen字段表示整个数据包的长度,而最后一个pbuf的tot_len字段必同len字段相等(只有在很特殊的情况下,才可能存在一条pbuf链表上保存多个数据包的情况)。
type字段表示pbuf的类型。
flags字段在源代码中并未被使用到,在初始化一个pbuf的时候,该字段的值通常被设为0,而在其他地方也未使用到该字段。
最后ref字段表示该pbuf被引用的次数。引用表示有其他指针指向当前pbuf,这里的指针可以是其他pbuf的next指针,也可以是其他任何形式的指针。初始化一个pbuf的时候,ref字段值被设置为1(因为该pbuf的地址一定会被返回给一个指针变量)。
  1.   //将接收到的BOOT.bin文件写入到QSPI中
  2. int transfer_data()
  3. {
  4.       charmsg[60];
  5.       if (start_update_flag) {
  6.           xil_printf("StartQSPI Update!\rundefined");
  7.           xil_printf("filesize of BOOT.bin is %lu Bytes\rundefined", total_bytes);
  8.           sprintf(msg, "file size of BOOT.bin is %luBytes\rundefined",total_bytes);
  9.           sent_msg(msg);
  10.           if (qspi_update(total_bytes, rxbuffer) != XST_SUCCESS){
  11.               sent_msg("UpdateQspi Error!\rundefined");
  12.               xil_printf("UpdateQspi Error!\rundefined");
  13.           }
  14.           else
  15.               total_bytes = 0;
  16.       }

  17.       start_update_flag = 0;

  18.       return 0;
  19. }
复制代码

在《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”,如下图所示:
image010.jpg

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

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

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

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

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

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

31.5.2 加载BOOT.bin 文件
传输完成后,输入更新QSPI命令“update”,如下图所示:
image024.jpg

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

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

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


正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2024-10-3 16:40

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

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