赞
踩
未经允许,本文禁止转载
目录
本文简要介绍xilinx 7系的AXI quad spi IP核的使用,主要用于读写boot用的flash(n25q128为例)做在线升级用。本文会略去很多细节,主要是因为我也没有搞得很懂,其次是很多细节可以在其他博客找到介绍。目前为止,我只尝试了使用axi lite接口配置寄存器,对flash读id,读数据,擦除扇区,写数据。后期会学习如何对flash进行分区管理,做升级备份以及针对不同flash加入quad的读写命令提高速率。
串行flash通常指spi flash,有standard,dual,quad三种,flash的操作就是发送命令,发送地址(可选),写数据/读数据(可选)。各种模式间的区分主要在于传输数据在数据线上的分布。这里我描述不清楚,细节暂且略过。
手册pg153介绍了该ip的寄存器含义,在第五章节Example Programming Sequence介绍了几种flash操作方式的寄存器写顺序。IP配置中,XIP(eXecute In Place)即芯片内执行,指应用程序可以直接在flash闪存内运行,就是说提供一个memory map的操作接口让CPU直接访问地址,就像访问内存一样,而不是发送flash的cmd命令,相当于是flash里再集成了一个控制器,把读地址命令转换为各种读时序。注意XIP只能读flash。这里我用不上不勾选。勾选performance Mode就能有AXI4接口支持突发,目前也不需要。配置IP为quad模式,只有1个slave设备,设备类型是混合的,支持winbond,micron,spansion,macronix共有的命令。如果勾选Micron,就能支持micron的特殊命令,否则发送它的特殊命令,IPISR状态寄存器就会报command error。FIFO深度只有16和256两种选择。STARTUP原语勾选上后指SPI的clk就会从FPGA专用的CCLK引脚输出时钟。
axi lite和spi的时钟频率在手册上有说明。spi_clk是操作flash clk的2倍,这个频率也要受到flash器件的约束。STARTUP_IO不用接,SPI_IO输出后用IOBUF引出到inout管脚即可,也可以自己写三态控制,spi_io0_t = 1时输出高阻。
寄存器说明在pg153的第二章节Register Space。主要寄存器如下。
该IP的操作原理就是,先配置SPI为master,配置相位/极性,复位fifo,禁止传输;再把命令和数据写到SPI_DTR寄存器里,再使能设备片选,使能传输,关闭片选,关闭传输;从SPI_DRR里读取出数据(可选)。此外可以配置中断,选择使能哪些中断,再打开全局中断使能,传输完后查询IPISR就知道当前传输有没有错误。
40h:复位寄存器,写0xa复位整个IP,自动解复位。
60h:控制寄存器,控制SPI的工作方式。
64h:状态寄存器,查看fifo是否空满,用来判断是否传输结束。
68h:发送fifo,往里面写数据,写满了会覆盖。所以不要写满。
6ch:接收fifo,接收满了会自动丢弃后续数据。
70h:片选,写0就表示0设备的cs拉低,某个设备片选结束后,写一个没有用的设备拉高cs。
74h:发送fifo里有多少个数据。比如值5,表示里面有6个数据待发送。
78h:接收fifo里有多少个数据。比如值5,表示里面有6个数据还没有读走。
查看flash手册可以发送,应该绝大多数flash都支持下命的这些命令。尤其是02/06/9f/d8,这也是我目前成功应用过的命令。
读flash id和普通读操作没有区别,先配置SPI,禁止发送,就是发送读cmd(9f),再写几个dummy(假的,没有意义的数据)用于交换数据出来,使能片选,使能发送,关闭片选,禁止发送,读数据。对应的手册描述如下所述。
具体代码如下所示。dummy的个数是可以自己控制的,要读多少数据就写几个dummy数据进去。
- REG_W(pstDev, 0x80040, 0xa); //Software Reset Register
- REG_W(pstDev, 0x80028, 0x00003fff);//使能所有中断
- REG_W(pstDev, 0x8001c, 0x80000000);//打开全局中断使能
- REG_W(pstDev, 0x80060, 0x000001e6);//复位tx rx fifo,
- REG_W(pstDev, 0x80060, 0x00000186);//解复位fifo
- REG_W(pstDev, 0x80068, 0x0000009f);//cmd = 9f,读flash id
- REG_W(pstDev, 0x80068, 0x00000000);//dummy
- REG_W(pstDev, 0x80068, 0x00000000);//dummy
- REG_W(pstDev, 0x80068, 0x00000000);//dummy
- REG_W(pstDev, 0x80068, 0x00000000);//dummy
- REG_W(pstDev, 0x80068, 0x00000000);//dummy
- REG_W(pstDev, 0x80068, 0x00000000);//dummy
- REG_W(pstDev, 0x80068, 0x00000000);//dummy
- REG_W(pstDev, 0x80068, 0x00000000);//dummy
- REG_W(pstDev, 0x80068, 0x00000000);//dummy
- REG_W(pstDev, 0x80068, 0x00000000);//dummy
- printf("Reg[0x%04X] : 0x%08X\n", 0x80074, REG_R(pstDev, 0x80074));
- REG_W(pstDev, 0x80070, 0x00000000);//选择0通道cs
- REG_W(pstDev, 0x80060, 0x00000086);//使能master,开始发数据
- REG_W(pstDev, 0x80070, 0x00000001);//选择0通道cs拉高
- REG_W(pstDev, 0x80060, 0x00000186);//禁止master spe
- printf("Reg[0x%04X] : 0x%08X\n", 0x80078, REG_R(pstDev, 0x80078));
- for(i=0;i<11;i++){
- printf("data = 0x%08X\n", REG_R(pstDev, 0x8006c));
- }
- printf("Reg[0x%04X] : 0x%08X\n", 0x80020, REG_R(pstDev, 0x80020));
- REG_W(pstDev, 0x80020, REG_R(pstDev, 0x80020));//clear
手册描述和上面读id一致,只不过cmd = 03。并不是每次读需要复位0x40和设置中断。读数据时默认03命令后面需要3byte的addr,如果需要4byte的地址,命令根据flash不同会是不同的cmd,但一定不会是03。先发送高位地址。要注意,读回来的数据是4 + dummy 个数据。也就是说只要写一个数据到DTR里,就会有一个数据接收到写入DRR。所以读回来的数据会是FF FF FF FF xx xx......(真正数据)。要注意DRR fifo只有256深度,不要读太多数据。
- REG_W(pstDev, 0x80040, 0xa); //Software Reset Register
- REG_W(pstDev, 0x80028, 0x00003fff);//使能所有中断
- REG_W(pstDev, 0x8001c, 0x80000000);//打开全局中断使能
- REG_W(pstDev, 0x80060, 0x000001e6);//复位tx rx fifo,
- REG_W(pstDev, 0x80060, 0x00000186);//解复位fifo
- REG_W(pstDev, 0x80068, 0x00000003);//cmd = 03,读flash data
- //write addr
- REG_W(pstDev, 0x80068, (nOffset>>16)&0xff);
- REG_W(pstDev, 0x80068, (nOffset>>8)&0xff);
- REG_W(pstDev, 0x80068, (nOffset&0xff));
- for(i=0;i<nCount;i++){
- REG_W(pstDev, 0x80068, 0);//dummy
- }
- REG_W(pstDev, 0x80070, 0x00000000);//选择0通道cs
- REG_W(pstDev, 0x80060, 0x00000086);//使能master,开始发数据
- REG_W(pstDev, 0x80070, 0x00000001);//选择0通道cs拉高
- REG_W(pstDev, 0x80060, 0x00000186);//禁止master spe
- for(i=0;i<nCount+4;i++){
- printf("data = 0x%08X\n", REG_R(pstDev, 0x8006c));
- }
- printf("Reg[0x%04X] : 0x%08X\n", 0x80020, REG_R(pstDev, 0x80020));
- REG_W(pstDev, 0x80020, REG_R(pstDev, 0x80020));//clear
每个flash都有自己的扇区,页写参数,d8命令是擦除1个扇区,但扇区的大小是不一样大的。例如n25q128手册上描述:
可以看到,总共有16777216个字节,256个扇区(每个扇区64KB)有65536页(每页256字节)。所以执行一次擦除命令会擦除64KB的数据,把数据都写成0xFF,写数据只能把bit写成0。要注意的是,每一个擦除命令和写数据命令前都要有一个写使能命令(06)。擦除命令后面跟地址,就会擦除地址所在的地址对齐64KB。
测试代码为:
- REG_W(pstDev, 0x80040, 0xa); //Software Reset Register
- REG_W(pstDev, 0x80060, 0x000001e6);//复位tx rx fifo,
- REG_W(pstDev, 0x80060, 0x00000186);//解复位fifo
- REG_W(pstDev, 0x80068, 0x00000006);//cmd = 06,写使能
- REG_W(pstDev, 0x80070, 0x00000000);//选择0通道cs
- REG_W(pstDev, 0x80060, 0x00000086);//使能master,开始发数据
- REG_W(pstDev, 0x80070, 0x00000001);//选择0通道cs拉高
- REG_W(pstDev, 0x80060, 0x00000186);//禁止master spe
- REG_W(pstDev, 0x80060, 0x000001e6);//复位tx rx fifo,
- REG_W(pstDev, 0x80060, 0x00000186);//解复位fifo
- REG_W(pstDev, 0x80068, 0x000000d8);//cmd = d8,擦除扇区。
- //write addr
- REG_W(pstDev, 0x80068, (nOffset>>16)&0xff);
- REG_W(pstDev, 0x80068, (nOffset>>8)&0xff);
- REG_W(pstDev, 0x80068, (nOffset&0xff));
- REG_W(pstDev, 0x80070, 0x00000000);//选择0通道cs
- REG_W(pstDev, 0x80060, 0x00000086);//使能master,开始发数据
- REG_W(pstDev, 0x80070, 0x00000001);//选择0通道cs拉高
- REG_W(pstDev, 0x80060, 0x00000186);//禁止master spe
和擦除扇区是类似的,写使能,写命令,写地址,写数据。
要注意写FIFO也只有256字节深度,虽然页写是256字节。页写的意思,写命令后面最多跟这么多个数据,多余的数据就重复写入了。所以可以从0开始写128个,再从128写128个字节。
测试代码如下:
- REG_W(pstDev, 0x80040, 0xa); //Software Reset Register
- REG_W(pstDev, 0x80060, 0x000001e6);//复位tx rx fifo,
- REG_W(pstDev, 0x80060, 0x00000186);//解复位fifo
- REG_W(pstDev, 0x80068, 0x00000006);//cmd = 06,写使能
- REG_W(pstDev, 0x80070, 0x00000000);//选择0通道cs
- REG_W(pstDev, 0x80060, 0x00000086);//使能master,开始发数据
- REG_W(pstDev, 0x80070, 0x00000001);//选择0通道cs拉高
- REG_W(pstDev, 0x80060, 0x00000186);//禁止master spe
- REG_W(pstDev, 0x80060, 0x000001e6);//复位tx rx fifo,
- REG_W(pstDev, 0x80060, 0x00000186);//解复位fifo
- REG_W(pstDev, 0x80068, 0x00000002);//cmd = 02 页写,256字节每页
- //write addr
- REG_W(pstDev, 0x80068, (nOffset>>16)&0xff);
- REG_W(pstDev, 0x80068, (nOffset>>8)&0xff);
- REG_W(pstDev, 0x80068, (nOffset&0xff));
- for(i=0;i<128;i++){
- REG_W(pstDev, 0x80068, i);//固定写入递增的数据
- }
- REG_W(pstDev, 0x80070, 0x00000000);//选择0通道cs
- REG_W(pstDev, 0x80060, 0x00000086);//使能master,开始发数据
- REG_W(pstDev, 0x80070, 0x00000001);//选择0通道cs拉高
- REG_W(pstDev, 0x80060, 0x00000186);//禁止master spe
对于mcs,bin,bit,hex文件的区别,可以查看ug470 7 Series FPGAsConfiguration.
简单说就是bit文件,bit没有反序(每个字节的bit反序),是二进制文件。bin文件没有bit反序,二进制文件。MCS bit反序了,是ASCII文件,带有地址和校验。对于我们升级来说,bin文件就可以了。FPGA升级时从0开始读数据从到同步头aa 99 55 66,就表示一个有效的配置文件开始了。如下所示。
- ffff ffff ffff ffff ffff ffff ffff ffff
- ffff ffff ffff ffff ffff ffff ffff ffff
- 0000 00bb 1122 0044 ffff ffff ffff ffff
- aa99 5566 2000 0000 3003 e001 0000 026b
- 3000 8001 0000 0012 2000 0000 3002 2001
- 0000 0000 3002 0001 0000 0000 3000 8001
- 0000 0000 2000 0000 3000 8001 0000 0007
所以在线升级的简单设计就是拿到bin文件后,先根据bin文件大小擦除扇区,擦除每个扇区是是需要时间的,在手册里也有说明,擦除命令之间留出间隔即可,再从0开始直接写bin文件就可以了,写完后再读出校验。
后续会研究如何做备份,普通升级写镜像时只写user image区域,当启动时发现user image启动失败,会自动跳转factory image,保证有出厂镜像里有在线升级的代码,防止一次升级失败导致必须返厂使用JTAG的问题。
如果使用JTAG to AXI Master,可以用JTAG去发送axi lite读写命令。
使用方法:
1.在bd中添加jtag to axi master ip,连接axi lite端口,配置address;
2.编译工程,下载。
3.先建立axi lite读写操作,再在vivado tcl console里执行,建立一次就可以了。
- create_hw_axi_txn reset_qspi [get_hw_axis hw_axi_1] -address 0x00000040 -data 0x0000000a -type write -force
- create_hw_axi_txn rd_txn_lite_read_60 [get_hw_axis hw_axi_1] -address 00000060 -type read -force
- run_hw_axi rd_txn_lite_read_74
我的测试是把xdma的bypas接口接到了SPI IP,访问寄存器时,会发现读写数据都在变化,增加操作寄存器之间的延时,增加一定时间,例如10us即可。
目前已验证A7上使用pcie xdma烧写bin文件到n25q128 flash,新bin能正常加载。驱动源码支持按文件大小擦除,逐页写入,支持读出校验。有需要可通过CSDN私信我,有偿提供技术支持,详解文章中未提及的部分编码细节。
本文是本人原创,禁止任何形式的转载。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。