SPI试验---verilog(实用单通模式)
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
仿真图片: