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

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

[复制链接]
gaosmile 发布时间:2020-12-29 19:52

[导读] 最近使用ZYNQ做一个高速数据采集,需要访问一个ADI的高速模数采样芯片,该芯片是利用三线制实现读以及写的功能。三线制实现写通信或许大家都经常会这样用,三线制实现读/写或许有的朋友就未曾这样用过。今天就分享一下利用现成IP不写任何代码怎么实现三线制SPI。; ]; A: k: P( q! z3 F/ p5 A1 _5 x

背景

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

  • 多载波,多模式蜂窝接收机
  • 天线阵列定位
  • 功率放大器线性化
  • 无线宽带通信系统
  • 雷达系统
  • 红外成像系统
  • 通讯仪表系统等
    1 T: ], i  w+ X$ n5 U/ F! c

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

微信图片_20201229194937.png

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

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

微信图片_20201229194949.png

结合SPI模式时序图:

微信图片_20201229194952.png
  • 在上升沿采样
  • 下一位数据在CLK低期间变换
    . D& X( |! ]" r, p# X

故,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设备驱动,这个方案可以。可惜,比较懒,不想重新造轮子!
  • .......
    6 k& C1 }: ^/ |8 P( W: z

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

微信图片_20201229194955.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执行模块6 L% j( D* w, i( F2 K) X

其verilog HDL代码库位于:

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

PS/PL设计

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

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

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

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

微信图片_20201229195002.png
  • 如果是三线模式时,three_wire会变成1,这个通过AXI总线命令传过来。
  • sdo_t则可以控制sdo内部信号是否输出,如果门控关断则mosi脚变成高阻,可以采样外部信号,从而传入可以通过2路选择器传入sdi转而为读信号。8 k) I5 b2 }  C" {5 @0 w3 n

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

assign phy_sclk = spi_sclk;
9 G# W- m4 `* [& ?! i" t  x4 passign phy_cs = spi_cs;* X) t! E/ ?3 N% m. k- D; w
assign phy_mosi = spi_sdo_t ? 1'bz : spi_sdo;
& \# h# @. j3 t2 L) j& _  @* W7 Passign spi_sdi = spi_three_wire ? phy_mosi : phy_miso;+ o4 b' Y; @2 ]0 P8 t6 U

比如,我是这样写的:

`timescale 1ns / 100ps& M1 l1 V5 K, ~3 {. S; k
//顶层设计文件
0 G7 s3 n* ?, tmodule system_top (1 P& O- C9 K* V' a- y! {2 A7 Z
//DDR信号
0 b# Y3 @! V. z- @4 ninout   [14:0]  ddr_addr,
3 H& F0 c+ k1 x8 Y, s! minout   [ 2:0]  ddr_ba,' _- q$ o: c: [5 N* R0 ~' J2 P6 b
inout           ddr_cas_n,
+ E2 t3 s8 K% ]4 ]! u' Rinout           ddr_ck_n,
. A, `# h4 x! Qinout           ddr_ck_p,/ j4 V9 I! ^: f0 U0 p* Z* K* T
inout           ddr_cke,4 x1 V' ]; h9 q& r4 |- c
inout           ddr_cs_n,$ T5 R2 V* W/ b  k1 y- P
inout   [ 3:0]  ddr_dm,
- q2 G, o8 q( O! C/ _inout   [31:0]  ddr_dq,. y) e3 K( X: ~; N& \
inout   [ 3:0]  ddr_dqs_n,
- v( j3 P4 T. a& N! P7 Oinout   [ 3:0]  ddr_dqs_p,) f9 r4 h0 y! c; X7 m
inout           ddr_odt,
, |" f. V: C) A# Z" Q1 Binout           ddr_ras_n,
% m- ?# F" y( Dinout           ddr_reset_n,
' T/ G7 \, o# o+ d1 V4 e$ v- ]inout           ddr_we_n,
, U9 ?9 F" }, L- J2 Y1 J# q//必须的一些PS信号
4 R4 z+ |, T/ G& Q  q* L8 D, sinout           fixed_io_ddr_vrn,
" J/ I, r  u8 `- _) [7 s% uinout           fixed_io_ddr_vrp,
; z" t; y  C+ S) X1 |//54个PS MIO引脚
0 A5 w" r: s2 v1 }. h1 kinout   [53:0]  fixed_io_mio,$ b4 M+ K7 B( ?/ F, R: L
//PS时钟引脚
" G" `! T% F. j8 W3 ]4 linout           fixed_io_ps_clk,' O; ]2 z; P- D; e% A
//PS上电复位信号+ `$ U, |2 s2 r( n, S
inout           fixed_io_ps_porb,
; |: Z- L4 W0 I& ~6 ^4 c//PS SRSTB信号/ X! L9 i- k% Z0 c1 T
inout           fixed_io_ps_srstb,
( `' a; K" m, {# F9 F7 o" I& n
output [1:0]       spi_0_cs,5 }7 R" l. u6 M5 o$ k+ x- h
output             spi_0_sclk,
3 w) s4 I' r) Q5 O) C% }input              spi_0_sdi,
+ L3 M0 l9 O6 q8 t4 Coutput             spi_0_sdo,2 e/ H" ^$ {  [7 K4 Q
);
6 p3 T& r. B/ Q+ |9 c  % u: T6 T$ Z8 ]% r7 j. O! i
wire ip_spi_0_cs;
8 k/ M- ?3 N; w; _9 [4 z! \& `, cwire ip_spi_0_sclk;
4 ^+ y, b+ [; K4 s. jwire ip_spi_0_sdi;3 b6 V" S2 E6 l: d2 p  A$ X
wire ip_spi_0_sdo;
" y- w' [' f8 U9 W& Gwire ip_spi_0_three_wire;   
4 p5 q5 d4 C9 Ywire ip_spi_0_sdo_t;2 O8 n' W. M  J( l  C

, F6 o' f- F6 o$ i' Sassign spi_0_cs = ip_spi_0_cs;
! J! D! T) M1 ^0 _% kassign spi_0_sclk = ip_spi_0_sclk;' S0 L$ E4 Y4 S4 L( D
//如此处理,这样引出的SPI可以兼容3线制以及4线制SPI8 Z& n" b# f: Z  H/ N4 f% Z
assign spi_0_sdo = ip_spi_0_sdo_t ? 1'bz : ip_spi_0_sdo;
$ Y5 g  r! c3 d; _assign ip_spi_0_sdi = ip_spi_0_three_wire ? spi_0_sdo : spi_0_sdi;
& r( u5 ~- Z* q' r3 y$ E8 O! U/ [2 b$ |' C; F
//例化block设计3 L5 l. n( g& t
ip_block_wrapper i_system_wrapper (
7 {' {5 M. V, u, q7 f% {    //DDR以及常规MIO、时钟、复位等信号! X8 ?, h6 E; L' J2 g
    .DDR_addr(ddr_addr),! _. N$ }6 j+ j0 S$ a
    .DDR_ba(ddr_ba),* e9 t3 j$ j# c, g8 O
    .DDR_cas_n(ddr_cas_n),
( p; x  A$ J" e9 J+ W6 O9 c    .DDR_ck_n(ddr_ck_n),/ o2 S, |/ h0 H  M2 B: C/ F
    .DDR_ck_p(ddr_ck_p),
* z" {: m' v8 w/ h) ?    .DDR_cke(ddr_cke),
. ]2 I  P) J! i& t9 n7 b1 u    .DDR_cs_n(ddr_cs_n),6 W( k6 x# S/ C
    .DDR_dm(ddr_dm),
1 L* N& h3 N( K4 D    .DDR_dq(ddr_dq),
' ?. [) G. `$ \5 i* [    .DDR_dqs_n(ddr_dqs_n),
2 \( M' w  B: b    .DDR_dqs_p(ddr_dqs_p),: u4 B$ v6 _# Y! L* {
    .DDR_odt(ddr_odt),
+ }- Q$ u! [! G4 i9 ]    .DDR_ras_n(ddr_ras_n),
( l) [  c( G5 b9 l& q% B% F7 Y    .DDR_reset_n(ddr_reset_n),
, m5 n& ]( E" ?/ i  ^9 u1 r    .DDR_we_n(ddr_we_n),3 n! X) S" T5 x8 h
    .FIXED_IO_ddr_vrn(fixed_io_ddr_vrn),
  E, Y) p5 c) q) J2 Q* }* z+ G3 i    .FIXED_IO_ddr_vrp(fixed_io_ddr_vrp),
4 ?* _  Y* T7 C    .FIXED_IO_mio(fixed_io_mio),. B7 u2 U% [+ e4 ~6 M+ c+ M8 L
    .FIXED_IO_ps_clk(fixed_io_ps_clk),* f# Y. o' m) K, ~0 |
    .FIXED_IO_ps_porb(fixed_io_ps_porb),, @4 u3 A, E& w- S! j
    .FIXED_IO_ps_srstb(fixed_io_ps_srstb),       * E6 Z2 N# E) i- C9 n+ Y# r, L
2 L3 o- U3 D& ?5 R
      //连至顶层
; A0 z( P; r; {0 \: Y0 A4 X2 W: b- v6 O    .spi_0_cs(ip_spi_0_cs),
! f% v5 q- h# a/ L5 {! g    .spi_0_sclk(ip_spi_0_sclk),! e! ^% {8 f& S8 |
    .spi_0_sdi(ip_spi_0_sdi),3 Q: C5 n- e* z
    .spi_0_sdo(ip_spi_0_sdo),
# j  @% P% s3 L& M3 H3 |    .spi_0_sdo_t(ip_spi_0_sdo_t),; \3 V# `' @9 L+ ~7 ?, {
    .spi_0_three_wire(ip_spi_0_three_wire)     4 B/ k4 t; H; K+ R# D
  );   
" [& I# T3 E+ V2 H' o4 {( A* cendmodule
- D% j  J  Y+ c
Linux端配置

首先需要配置设备树:

&axi_spi_engine_0 {- t6 I: s, G. r* h5 N) m5 m
    status = "okay";
& j2 Y9 @" ^0 L7 j+ B    //配置SPI控制器匹配字段,这样会自动编译ADI 提供的SPI 控制器驱动' Z, {  c& _' [* K
    compatible = "adi,axi-spi-engine-1.00.a";
8 d( T9 j  F- V    spi-rx-bus-width = <1>;$ r- |; T& ^! U& j
    spi-tx-bus-width = <1>; " H! U9 N% C: p  z6 K( V4 l0 P) Q
    bits-per-word = <8>;
! T" X  w" ^% T) O    interrupt-parent = <&intc>;
4 f) ^. R, T$ y    interrupts = <0 30 4>;      
% X; X( M# U) z! O; D5 l! a    num-cs = <4>;
5 }! |; ~- ~/ H1 S( g# p3 y
& T8 f- p. x1 x* X& S6 s7 g; D    #address-cells = <0x1>;* Z6 G8 v: w, c: G7 w- `7 y) `
    #size-cells = <0x0>;   
2 N% D) ^" x0 b4 j) n* o    ad9467_0: ad9467@0 {
+ ^  s! P) O- [" O/ i, @: z        compatible = "adi,ad9467";% U; K* ^! ^8 u3 c4 e3 U7 _
        reg = <0>;$ h7 t2 N4 L# q0 E
        spi-max-frequency = <500000>;
  ~) I! D5 r% B6 F9 S        #address-cells = <1>;% r2 n- z0 X7 o. s
        #size-cells = <1>;
4 m. K* R9 i( c5 }; j4 c9 o, i) q5 d8 E9 a
        spi-rx-bus-width = <1>; , ~, [) U1 p7 d0 ^" p
        spi-tx-bus-width = <1>;' K. j6 Z8 a; W0 |! o
        bits-per-word = <8>;
* D1 I8 n2 O! O/ h, k3 n        spi-3wire;        1 @$ a$ a% P; l2 ^+ H- L# |6 Q) R& n
    };) ~3 g* o6 [, D! N% F9 K

: L' P% {' J+ j* M    ad9467_1: ad9467@1 {$ r  n+ G# v. @
        compatible = "spidev";5 b  q! o; l! E. R5 V+ M
        reg = <1>;
- g$ F4 X9 S4 |9 m$ Q5 _        spi-max-frequency = <500000>;
: ~1 q; I8 T- d$ @9 X        #address-cells = <1>;
% _9 X; i/ R( l3 ?9 @        #size-cells = <1>;        ' i* y6 _1 b/ O% b" D) k$ z. O
5 E  R# R3 g6 C$ g3 b1 c
        spi-rx-bus-width = <1>; 2 F/ S* `2 z& e; t
        spi-tx-bus-width = <1>;
7 _/ ?$ T: }8 r        bits-per-word = <8>;6 c; V9 `' x7 Y# f4 o7 K$ [$ u* {! d# b
        //这个字段需要使能,表示该设备是三线制' m' {) ^& _* s+ u3 C5 K: J
        spi-3wire;           2 _  M0 m5 V% ~9 Z
    };      7 T7 s, e* V. c" b, q
};
- L6 ^7 p& t* X5 _  ~/ b$ e$ W

基于ADI提高的Linux代码库:

https://github.com/analogdevicesinc/Linux

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

微信图片_20201229195005.png

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

总结一下

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

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


9 L/ v  ?3 L' ^
赞 收藏 评论0 发布时间:2020-12-29 19:52

举报

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