• FPGA--SPI通信


    一,SPI说明:

    1、什么是SPI?
    SPI是串行外设接口(Serial Peripheral Interface)的缩写。是 Motorola 公司推出的一 种同步串行接口技术,是一种高速的,全双工,同步的通信总线。

    2、SPI优点
    支持全双工通信、通信简单、数据传输速率块

    3、缺点
    没有指定的流控制,没有应答机制确认是否接收到数据,所以跟IIC总线协议比较在数据 可靠性上有一定的缺陷。

    4、特点
    1):高速、同步、全双工、非差分、总线式
    2):主从机通信模式

    5、协议通信时序详解
    1):SPI的通信原理很简单,它以主从方式工作,这种模式通常有一个主设备和一个或多 个从设备,需要至少4根线,事实上3根也可以(单向传输时)。也是所有基于SPI的设备共有的,它们是

    SDI(数据输入)、SDO(数据输出)、SCLK(时钟)、CS(片选)。
    (1)SDO/MOSI ((master out slaver in))– 主设备数据输出,从设备数据输入;
    (2)SDI/MISO – 主设备数据输入,从设备数据输出;
    (3)SCLK – 时钟信号,由主设备产生;
    (4)CS/SS – 从设备使能信号,由主设备控制。当有多个从设备的时候,因为每个从设 备上都有一个片选引脚接入到主设备机中,当我们的主设备和某个从设备通信时将需 要将从设备对应的片选引脚电平拉低或者是拉高。

    二,协议举例说明(某通过SPI通信的芯片):

    1,芯片管脚说明:

    MISO:output data for spi port,output on falling edge of sclk;

    MOSI:input data for spi port,latches on rising edge of sclk,MSB first;

    CSB:chip select input,active low,CSB frames SPI commands and enables SPI port;

    SCLK:clock for SPI;

    SPI Protocol:
    SPI MOSI data is read on SCLK rising edge.
    MISO data is changed on SCLK falling edge.
    SPI communication is “MSB first”.
    SPI command is composed of a multiple of 24 SCLK cycles.
    The number of clock cycles occurring on the pin SCLK while the CSB pin is asserted low must be a multiple of 24.
    The serial output data is available on the rising edge of SCLK and transitions on the falling edge of SCLK .

    2,时序图:

     3,FPGA 代码举例

    A:作为主机发送数据:

    发送模块:

    `timescale 1ns / 1ps
    module clk(
                        clk50,//系统时钟
                        clkout,//输出时钟,sclk管脚
                              send_flag,//发送数据指令
                              send_over,//数据发送完毕提示
                              sdo,
                              DATA
    );
    input clk50;
    output clkout;
    input send_flag;
    output send_over;
    input [23:0] DATA;//要发送的数据
    output sdo;
    
    
    //寄存器型数据对象的定义
    reg clkout;
    reg [15:0] cnt;//分频因子
    reg send_over;
    reg [7:0] clock_num;//clkout的数目,没24个clk后清零一次
    reg [7:0] cnt1;//发送数据位寄存器
    reg sdo;
    
    
    //50mhz时钟100分频,sclk时钟500k
    always @(posedge clk50) 
    begin
            if((send_flag==1'b1)&&(clock_num<8'd24))begin
                send_over<=1'b1;//正在发送数据
                 if(cnt == 16'd1) begin
                            clkout <= 1'b0;//下降沿sdo管脚数据change
                            cnt <= cnt + 16'd1;
                            sdo<=DATA[23-cnt1];        //MSB前            
                            cnt1<=cnt1+8'd1;
                    end        
                            
                 else if(cnt == 16'd49) begin
                            clkout <= 1'b1;
                            cnt <= cnt + 16'd1;
                      end
                else if(cnt == 16'd99) begin
                            cnt <= 16'd0;
                            clock_num<=clock_num+8'b1;
                      end
                 else 
                      cnt <= cnt + 16'd1;
            end
            else begin
                clock_num<=8'b0;
                send_over<=1'b0;
                clkout<=1'b0;
                sdo<=1'b0;
                cnt1<=8'd0;    
            end
    end
    
    endmodule

    顶层模块:

    `timescale 1ns / 1ps
    module SPI(
                        clk, // 输入时钟: 50Mhz
                        clkout,
                              sdo,
                              sdi,
                              cs
    );
    input clk;
    output clkout;
    output sdo;
    output cs;
    input sdi;
    
    reg [15:0] count;//cs,clk开始发出的时序控制寄存器
    reg send_flag;
    wire send_over;
    wire [23:0] DATA;
    reg cs;
    reg [23:0] DATA1;
    reg [7:0] cycle;//发送的数据选择位
    reg [31:0] delay_count;//延时计数器
    reg  delay_flag;//延时结束标志位
    parameter cs_num=8'd6;//发送的数据的个数
    
    assign DATA=DATA1;
    
    always @(posedge clk)
    begin
            if(delay_flag==1'b0)begin//上电延时一段时间
                delay_count<=delay_count+1'b1;
                if(delay_count==32'd50_000)
                    delay_flag<=1'b1;
                end
    
            if((send_over==1'b0)&&(cycle<(cs_num+1))&&(delay_flag==1'b1))begin//cs,clk开始发出的时序生成代码
                count<=count+16'b1;
                end
            else if(send_over==1'b1)begin
                count<=16'b0;
                end
                
            if(count==16'd100)begin
                cs<=1'b1;
                end
            else if(count==16'd2800)begin
                case(cycle)
                 8'd0:begin
                            DATA1<=24'h555555;
                  end
                 8'd1:begin
                            DATA1<=24'hAAAAAA;
                  end
                 8'd2:begin
                            DATA1<=24'h5A5A5A;
                  end
                 8'd3:begin
                            DATA1<=24'hA5A5A5;
                  end
                 8'd4:begin
                            DATA1<=24'hDDDDDD;
                  end
                 8'd5:begin
                            DATA1<=24'hb81d69;
                  end
                endcase
                cycle<=cycle+8'b1;
            end
            else if(count==16'd2900)begin
                cs<=1'b0;
                end
            else if(count==16'd3000)begin
                send_flag<=1'b1;
                end
                
            if(delay_flag==1'b0)begin
                cs<=1'b1;
                send_flag<=1'b0;
                end
            else if((send_over==1'b0)&&(count<16'd10)&&(delay_flag==1'b1))begin//发送完成后send_flag为清零,为下一次发送做准备
                send_flag<=1'b0;
                end
    end
    
    
    
    //例化时钟模块    
    clk clk_SPI(
                        .clk50(clk),
                        .clkout(clkout),
                              .send_flag(send_flag),
                              .send_over(send_over),
                              .sdo(sdo),
                              .DATA(DATA)
    );
    
    endmodule

    B,接收数据,并发出:

    发送模块:

    `timescale 1ns / 1ps
    module clk(
                        clk50,//系统时钟
                        clkout,//输出时钟
                              send_flag,
                              send_over,
                              sdo,
                              DATA
    );
    input clk50;
    output clkout;
    input send_flag;
    output send_over;
    input [23:0] DATA;
    output sdo;
    
    
    //寄存器型数据对象的定义
    reg clkout;
    reg [15:0] cnt;
    reg send_over;
    reg [7:0] clock_num;
    reg [7:0] cnt1;
    reg sdo;
    
    //initial begin
    //clkout<=0;
    //cnt<=0;
    //send_over<=0;
    //clock_num<=0;
    //cnt1<=0;
    //sdo<=0;
    //
    //end
    
    
    
    //50mhz时钟100分频
    always @(posedge clk50) begin
            if((send_flag==1'b1)&&(clock_num<8'd24))begin
                send_over<=1'b1;
                 if(cnt == 16'd1) begin
                            clkout <= 1'b0;
                            cnt <= cnt + 16'd1;
                            sdo<=DATA[23-cnt1];                    
                            cnt1<=cnt1+8'd1;
                    end        
                            
                 else if(cnt == 16'd49) begin
                            clkout <= 1'b1;
                            cnt <= cnt + 16'd1;
                      end
                else if(cnt == 16'd99) begin
                            cnt <= 16'd0;
                            clock_num<=clock_num+8'b1;
                      end
                 else 
                      cnt <= cnt + 16'd1;
            end
            else begin
                clock_num<=8'b0;
                send_over<=1'b0;
                clkout<=1'b0;
                sdo<=1'b0;
                cnt1<=8'd0;    
            end
    end
    
    endmodule

    接收模块:

    `timescale 1ns / 1ps
    module read(
                        clkin,//系统时钟
                              cs,
                              sdi,
                              DATA,
                              flag//一个24bit读取完毕标志位,读取下一个字节到一半的时候清零
    );
    input clkin;
    input cs;
    output [23:0] DATA;
    input sdi;
    output  flag;
    
    reg [7:0] cnt2;
    reg [23:0] DATA;
    reg [23:0] data_receive;
    reg  flag;
    //仿真使用
    //initial begin
    //cnt2<=0;
    //DATA<=0;
    //data_num<=0;
    //data_receive<=0;
    //flag<=0;
    //end
    //
    
    //
    always @(posedge clkin) begin
            if(cs==1'b0) begin
                data_receive[23-cnt2]<=sdi;
                if(cnt2==8'd23)begin
                    cnt2<=1'b0;
                    DATA<=(data_receive&24'hFFFFFE)|sdi;//最后一位这里读不到,所以直接把sdi给他
                    flag<=1'b1;
                end
                else if(cnt2==8'd11) begin
                    flag<=1'b0;
                    cnt2<=cnt2+1'b1;
                    end
                else begin
                    cnt2<=cnt2+1'b1;
                end
            end
    end
    
    endmodule

    顶层模块:

    `timescale 1ns / 1ps
    module SPI(
                        clk, // 输入时钟: 50Mhz
                        clkout,
                              sdo,
                              sdi,
                              cs,
                          
                              clk_in,
                              sdi_in,
                              cs_in
    );
    
    
    input clk;
    output clkout;
    output sdo;
    output cs;
    input sdi;
    
    input clk_in;
    input sdi_in;
    input cs_in;
    /////////////////////////////////////////////
    
    wire [23:0] data_in;
    wire  read_flag;
    reg [15:0] count;
    reg send_flag;
    wire send_over;
    reg [23:0] DATA;
    reg cs;
    reg [7:0] cycle;
    reg  delay_flag;//这里变成了开启发送数据的标志位
    reg [7:0] cs_num;
    reg [7:0] read;//保证每次读取数据的时候,顶层里面的处理代码只执行一次,类似状态机
    reg [7:0] read_num;//读取数据的个数
    /////////////////////////////////////////////
    //数据存储寄存器
    reg [23:0] DATAIN_1;
    reg [23:0] DATAIN_2;
    reg [23:0] DATAIN_3;
    reg [23:0] DATAIN_4;
    reg [23:0] DATAIN_5;
    reg [23:0] DATAIN_6;
    
    //仿真使用
    initial begin
    
    
    //count<=0;
    //send_flag<=0;
    
    
    //cs<=0;
    //cycle<=0;
    delay_flag<=0;
    //cs_num<=0;
    
    
    //DATAIN_1<=0;
    //DATAIN_2<=0;
    //DATAIN_3<=0;
    //DATAIN_4<=0;
    //DATAIN_5<=0;
    //DATAIN_6<=0;
    
    end
    
    
    //
    always @(posedge clk) begin
    
        if((read_flag==1'b1)&&(read<=4))begin
            read<=read+1'b1;
            if(read==1)begin read_num=read_num+1'b1;end
            else if(read==2)begin
                if(data_in==24'hB81D69)begin//B81D69
                    if(read_num>4'd6) begin cs_num<=4'd6;end
                else begin cs_num<=read_num-4'd1;end
                end
            end
            else if(read==3)begin
                if(data_in==24'hB81D69)begin     delay_flag<=1'b1;read_num<=0;end
            end
            else if(read==4)begin
                case(read_num)
                    4'd1:begin DATAIN_1<=data_in;end
                    4'd2:begin DATAIN_2<=data_in;end
                    4'd3:begin DATAIN_3<=data_in;end
                    4'd4:begin DATAIN_4<=data_in;end
                    4'd5:begin DATAIN_5<=data_in;end
                    4'd6:begin DATAIN_6<=data_in;end
                    endcase
                end
            end
            else if(read_flag==0) begin read<=0;end
    
    
    //////////////////////////////////
            if((send_over==1'b0)&&(cycle<(cs_num+1))&&(delay_flag==1'b1))begin count<=count+16'b1;end
            else if(send_over==1'b1)begin count<=16'b0;end
                
            if(count==16'd100)begin cs<=1'b1;end
            else if(count==16'd2800)begin
                case(cycle)
                 8'd0:begin DATA<=DATAIN_1;end
                 8'd1:begin DATA<=DATAIN_2;end
                 8'd2:begin DATA<=DATAIN_3;end
                 8'd3:begin DATA<=DATAIN_4;end
                 8'd4:begin DATA<=DATAIN_5;end
                 8'd5:begin DATA<=DATAIN_6;end
                endcase
                cycle<=cycle+8'b1;
            end
            else if(count==16'd2900)begin cs<=1'b0;end
            else if(count==16'd3000)begin
                    send_flag<=1'b1;
                    if(cycle==cs_num) begin cycle<=8'b0;delay_flag<=1'b0;end
                end
                
            
        if((send_over==1'b0)&&(count<16'd10))begin send_flag<=1'b0;end
    
    
    end
    
    //
    
    //例化时钟模块    
    clk clk_SPI(
                        .clk50(clk),
                        .clkout(clkout),
                              .send_flag(send_flag),
                              .send_over(send_over),
                              .sdo(sdo),
                              .DATA(DATA)
    );
    //
    
    //
    read read_SPI(
                        .clkin(clk_in),
                              .cs(cs_in),
                              .sdi(sdi_in),
                              .DATA(data_in),
                              .flag(read_flag)
    );
    
    endmodule
  • 相关阅读:
    JAVA版SqlHelper
    JAVA中的继承特点1
    C# SqlBulkCopy类批量导入数据
    动态注册HttpModule管道,实现global.asax功能
    实现自己的前端模板轻量级框架
    事务消息中心-TMC
    Win10应用设计的那些事儿
    考拉定时任务框架kSchedule
    如何玩转基于风险的测试
    谈谈Java异常处理这件事儿
  • 原文地址:https://www.cnblogs.com/caiya/p/12986952.html
Copyright © 2020-2023  润新知