软件版本:VIVADO2017.4
操作系统:WIN10 64bit
硬件平台:适用米联客 ZYNQ系列开发板
米联客(MSXBO)论坛:www.osrc.cn答疑解惑专栏开通,欢迎大家给我提问!!
29.1 概述
在之前的应用中,ZYNQ将BOOT.bin文件烧写至QSPI Flash基本都是通过USB Cable连接PC,通过JTAG接口连接开发板,在SDK软件中使用“Program Flash”功能进行现场在线烧写。这种常规方法存在两个缺点。
1、速度慢。Flash的擦除(Erase)、写入(Program)、校验(Verify)3个过程所费的时间总和通常都需要若干分钟。
2、无法脱离JTAG口。对于某些产品而言,当产品量产上市后,进入维护升级阶段,若需修改、更新Flash中的bin文件,则需对产品进行拆解才可进行。
本例程实现了一种基于TCP协议的Flash bin文件更新方法。一方面,在较大程度上提高了bin文件的烧写速度(4MB大小bin文件可缩短至20秒左右);另一方面,提供了一种远程更新Flash的方法,可脱离JTAG接口,同时避免固件升级时对产品进行拆解。
29.2 基本原理
首先,在ZYNQ的ARM中基于LWIP库建立一个TCP Server,板卡通过网线与电脑连接。在电脑中通过网络调试助手以TCP Client模式与ZYNQ中的TCP Server建立TCP连接。然后,通过网络调试助手将BOOT.bin文件以二进制形式发送至TCP Server,并存储在ZYNQ所连接的DDR中。最后,当TCP Server接收完bin文件所有的数据后,网络调试助手发送烧写启动命令,将bin文件的数据按顺序一一连续写入QSPI Flash中,随后再全部读出与所接收的bin文件进行一一比对检验。断电重启板卡,便可验证bin文件的更新。
29.3 Bin文件
在SDK中所生成的BOOT.bin文件为普通二进制文件,通过UltraEdit或Binary Viewer软件可打开并查看bin文件的内容。将bin文件中所有的数据按顺序一一连续写入QSPI Flash中,即可完成bin文件的烧写,也就是说Flash中的数据与bin文件中的数据完全是一一对应的。由此可见,烧写bin文件的原理并不复杂,不存在类似编码、解码的过程,且与SDK中的“Program Flash”的所完成的功能相同。使用Binary Viewer软件打开查看bin文件的效果如下图所示。
29.4 QSPI Flash
Flash在写入数据之前必须先进行擦除(Erase),擦除过程以扇区(Sector)为单位。然后以页(Page)为单位,依次将数据写入Flash中连续的各页。
以米联ZYNQ开发板所使用的QSPI Flash:S25FL256S为例。Sector的大小为64KB,Page的大小为256B。另外,还存在两个重要参数:单位Sector擦除时间和单位Page写入时间,这决定了Flash的烧写速度。S25FL256S所对应的单位Sector擦除时间为130ms,单位Page写入时间250μs。如下图最右侧的两栏所示。具体可参考芯片datasheet。
以4MB大小的BOOT.bin文件为例,写入之前需要擦除4096/64 = 64个Sector,最短需耗时64×130ms = 8.32s。接着,需要写入4096×1024/256 = 16384个Page,最短需耗时16384×250μs = 4.096s。加上ARM中应用程序的软件开销,QSPI Flash的擦除、写入时间总和不超过15s。若需读出进行校验,则再额外增加读出时间、比对时间。QSPI Flash的读出速度很快,读出整个bin文件耗时小于1s,ARM中应用程序的比对时间通常也很短,1至2s即可完成。因此,对于4MB大小的bin文件,QSPI Flash的擦除(Erase)、写入(Program)、校验(Verify)3个过程所耗费的时间总和可以控制在20s以内。
29.5 驱动程序
驱动程序请参考提供例程的SDK工程的源文件。
main函数的完成的功能如下:
- 关闭Data Cache,避免bin文件在DDR拷贝过程中维护Cache一致性造成的麻烦
- 配置QSPI接口及QSPI Flash
- 配置Timer及其中断
- 初始化中断控制器及系统中断
- 初始化LWIP协议栈
- 建立TCP Server,启动Timer
- 持续从LWIP协议栈接收数据
29.5.1 建立TCP Server
基于LWIP库在ARM中建立一个TCP Server,IP地址为192.168.1.10,端口号为5010。
29.5.2 lwip库设置
本例程使用RAW API,即函数调用不依赖操作系统。传输效率也比SOCKET API高,(具体可参考xapp1026)。将use_axieth_on_zynq和use_emaclite_on_zynq设为0。如下图所示。
修改lwip_memory_options设置,将mem_size,memp_n_pbuf,mem_n_tcp_pcb,memp_n_tcp_seg这4个参数值设大,这样会提高TCP传输效率。如下图所示。
修改pbuf_options设置,将pbuf_pool_size设大,增加可用的pbuf数量,这样同样会提高TCP传输效率。如下图所示。
修改tcp_options设置,将tcp_snd_buf,tcp_wnd参数设大,这样同样会提高TCP传输效率。如下图所示。
修改temac_adapter_options设置,将n_rx_descriptors和n_tx_descriptors参数设大。这样可以提高zynq内部emac dma的数据搬移效率,同样能提高TCP传输效率。如下图所示。
MZ7X工业级开发板板载网口芯片是RTL8211FDI,由于默认的驱动不支持RTL8211FDI 的寄存器配置,所以无法支持自动适应速度 (通过自己修改库可以实现自动适应但是工作量大,考虑到投入时间和产出比,这里就不修改了)。所以需要手动修改 LWIP 库让网口芯片工作于1000Mbps。
29.5.3 程序解析
TCP Server建立由tcp_transmission.c文件中的tcp_recv_init和connect_accept_callback函数完成。
- tcp_recv_init函数:
- 调用tcp_new函数建立1个建立TCP连接所需的结构体。
- 调用tcp_bind函数绑定TCP Server的本地IP地址和TCP端口号。
- 调用tcp_listen函数启动监听外部任何TCP Client向TCP Server发送的TCP连接请求。
- 调用tcp_accept函数指定当TCP Server与外部TCP Client建立TCP连接后的回调函数为connect_accept_callback。
- connect_accept_callback函数:
- 当ARM中建立的TCP Server与外部TCP Client建立连接后,connect_accept_callback函数将会被调用。
- 调用tcp_recv函数指定当TCP Server接收来自TCP Client所发送数据包的回调函数为tcp_recv_callback。
- 调用tcp_sent函数指定当TCP Server向TCP Client发送数据包时的回调函数为tcp_sent_callback,在例程tcp_sent_callback为空函数,无实际作用。
29.5.4 接收保存BOOT.bin文件
接收BOOT.bin文件通过tcp_transmission.c中的tcp_recv_callback函数完成,该函数为TCP Server接收数据包的回调函数,每当接收到TCP Client的数据包时该函数都会被调用。该函数将TCP Server所接收到的bin文件的各数据包依次拷贝至DDR中首地址为FILE_BASE_ADDR的区域中,FILE_BASE_ADDR为宏定义。
#define FILE_BASE_ADDR 0x10000000
可根据具体要求定义其地址,注意要与lscript.ld中的DDR区域分开,不能重合。
29.5.5 烧写QSPI Flash
烧写QSPI Flash由qspi_g128_flash.c文件中的update_flash、FlashErase、FlashWrite、FlashRead等函数完成,可支持Micron、Spansion等多个厂商,128Mb以上多种容量的QSPI Flash。qspi_g128_flash.c是根据SDK中QSPI接口的example:xqspips_g128_flash_example.c修改而成。
当接收完整个bin文件后,在网络调试助手中输入“start update”,含空格一共12个字符。tcp_recv_callback函数接收到该命令之后便调用update_flash函数启动bin文件至QSPI Flash的烧写。该函数需要1个读缓存和1个写缓存,分别存放从flash中读出的bin文件数据和需要写入flash中的bin文件数据。读缓存和写缓存的地址分别由READ_BASE_ADDR和WRITE_BASE_ADDR宏定义指定。可根据具体要求定义其地址,同样需要注意要与lscript.ld中的DDR区域分开,不能重合。
#define READ_BASE_ADDR 0x11000000
#define WRITE_BASE_ADDR 0x12000000
update_flash函数:
- 将TCP Server接收的bin文件数据拷贝至写缓存中起始地址为WRITE_BASE_ADDR + 4的区域中,增加4字节的地址偏移是由于在FlashWrite函数中,写缓存的前4字节将被用于填充命令和写入地址字段。
- 调用FlashErase函数将bin文件数据所对应数量的扇区擦除。
- 调用FlashWrite将bin文件数据以页为单位依次全部写入Flash中。
- 调用FlashRead函数将刚才写入Flash的bin文件全部读出存入读缓存中。
- 将读出的bin文件数据与TCP Server接收的原始bin文件数据进行一一比对验证烧写的正确性。
FlashErase、FlashWrite、FlashRead函数均源自于SDK中QSPI接口的example code。可参考相应的example具体分析函数功能。
29.5.6 TCP调试信息输出
例程中设计了一个tcp_printf函数,用于向网络调试助手输出字符串调试信息。该函数暂时只支持字符串以“\n”结尾。
在本例程中使用该函数存在一个问题,即在Flash烧写开始直至结束前所有通过tcp_printf输出的调试信息,将无法及时发送,在烧写结束后应用程序回到main函数中包含xemacif_input(netif)函数的while循环中时,同时全部通过TCP发送至网络调试助手。
笔者尝试过几种方法均未能解决这个问题,例如通过tcp_nagle_disable()函数关闭nagle算法功能。笔者认为,一方面,可能跟LWIP库TCP部分函数的设计原理有关,另一方面,由于flash烧写部分应用程序将长时间占用ARM,使得xemacif_input(netif)函数长时间无法被调用,而ZYNQ中的LWIP协议栈所有的数据接收都需要依靠应用程序调用xemacif_input()函数而实现。因此在这段时间中,可能由于长时间无法调用该函数而对TCP部分函数的运行造成某些影响,使数据发送产生阻塞或者延迟。由于LWIP中TCP部分库函数结构较复杂,笔者对于TCP协议研究也不多,限于时间和精力未作深究。
29.6 网络调试助手操作方法
29.6.1 发送bin文件
在SDK中下载程序至ZYNQ中。打开网络调试助手,选择TCP Client方式,输入ARM中定义的TCP Server的IP地址和端口号,然后点击连接按键,建立TCP连接。SDK串口终端打印信息如下图所示。
在网络调试助手发送区设置里选择“启用文件数据源”,选择需要发送的BOOT.bin文件,然后点击发送。如下图所示。
29.6.2 发送启动Flash烧写命令
然后输入烧写启动命令“start update”,不要选择“按十六进制发送”,本例程中需要以ASCII码形式发送,含空格一共12个字符(不要在末尾加回车),千万不要输错,否则需要全部重新再来一遍。如下图所示。
启动烧写后,SDK串口终端打印信息如下图所示。当提示“verify done!”表示整个烧写过程成功完成。
网络调试助手接收tcp_printf函数的输出的信息如下图所示。
29.7 Bin文件更新验证
烧写完成后,此时可断电重启验证更新的BOOT.bin文件。本例程作为演示,烧入的bin文件为hello world工程。重启开发板,SDK串口终端打印信息如下图所示,代表bin文件更新成功。
29.8 待改进之处
- tcp_printf函数在flash烧写过程中无法及时发送数据的问题有待解决。
- 无校验差错重新写入功能。当将bin文件写入Flash之后再读出校验时,若出现错误,则需将错误的Page重新写入。
- 由于例程中使用网络调试助手作为传输BOOT.bin文件的工具,仅使用了TCP协议,无法在TCP协议之上设计自定义协议来规范并完善bin文件的传输过程,用户可控性较差。例如,启动命令“start update”一旦输入错误并发送至ZYNQ,则板卡将需断电重启,整个过程需从头重来一遍。理想情况下,用户应根据实际需求在TCP之上设计相应的bin文件传输协议(例如给每一个bin文件数据包加上含有协议信息的包头、包尾等)来提高传输的可靠性和可控性。另外,用户还需设计与之相对应的上位机软件进行bin文件的发送,以及与ZYNQ的通信。