(二)SPI通信的初始化设置verilog实现

emmmmm,一下子跳到了SPI通信,跨度有点大,刚好学到这里,OK少废话。

相信学过ARM的同学对SPI通信也有一定的认识,很多模块都需要用到SPI通信。我就直接用黑金开发板AX301的SPI_Flash例程里面的SPI_master给大家讲解一下。够良心的啦,黑金开发板的资料都没有给出相应的SPI知识,这让学过ARM但基础知识不扎实的同学怎么办(说的好像就是我。。。。。。。)来吧来吧,哥给你普及一下知识吧。(讲得不好体谅体谅,博主水平有限,欢迎给修改意见)

特地去原子哥里面找到SPI的时序图,放两张重要的:

两张图中可以看出,SPI通信是通过CPHA和CPOL组合成四种方式进行通信的,CPOL=1时,时钟在空闲状态下始终是高电平,反之则为低电平,而CPHA=0时捕捉的是第一个边缘,否则为第二个时钟边缘。

MISO和MOSI又是什么意思呢?M:master;I:input;S:slaver;O:output;意思就是主从设备的输出和输入,那么他们是通过什么样的原理传输数据的呢?很开心继续为大家呈上一张图:

如图,SPI是通过主从设备不断移位实现数据的传输的。移位的概念懂不懂?10111000向右移一位就是01011100,这就不详细解释了,闲的时候多翻翻数电书,哈哈哈哈哈。

OK,说了那么多,给大家直接呈上代码了:

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
            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
            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

(好长,害怕,其实我不是很想码字,哎,不过算了,大家记得给我点赞,谢谢。)

自己用visio画的状态机,够良心吧,参见注释说的图一:

我觉得这段代码有很多值得学习的地方。状态机、计数器这些常见的代码我就忽略不说了,我来讲讲MISO和MOSI 的过程。

先讲input吧:

 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};
可以从我画的状态机图清晰地看到clk_edge_cnt是在DCLK_EDGE状态开始计数,当CPHA=1时,捕捉的是第一个边缘,因为从0开始数,所以只要判断clk_edge_cnt[0] 也就是clk_edge_cnt的末位为0时,则是第一个时钟边缘。(佩服这句代码真的很严谨)然后输入的MISO作为MISO_shift的末位进行移位。那么else if也是同样的理解方法。

那么output也是同样的道理。

哇写完博客觉得自己理解得好透彻哈哈哈哈。虽然还不知道这段代码怎么用,那就期待下一篇吧。下一篇详细去讲SPI+FLASH的实例。

猜你喜欢

转载自blog.csdn.net/weixin_41892263/article/details/82942189
今日推荐