|  **银杏科技有限公司旗下技术文档发布平台**  ||||
|技术支持电话|**0379-69926675-801**|||
|技术支持邮件|Gingko@vip.163.com|||
^  版本  ^  日期  ^  作者  ^  修改内容  ^
|  V1.0  |  2020-03-06  |  gingko  |  初次建立  | 
===== 实验十七:SPI通信实验——基于SPI总线的ARM与FPGA通信 =====
==== 一、 实验目的与意义 ====
  - 掌握SPI通信协议及实现方法。
  - 掌握QuartusII的使用方法。
==== 二、 实验设备及平台 ====
  - iCore4T 双核心板。
  - iTool A(或相同功能)仿真器。
  - USB Type C 线缆。
  - Keil MDK 开发平台。
  - Quartus开发平台。
  - 电脑一台。
==== 三、 实验原理 ====
=== 1.SPI简介 ===
  * SPI是串行外设接口(Serial PeripheralInterface)的缩写。SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,如今越来越多的芯片集成了这种通信协议。SPI的通信原理很简单,它以主从方式工作,这种模式通常有一个主设备和一个或多个从设备,需要至少4根线,事实上3根也可以(单向传输时)。也是所有基于SPI的设备共有的,它们是SDI(数据输入)、SDO(数据输出)、SCLK(时钟)、CS(片选)。
  * **SPI硬件接口:**
    * MISO  :主设备数据输入,从设备数据输出
    * MOSI  :主设备数据输出,从设备数据输入
    * SCLK :时钟信号,由主设备产生
    * CS :从设备片选信号,由主设备控制
=== 2.SPI功能说明 ===
  * **SPI时钟极性和相位:**
    * CPOL决定时钟空闲时的稳定电平,对主/从都有效
    * CPOL=0:空闲时低电平
    * CPOL=1:空闲时高电平
    * CPHA决定数据采样时刻
    * CPHA=0:第一个时钟延开始采样MSBit
    * CPHA=1:第二个时钟延开始采样MSBit
  * SPI总线四种工作方式 SPI 模块为了和外设进行数据交换,根据外设工作要求,其输出串行同步时钟极性和相位可以进行配置,时钟极性(CPOL)对传输协议没有重大的影响。如果 CPOL=0,串行同步时钟的空闲状态为低电平;如果CPOL=1,串行同步时钟的空闲状态为高电平。时钟相位(CPHA)能够配置用于选择两种不同的传输协议之一进行数据传输。如果CPHA=0,在串行同步时钟的第一个跳变沿(上升或下降)数据被采样;如果CPHA=1,在串行同步时钟的第二个跳变沿(上升或下降)数据被采样。SPI主模块和与之通信的外设备时钟相位和极性应该一致。如图17.1所示
{{ :icore4t:icore4t_fpga_17_1.png?direct |}}
=== 3.SPI通信指令表 ===
  * **表15.1 SPI通信指令表**
^指令名称	^字节1	^字节2	^字节3	^字节4^
|器件ID	        |01h	|	|	|     |
|写数据长度      |02h	|A15~A8 |A7~A0  |      |
|写数据	        |04h	|A15~A8	|A7~A0	|数据(直至写完所有数据)|
|读数据长度	|05h	|A15~A8	|A7~A0	|     |
|读数据	        |07h	|A15~A8	|A7~A0	|数据(直至读完所有数据)|
|读错误信息	|08h	|	|       |     |
  * ARM与FPGA通信采用的是半双工式通信,FPGA通过识别指令完成与ARM的交互。
  * 器件ID指令为01h,接下来为两字节的伪指令,第四字节仍为伪指令读取ID标志。 
  * 写数据长度指令02h,接下来两个字节为写数据的长度,先发高字节,后发低字节,接下来为一个字节的伪指令00h。
  * 写数据指令为04h,接下来为两字节的地址指令,后为要写入的数据,数据写入完毕以伪指令00h结束数据传输。
  * 读数据长度指令05h,接下来两个字节为写数据的长度,先发高字节,后发低字节,接下来为一个字节的伪指令00h。
  * 读数据指令为07h,接下来为两字节的地址指令,其后为伪指令00h开始读取数据进行数据传输,第五字节以后为要读取的数据。
  * 读错误信息指令08h,FPGA接收数据是否出错,先发送两个字节的伪指令,第四字节仍为伪指令读取错误标志信息。
==== 四、 代码讲解 ====
  * 本实验基于ARM+FPGA构架,通过ARM首先发送查询ID指令,然后依次通过指令设置写数据的长度、写数据地址、读数据的长度、读数据的地址,然后对比写入数据和读出数据是否一致判断数据是否写入成功,从而基于SPI总线实现ARM与FPGA之间的通信。
  * 1、更改写数据长度、写数据地址、读数据长度、读数据地址参数时,秩序更改初始化值即可,代码如下:
  * **注意:**在设置过程中,地址和长度之和不能大于1024,这是由FPGA内部设置的RAM容量决定的。
 
    //Parameter Value
    //注意:地址和长度之和不能大于1024
    write_address = 0;
    write_length = 1024;
    read_address = 0;
    read_length = 1024;
  * 2、实现ARM与FPGA通信,对FPGA而言,其关键就在于SPI时序的模拟,实现SPI数据的接收与发送,实现数据与传输信号之间的串并转换。FPGA首先接收ARM指令,然后解析指令,存储相应的信息与数据,并根据指令需求将相应的指令数据放到SOMI总线上,等待ARM读取,从而实现两者之间的数据交互。SPI时序的硬件语言描述如下:
//------------------------------------------------//
////////////////按字节接收SPI发送过来的数据//////////////
///////接收模块///////
reg [3:0]receive_state;
reg [7:0]data_in;
reg [7:0]receive_byte_r;
reg spi_rx_en_r;
        
always@(posedge spi_clk or negedge rst_n or posedge cs_delay)
    begin
        if((!rst_n)||(cs_delay))
        begin
            receive_state <= 4'd0;
            receive_byte_r <= 8'd0;
            data_in <= 8'd0;
            
            spi_rx_en_r <= 1'd0;
        end
        else //低时钟时可以利用提取沿的方式
        begin
            case(receive_state)   //从高位开始接收数据,每8个spi_clk时钟接收一个Byte
                4'd0:begin
                    receive_state <= receive_state + 1'd1;
                    data_in <= {data_in[6:0],spi_mosi};
                    spi_rx_en_r <= 1'd0;
                end
                4'd1,4'd2:begin
                    receive_state <= receive_state + 1'd1;
                    data_in <= {data_in[6:0],spi_mosi};
                end
                4'd3:begin
                    receive_state <= receive_state + 1'd1;
                    data_in <= {data_in[6:0],spi_mosi};
                    spi_rx_en_r <= 1'd1;
                end             
                4'd4,4'd5:begin
                    receive_state <= receive_state + 1'd1;
                    data_in <= {data_in[6:0],spi_mosi};
                    spi_rx_en_r <= 1'd0;
                end
                4'd6:begin
                    receive_state <= receive_state + 1'd1;
                    data_in <= {data_in[6:0],spi_mosi};
                end
                4'd7:begin
                    receive_state <= 4'd0;
                    data_in <= {data_in[6:0],spi_mosi};
                    receive_byte_r <= {data_in[6:0],spi_mosi};
                end                     
            endcase
        end
    end
//------------------------------------------------//
///////发送模块///////
reg [3:0]send_state;
reg spi_miso_r;
reg spi_tx_en_r;
reg [7:0]data_out;
always@(negedge spi_clk or negedge rst_n or posedge cs_delay)
    if((!rst_n) || (cs_delay))
        begin
            send_state <= 4'd0;
            spi_tx_en_r <= 1'd0;
            data_out <= 8'd0;
        end
    else 
    begin
        case(send_state) 
            4'd0:begin
                spi_miso_r <= data_out[7];
                send_state <= send_state + 1'd1;
            end 
            4'd1:begin
                spi_miso_r <= data_out[6];
                send_state <= send_state + 1'd1;
            end 
            4'd2:begin   
                spi_miso_r <= data_out[5];
                send_state <= send_state + 1'd1;
            end 
            4'd3:begin
                spi_miso_r <= data_out[4];
                send_state <= send_state + 1'd1;
            end 
            4'd4:begin
                spi_miso_r <= data_out[3];
                send_state <= send_state + 1'd1;
                spi_tx_en_r <= 1'd0;
            end 
            4'd5:begin
                spi_miso_r <= data_out[2];
                send_state <= send_state+ 1'd1;
                spi_tx_en_r <= 1'd1;
            end
            4'd6:begin
                spi_miso_r <= data_out[1];
                send_state <= send_state + 1'd1;    
                spi_tx_en_r <= 1'd0;        
            end 
            4'd7:begin
                data_out <= send_byte;
                spi_miso_r <= data_out[0];
                send_state <= 4'd0;
                spi_tx_en_r <= 1'd0;
            end
        endcase
    end     
==== 五、 实验步骤及实验结果 ====
{{ :icore4t:icore4t_ide_1_37.jpg?600 |}}
  - 将硬件正确连接,如图17.2所示。
  - 打开putty串口调试工具,打开设备管理器查看对应的端口信息,在putty中打开对应的端口,用于打印串口信息;
  - 将编写好的FPGA代码进行编译,并下载到开发板中;
  - 将编写好的ARM代码编译,并下载到开发板中,putty工具中会打印相应的SPI通信相关信息;
  - 观察实验现象及putty终端打印信息——FPGA_LED闪烁,putty终端打印如图17.3所示.
{{ :icore4t:icore4t_fpga_17_3.png?direct |图17.3}} 
==== 六、 拓展实验 ====
  - 通过Signaltap观察SPI通信的时序是否和参考时序一致。
  - 实现错误信息读取指令功能。