未经允许,本文禁止转载
目录
简介
AXI Quad SPI IP设置
寄存器说明
AXI Quad SPI支持的通用命令
读flash id
读flash 数据
擦除扇区
写flash 数据
注意事项
简介
本文简要介绍xilinx 7系的AXI quad spi IP核的使用,主要用于读写boot用的flash(n25q128为例)做在线升级用。本文会略去很多细节,主要是因为我也没有搞得很懂,其次是很多细节可以在其他博客找到介绍。目前为止,我只尝试了使用axi lite接口配置寄存器,对flash读id,读数据,擦除扇区,写数据。后期会学习如何对flash进行分区管理,做升级备份以及针对不同flash加入quad的读写命令提高速率。
串行flash通常指spi flash,有standard,dual,quad三种,flash的操作就是发送命令,发送地址(可选),写数据/读数据(可选)。各种模式间的区分主要在于传输数据在数据线上的分布。这里我描述不清楚,细节暂且略过。
AXI Quad SPI IP设置
手册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个数据还没有读走。
AXI Quad SPI支持的通用命令
查看flash手册可以发送,应该绝大多数flash都支持下命的这些命令。尤其是02/06/9f/d8,这也是我目前成功应用过的命令。
读flash id
读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
读flash 数据
手册描述和上面读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
写flash 数据
和擦除扇区是类似的,写使能,写命令,写地址,写数据。
要注意写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私信我,有偿提供技术支持,详解文章中未提及的部分编码细节。文章来源:https://www.toymoban.com/news/detail-434854.html
本文是本人原创,禁止任何形式的转载。文章来源地址https://www.toymoban.com/news/detail-434854.html
到了这里,关于AXI Quad SPI读写Flash做远程升级的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!