SPI通信的讀寫操作
一、 SPI簡介:
SPI的通信原理很簡單,它以主從方式工作,這種模式通常有一個主設備和一個或多個從設備,需要至少4根線,事實上3根也可以(單向傳輸時)。也是所有基於SPI的設備共有的,它們是SDI(數據輸入)、SDO(數據輸出)、SCLK(時鍾)、CS(片選)。
(1)SDO – 主設備數據輸出,從設備數據輸入;
(2)SDI – 主設備數據輸入,從設備數據輸出;
(3)SCLK – 時鍾信號,由主設備產生;
(4)CS – 從設備使能信號,由主設備控制。
其中,CS是控制芯片是否被選中的,也就是說只有片選信號為預先規定的使能信號時(高電位或低電位),對此芯片的操作才有效。這就允許在同一總線上連接多個SPI設備成為可能。
由SCLK提供時鍾脈沖,SDI,SDO則基於此脈沖完成數據傳輸。數據輸出通過 SDO線,數據在時鍾上升沿或下降沿時改變,在緊接着的下降沿或上升沿被讀取。完成一位數據傳輸,輸入也使用同樣原理。
要注意的是,SCLK信號線只由主設備控制,從設備不能控制信號線。同樣,在一個基於SPI的設備中,至少有一個主控設備。這樣傳輸的特點:這樣的傳輸方式有一個優點,與普通的串行通訊不同,普通的串行通訊一次連續傳送至少8位數據,而SPI允許數據一位一位的傳送,甚至允許暫停
二、 SPI的時序電路圖:
SPI時鍾極性CPOL = 0表示在沒有數據傳輸時為低電平,= 1表示沒有數據傳輸時為高電平。
SPI時鍾相位CPHA,= 0表示時鍾的第一個沿更新數據、第二個沿鎖存數據,= 1表示時鍾的第一個沿鎖存數據、第二個沿更新數據。
程序代碼:

/********************************Copyright************************************** **----------------------------File information-------------------------- ** File name :spi_writeread.v ** CreateDate :2015.04 ** Funtions : SPI作為主機向從機讀寫,讀的時候要注意,總共為15個時鍾,在最后一個寫地址時鍾的下降沿就開始讀取數據, 若要在下一個時鍾的下降沿讀取數據則要根據需要修改程序。注:本程序先發送最高位,先接收最高位 ** Operate on :M5C06N3L114C7 ** Copyright :All rights reserved. ** Version :V1.0 **---------------------------Modify the file information---------------- ** Modified by : ** Modified data : ** Modify Content: *******************************************************************************/ module spi_writeread ( clk, rst_n, spi_re_en, spi_wr_en, spi_addr, spi_send_data, spi_read_data, spi_cs, spi_clk, spi_mi, spi_mo, spi_busy, spi_over ); input clk; input rst_n; input spi_re_en; //接收使能 input spi_wr_en; //發送使能 input [7:0] spi_addr; //待發送的地址 input [7:0] spi_send_data; //待發送的數據 output spi_cs; //片選信號 output spi_clk; //時鍾信號 input spi_mi; //主機從芯片讀取的數據 output spi_mo; //主機向芯片發送的數據 output reg spi_over; //spi操作完成 output reg [7:0] spi_read_data; //spi接收的數據,即讀取的數據 output reg spi_busy; //spi忙信號 reg temp_cs; reg temp_scl; reg temp_mo; assign spi_cs = temp_cs; assign spi_clk = temp_scl; assign spi_mo = temp_mo; reg sendbit_over; //字節發送完成標志 reg resbit_over; //接收字節完成標志 reg [7:0] res_data; //接收的數據 //*******************狀態機*************************** parameter cnt_delay = 4; //CS的延時時鍾的計數(根據芯片決定) reg [3:0] state; //狀態機 reg [7:0] send_data; //待發送的移位數據寄存器 reg [7:0] read_data; //接收數據寄存器 reg [2:0] delay; //發送完成,延時到可以再次發送,然后待命 reg wr_flag; //寫操作標志 reg re_flag; //讀操作標志 reg send_en; reg resive_en; always @(posedge clk or negedge rst_n) begin if(!rst_n) begin temp_cs <= 1; state <= 4'd0; send_data <= 8'd0; read_data <= 8'd0; delay <= 0; wr_flag <= 0; re_flag <= 0; spi_busy <= 0; spi_over <= 0; resive_en <= 0; send_en <=0; end else begin case(state) 4'd0: begin delay <= 0; temp_cs <= 1; send_data <= 8'd0; read_data <= 8'd0; wr_flag <= 0; re_flag <= 0; spi_busy <= 0; spi_over <= 0; resive_en <= 0; send_en <=0; if(spi_wr_en) //寫使能 begin spi_busy <=1; state <= 4'd1; wr_flag <= 1; //寫操作標志置位高 end else if(spi_re_en) begin spi_busy <=1; state <= 4'd1; re_flag <= 1; //讀操作標志置位高 end end 4'd1: begin temp_cs <= 0; //拉低cs信號 state <= 4'd2; end 4'd2: //拉低時鍾和數據輸出線 begin if(sendbit_over) begin send_en <=0; if(wr_flag) begin state <= 4'd3; end else if(re_flag) begin state <= 4'd4; resive_en <=1; /* 接收使能置高 */ end else begin state <= 4'd0; end end else begin send_data <= spi_addr; //將地址寄存,然后發送地址 state <= 4'd2; send_en <=1; end end 4'd3: begin if(sendbit_over) begin state <= 4'd5; send_en <=0; end else begin send_data <= spi_send_data; //將地址寄存,然后發送地址 state <= 4'd3; send_en <=1; end end 4'd4: begin if(resbit_over) begin state <= 4'd5; resive_en <=0; read_data <= res_data; end else begin state <= 4'd4; resive_en <=1; end end 4'd5: begin temp_cs <= 1; if(delay == cnt_delay) begin state <= 4'd6; delay <= 0; spi_over <= 1; end else delay <= delay + 1; end 4'd6: begin spi_over <= 0; spi_busy <= 0; state <= 4'd0; end default : state <= 4'd0; endcase end end always @(posedge clk or negedge rst_n) begin if(!rst_n) begin spi_read_data <= 8'd0; end else begin if(spi_over) spi_read_data <= read_data; else spi_read_data <= spi_read_data; end end //****************發送**************** reg [3:0] send_state; reg [7:0] shift_data; reg [3:0] send_num; reg [3:0] resive_state; reg [2:0] res_num; always @(posedge clk or negedge rst_n) begin if(!rst_n) begin temp_scl <= 0; //模式0狀態,數據、時鍾都為低電平 temp_mo <= 0; shift_data <= 0; send_num <= 0; send_state<= 0; sendbit_over <= 0; resive_state <= 0; res_data <= 8'd0; res_num <= 0; resbit_over <= 0; end else if(send_en) begin case(send_state) 4'd0: begin temp_scl <= 0; temp_mo <= 0; send_num <= 4'd0; shift_data <= send_data; send_state <= 4'd1; sendbit_over <= 0; end 4'd1: begin temp_mo <= shift_data[7] ; /* 先發最高位,再發最低位 */ send_state <= 4'd2; end 4'd2: begin temp_scl <= 1; send_state <= 4'd3; end 4'd3: begin if(send_num == 4'd7) begin send_state <= 4'd5; sendbit_over <= 1; send_num <= 4'd0; end else begin send_state <= 4'd4; end end 4'd4: begin temp_scl <= 0; shift_data <= shift_data << 1; send_num <= send_num + 1; send_state <= 4'd1; end 4'd5: begin send_state <= 4'd5; sendbit_over <= 0; end default: send_state <= 4'd0; endcase end else if(resive_en) begin case(resive_state) 'd0: begin resive_state <= 4'd1; res_num <= 0; resbit_over <= 0; end 'd1: begin temp_scl <= 0; res_data[0] <= spi_mi; /* 接收最低位,然后左移,故實際是先接收最高位 */ resive_state <= 4'd2; end 'd2: begin if(res_num == 4'd7) begin resive_state <= 4'd5; res_num <= 4'd0; end else begin res_data <= res_data << 1; resive_state <= 4'd3; end end 'd3: begin temp_scl <= 1; resive_state <= 4'd4; end 'd4: begin res_num <= res_num + 1; resive_state <= 4'd1; end 'd5: begin resbit_over <= 1; resive_state <= 4'd6; end 'd6: begin resbit_over <= 0; resive_state <= 4'd6; end default: resive_state <= 4'd0; endcase end else begin temp_scl <= 0; temp_mo <= 0; shift_data <= 0; send_num <= 0; sendbit_over <= 0; send_state<= 0; resive_state <= 4'd0; res_data <= 8'd0; res_num <= 0; resbit_over <= 0; end end endmodule
測試程序:

/********************************Copyright************************************** **----------------------------File information-------------------------- ** File name :spi_writeread_tb.v ** CreateDate :2015.04 ** Funtions : SP的測試文件 ** Operate on :M5C06N3L114C7 ** Copyright :All rights reserved. ** Version :V1.0 **---------------------------Modify the file information---------------- ** Modified by : ** Modified data : ** Modify Content: *******************************************************************************/ module spi_writeread_tb; reg clk; reg rst_n; reg spi_re_en; //接收使能 reg spi_wr_en; //發送使能 reg [7:0] spi_addr; //待發送的地址 reg [7:0] spi_send_data; //待發送的數據 wire spi_cs; //片選信號 wire spi_clk; //時鍾信號 reg spi_mi; //主機從芯片讀取的數據 wire spi_mo; //主機向芯片發送的數據 wire spi_over; //spi操作完成 wire [7:0] spi_read_data; //spi接收的數據,即讀取的數據 wire spi_busy; //spi忙信號 spi_writeread spi_writeread_1( .clk, .rst_n, .spi_re_en, .spi_wr_en, .spi_addr, .spi_send_data, .spi_read_data, .spi_cs, .spi_clk, .spi_mi, .spi_mo, .spi_busy, .spi_over ); parameter tck = 24; parameter t = 1000/tck; always #(t/2) clk = ~clk; always #(5*t) spi_mi = ~spi_mi; initial begin clk = 0; rst_n = 0; spi_re_en = 0; spi_wr_en = 0; spi_addr = 0; spi_send_data = 0; spi_mi = 0; #(10*t) rst_n = 1; #(5*t) spi_addr = 8'h55; spi_send_data = 8'haa; #(2*t) spi_wr_en = 1; #(2*t) spi_wr_en = 0; #(100*t) ; #(5*t) spi_addr = 8'h0f; #(2*t) spi_re_en = 1; #(2*t) spi_re_en = 0; end endmodule
仿真圖片: