SPI的通信试验 --verilog (从机-全双工)
SPI的 有关知识参考FPGA作为主机的通信实验。
本实验中FPGA作为从机通过SPI与MCU等通信的试验,可以在时钟上升沿接收数据并且在时钟下降沿发送数据,模仿全双工模式。接收的
数据作为地址,通过读取ROM中地址的数据然后发送出去。注意 发送完成以及接收完成之后的数据处理的关系。
程序:
顶层文件:
/********************************Copyright************************************** **----------------------------File information-------------------------- ** File name :spi_slave_send.v ** CreateDate :2015.04 ** Funtions :FPGA作为从机在接收主机发来的地址信号时,输出数据信号,同时还接收主机发来的地址数据(全双工),一直到CS线拉高,没有信号再从主机中发出。 数据地址都为8bit,spi的时钟在发送完8位之后最好能够间隔一下再发生另外8个时钟 ** Operate on :M5C06N3L114C7 ** Copyright :All rights reserved. ** Version :V1.0 **---------------------------Modify the file information---------------- ** Modified by : ** Modified data : ** Modify Content: *******************************************************************************/ module spi_slave ( clk, rst_n, spi_cs, spi_sck, spi_miso, spi_mosi, spi_over ); input clk; input rst_n; input spi_cs; input spi_sck; output spi_miso; input spi_mosi; output spi_over; //----------------------------- wire [7:0] rdata; wire rover; wire txd_en; wire [7:0] txd_data; wire txd_over; spi_slave_rxd spi_slave_rxd_1( .clk(clk), .rst_n(rst_n), .spi_cs(spi_cs), .spi_sck(spi_sck), .spi_miso(), .spi_mosi(spi_mosi), .rdata_out(rdata), .rover(rover) ); data_read data_read_1( .clk(clk), .rst_n(rst_n), .rover(rover), .rdata(rdata), .txd_en(txd_en), .txd_data(txd_data) ); spi_slave_txd spi_slave_txd_1( .clk(clk), .rst_n(rst_n), .txd_en(txd_en), .txd_data(txd_data), .spi_cs(spi_cs), .spi_sck(spi_sck), .spi_mosi(spi_mosi), .spi_miso(spi_miso), .spi_over(spi_over), .txd_over(txd_over) ); endmodule
接收程序:
/********************************Copyright************************************** **----------------------------File information-------------------------- ** File name :SPI_slave_rd.v ** CreateDate :2015.04 ** Funtions :SPI的通信实验,FPGA作为从机,接收主机数据以及向主机发送数据 ** Operate on :M5C06N3L114C7 ** Copyright :All rights reserved. ** Version :V1.0 **---------------------------Modify the file information---------------- ** Modified by : ** Modified data : ** Modify Content: *******************************************************************************/ module spi_slave_rxd ( clk, rst_n, spi_cs, spi_sck, spi_miso, spi_mosi, rdata_out, rover ); input clk; input rst_n; input spi_cs; input spi_sck; input spi_mosi; output spi_miso; output reg [7:0] rdata_out; output reg rover; //--------------------------------------- reg spi_cs_0,spi_cs_1; /* 延时两个时钟,配合检测时钟边沿 */ reg spi_sck_0,spi_sck_1; reg spi_mosi_0,spi_mosi_1; wire spi_sck_pos; // wire spi_sck_neg; wire spi_cs_pos; wire spi_cs_neg; wire spi_cs_flag; wire spi_miso_flag; always @(posedge clk or negedge rst_n) begin if(!rst_n) begin {spi_cs_1,spi_cs_0} <= 2'b11; {spi_sck_1,spi_sck_0} <= 2'b00; {spi_mosi_1,spi_mosi_0} <= 2'b00; end else begin {spi_cs_1,spi_cs_0} <= {spi_cs_0,spi_cs}; {spi_sck_1,spi_sck_0} <= {spi_sck_0,spi_sck}; {spi_mosi_1,spi_mosi_0} <= {spi_mosi_0,spi_mosi}; end end assign spi_sck_pos = ~spi_sck_1 &spi_sck_0; /* 取上升沿 */ // assign spi_sck_neg = ~spi_sck_0 &spi_sck_1; /* 取下降沿 */ assign spi_cs_pos = ~spi_cs_1&spi_cs_0; /* 取spi_cs上升沿,作为结束信号 */ assign spi_cs_neg = ~spi_cs_0&spi_cs_1; /* 取spi_cs下降沿,作为开始信号 */ assign spi_cs_flag = spi_cs_1; assign spi_miso_flag = spi_mosi_1; //---------------------------------------------------------- localparam idel = 3'd0; localparam rxd_sta = 3'd1; localparam rxd_over = 3'd2; reg [3:0] cnt; reg [7:0] rdata; reg [2:0] state; always @(posedge clk or negedge rst_n) begin if(!rst_n) begin cnt <= 0; rdata <= 0; state <= idel; rover <= 0; rdata_out <= 0; end else begin case(state) idel: begin cnt <= 0; rdata <= 0; rover <= 0; rdata_out <= 0; if(spi_cs_neg) state <= rxd_sta; else state <= idel; end rxd_sta: begin if(spi_cs_flag) state <= idel; else begin // if((cnt == 8)&&(spi_sck_neg)) /* 更严谨 */ if((cnt == 8)) begin cnt <= 0; state <= rxd_over; rdata_out <= rdata; rover <= 1; end else begin if(spi_sck_pos) begin cnt <= cnt + 1; /* 最后cnt=8 */ rdata[4'd7 - cnt] <= spi_miso_flag; /* 从最高位到最低位逐渐接收数据 */ end else begin cnt <= cnt ; rdata <= rdata; end end end end rxd_over: begin rover<= 0; state <= rxd_sta; end default : state <= idel; endcase end end // assign rdata_out = rdata; // else if(!psi_cs_flag) // begin // if(spi_sck_pos) // begin // cnt <= cnt + 1; /* 最后cnt=8 */ // rdata[4'd7 - cnt] <= spi_miso_flag; /* 从最高位到最低位逐渐接收数据 */ // end // else // begin // cnt <= cnt ; // rdata <= rdata; // end // end // else // begin // cnt <= 0 ; // rdata <= rdata; /* 注意:保持,不清除 */ // end // end // // //------------------------ // always @(posedge clk or negedge rst_n) // begin // if(!rst_n) // begin // rdata_out <= 8'd0; // rover <= 0; // end // else if(spi_cs_pos) // begin // rdata_out <= rdata; /* 赋值 */ // rover <= 1; /* 置位 */ // end // else // begin // rdata_out <= rdata_out; /* 注意:保持,不清除 */ // rover <= 0; /* 清除 */ // end // end endmodule
数据转换程序:
/********************************Copyright************************************** **----------------------------File information-------------------------- ** File name :data_read.v ** CreateDate :2015.04 ** Funtions : 因为接收完成标志只有一个时钟周期,所以需要马上寄存地址数据,并且地址读取待发送的数据,然后置位发送是能信号。 ** Operate on :M5C06N3L114C7 ** Copyright :All rights reserved. ** Version :V1.0 **---------------------------Modify the file information---------------- ** Modified by : ** Modified data : ** Modify Content: *******************************************************************************/ module data_read( clk, rst_n, rover, rdata, txd_en, txd_data ); input clk; input rst_n; input rover; input [7:0] rdata; output txd_en; output [7:0] txd_data; //------------------------------// reg r_over_1; reg [7:0] r_addr; always @(posedge clk or negedge rst_n) begin if(!rst_n) begin r_addr <= 0; r_over_1 <= 0; end else if(rover) begin r_addr <= rdata; r_over_1 <= 1; end else if(txd_en) r_over_1 <= 0; end reg r_over_1_1; reg r_over_1_2; reg r_over_1_3; always @(posedge clk or negedge rst_n) begin if(!rst_n) begin {r_over_1_3,r_over_1_2,r_over_1_1} <= 3'b000; end else begin {r_over_1_3,r_over_1_2,r_over_1_1} <= {r_over_1_2,r_over_1_1,r_over_1}; end end assign txd_en = ~r_over_1_3&r_over_1_2; data_rom data_rom_1( .addr(r_addr), .data(txd_data) ); endmodule
rom程序:
/********************************Copyright************************************** **----------------------------File information-------------------------- ** File name :data_rom.v ** CreateDate :2015.04 ** Funtions : 简单 rom 文件 ** Operate on :M5C06N3L114C7 ** Copyright :All rights reserved. ** Version :V1.0 **---------------------------Modify the file information---------------- ** Modified by : ** Modified data : ** Modify Content: *******************************************************************************/ module data_rom ( addr, data ); input [7:0] addr; output [7:0] data; // always @(*) // begin // case() // end assign data = addr + 1; endmodule
发送程序:
/********************************Copyright************************************** **----------------------------File information-------------------------- ** File name :spi_slave_txd.v ** CreateDate :2015.04 ** Funtions :FPGA作为从机的发送数据程序 ** Operate on :M5C06N3L114C7 ** Copyright :All rights reserved. ** Version :V1.0 **---------------------------Modify the file information---------------- ** Modified by : ** Modified data : ** Modify Content: *******************************************************************************/ module spi_slave_txd ( clk, rst_n, txd_en, txd_data, spi_cs, spi_sck, spi_mosi, spi_miso, spi_over, txd_over ); input clk; input rst_n; input txd_en; input [7:0] txd_data; input spi_cs; input spi_sck; input spi_mosi; output reg spi_miso; output spi_over; output reg txd_over; //---------------------------------- reg spi_cs_0,spi_cs_1; reg spi_sck_0,spi_sck_1; wire spi_sck_neg; wire spi_over; wire spi_cs_flag; always @(posedge clk or negedge rst_n) begin if(!rst_n) begin {spi_cs_1,spi_cs_0} <= 2'b11; {spi_sck_1,spi_sck_0} <= 2'b00; end else begin {spi_cs_1,spi_cs_0} <= {spi_cs_0,spi_cs}; {spi_sck_1,spi_sck_0} <= {spi_sck_0,spi_sck}; end end assign spi_cs_flag = spi_cs_1; assign spi_sck_neg = (spi_sck_1&(~spi_sck_0)); assign spi_over = ~spi_cs_1&spi_cs_0; //---------------------------------------------// localparam idel = 2'd0; localparam txd_sta= 2'd2; localparam txd_data_sta = 2'd1; reg [1:0] state; reg [3:0] cnt; reg [7:0] txdata; always @(posedge clk or negedge rst_n) begin if(!rst_n) begin state <= idel; cnt <= 0; txdata <= 0; spi_miso <= 1; /* 拉高 */ end else begin case(state) idel: begin cnt <= 0; txdata <= 0; spi_miso <= 1; /* 拉高 */ if(txd_en) begin state <= txd_data_sta; end else begin state <= idel; end end txd_data_sta: begin txdata <= txd_data; state <= txd_sta; txd_over <=0; end txd_sta: begin if(spi_cs_flag ) state <= idel; else if(cnt == 8) begin cnt <= 0; txd_over <= 1; state <= txd_data_sta; end else begin if(spi_sck_neg) begin spi_miso <= txdata[7-cnt[2:0]] ; /* 先高位再低位传输 */ cnt <= cnt +1; /* 范围:0-8 */ end else begin spi_miso <= spi_miso; /* 保持 */ cnt <= cnt; end end end default:state <= idel; endcase end end endmodule
仿真程序:
/********************************Copyright************************************** **----------------------------File information-------------------------- ** File name :spi_slave_tb.v ** CreateDate :2015.04 ** Funtions :测试文件 ** Operate on :M5C06N3L114C7 ** Copyright :All rights reserved. ** Version :V1.0 **---------------------------Modify the file information---------------- ** Modified by : ** Modified data : ** Modify Content: *******************************************************************************/ `timescale 1 ns/1 ns module spi_slave_tb ; reg clk; reg rst_n; reg spi_cs; reg spi_sck; wire spi_miso; reg spi_mosi; wire spi_over; spi_slave spi_slave_1( .clk, .rst_n, .spi_cs, .spi_sck, .spi_miso, .spi_mosi, .spi_over ); parameter tck = 24; parameter t = 1000/tck; always #(t/2) clk = ~clk; //------------------------------- /* 模仿spi主机的发送程序,这个task很好,仿顺序操作,可以直观的显示过程 */ task spi_sd; input [7:0] data_in; begin #(5*t); spi_sck = 0; spi_mosi= data_in[7]; #(5*t); spi_sck = 1; #(5*t); //send bit[7] spi_sck = 0; spi_mosi= data_in[6]; #(5*t); spi_sck = 1; #(5*t); //send bit[6] spi_sck = 0; spi_mosi= data_in[5]; #(5*t); spi_sck = 1; #(5*t); //send bit[5] spi_sck = 0; spi_mosi= data_in[4]; #(5*t); spi_sck = 1; #(5*t); //send bit[4] spi_sck = 0; spi_mosi= data_in[3]; #(5*t); spi_sck = 1; #(5*t); //send bit[3] spi_sck = 0; spi_mosi= data_in[2]; #(5*t); spi_sck = 1; #(5*t); //send bit[2] spi_sck = 0; spi_mosi= data_in[1]; #(5*t); spi_sck = 1; #(5*t); //send bit[1] spi_sck = 0; spi_mosi= data_in[0]; #(5*t); spi_sck = 1; #(5*t); //send bit[0] spi_sck = 0; end endtask initial begin clk = 0; rst_n = 0; spi_cs = 1; spi_sck = 0; spi_mosi = 0; #(20*t) rst_n = 1; #(10*t); spi_cs = 0; spi_sd(8'h01); #(50*t); spi_sd(8'h04); #(50*t); spi_sd(8'h00); #(50*t); spi_cs = 1; end endmodule
仿真图: