你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

利用现成IP不写任何代码如何实现三线制SPI

[复制链接]
gaosmile 发布时间:2021-2-18 23:07

导读:最近使用ZYNQ做一个高速数据采集,需要访问一个ADI的高速模数采样芯片,该芯片是利用三线制实现读以及写的功能。三线制实现写通信或许大家都经常会这样用,三线制实现读/写或许有的朋友就未曾这样用过。今天就给大家分享一下,利用现成IP不写任何代码如何实现三线制SPI。
- H! Y, G4 w$ d" d9 o% S2 P

背景

ADI很多芯片都采用三线制SPI进行控制,以AD9467为例,AD9467是一款 pipeline架构16位高速ADC芯片,采样率高达250MSPS。在一些复杂系统中其应用领域比较广泛:

  • 多载波,多模式蜂窝接收机
  • 天线阵列定位
  • 功率放大器线性化
  • 无线宽带通信系统
  • 雷达系统
  • 红外成像系统
  • 通讯仪表系统等* E; E! g$ d' f: Q+ s2 d. }3 \

从芯片框图,大致可以看出,该芯片主要由以下部分组成:

微信图片_20210218230106.png

  • 三线制SPI通信接口,实现芯片的寄存器读写控制。主要用于芯片模式配置。
  • LVDS接口:则负责数据的对外传输,遵循ANSI-644 标准。
  • CLK+/CLK-:为输入时钟,时钟之于数字芯片相当于心脏之于人,一切的动作都是由时钟驱动的。
  • VIN+/VIN- :差分输入,模拟信号输入通道。
    ( H$ m9 ~- [2 }# l+ l4 V

对于芯片的其他部分,不是本文介绍的重点,这里来看看其SPI的通信时序图:

微信图片_20210218230110.png

6 _2 r# k; R; a; i, Y+ o. g' n& y

结合SPI模式时序图:

微信图片_20210218230113.png
  • 在上升沿采样
  • 下一位数据在CLK低期间变换
    7 }" r: x0 ?3 B: A

故,CPOL=0,CPHA=0.

另外,第一个bit用于标识本次报文你发起的是读还是写操作,这种设计是不是有点类似I2C标准中的读写位?

柳暗花明

那么问题来了,我们需要做的SPI通信需要实现三线制SPI进行读以及写:

  • 如果用单片机编程IO口去翻比较容易,但是要实现高速AD数据传输,常规的单片机就捉襟见肘了。LVDS接口的数据吞吐率很难做到。
  • 如果使用ZYNQ内置的SPI外设也很容易,该外设很容易配成三线制模式。很不幸,外设引脚基本用掉了。不过可以考虑用EMIO把相应的脚从PL端拉出去。
  • 如果利用ZYNQ PS端的GPIO也可以做到,也很不幸,做的板子PS端GPIO所剩无几。
  • 利用赛灵思的AXI Quad SPI  IP在PL端去实现。折腾一段时间,发现这个IP貌似不支持三线制SPI。
  • 自己用verilog HDL写个IP挂在AXI总线上,实现Linux设备驱动,这个方案可以。可惜,比较懒,不想重新造轮子!
  • .......- ^9 O% O5 V7 r4 M

经过一番折腾,在ADI官方发现了一个宝藏:

微信图片_20210218230116.png

https://wiki.analog.com/resources/fpga/peripherals/spi_engine

官方实现了SPI engine IP 框架:

  • 执行模块 Execution Module:主模块,用于处理SPI引擎命令流并实现SPI总线接口逻辑
  • AXI接口模块:内存映射软件可访问SPI引擎命令流和/或卸载核心的接口
  • Offload模块:存储SPI引擎命令流,由外部事件触发执行
  • 互连Interconnect 模块:将多个SPI Engine命令流连接到SPI Engine执行模块
    ' T" z8 U3 }: D2 I& I1 R" J

其verilog HDL代码库位于:

https://github.com/analogdevicesinc/hdl.git

PS/PL设计

下好hdl库,按照向导将库make,执行对应的tcl脚本,生成了hdl库相应所需文件。然后按照需要设计以下block设计:

微信图片_20210218230120.png
  • 将PS端的DDR以及PL所需的时钟FCLK_CLK0配置好,这里输出100MHz
  • 从ip库里拉出来axi_spi_engine_v1_0以及spi_engine_execution_v1_0,按照上面图连好线
  • 连好AXI接口,以及相应的复位、时钟信号等
  • 设置需要几个片选信号,可根据需要几个从芯片可以设置多个片选信号,比如我设置2个,这样在linux设备树上就对应挂载两个设备。
    % u6 m0 \: N6 h* j3 ~

然后在顶层设计文件进行例化,这里问题来了,spi_1还是4个脚,如果就这样拉出到PL端的引脚上,还是四线制,那么该怎么改呢?

看看wiki中图以及描述,发现需要还需要在转一下:

微信图片_20210218230123.png

6 u7 O* N7 \1 T& {/ t( x2 G
  • 如果是三线模式时,three_wire会变成1,这个通过AXI总线命令传过来。
  • sdo_t则可以控制sdo内部信号是否输出,如果门控关断则mosi脚变成高阻,可以采样外部信号,从而传入可以通过2路选择器传入sdi转而为读信号。- I0 b+ @: j  e' l

从而添加如下代码在顶层文件:

assign phy_sclk = spi_sclk;3 t, |0 n% {  e7 R4 k3 O; m5 {
assign phy_cs = spi_cs;7 j! X" d6 t; v3 H: `. G6 S  a) h
assign phy_mosi = spi_sdo_t ? 1'bz : spi_sdo;
+ o9 r2 v0 d9 X4 L1 d: P2 J1 jassign spi_sdi = spi_three_wire ? phy_mosi : phy_miso;4 |0 w" d) Q" k8 g

比如,我是这样写的:

`timescale 1ns / 100ps* F' t" ]& C* v7 z/ P9 e
//顶层设计文件
; o) ]- t0 G! gmodule system_top (
7 I* a4 ?8 q: y( u% Y* R//DDR信号; k$ ]7 Y4 ]/ ^2 }
inout   [14:0]  ddr_addr,& N! U  I, J8 g  H' `0 U2 w9 f
inout   [ 2:0]  ddr_ba,
. w+ T1 t' H; b7 C- M/ M: p# U% Finout           ddr_cas_n,: q. O5 b, s+ L' h, [+ T; Z
inout           ddr_ck_n,  j. m2 f6 x" r4 F4 W$ k0 b
inout           ddr_ck_p,
5 n  N5 X4 d! m5 B* I; Zinout           ddr_cke,  `; l; Z% r1 p$ L* g
inout           ddr_cs_n,# O) X* Q( o6 K0 S
inout   [ 3:0]  ddr_dm,
+ E3 z! U0 y; u9 pinout   [31:0]  ddr_dq,- S0 C& k, }  e% f5 ~& q5 `
inout   [ 3:0]  ddr_dqs_n,. U7 X. ?! W2 v
inout   [ 3:0]  ddr_dqs_p,
' Z+ `5 \2 y% ^8 A9 Z) }; ainout           ddr_odt,# f9 \) K: A. B- [
inout           ddr_ras_n,
" ^. T: I  P; f8 j6 Binout           ddr_reset_n,
, U0 w# x" |. p$ z8 vinout           ddr_we_n,
5 W" |+ T0 I, C* T% g6 e; z//必须的一些PS信号
# i, h9 Y, Y4 b* X# N0 d& Z  C& sinout           fixed_io_ddr_vrn,: j" V# g& Q) d  O6 u; z6 j5 r
inout           fixed_io_ddr_vrp,5 Y: J& x! c# M4 Z2 U6 P
//54个PS MIO引脚' M; Q" Y0 S. ]
inout   [53:0]  fixed_io_mio,. V% j' L6 K5 V; Q9 u
//PS时钟引脚
0 R* j% B* t; P) c- ]inout           fixed_io_ps_clk,
+ r# |$ R1 h2 W% @3 l//PS上电复位信号+ D. r) ~3 x- u$ M. I
inout           fixed_io_ps_porb,, }; B6 ]+ C1 |1 v
//PS SRSTB信号
4 \- n3 ~6 U# _, W# {, |inout           fixed_io_ps_srstb,

' z5 L/ s- g- M9 joutput [1:0]       spi_0_cs,8 Y3 E2 B+ n9 \# ^* n7 u$ s8 v- a% n3 T
output             spi_0_sclk,* {! L! w1 q& u/ g( P
input              spi_0_sdi,
" ]! q' g: L2 I2 o9 m7 coutput             spi_0_sdo,
' O7 f' w8 j3 x- L7 n& A);
+ R* A. o' Z7 X5 N$ r( Z  ! ?- n+ |+ o4 D8 C
wire ip_spi_0_cs;
$ q# B& _9 t8 p! n) P& ?' vwire ip_spi_0_sclk;& d: L+ i4 O, r1 P! W
wire ip_spi_0_sdi;5 D! i6 {+ w$ V3 B
wire ip_spi_0_sdo;5 G, o$ l2 K2 a- \, [1 r
wire ip_spi_0_three_wire;   ) B; i: \* h# U: m
wire ip_spi_0_sdo_t;
  z( C& r. m9 |- p' C, m0 ^" ?. ^8 J
assign spi_0_cs = ip_spi_0_cs;
4 F8 v$ \' P9 L% E* x0 kassign spi_0_sclk = ip_spi_0_sclk;
$ \% T7 C% L, i% ~//如此处理,这样引出的SPI可以兼容3线制以及4线制SPI
* R8 _. q- r. F/ o0 N4 w2 Q4 z- Hassign spi_0_sdo = ip_spi_0_sdo_t ? 1'bz : ip_spi_0_sdo;3 P5 l3 [, L( ]+ r. _
assign ip_spi_0_sdi = ip_spi_0_three_wire ? spi_0_sdo : spi_0_sdi;0 f, t5 u) |* N# d

* l9 E' [. [0 g//例化block设计+ l& G4 G- ?! k* v! H
ip_block_wrapper i_system_wrapper (
  r) U7 V, S% T) H% h! R! ~/ y    //DDR以及常规MIO、时钟、复位等信号9 X1 O0 ^; n! |9 q
    .DDR_addr(ddr_addr),6 r, D4 h$ ~: ^' w
    .DDR_ba(ddr_ba),
. x3 K/ ]* R+ F) S    .DDR_cas_n(ddr_cas_n),0 x" N) O8 _5 N5 @9 R0 h
    .DDR_ck_n(ddr_ck_n),
) a, i: N3 o9 S6 t    .DDR_ck_p(ddr_ck_p),6 `0 s9 q: b+ @3 R5 J/ E" l- G8 d
    .DDR_cke(ddr_cke),
# [6 i' h8 D$ ?! i% m    .DDR_cs_n(ddr_cs_n),
, T$ r3 b4 R/ M6 |: K# d4 i, M& E    .DDR_dm(ddr_dm),  |. w% {! H( `6 Z/ h  ]# O
    .DDR_dq(ddr_dq),4 ^. W. A7 g' j+ `/ D
    .DDR_dqs_n(ddr_dqs_n),: A- C; ?4 W) y7 X0 i' R: I
    .DDR_dqs_p(ddr_dqs_p),
& K1 k$ X% n* h: ?* T* G5 n    .DDR_odt(ddr_odt),$ k; X& `/ O2 N5 n, d4 ^
    .DDR_ras_n(ddr_ras_n),( ?5 L/ @! V7 y# ^2 E
    .DDR_reset_n(ddr_reset_n),
6 n8 m$ |& w+ u' S5 V/ F1 J    .DDR_we_n(ddr_we_n),
8 ~8 u# l# O: n    .FIXED_IO_ddr_vrn(fixed_io_ddr_vrn),
; W5 Y2 A* ~; X# k% n' c    .FIXED_IO_ddr_vrp(fixed_io_ddr_vrp),+ m8 V8 k% J' ]. R% F3 V
    .FIXED_IO_mio(fixed_io_mio),) h2 R& m7 \& N7 \7 R
    .FIXED_IO_ps_clk(fixed_io_ps_clk),
$ E7 w; x/ p2 N  }. m! ?    .FIXED_IO_ps_porb(fixed_io_ps_porb),
( X7 m$ H& D/ K7 s9 C7 O    .FIXED_IO_ps_srstb(fixed_io_ps_srstb),      
# N8 ^  y* `- z( Y
) e$ d% m, T4 G6 E# L+ H* B1 b      //连至顶层
1 c6 i' P$ w9 |    .spi_0_cs(ip_spi_0_cs),' G/ |( C* P2 `# P
    .spi_0_sclk(ip_spi_0_sclk),: J7 u3 Z; q1 `
    .spi_0_sdi(ip_spi_0_sdi),
" C8 q* s+ Q7 ~. J/ ^    .spi_0_sdo(ip_spi_0_sdo),) Z& @) i8 A4 E' f  _9 r# O
    .spi_0_sdo_t(ip_spi_0_sdo_t),
$ A. |# {- s, @+ I7 K0 \& H    .spi_0_three_wire(ip_spi_0_three_wire)     + r0 W- i0 N( Q2 O% i5 m- u# T
  );   
: V5 _& S' e. gendmodule% i" y& m" f/ m% x3 W2 W! k
Linux端配置

首先需要配置设备树:

&axi_spi_engine_0 {
% A1 p& W" n7 [2 x    status = "okay";
9 Q( M7 j  c6 q! C    //配置SPI控制器匹配字段,这样会自动编译ADI 提供的SPI 控制器驱动& ?, k" q' x% q3 u( }
    compatible = "adi,axi-spi-engine-1.00.a";
% W3 l  X- w8 V9 v4 X5 s2 H1 O    spi-rx-bus-width = <1>;
; v! C2 f5 b5 j2 W6 z1 X( J    spi-tx-bus-width = <1>;
1 L7 G# N  M9 h" u5 @; q% h    bits-per-word = <8>;# K: w) T$ g4 A7 q
    interrupt-parent = <&intc>;" P+ o6 W2 q6 |. z! R# @& b
    interrupts = <0 30 4>;      ) d! I: H2 Q; {$ `& m  k' g+ O% A
    num-cs = <4>;
) P0 ~! Q& V% M8 d8 z; A0 k
; E$ M, {& Y3 s1 E    #address-cells = <0x1>;! R, t4 ^' t& C! H# _
    #size-cells = <0x0>;    ; p% [2 h4 }( n: J) Q: Q
    ad9467_0: ad9467@0 {: p1 w' k9 O; K2 V% g, `
        compatible = "adi,ad9467";
4 }7 ~, G) T4 v' A! k3 |: [6 O        reg = <0>;" ^" N( O! s6 x$ L2 q
        spi-max-frequency = <500000>;
& c4 G: \+ I$ r. l* D        #address-cells = <1>;( g  B  d5 o" t3 I; s4 z# B9 Q
        #size-cells = <1>;" z+ m& i  Y; J6 ]

7 f7 S( W& H# h) h        spi-rx-bus-width = <1>; , E! K  V( e6 J4 C. m
        spi-tx-bus-width = <1>;
3 N1 `' S2 z; ?; s8 |        bits-per-word = <8>;7 y+ C: T+ ^7 C
        spi-3wire;        , V# q* W" P. ], T9 Z7 V7 o
    };
) ]$ g* r$ X* ^1 I: \) K6 p% e5 l9 m0 h) w) L' I) g
    ad9467_1: ad9467@1 {
. g6 d9 _/ e, x7 \0 E        compatible = "spidev";
. W1 o4 z, _$ I2 m        reg = <1>;; p2 @/ u4 c- K
        spi-max-frequency = <500000>;- \1 O7 w' K: `
        #address-cells = <1>;
; F  g2 \% e8 U3 f& \( |% n0 H4 z2 Y) i        #size-cells = <1>;        
8 G6 \* X$ C7 H
2 R, Z3 q" Q: T, h4 @        spi-rx-bus-width = <1>; 7 j5 W' a$ D$ X6 T; `; W2 ~
        spi-tx-bus-width = <1>;6 J* c. ?$ x$ k7 d5 w
        bits-per-word = <8>;
) r) h9 R6 T* L        //这个字段需要使能,表示该设备是三线制7 T) t+ d' m7 T" v3 f4 D
        spi-3wire;           : r( C7 K/ g5 w9 |, H( }! {" T
    };      2 a6 q/ N; J( T+ v
};4 A# L# W: o+ t1 N  h5 T# S( X

基于ADI提高的Linux代码库:

https://github.com/analogdevicesinc/Linux

配置相应的SPI控制器驱动,然后编译部署。由于该demo涉及些项目其他的技术细节,这里就不描述了,来看看疗效:

微信图片_20210218230128.png

6 U6 z0 E6 c0 e: s: i2 ]5 O  l: w

看这个波形或许会有朋友问:为啥数据发送结束,SDO/SDI复用脚波形有一个上升的渐变暂态过程,这是由于此时从端芯片从输出态转为高阻态,而主端芯片此时也是高阻态,由于线路电容效应故而会有这样一个变化过程。

总结一下

利用ADI提高的IP库,不用敲一行代码可以很容易就实现了三线制SPI,香吧?该方案可以同时兼容三线制/四线制SPI,是一个成熟稳定的方案。为什么ZYNQ芯片这样一款SOC芯片以及Linux会被人喜欢,由此可见一斑。因为有大量成熟的轮子可供使用,而不必自己去造轮子。从而加速产品的研发进度,使用户可以专注于自己需要解决的应用问题。这里有一个tips拿走不谢:

在做应用开发时,首先梳理出需求,要干什么?去往哪里?但别急着撸代码,先搜搜看有没有现成的轮子,拒绝重新造轮子!一定会加速开发进程。但也需要注意一下开源资源是否可以免费商用,注意知识产权IP问题!


8 x) @& _. ~  b4 {$ ]) |
赞 收藏 评论0 发布时间:2021-2-18 23:07

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版