SPI协议实现回环—控制模块
spi基本原理
SPI是一个同步的数据总线,由一根时钟线SCLK,两根数据线MISO、MOSI,一个片选线组成。传输的数据的过程中先拉低数据的片选信号,选择对应的设备,再按照规定的时钟的传输的格式对其进行传输。主要的原理由图所示。
根据时钟的上升沿和下降沿、数据的输出和采样,一共可以分成4种不同的传输的方式。
miso:Master input slave output 主机输入,从机输出;
mosi:Master output slave input 主机输出,从机输入;
scl :Serial Clock 串行时钟信号,由主机产生发送给从机;
cs_n:Select 片选信号,控制与从机通信,通常使用_n表示改数据是低位有效。
这里我们使用模式3,也就是时钟位SCL起始为高电平,拉低后输出,拉高后采样的数据传输方式
*具体的模块要求如下所示:
spi_master主控模块的控制模块
①信号定义
// An highlighted block
moddule master_ctrl(
input clk ,//时钟
input rst_n ,//复位
input write ,//写命令
output read ,//读命令
output req ,//控制模块给接口模块的请求
output [7:0] din ,//输出的写的数据
input [7:0] dout ,//读出的数据
input done //发送一个字节对应的done信号
)
②逻辑设计
状态机的跳转条件:
使用状态机控制数据的写和读,定义的状态机的为4个状态:
localparam IDLE = 4'b0001,
CMD = 4'b0010,
ADDR = 4'b0100,
DATA = 4'b1000;
发送数据的格式按照命令、地址、数据的方式进行的操作,首先对发送的命令进行判断,在对地址进行定义,最后再是数据。
assign idle2cmd = state_c == IDLE && (xx[1]||xx[0]);//通过读和写指令是的状态机跳出idle
assign cmd2addr = state_c == CMD && end_cnt_byte ;//判断命令的计数结束跳转
assign addr2data = state_c == ADDR && end_cnt_byte ;//判断地址的计数结束跳转
assign data2idle = state_c == DATA && end_cnt_byte ;//判断数据的计数结束跳转
计数器的跳转:
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_byte <= 1'b0;
end
else if(add_cnt_byte)begin
if(end_cnt_byte)begin
cnt_byte <= 1'b0;
end
else begin
cnt_byte <= cnt_byte + 1'b1;
end
end
end
assign add_cnt_byte =(state_c != IDLE) && done;//每次发送完一个byte,返回一个done作为计数器开启的条件(这个操作在计数器的使用中很常见)
assign end_cnt_byte = add_cnt_byte && cnt_byte == XX - 1'b1;//开始定义每个状态需要发送多少字节
always@(*)begin
if(state_c == CMD)begin
XX = 1;//命令1byte
end
if(state_c == ADDR)begin
XX = 1;//地址1byte
end
if(state_c == DATA)begin
XX = 8;//数据8byte
end
end
flag信号:
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
flag <= 1'b0;
end
else if(read)begin
flag <= 1'b1;
end
else if(write)begin
flag <= 1'b0;
end
end
在一个控制模块需要进行读和写的操作的时候,可以设置flag信号(很常见的一种操作,但是一定要习惯),flag为1时表示“读”,0时表示“写”。
读写地址的自增:
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
rd_addr <= 'd0;
else if(add_rd_addr)
if(end_rd_addr)
rd_addr <= 'd0;
else
rd_addr <= rd_addr + 'd8;
end
assign add_rd_addr = data2idle && flag;
assign end_rd_addr = add_rd_addr && rd_addr == MAX - 1;
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
wr_addr <= 'd0;
else if(add_wr_addr)
if(end_wr_addr)
wr_addr <= 'd0;
else
wr_addr <= wr_addr + 'd8;
end
assign add_wr_addr = data2idle && ~flag;
assign end_wr_addr = add_wr_addr && wr_addr == MAX - 1;
地址的自增是数据读取处理中比较重要的一项操作,我这里设置MAX=256,为的是防止在读写的过程中超过地址的上限,地址自增的条件是它所处的状态(也就是上面我们所设定的flag,如果不设定flag,只用write和read就只是一个脉冲,就只激活了一次计数器)。
请求:
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
trans_req <= 1'b0;
end
else if(idle2cmd || cmd2addr || addr2data)begin
trans_req <= 1'b1;
end
else if(state_c == DATA && add_cnt_byte && ~end_cnt_byte) begin
trans_req <= 1'b1;
end
else
trans_req <= 1'b0