• D2--FPGA SPI接口通信2022-08-03


    1.SPI简介

    SPI是串行外设接口(Serial Peripheral Interface)的缩写,通常说SPI接口或SPI协议都是指SPI这一种串行外设接口规范。相对于串口,SPI是一种高速的(可达10Mb\s以上),全双工,同步的通信总线。串口是点对点的全双工的异步通信,因此要通信双方按照相同的约定(指起始、暂停位、波特率)才能在数据上同步,准确通信。而SPI是一种同步通信的总线结构,同步是指有专门时钟线用来同步源端和目的端,总线结构是指主从之分,通常是一个设备做主,可以多个设备做从,也正因为如此才有了CS片选线,指定所选从设备。

     而在实际的应用中,通常也只是一主一从。SPI接口多应用于Flash、ADC、LCD控制器,CMOS寄存器配置接口等场景。SPI在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,越来越多的芯片集成了这种通信协议。

    2.时序

    SPI有四根线,如下,

    (1)MISO– Master Input Slave Output,主设备数据输入,从设备数据输出;

    (2)MOSI– Master Output Slave Input,主设备数据输出,从设备数据输入;

    (3)SCLK – Serial Clock,时钟信号,由主设备产生;

    (4)CS – Chip Select,从设备使能信号,由主设备控制。

    SPI有四种数据传输的时序模式,这是有设备的属性的决定的,用CPOL和CPHA表示这四种时序。CPOL是用来决定SCK时钟信号空闲时的电平,CPOL=0(1),空闲电平为低电平(高电平);CPHA是用来决定采样时刻的,CPHA=0,在每个周期的第一个时钟沿采样,CPHA=1,则在每个周期的第二个时钟沿采样。通常使用的是MODE0模式,即CPOL=CPHA=0,空闲时为低电平,在第一个时钟沿采样。其他几种模式如果需要可以上网搜索,资料很多。

    使用SPI接口时需要注意,很多SPI接口的芯片并不是标准的SPI协议,可能是稍有变化,例如对哪个沿采样会有明确的说明。此外,还有一种可能是在SPI协议的基础上进一步做限制,例如要求一次读写过程结束后CS必须拉高等。SPI要求是高位在前,MSB的方式。还有就是SPI一个时钟周期必然要执行接收一位数据和发送一位数据。

     

    3.Dual、Qual模式

    上面描述的就是标准SPI,有4根引脚信号:clk , cs, mosi, miso。针对SPI Flash而言,有Dual SPI和Qual SPI两种模式,这是因为,Flash器件的全双工并不常用,因此扩展mosi和miso的用法,让它们工作在半双工,用以加倍数据传输。也就是对于Dual SPI Flash,可以发送一个命令字节进入dual mode,这样mosi变成SIO0(serial io 0),mosi变成SIO1(serial io 1),这样一个时钟周期内就能传输2个bit数据,加倍了数据传输。与Dual SPI类似,Qual SPI Flash增加了两根I/O线(SIO2,SIO3),目的是一个时钟内传输4个bit。

    4.例程设计与代码解读

    如图所示,把Spi_driver模块的输入输出分成两个部分,一个部分为图示左侧的与其他模块交互的接口,另一部分为图示右侧与物理管脚相连。Spi_en信号为外部的脉冲信号,收到该脉冲执行一次8Bit的发送动作,和8Bit的接收动作。通过VIO产生spi_en信号和要发送的数据信号data[7:0],将板卡上的mosi和miso管脚相连,判断发送数据和接收数据是否一致,若一致则点亮led。由于缺乏带SPI设备的板卡,故设计此例程。一次发送动作结束后会产生spi_done的脉冲信号,在发送动作执行过程中,spi_busy持续为拉高状态。另外,spi_sck作为同步时钟直接决定传输数据的速度,通常在10Mb/s以下,或者依据对端设备来指定。

    在使用SPI设备时可以修改此驱动模块以满足需求,左侧信号与外部设备交互,当busy拉高的时候不允许发送spi_en信号;可以对data信号采用多路选择器,以完成某些固定的指令或者读写操作;同样,数据的位宽也可以扩展。另外,实际使用的时候应该使用cs管脚,按照相关设备的要求灵活运用该管脚。

     下面代码在硬件板卡上完成测试。该代码使用MODE0,即CPOL=CPHA=0的模式。程序分为以下四步,第一步产生sck同步时钟,第二步依据en指示信号写数据,第三步依据en指示信号并发读数据,第四步产生ui交互信号。

    1. module spi_wr(
    2.     input        clk_i       ,
    3.     //user interface
    4.     // input        spi_en     ,//其他模块的spi使能信号
    5.     // input    [7:0]data      ,
    6.     output       spi_busy      ,//指示spi的状态,表示SPI过程
    7.     output       spi_done      ,//指示spi结束一次动作
    8.     output       led           ,
    9.     // output       spi_cs        ,//SPI从机的片选信号,低电平有效。
    10.     output       spi_clk       ,//主从机之间的数据同步时钟。
    11.     output       spi_mosi      ,//数据引脚,主机输出,从机输入。
    12.     input        spi_miso      //数据引脚,主机输入,从机输出。
    13.     );
    14. // assign spi_cs = 0;
    15. //1.同步时钟产生模块 用50MHz分频为50KHz
    16. assign spi_clk = m_clk;
    17. parameter [9:0] SPI_DIV    = 10'd499;//分频定为50KHz,50Mhz/50K/2- 1'b1=499
    18. reg [9:0] clk_cnt = 10'd0;//分频计数器
    19. always@(posedge clk_i)begin
    20.     if(clk_cnt < SPI_DIV&&spi_busy)
    21.         clk_cnt <= clk_cnt + 1'b1;
    22.     else
    23.         clk_cnt <= 10'd0;
    24. end
    25. reg m_clk = 1'b0;
    26. always@(posedge clk_i)begin
    27.     if(spi_en)
    28.         m_clk <= 1'b0;
    29.     else if(clk_cnt==SPI_DIV)
    30.         m_clk <= ~m_clk;
    31. end
    32. //2.接收en信号和数据,执行spi mosi操作(写数据)
    33. //计数发送个数
    34. reg [3:0]tx_cnt = 0;
    35. always@(posedge clk_i)begin
    36.     if(spi_en)begin
    37.         tx_cnt <= 0;
    38.     end else if((clk_cnt == SPI_DIV)&&(tx_cnt==4'd8))begin
    39.         tx_cnt <= 0;
    40.     end else if((clk_cnt == SPI_DIV)&&spi_clk==0)
    41.         tx_cnt<=tx_cnt+1;
    42. end
    43. //移位寄存器发送
    44. wire [7:0]data;
    45. reg  [7:0]data_reg=8'b0;//数据源
    46. always@(posedge clk_i)begin
    47.     if(spi_en)
    48.         data_reg <= data;
    49.     else if(spi_done)
    50.         data_reg <=8'b0;
    51.     else if(clk_cnt == SPI_DIV&&spi_clk==1)
    52.         data_reg[7:0] <= {data_reg[6:0],data_reg[7]};
    53. end
    54. assign spi_mosi = data_reg[7];
    55. //3.接收en信号和数据,执行spi miso操作(读数据)
    56. reg  [7:0]data_fifo=8'b0;
    57. always@(posedge clk_i)begin
    58.     if((clk_cnt == SPI_DIV)&&spi_clk==0)begin
    59.         case (tx_cnt)
    60.            0 : data_fifo[7] <= spi_miso;
    61.            1 : data_fifo[6] <= spi_miso;
    62.            2 : data_fifo[5] <= spi_miso;
    63.            3 : data_fifo[4] <= spi_miso;
    64.            4 : data_fifo[3] <= spi_miso;
    65.            5 : data_fifo[2] <= spi_miso;
    66.            6 : data_fifo[1] <= spi_miso;
    67.            7 : data_fifo[0] <= spi_miso;
    68.            default: ;
    69.         endcase
    70.     end else
    71.         data_fifo <= data_fifo;
    72. end
    73. //4.发出user interface相关指示信号
    74. wire spi_en;
    75. reg spi_busy =0;
    76. assign spi_done = ((clk_cnt == SPI_DIV)&&(tx_cnt==4'd8)) ? 1'b1 : 1'b0;
    77. assign led = (data_fifo==8'b11010111) ? 1'b1 : 1'b0;
    78. //产生busy信号,指示spi进程
    79. always@(posedge clk_i)begin
    80.     if(spi_en)
    81.         spi_busy<=1'b1;
    82.     else if(spi_done)
    83.         spi_busy<=1'b0;
    84.     else
    85.         spi_busy<=spi_busy;
    86. end
    87. vio_0 use_vio (
    88.   .clk(clk_i),                // input wire clk
    89.   .probe_out0(spi_en),  // output wire [0 : 0] probe_out0
    90.   .probe_out1(data)  // output wire [7 : 0] probe_out1
    91. );
    92. ila_0 your_instance_name (
    93.     .clk(clk_i), // input wire clk
    94.     .probe0({data_reg,data_fifo}), // input wire [15:0]  probe0  
    95.     .probe1(tx_cnt), // input wire [3:0]  probe1
    96.     .probe2({spi_en,spi_mosi,spi_miso,spi_busy}) // input wire [3:0]  probe2
    97. );

  • 相关阅读:
    apollo中配置map,list
    理解ROS tf
    访问控制1
    [附源码]计算机毕业设计JAVAjsp毕业设计管理系统
    中文写代码?开始不信后来用中文写了剧情小游戏!嗯,真香~
    Ribbon源码解析
    深入理解final关键字
    周赛371(模拟、哈希+排序+枚举)
    量化投资 日周月报 2024-06-28
    计算机专业毕业设计项目推荐12-志愿者管理系统(Spring+Js+Mysql)
  • 原文地址:https://blog.csdn.net/weixin_40615338/article/details/126145035