搜索
bottom↓
回复: 0

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

[复制链接]

出0入234汤圆

发表于 2020-8-19 17:05:31 | 显示全部楼层 |阅读模式
本帖最后由 正点原子 于 2020-10-24 11:02 编辑

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
QQ群头像.png
5)关注正点原子公众号,获取最新资料


100846rel79a9p4uelap24.jpg

100846f1ce1fg14zbg0va4.png

第三十六章基于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 更新方式比较
       3611.png

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

图 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,如下图所示:
阿莫论坛发帖领航者专用12045.png

图 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”。
阿莫论坛发帖领航者专用13066.png

图 36.4.1 新建SDK应用工程

5-2 大家可以从提供的例程中拷贝SDK的源文件,需拷贝的文件如下:
阿莫论坛发帖领航者专用13166.png

图 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. 1  #ifndef SRC_QSPI_REMOTE_UPDATE_H_
  2. 2  #define SRC_QSPI_REMOTE_UPDATE_H_
  3. 3  
  4. 4  #include "xparameters.h"
  5. 5  #include "xtime_l.h"
  6. 6  #include "xstatus.h"
  7. 7  #include <stdio.h>
  8. 8  
  9. 9  //服务器端口
  10. 10 #define SER_PORT            6789
  11. 11 //接收的最大文件大小16MB
  12. 12 #define MAX_FLASH_LEN       16*1024*1024
  13. 13
  14. 14 int qspi_init();
  15. 15 int qspi_update(u32 total_bytes, const u8 *flash_data);
  16. 16 void process_print(u8 percent);
  17. 17 void sent_msg(const char *msg);
  18. 18 float  get_time_s();
  19. 19
  20. 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实现,代码如下:
  1. 116 //接收回调函数
  2. 117 static err_t recv_callback(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
  3. 118 {
  4. 119     struct pbuf *q;
  5. 120
  6. 121     if (!p) {
  7. 122         tcp_close(tpcb);
  8. 123         tcp_recv(tpcb, NULL);
  9. 124         xil_printf("tcp connection closed\r\n");
  10. 125         return ERR_OK;
  11. 126     }
  12. 127     q = p;
  13. 128
  14. 129     if (q->tot_len == 6 && !(memcmp("update", p->payload, 6))) {
  15. 130         start_update_flag = 1;
  16. 131         sent_msg("\r\nStart QSPI Update\r\n");
  17. 132     } else if (q->tot_len == 5 && !(memcmp("clear", p->payload, 5))) {
  18. 133         start_update_flag = 0;
  19. 134         total_bytes = 0;
  20. 135         sent_msg("Clear received data\r\n");
  21. 136         xil_printf("Clear received data\r\n");
  22. 137     } else {
  23. 138         while (q->tot_len != q->len) {
  24. 139             memcpy(&rxbuffer[total_bytes], q->payload, q->len);
  25. 140             total_bytes += q->len;
  26. 141             q = q->next;
  27. 142         }
  28. 143         memcpy(&rxbuffer[total_bytes], q->payload, q->len);
  29. 144         total_bytes += q->len;
  30. 145     }
  31. 146
  32. 147     tcp_recved(tpcb, p->tot_len);
  33. 148     pbuf_free(p);
  34. 149
  35. 150     return ERR_OK;
  36. 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,其定义如下:
  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的tot len字段表示整个数据包的长度,而最后一个pbuf的tot_len字段必同len字段相等(只有在很特殊的情况下,才可能存在一条pbuf链表上保存多个数据包的情况)。
type字段表示pbuf的类型。
flags字段在源代码中并未被使用到,在初始化一个pbuf的时候,该字段的值通常被设为0,而在其他地方也未使用到该字段。
最后ref字段表示该pbuf被引用的次数。引用表示有其他指针指向当前pbuf,这里的指针可以是其他pbuf的next指针,也可以是其他任何形式的指针。初始化一个pbuf的时候,ref字段值被设置为1(因为该pbuf的地址一定会被返回给一个指针变量)。
  1. 70  //将接收到的BOOT.bin文件写入到QSPI中
  2. 71  int transfer_data()
  3. 72  {
  4. 73      char msg[60];
  5. 74      if (start_update_flag) {
  6. 75          xil_printf("Start QSPI Update!\r\n");
  7. 76          xil_printf("file size of BOOT.bin is %lu Bytes\r\n", total_bytes);
  8. 77          sprintf(msg, "file size of BOOT.bin is %lu Bytes\r\n",total_bytes);
  9. 78          sent_msg(msg);
  10. 79          if (qspi_update(total_bytes, rxbuffer) != XST_SUCCESS){
  11. 80              sent_msg("Update Qspi Error!\r\n");
  12. 81              xil_printf("Update Qspi Error!\r\n");
  13. 82          }
  14. 83          else
  15. 84              total_bytes = 0;
  16. 85      }
  17. 86  
  18. 87      start_update_flag = 0;
  19. 88  
  20. 89      return 0;
  21. 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”,如下图所示:
阿莫论坛发帖领航者专用18280.png

图 36.4.3 打开BSP设置

在打开的界面中,点击standalone下的lwip202,设置右侧界面的选项。主要设置的选项如下:
设置lwip_memory_options选项。将mem_size设置为524288,增加可得到的总的堆空间;将memp_n_pbuf设置为1024,增加pbuf数;将memp_n_tcp_seg设置为1024,提高同时排队的TCP段数。如下图所示。
阿莫论坛发帖领航者专用18519.png

图 36.4.4 设置lwip_memory_options选项

设置pbuf_options选项。将pbuf_pool_size为pbuf池中的缓冲区数量。对于高性能系统,可以考虑将pbuf池大小增加到一个较高的值,此处设为16384,如下图所示。
阿莫论坛发帖领航者专用18690.png

图 36.4.5 设置pbuf_options选项

设置tcp_options选项,将tcp_snd_buf和tcp_wnd设为65535,增大tcp发送缓冲空间和窗口大小,如下图所示:
阿莫论坛发帖领航者专用18829.png

图 36.4.6 设置tcp_options选项

设置temac_adapter_options选项,将n_rx_descriptors 和n_tx_descriptors设置为512,以提高系统性能,如下图所示
阿莫论坛发帖领航者专用18981.png

图 36.4.7 设置temac_adapter_options选项

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

图 36.5.1 显示打印结果

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

图 36.5.2 加载BOOT.bin 文件

传输完成后,输入更新QSPI命令“update”,如下图所示:
阿莫论坛发帖领航者专用19742.png

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

输入更新QSPI命令“update”后,启动QSPI更新,更新信息实时通过网络传送回发送方,显示在网络调试助手中,如下图所示:
阿莫论坛发帖领航者专用19879.png

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

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

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

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

阿莫论坛20周年了!感谢大家的支持与爱护!!

月入3000的是反美的。收入3万是亲美的。收入30万是移民美国的。收入300万是取得绿卡后回国,教唆那些3000来反美的!
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-26 19:48

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

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