這裏主要放兩個代碼第一個是正常的不使用狀態機的SPI主機代碼;第二個是狀態機SPI代碼網絡
1.不使用狀態機:特權同窗《深刻淺出玩轉FPGA》中DIY數碼相框部分代碼:this
//////////////////////////////////////////////////////////////////////////////// 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(來源網絡)spa
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相位和極性,有較高的參考價值code
以上兩個源代碼可供你們參考blog