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 [:] spi_addr; //待发送的地址
input [:] spi_send_data; //待发送的数据 output spi_cs; //片选信号
output spi_clk; //时钟信号
input spi_mi; //主机从芯片读取的数据
output spi_mo; //主机向芯片发送的数据
output reg spi_over; //spi操作完成
output reg [:] 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 [:] res_data; //接收的数据 //*******************状态机***************************
parameter cnt_delay = ; //CS的延时时钟的计数(根据芯片决定)
reg [:] state; //状态机
reg [:] send_data; //待发送的移位数据寄存器
reg [:] read_data; //接收数据寄存器
reg [:] 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 <= ; state <= 'd0;
send_data <= 'd0;
read_data <= 'd0;
delay <= ;
wr_flag <= ;
re_flag <= ;
spi_busy <= ;
spi_over <= ;
resive_en <= ;
send_en <=;
end
else
begin
case(state)
'd0:
begin
delay <= ;
temp_cs <= ;
send_data <= 'd0;
read_data <= 'd0;
wr_flag <= ;
re_flag <= ;
spi_busy <= ;
spi_over <= ;
resive_en <= ;
send_en <=; if(spi_wr_en) //写使能
begin
spi_busy <=;
state <= 'd1;
wr_flag <= ; //写操作标志置位高
end
else if(spi_re_en)
begin
spi_busy <=;
state <= 'd1;
re_flag <= ; //读操作标志置位高
end
end
'd1:
begin
temp_cs <= ; //拉低cs信号
state <= 'd2;
end
'd2: //拉低时钟和数据输出线
begin
if(sendbit_over)
begin
send_en <=;
if(wr_flag)
begin
state <= 'd3;
end
else if(re_flag)
begin
state <= 'd4;
resive_en <=; /* 接收使能置高 */
end
else
begin
state <= 'd0;
end
end
else
begin
send_data <= spi_addr; //将地址寄存,然后发送地址
state <= 'd2;
send_en <=;
end
end
'd3:
begin
if(sendbit_over)
begin
state <= 'd5;
send_en <=;
end
else
begin
send_data <= spi_send_data; //将地址寄存,然后发送地址
state <= 'd3;
send_en <=;
end
end
'd4:
begin
if(resbit_over)
begin
state <= 'd5;
resive_en <=;
read_data <= res_data;
end
else
begin
state <= 'd4;
resive_en <=;
end
end
'd5:
begin
temp_cs <= ;
if(delay == cnt_delay)
begin
state <= 'd6;
delay <= ;
spi_over <= ;
end
else
delay <= delay + ;
end
'd6:
begin
spi_over <= ;
spi_busy <= ;
state <= 'd0;
end
default : state <= 'd0;
endcase
end
end always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
spi_read_data <= 'd0;
end
else
begin
if(spi_over)
spi_read_data <= read_data;
else
spi_read_data <= spi_read_data;
end
end //****************发送****************
reg [:] send_state;
reg [:] shift_data;
reg [:] send_num; reg [:] resive_state;
reg [:] res_num; always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
temp_scl <= ; //模式0状态,数据、时钟都为低电平
temp_mo <= ; shift_data <= ;
send_num <= ; send_state<= ;
sendbit_over <= ; resive_state <= ;
res_data <= 'd0;
res_num <= ;
resbit_over <= ; end
else if(send_en)
begin
case(send_state)
'd0:
begin
temp_scl <= ;
temp_mo <= ;
send_num <= 'd0;
shift_data <= send_data;
send_state <= 'd1;
sendbit_over <= ;
end
'd1:
begin
temp_mo <= shift_data[] ; /* 先发最高位,再发最低位 */
send_state <= 'd2;
end
'd2:
begin
temp_scl <= ;
send_state <= 'd3;
end
'd3:
begin
if(send_num == 'd7)
begin
send_state <= 'd5;
sendbit_over <= ;
send_num <= 'd0;
end
else
begin
send_state <= 'd4;
end
end
'd4:
begin
temp_scl <= ;
shift_data <= shift_data << ;
send_num <= send_num + ;
send_state <= 'd1;
end 'd5:
begin
send_state <= 'd5;
sendbit_over <= ;
end
default: send_state <= 'd0;
endcase
end
else if(resive_en)
begin
case(resive_state)
'd0:
begin
resive_state <= 'd1;
res_num <= ;
resbit_over <= ;
end
'd1:
begin
temp_scl <= ;
res_data[] <= spi_mi; /* 接收最低位,然后左移,故实际是先接收最高位 */
resive_state <= 'd2;
end
'd2:
begin
if(res_num == 'd7)
begin
resive_state <= 'd5;
res_num <= 'd0;
end
else
begin
res_data <= res_data << ;
resive_state <= 'd3;
end
end
'd3:
begin
temp_scl <= ;
resive_state <= 'd4;
end
'd4:
begin
res_num <= res_num + ;
resive_state <= 'd1;
end
'd5:
begin
resbit_over <= ;
resive_state <= 'd6;
end
'd6:
begin
resbit_over <= ;
resive_state <= 'd6;
end
default: resive_state <= 'd0;
endcase
end
else
begin
temp_scl <= ;
temp_mo <= ; shift_data <= ;
send_num <= ;
sendbit_over <= ;
send_state<= ; resive_state <= 'd0;
res_data <= 'd0;
res_num <= ;
resbit_over <= ;
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 [:] spi_addr; //待发送的地址
reg [:] spi_send_data; //待发送的数据 wire spi_cs; //片选信号
wire spi_clk; //时钟信号
reg spi_mi; //主机从芯片读取的数据
wire spi_mo; //主机向芯片发送的数据
wire spi_over; //spi操作完成
wire [:] 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 = ;
parameter t = /tck; always
#(t/) clk = ~clk; always
#(*t) spi_mi = ~spi_mi; initial
begin
clk = ;
rst_n = ;
spi_re_en = ;
spi_wr_en = ;
spi_addr = ;
spi_send_data = ;
spi_mi = ; #(*t) rst_n = ; #(*t) spi_addr = 'h55;
spi_send_data = 'haa;
#(*t) spi_wr_en = ;
#(*t) spi_wr_en = ; #(*t) ;
#(*t) spi_addr = 'h0f;
#(*t) spi_re_en = ;
#(*t) spi_re_en = ; end endmodule
仿真图片: