初次学习spi,写了一个spi主机程序,供学习交流
CPOL = 0:空闲时时钟处于低电平;CPOL = 1:空闲时时钟处于高电平
CPHA = 0:第一个边沿采样,第二个边沿发送 CPHA = 0:第一个边沿发送,第二个边沿采样
主机和从机的行为相同,要么都在一个边沿读,要么都在一个边沿写,不存在主机读的同时从机写或主机写的同时从机读。
模式0:CPOL = 0,CPHA = 0;
模式1:CPOL = 0,CPHA = 1;
模式2:CPOL = 1,CPHA = 0;
模式3:CPOL = 1,CPHA = 1;
目前实现了CPOL = 0,CPHA = 0和CPOL = 1,CPHA = 0两种模式,其他两种模式正在研究
(注:可实现4种模式代码已补充在文末)
简介:用状态机编写,有两个状态:空闲状态和工作状态,空闲状态下不断检测en信号,当en为高,经过一系列处理进入工作状态,工作状态下可连续收发任意字节,时钟无间隔,通过sti信号任意延长工作状态,进而收发任意字节。这里不提供片选信号输出,需要用户来提供,可以提高灵活度。
端口说明:
rst_n:复位信号,低电平有效
dri_clk:输入时钟
sck:spi输出时钟,频率为dri_clk的4分频,想要特定频率可自行设置dri_clk
MISO:主机输入从机输出
MOSI:主机输出从机输入
en:使能信号,上升沿有效,但不能等到传输结束后仍为高电平,否则会在空闲状态中检测到高电平,从而再次触发。空闲状态下在每个dri_clk的上升沿检测en,从检测到en信号到sck第一个时钟边沿之间有4个dri_clk周期,控制spi片选信号时要用到这个数字。
CPOL:模式选择,CPHA默认为0,在整个工作过程中不能改变
priorH:为0时先发送低位,为1时先发送高位,在整个工作过程中不能改变
data_in:输入的数据,持续时间最少为2个dri_clk周期
data_out:接收的数据,当检测到done的下降沿时,可以读取数据
sti:控制多字节发送,为1时继续发送,为0时在下个字节传输结束后进入空闲状态,持续时间自定
done:与sti配合使用,每当传输一个字节后,done为1,经过2个dri_clk周期后变为0。done为1表示当前字节传输即将结束,在done的上升沿会自动读取sti,在下降沿会自动读取data_in,当首次检测到done为1时,应根据需要改变sti和data_in,在此之后,若检测到done为0,可以读取data_out。若发送n个字节数据,应在检测到第n-1个done时将sti置0。
代码如下:
module spi_CPHA_L
(
input dri_clk,
input rst_n,
input en,
input CPOL,
input priorH,
input sti,
input [7:0]data_in,
output reg done,
output reg sck,
input MISO,
output MOSI,
output reg [7:0]data_out
);
parameter IDLE = 1'b0;
parameter WORK = 1'b1;
reg cur_state;
reg next_state;
reg idle_done;
reg work_done;
always @(*)
case(cur_state)
IDLE:if(idle_done)
next_state = WORK;
else
next_state = IDLE;
WORK:if(work_done)
next_state = IDLE;
else
next_state = WORK;
endcase
always @(posedge dri_clk or negedge rst_n)
if(!rst_n)
cur_state <= IDLE;
else
cur_state <= next_state;
//sck_cnt;
reg [1:0]sck_cnt;
always @(posedge dri_clk or negedge rst_n)
if(!rst_n)
sck_cnt <= 2'b0;
else if(cur_state == WORK)
sck_cnt <= sck_cnt + 1'b1;
//sck
always @(posedge dri_clk or negedge rst_n)
if(!rst_n)
case(CPOL)
1'b0:sck <= 1'b0;
1'b1:sck <= 1'b1;
endcase
else if(cur_state == WORK)
if(sck_cnt == 2'd0)
case(CPOL)
1'b0:sck <= 1'b1;
1'b1:sck <= 1'b0;
endcase
else if(sck_cnt == 2'd2)
case(CPOL)
1'b0:sck <= 1'b0;
1'b1:sck <= 1'b1;
endcase
//bit_cnt
reg [2:0]bit_cnt;
always @(posedge dri_clk or negedge rst_n)
if(!rst_n)
bit_cnt <= 3'b0;
else if(cur_state == WORK && sck_cnt == 2'd1)
bit_cnt <= bit_cnt + 1'b1;
//预置数移位寄存器
reg [8:0]data_in_reg;
always @(posedge p_load or posedge n_load or negedge left or negedge right or negedge rst_n)
if(!rst_n)
data_in_reg <= 9'b0;
else if(p_load || n_load)
if(priorH)
data_in_reg[7:0] <= data_in;
else
data_in_reg[8:1] <= data_in;
else if(!left)
data_in_reg <= data_in_reg << 1;
else if(!right)
data_in_reg <= data_in_reg >> 1;
//MOSI,MISO
assign MOSI = (priorH & data_in_reg[8])|(~priorH & data_in_reg[0]);
reg left,right,p_load;
reg [1:0]idle_cnt;
always @(posedge dri_clk or negedge rst_n)
if(!rst_n)begin
data_out <= 8'b0;
idle_done <= 1'b0;
idle_cnt <= 2'b0;
left <= 1'b1;
right <= 1'b1;
p_load <= 1'b0;
end
else begin
case(cur_state)
IDLE:begin
idle_cnt <= idle_cnt + 1'b1;
case(idle_cnt)
2'b00:begin
case(en)
1'b0:idle_cnt <= 2'b0;
1'b1:p_load <= 1'b1;
endcase
end
2'b01:p_load <= 1'b0;
2'b10:begin
case(priorH)
1'b0:right <= 1'b0;
1'b1:left <= 1'b0;
endcase
idle_done <= 1'b1;
end
2'b11:begin
idle_cnt <= 2'b0;
idle_done <= 1'b0;
left <= 1'b1;
right <= 1'b1;
end
endcase
end
WORK:begin
left <= 1'b1;
right <= 1'b1;
if(sck_cnt == 2'd0)
if(priorH)
data_out[7-bit_cnt] <= MISO;
else
data_out[bit_cnt] <= MISO;
else if(sck_cnt == 2'd2)
if(priorH)
left <= 1'b0;
else
right <= 1'b0;
end
endcase
end
//done,sti
reg sti_reg,n_load;
always @(posedge dri_clk or negedge rst_n)
if(!rst_n)begin
done <= 1'b0;
work_done <= 1'b0;
sti_reg <= 1'b0;
n_load <= 1'b0;
end
else if(bit_cnt == 3'd7 && sck_cnt == 2'd3)begin
done <= 1'b1;
sti_reg <= sti;
end
else if(bit_cnt == 3'd7 && sck_cnt == 2'd1)begin
n_load <= 1'b1;
done <= 1'b0;
end
else if(bit_cnt == 3'd0 && sck_cnt == 2'd2)begin
n_load <= 1'b0;
case(sti_reg)
1'b0:work_done <= 1'b1;
1'b1:work_done <= 1'b0;
endcase
end
else
work_done <= 1'b0;
endmodule
测试代码:
`timescale 1ns/1ns
module spi_tb;
reg clk,rst_n,en,CPOL,priorH,sti,MISO;
reg [7:0]data_in;
wire done,sck,MOSI;
wire [7:0]data_out;
spi_CPHA_L u_spi_CPHA_L
(
.dri_clk(clk),
.rst_n(rst_n),
.en(en),
.CPOL(CPOL),
.priorH(priorH),
.sti(sti),
.data_in(data_in),
.done(done),
.sck(sck),
.MOSI(MOSI),
.MISO(MISO),
.data_out(data_out)
);
always #10 clk = ~clk;
initial begin
clk = 1'b0;
data_in = 8'b1000_0001;
sti = 1'b1;
en = 1'b0;
CPOL = 1'b1;
priorH = 1'b1;
rst_n = 1'b0;
#40;
rst_n = 1'b1;
#40;
en = 1'b1;
#200 en = 1'b0;
wait(done == 1'b1);
sti = 1'b0;
data_in = 8'b1100_0001;
#100;
data_in = 8'b0000_0000;
end
endmodule
测试波形:
这里演示两个字节的波形
如果有错误之处请大佬们指正。
补充:可以实现4个模式的完整spi代码,使用方法和上述相同
module spi
(
input dri_clk,
input rst_n,
input en,
input priorH,
input CPOL,
input CPHA,
input sti,
input [7:0]data_in,
output reg done,
output reg sck,
input MISO,
output MOSI,
output reg [7:0]data_out
);
localparam IDLE = 1'b0;
localparam WORK = 1'b1;
reg cur_state;
reg next_state;
reg idle_done;
reg work_done;
reg left,right,p_load;
reg [1:0]idle_cnt;
reg [1:0]sck_cnt;
reg sti_reg,n_load;
reg [2:0]bit_cnt;
reg [8:0]data_in_reg;
wire [1:0]prha;
assign prha = {priorH,CPHA};
always @(*)
case(cur_state)
IDLE:if(idle_done)
next_state = WORK;
else
next_state = IDLE;
WORK:if(work_done)
next_state = IDLE;
else
next_state = WORK;
endcase
always @(posedge dri_clk or negedge rst_n)
if(!rst_n)
cur_state <= IDLE;
else
cur_state <= next_state;
//sck_cnt;
always @(posedge dri_clk or negedge rst_n)
if(!rst_n)
sck_cnt <= 2'b0;
else if(cur_state == WORK)
sck_cnt <= sck_cnt + 1'b1;
//sck
always @(posedge dri_clk or negedge rst_n)
if(!rst_n)
case(CPOL)
1'b0:sck <= 1'b0;
1'b1:sck <= 1'b1;
endcase
else if(cur_state == WORK)
if(sck_cnt == 2'd0)
case(CPOL)
1'b0:sck <= 1'b1;
1'b1:sck <= 1'b0;
endcase
else if(sck_cnt == 2'd2)
case(CPOL)
1'b0:sck <= 1'b0;
1'b1:sck <= 1'b1;
endcase
//bit_cnt
always @(posedge dri_clk or negedge rst_n)
if(!rst_n)
bit_cnt <= 3'b0;
else if(cur_state == WORK && sck_cnt == 2'd1)
bit_cnt <= bit_cnt + 1'b1;
//预置数移位寄存器
always @(posedge p_load or posedge n_load or negedge left or negedge right or negedge rst_n)
if(!rst_n)
data_in_reg <= 9'b0;
else if(p_load || n_load)
case(prha)
2'b00,
2'b01:data_in_reg[8:1] <= data_in;
2'b10,
2'b11:data_in_reg[7:0] <= data_in;
endcase
else if(!left)
data_in_reg <= data_in_reg << 1;
else if(!right)
data_in_reg <= data_in_reg >> 1;
//MOSI,MISO
assign MOSI = (priorH & data_in_reg[8])|((~priorH) & data_in_reg[0]);
always @(posedge dri_clk or negedge rst_n)
if(!rst_n)begin
data_out <= 8'b0;
idle_done <= 1'b0;
idle_cnt <= 2'b0;
left <= 1'b1;
right <= 1'b1;
p_load <= 1'b0;
end
else begin
case(cur_state)
IDLE:begin
idle_cnt <= idle_cnt + 1'b1;
case(idle_cnt)
2'b00:begin
case(en)
1'b0:idle_cnt <= 2'b0;
1'b1:p_load <= 1'b1;
endcase
end
2'b01:p_load <= 1'b0;
2'b10:begin
if(!CPHA)
case(priorH)
1'b0:right <= 1'b0;
1'b1:left <= 1'b0;
endcase
idle_done <= 1'b1;
end
2'b11:begin
idle_cnt <= 2'b0;
idle_done <= 1'b0;
left <= 1'b1;
right <= 1'b1;
end
endcase
end
WORK:begin
left <= 1'b1;
right <= 1'b1;
if(sck_cnt == 2'd0)
case(CPHA)
1'b0:if(priorH)
data_out[7-bit_cnt] <= MISO;
else
data_out[bit_cnt] <= MISO;
1'b1:if(priorH)
left <= 1'b0;
else
right <= 1'b0;
endcase
else if(sck_cnt == 2'd2)
case(CPHA)
1'b0:if(priorH)
left <= 1'b0;
else
right <= 1'b0;
1'b1:if(priorH)
data_out[7-bit_cnt] <= MISO;
else
data_out[bit_cnt] <= MISO;
endcase
end
endcase
end
//done,sti
always @(posedge dri_clk or negedge rst_n)
if(!rst_n)begin
work_done <= 1'b0;
sti_reg <= 1'b0;
n_load <= 1'b0;
done <= 1'b0;
end
else begin
n_load <= 1'b0;
case(CPHA)
1'b0:if(bit_cnt == 3'd7 && sck_cnt == 2'd3)begin
sti_reg <= sti;
done <= 1'b1;
end
else if(bit_cnt == 3'd7 && sck_cnt == 2'd1)begin
done <= 1'b0;
n_load <= 1'b1;
end
1'b1:if(bit_cnt == 3'd7 && sck_cnt == 2'd1)begin
sti_reg <= sti;
done <= 1'b1;
end
else if(bit_cnt == 3'd0 && sck_cnt == 2'd3)begin
done <= 1'b0;
n_load <= 1'b1;
end
endcase
if(bit_cnt == 3'd0 && sck_cnt == 2'd2)begin
case(sti_reg)
1'b0:work_done <= 1'b1;
1'b1:work_done <= 1'b0;
endcase
end
else
work_done <= 1'b0;
end
endmodule