
初识AXI quad SPI; O* E8 s$ q$ U6 |8 ^3 \1 N![]() 自《PG153 AXI Quad SPI v3.2》 支持:
![]() 在标准模式下,支持高达32个从站,这是非常灵活的指标。本文对于手册中的详细技术细节不做过多阐述,有兴趣的自行深入阅读研究。 该SPI IP能干神马呢?完成如下这样一个应用场景: ![]() 所需要实现的需求用例为: ![]() 本文实现用例描述
从软件分层的视角来看,上述的需求需要实现下面的访问层级: ![]() 为什么要研究这个呢?实际用ZYNQ芯片做产品时,很有可能外部有多个SPI从设备芯片需要利用Linux访问,你或许会说ZYNQ的PS端不是自带了两个SPI控制器吗?但有时候项目中这两个SPI对应的引脚可能用做其他用途了,而一个复杂的项目中又不得不使用多个SPI从设备芯片时,本文所讨论的话题就能很好的解决这样的需求场景了。通过本文,你会发现,原来ZYNQ的SPI IP是如此灵活好用! 本文目的实战描述,如何一步一步从PL端设计:
乃至PS端:
按照这个流程,那么第一步需要设计PL端与PS端的配置,且看: AXI Quad SPI 之配置从IP catalog中按下图从ip库中添加如下IP:
![]() Block设计图: M/ R7 q7 O8 i; |( L+ a 使能ZYNQ7 processing System的时钟PL Fabric clocks,用以驱动PL端的IP: ![]() PL Fabric clocks设置 使能M AXI GP0接口如下: ![]() M AXI GP0 设置双击AXI interconnect,设置2主1从: ![]() AXI interconnect 设置双击axi_quad_spi_0设置如下,设置4个从设备(最多可支持32个从设备,PS端内置的SPI控制器1个最多支持3个从设备,从这一点可看出该IP的灵活性) ![]() axi_quad_spi 设置同样将axi_quad_spi_1设置为2个从设备接口。 然后按照前面的连线图,将各块连接好,做过硬件的盆友会比较适应,这就像画原理图一样,就将各IP建立了逻辑连接关系了。除此之外,对于一个ZYNQ的板子而言,你还需要做如下的PS端设置:
至于这些怎么配置,比较常见这里就不赘述了。 对于AXI quad SPI外设还有一个很重要的配置,就是其地址范围: ![]() AXI quad SPI 地址设置该地址最终将导出到设备树描述文件,用于SPI控制器驱动访问,从而让SPI控制器驱动得以与该IP通过AXI总线进行通信。 导出硬件文件点击open elaborated design ,然后打开io ports进行管脚分配,这需要根据各自的硬件实际情况进行设置,比如我是这样设置的: ![]()
然后点击Run synthesis进行综合,成功之后点击生成bit stream。再点击export hardware,得到.hdf文件,这个文件用于构建内核。 ![]() 将得到的硬件描述hdf文件以及bitstream文件拷贝至内核编译文件夹下: ![]() 运行命令读取硬件描述文件: petalinux-config --get-hw-description ../base.sdk注:这里将hdf文件以及.bit文件放置在petalinux编译路径的上级目录的base.sdk,根据习惯可自行设置,只有上述命令传入的路径正确即可。 等待一段时间后,可得到一个配置界面,用于配置内核源、u-boot源、Image 等配置。 ![]() petalinux-config 根据实际情况配置好后,退出配置并保存配置。使用过的会比较熟悉,这里不赘述了。 配置设备树编辑用户设备树文件,用户设备树文件在下面路径中: ./project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi配置设备树如下: /include/ "system-conf.dtsi"/ ?; b0 ]2 q; G& V/ {- \# i+ S l& E f }; & D' [. O- x! n+ K, j5 c &axi_quad_spi_0 {8 a1 g1 q1 M# U& P! D; X# l7 j8 _ status = "okay"; clock-names = "axi_clk", "axi4_clk", "spi_clk"; clocks = <&clkc 15>, <&clkc 15>, <&clkc 15>; spi0_dev_0@0 { o ]! S+ f( [2 W; c& U compatible = "spidev";( @3 u& k2 ]/ G0 O1 F$ y2 A1 \ reg = <0>; spi-max-frequency = <500000>;2 W- ^7 Y3 u" B: q% w( E- e# ? #address-cells = <1>;/ o3 n+ e4 _- j" I, Y+ V M #size-cells = <1>; };* ?) C# [% `2 j# h1 C: {$ V spi0_dev_1@1 {) b& q& m" h. t2 H compatible = "spidev"; reg = <1>;' n) _6 [" e5 u3 ]( m spi-max-frequency = <500000>;3 V; o# ~5 ?2 U9 ^5 j4 g8 l% R #address-cells = <1>;+ ^5 g" F0 {. `. u* _' q5 Z6 Y/ h$ I #size-cells = <1>; };9 }3 b( x! k+ l) r- ^4 y+ s' E spi0_dev_2@2 { compatible = "spidev";4 t P& k; u; s$ A4 }: a reg = <2>;7 J" e, }: u/ @ spi-max-frequency = <500000>; #address-cells = <1>; #size-cells = <1>;3 {+ B2 {, D& Q9 v) P6 H2 u };; s, L3 Q {& Q+ X2 m spi0_dev_3@3 { compatible = "spidev";" I' T7 B% x8 v$ s% B* Z reg = <3>;8 A) i) Q- b. w% w spi-max-frequency = <500000>;4 K& f5 P9 w0 D3 g8 y& g #address-cells = <1>;- `/ g- b1 u2 e* S! g% a #size-cells = <1>;/ q! }* Z$ d6 O& D }; }; 9 {3 ~- J/ _8 k9 Z &axi_quad_spi_1 { status = "okay"; clock-names = "axi_clk", "axi4_clk", "spi_clk";; v5 @4 X/ [* |! b; ` clocks = <&clkc 15>, <&clkc 15>, <&clkc 15>; spi1_dev_0@0 { R _6 V6 j5 O! H compatible = "spidev"; reg = <0>; spi-max-frequency = <500000>;2 Q$ z) m9 \4 |! M( A! ^7 R #address-cells = <1>; #size-cells = <1>;3 T: [3 X3 |* B F& Z: U ^7 g };6 i3 w! V; a6 U2 j# H1 }9 M7 u a" V8 j. G* d+ V" A spi1_dev_1@1 {+ v/ s( R1 g1 N ` compatible = "spidev";5 C2 J4 x) i% |9 T# h reg = <1>; spi-max-frequency = <500000>; #address-cells = <1>; #size-cells = <1>; }; ' [* g) y/ N; } e( S0 e }; $ V! F( S. k" M S 这里直接使用内置spidev兼容从设备驱动,当然如果需要自己定义一个SPI设备驱动也是非常容易的,但是对于大部分普通的SPI从芯片而言直接使用spidev设备驱动即可,只需要在读写时按照芯片手册协议进行访问即可。 配置内核运行下面命令进行内核配置: petalinux-config -c kernel![]() 内核配置( ~4 F& d4 @2 F1 U 对于本应用而言,需要配置SPI驱动: Device Drivers --->: y+ u/ [. C: C' g+-SPI support---> ! I- O" y% G5 b: l& V9 q' Y 配置如下: ![]() SPI控制器及设备驱动配置 这里调试中遇到一个奇怪的问题,CONFIG_SUSPEND需要禁止,否则控制器驱动加载不成功,目前还没有深入研究为什么不成功,猜想可能是主控制器驱动关于SUSPEND功能还不支持或者有bug,如果有哪位大神知道怎么解决请求留言指点。 Power management options --->( ]& t# ]7 h+ H' R' wSuspend to RAM and standby ![]() 功能管理配置- X; v; A1 l# N 退出并保存配置,然后运行下面命令编译系统: petalinux-build2 M' L8 ^/ ]5 \( ^: E* F等待编译成功后,运行下面命令将bitstream文件包进BOOT.bin中。 petalipackage --boot --fsbl ./images/linux/zynq_fsbl.elf --fpga ../base.sdk/design_1_wrapper.bit --u-boot --force& V. E/ H- C* [& V/ f6 o/ Q将得到下面的输出信息,表示操作成功: INFO: File in BOOT BIN: "/home/zynq/ALINX/spi_ip/ax_peta/images/linux/zynq_fsbl.elf"INFO: File in BOOT BIN: "/home/zynq/ALINX/spi_ip/base.sdk/design_1_wrapper.bit"4 q' {1 o" h- ] INFO: File in BOOT BIN: "/home/zynq/ALINX/spi_ip/ax_peta/images/linux/u-boot.elf" INFO: Generating zynq binary package BOOT.BIN... INFO: Binary is ready.7 m/ `. }, w! l/ g7 j2 a T WARNING: Unable to access the TFTPBOOT folder /tftpboot!!! WARNING: Skip file copy to TFTPBOOT folder!!! 注:/home/zynq/ALINX/spi_ip/ax_peta 是本文工程的目录 测试SPI从设备编写驱动测试程序,代码如下: #include <stdio.h>( P, h/ a& d! K- y5 l#include <string.h> #include <unistd.h>5 f# R3 h; f) C/ ] #include <fcntl.h>' B0 H* y) s. h9 o! ^. u! e5 o% r $ Y, ^/ [& C3 T; j int main(int argc, char **argv)6 \ G% a- P& \ {3 b. R; u9 \: Q0 W) r, v int fd;7 B8 A8 L$ ^! M3 W int len; unsigned char buf[10];' C7 }2 t3 m4 Q unsigned char tmp;+ w0 s0 u+ w. @$ N /* 验证输入参数个数 */ if(3 != argc) {+ I! P& U7 e8 T2 B& Z printf("none para\n"); return -1;8 \/ l, ^: o+ G" R) n8 G } /* 打开输入的设备文件, 获取文件句柄 */ fd = open(argv[1], O_RDWR); if(fd < 0) {( a; t2 p4 p4 M. H$ E" e; h /* 打开文件失败 */1 Y a$ V2 B/ ?; o8 K2 A printf("Can't open file %s\r\n", argv[1]); return -1; }1 o/ r$ K5 N, }7 z$ z 3 s! ^/ k! p; \. K& d. V int i = 0;) U, f+ W; B0 h, r5 j$ X. x int j = 0; len =strlen(argv[2]); for(i=0;i<len;i++) {8 ]5 h6 a$ V U; ? if(argv[2]>='0' && argv[2]<='9') {- R# _( Q+ I, R# a9 J tmp = argv[2] - '0';+ r* A, j6 s l+ j0 z8 @" Y. O* X }! ^! z) F8 ]6 C else if(argv[2]>='a' && argv[2]<='f') {) I5 ]1 I& W/ I! C tmp = argv[2] - 'a'+10;+ Y0 i( S% r$ B* e } else if(argv[2]>='A' && argv[2]<='F') {/ z$ X' b- D% ^7 e$ B, y' O9 J0 s tmp = argv[2] - 'A'+10; } else {$ v7 |6 o# ]5 \8 A3 D1 H7 A% N printf("Invalid input parameters \r\n"); return -1;, X9 O. r' @0 J5 O }, ]8 A/ s1 a2 f2 d( j, [% N0 |$ ?+ s if(i%2==0)5 t2 t. W8 Q0 C* R buf[j] = tmp<<4; else6 x. O- k6 D( \ { buf[j] += tmp;, `: ~& }/ h$ u8 ?. \# D- k) _ j++; }$ e+ F$ ?! W8 R) V; U }# J; h; {' b6 @0 Q: L* n9 z7 p) S len = j;7 c6 E" V" I; D5 Z) a% t/ h) h0 S printf("Test wr:"); for(i=0;i<len;i++) printf(" %x",buf); write(fd, &buf[0], len);6 v/ u+ d7 @0 @) l1 Y2 ]+ N! C printf("\n");" f1 x' G+ q1 t7 h5 A1 G' I /* 操作结束后关闭文件 */$ E3 V3 W2 p1 b8 S" U close(fd); ^ ^. Q4 t: ?9 v! P" t return 0;6 E6 Y2 i+ ^+ R% r; |4 s; } } 编译: arm-linux-gnueabihf-gcc test.c -o test将编译所得的BOOT.BIN以及image.ub文件拷贝至制作好的SD的BOOT区,test文件拷贝至/home下。然后插上SD卡上电运行电路板: 登录控制台后,运行ls /dev查看spidev设备是否加载成功: ![]() spidev设备挂载情况 可见spedev1.0、spidev1.1以及spidev2.0--spidev2.3加载成功,与预期一样。 然后运行测试程序: root@ax_peta:/run/media/mmcblk0p2/home#./test /dev/spidev1.0 78aa$ V! m8 i9 }# A# qTest wr: 78 aa 用示波器或者逻辑分析仪观察对应引脚,将出现正确的SPI通信波形。 总结一下至此,就基本实现了从PS端Linux用户空间访问PL端的SPI从设备了。当然实际项目中还有很多细节需要进一步研究:
对于这些更细节的内容,相信在将基本框架搭建成功后,只要深入细致研究都不会有太大的难度。从本文可看出,ZYNQ之所以如此灵活好用,是其厂家或者第三方提供了大量成熟可供使用的IP以及配套的驱动程序。如有兴趣尝试用来开发项目,相信你会很快喜欢上这个体系的芯片,真的可以做到片上即可实现系统这一目标! |