ARM与FPGA通过spi通信设计2.spi master的实现

这里主要放两个代码第一个是正常的不使用状态机的SPI主机代码;第二个是状态机SPI代码

1.不使用状态机:特权同学《深入浅出玩转FPGA》中DIY数码相框部分代码:

////////////////////////////////////////////////////////////////////////////////
module spi_ctrl(
            clk,rst_n,
            spi_miso,spi_mosi,spi_clk,
            spi_tx_en,spi_tx_rdy,spi_rx_en,spi_rx_rdy,spi_tx_db,spi_rx_db
            );

input clk;        //FPAG输入时钟信号25MHz
input rst_n;    //FPGA输入复位信号

input spi_miso;        //SPI主机输入从机输出数据信号
output spi_mosi;    //SPI主机输出从机输入数据信号
output spi_clk;        //SPI时钟信号,由主机产生

input spi_tx_en;        //SPI数据发送使能信号,高有效
output spi_tx_rdy;        //SPI数据发送完成标志位,高有效
input spi_rx_en;        //SPI数据接收使能信号,高有效
output spi_rx_rdy;        //SPI数据接收完成标志位,高有效
input[7:0] spi_tx_db;    //SPI数据发送寄存器
output[7:0] spi_rx_db;    //SPI数据接收寄存器


//模拟SPI的时序模式为CPOL=1, CPHA=1,模拟速率为25Mbit

//-------------------------------------------------
//SPI时序控制计数器,所有SPI时序由该计数器值控制
reg[4:0] cnt8;    //SPI时序控制计数器,计数范围在0-18

always @(posedge clk or negedge rst_n)
    if(!rst_n) cnt8 <= 5'd0;
    else if(spi_tx_en || spi_rx_en) begin
            if(cnt8 < 5'd18)cnt8 <= cnt8+1'b1;    //SPI工作使能
            else ;    //计数到18停止,等待撤销spi使能
        end
    else cnt8 <= 5'd0;    //SPI关闭,计数停止

//-------------------------------------------------
//SPI时钟信号产生
reg spi_clkr;    //SPI时钟信号,由主机产生

always @(posedge clk or negedge rst_n)
    if(!rst_n) spi_clkr <= 1'b1;
    else if(cnt8 > 5'd1 && cnt8 < 5'd18) spi_clkr <= ~spi_clkr;    //在cnt8处于2-17时SPI时钟有效翻转

assign spi_clk = spi_clkr;

//-------------------------------------------------
//SPI主机输出数据控制
reg spi_mosir;    //SPI主机输出从机输入数据信号

always @(posedge clk or negedge rst_n)
    if(!rst_n) spi_mosir <= 1'b1;
    else if(spi_tx_en) begin
            case(cnt8[4:1])        //主机发送8bit数据
                4'd1: spi_mosir <= spi_tx_db[7];    //发送bit7
                4'd2: spi_mosir <= spi_tx_db[6];    //发送bit6
                4'd3: spi_mosir <= spi_tx_db[5];    //发送bit5
                4'd4: spi_mosir <= spi_tx_db[4];    //发送bit4
                4'd5: spi_mosir <= spi_tx_db[3];    //发送bit3
                4'd6: spi_mosir <= spi_tx_db[2];    //发送bit2
                4'd7: spi_mosir <= spi_tx_db[1];    //发送bit1
                4'd8: spi_mosir <= spi_tx_db[0];    //发送bit0
                default: spi_mosir <= 1'b1;    //spi_mosi没有输出时应保持高电平
                endcase
        end
    else spi_mosir <= 1'b1;    //spi_mosi没有输出时应保持高电平

assign spi_mosi = spi_mosir;

//-------------------------------------------------
//SPI主机输入数据控制
reg[7:0] spi_rx_dbr;    //SPI主机输入从机输出数据总线寄存器

always @(posedge clk or negedge rst_n)
    if(!rst_n) spi_rx_dbr <= 8'hff;
    else if(spi_rx_en) begin
            case(cnt8)        //主机接收并锁存8bit数据
                5'd3: spi_rx_dbr[7] <= spi_miso;    //接收bit7
                5'd5: spi_rx_dbr[6] <= spi_miso;    //接收bit6
                5'd7: spi_rx_dbr[5] <= spi_miso;    //接收bit5
                5'd9: spi_rx_dbr[4] <= spi_miso;    //接收bit4
                5'd11: spi_rx_dbr[3] <= spi_miso;    //接收bit3
                5'd13: spi_rx_dbr[2] <= spi_miso;    //接收bit2
                5'd15: spi_rx_dbr[1] <= spi_miso;    //接收bit1
                5'd17: spi_rx_dbr[0] <= spi_miso;    //接收bit0
                default: ;
                endcase
        end

assign spi_rx_db = spi_rx_dbr;

//-------------------------------------------------
//SPI数据发送完成标志位,高有效
assign spi_tx_rdy = (cnt8 == 5'd18)/* & spi_tx_en)*/;

//-------------------------------------------------
//SPI数据接收完成标志位,高有效
assign spi_rx_rdy = (cnt8 == 5'd18)/* & spi_rx_en)*/;


endmodule

2.使用状态机的SPI master(来源网络)

module spi_master
(
    input                       sys_clk,
    input                       rst,
    output                      nCS,       //chip select (SPI mode)
    output                      DCLK,      //spi clock
    output                      MOSI,      //spi master data output
    input                       MISO,      //spi master input
    input                       CPOL,
    input                       CPHA,
    input                       nCS_ctrl,
    input[15:0]                 clk_div,
    input                       wr_req,
    output                      wr_ack,
    input[7:0]                  data_in,
    output[7:0]                 data_out
);
//状态机状态
localparam                   IDLE            = 0;
localparam                   DCLK_EDGE       = 1;
localparam                   DCLK_IDLE       = 2;
localparam                   ACK             = 3;
localparam                   LAST_HALF_CYCLE = 4;
localparam                   ACK_WAIT        = 5;

reg                          DCLK_reg;
reg[7:0]                     MOSI_shift;//移位寄存器
reg[7:0]                     MISO_shift;
reg[2:0]                     state;
reg[2:0]                     next_state;
reg [15:0]                   clk_cnt;
reg[4:0]                     clk_edge_cnt;
assign MOSI = MOSI_shift[7];
assign DCLK = DCLK_reg;
assign data_out = MISO_shift;
assign wr_ack = (state == ACK);
assign nCS = nCS_ctrl;
 
/*************这个就是状态机的定义**************/
always@(posedge sys_clk or posedge rst)
begin
    if(rst)
        state <= IDLE;
    else
        state <= next_state;
end
/****************end*************************/
 
/****************状态机的具体过程*************/
always@(*)
begin
    case(state)
        IDLE:
            if(wr_req == 1'b1)
                next_state <= DCLK_IDLE;
            else
                next_state <= IDLE;
        DCLK_IDLE:
            //half a SPI clock cycle produces a clock edge//半个SPI时钟周期产生时钟边沿
            if(clk_cnt == clk_div)
                next_state <= DCLK_EDGE;
            else
                next_state <= DCLK_IDLE;
        DCLK_EDGE:
            //a SPI byte with a total of 16 clock edges//一个SPI字节,总共有16个时钟边沿
            if(clk_edge_cnt == 5'd15)
                next_state <= LAST_HALF_CYCLE;
            else
                next_state <= DCLK_IDLE;
        //this is the last data edge  //这是最后一个数据边缘      
        LAST_HALF_CYCLE:
            if(clk_cnt == clk_div)
                next_state <= ACK;
            else
                next_state <= LAST_HALF_CYCLE;
        //send one byte complete//发送一个字节完成
        ACK:
            next_state <= ACK_WAIT;
        //wait for one clock cycle, to ensure that the cancel request signal//等待一个时钟周期,以确保取消请求信号
        ACK_WAIT:
            next_state <= IDLE;
        default:
            next_state <= IDLE;
    endcase
end

 
/****************时钟翻转************************/
always@(posedge sys_clk or posedge rst)
begin
    if(rst)                        /*在空闲状态之前,SCK一直保持CPOL的极性*/               
        DCLK_reg <= 1'b0;
    else if(state == IDLE)
        DCLK_reg <= CPOL;
    else if(state == DCLK_EDGE)    /*边缘检测时,反转SCK*/
        DCLK_reg <= ~DCLK_reg;//SPI clock edge
end
/****************end*****************************/
 
//SPI clock wait counter                /*一个计数器*/
always@(posedge sys_clk or posedge rst)
begin
    if(rst)
        clk_cnt <= 16'd0;
    else if(state == DCLK_IDLE || state == LAST_HALF_CYCLE)
        clk_cnt <= clk_cnt + 16'd1;
    else
        clk_cnt <= 16'd0;
end
//SPI clock edge counter
always@(posedge sys_clk or posedge rst)
begin
    if(rst)
        clk_edge_cnt <= 5'd0;
    else if(state == DCLK_EDGE)
        clk_edge_cnt <= clk_edge_cnt + 5'd1;
    else if(state == IDLE)
        clk_edge_cnt <= 5'd0;
end
 
//SPI data output                    /*这里就是SPI输出的移位方式*/
always@(posedge sys_clk or posedge rst)
begin
    if(rst)
        MOSI_shift <= 8'd0;
    else if(state == IDLE && wr_req)
        MOSI_shift <= data_in;
    else if(state == DCLK_EDGE)
        if(CPHA == 1'b0 && clk_edge_cnt[0] == 1'b1)          /*两种方式,取决于CPHA*/
            MOSI_shift <= {MOSI_shift[6:0],MOSI_shift[7]};   /*常见的移位语句,大家要敏感*/
        else if(CPHA == 1'b1 && (clk_edge_cnt != 5'd0 && clk_edge_cnt[0] == 1'b0))
            MOSI_shift <= {MOSI_shift[6:0],MOSI_shift[7]};
end
//SPI data input
always@(posedge sys_clk or posedge rst)
begin
    if(rst)
        MISO_shift <= 8'd0;
    else if(state == IDLE && wr_req)    
        MISO_shift <= 8'h00;
    else if(state == DCLK_EDGE)
        if(CPHA == 1'b0 && clk_edge_cnt[0] == 1'b0)  
            MISO_shift <= {MISO_shift[6:0],MISO}; /*MISO输入,然后进行移位*/
        else if(CPHA == 1'b1 && (clk_edge_cnt[0] == 1'b1))
            MISO_shift <= {MISO_shift[6:0],MISO};
end
endmodule

第二个例子实现了较为全面的spi主机功能,可以设置SPI相位和极性,有较高的参考价值

以上两个源代码可供大家参考

 

posted @ 2019-03-27 21:08  三石li  阅读(2916)  评论(0编辑  收藏  举报