一、SPI
SPI的通信原理很简单,它以主从方式工作,这种模式通常有一个主设备和一个或多个从设备,需要至少4根线,事实上3根也可以(单向传输时)。也是所有基于SPI的设备共有的,它们是MISO(主设备数据输入)、MOSI(主设备数据输出)、SCLK(时钟)、CS(片选)。
(1)MISO– Master Input Slave Output,主设备数据输入,从设备数据输出;
(2)MOSI– Master Output Slave Input,主设备数据输出,从设备数据输入;
(3)SCLK – Serial Clock,时钟信号,由主设备产生;
(4)CS – Chip Select,从设备使能信号,由主设备控制。
二、看spi–flash手册找关键
1.描述
16Mbit的存储空间
单扇区擦除或者整块擦除
用spi协议与flash读写
2.flash接口信号
C是串行时钟
D是数据
S是片选信号
3.SPI模式选择
flash只支持mode0和mode3两种模式
CPOL时钟相位
时钟的极性(CPOL)用来决定在总线空闲时,同步时钟(SCK)信号线上的电位是高电平还是低电平。当时钟极性为0时(CPOL=0),SCK信号线在空闲时为低电平;当时钟极性为1时(CPOL=1),SCK信号线在空闲时为高电平;
CPHA时钟极性
当时钟相位为1时(CPHA=1),在SCK信号线的第二个跳变沿进行采样;这里的跳变沿究竟是上升沿还是下降沿?取决于时钟的极性。当时钟极性为0时,取下降沿;当时钟极性为1时,取上升沿
CPOL=0,CPHA=0
4.高字节MSB
MSB先,就是高字节先
5.指令
6. 写使能时序
7.读ID时序
8.读寄存器时序(我没用到)
判断WIP BIT是否为0才能进行下一步(我的代码里没有用到)
9.读数据时序
10.页编程
11.扇区擦除
12.重要的时间
三、状态机设计
1.spi接口状态机
2.flash读状态机
3.flash写状态机
四、代码部分
1.spi_interface.v
module spi_interface(
input clk,
input rst_n,
// 接口与主机
input [7:0] din,
input req,
output [7:0] dout,
output done,
// 接口与flash
input miso,// 主机采样从机发送
output mosi,// 主机发送从机
output sclk,// 串行时钟
output cs_n // 片选信号
);
parameter CPHA = 1,// 空闲状态高电平
CPOL = 1;// 下降沿发送,上升沿采样
// 16分频或8分频或4分频 不能2分频
parameter SCLK = 16,
SCLK_BEFORE = SCLK/4,
SCLK_AFTER = SCLK*3/4;
// 状态机
localparam IDLE = 4'b0001,
WAIT = 4'b0010,
DATA = 4'b0100,
DONE = 4'b1000;
reg [3:0] state_c;
reg [3:0] state_n;
wire idle2wait;
wire wait2data;
wire data2done;
wire done2idle;
// bit计数器
reg [2:0] cnt_bit;
wire add_cnt_bit;
wire end_cnt_bit;
// 分频串行时钟计数器
reg [4:0] cnt_sclk;
wire add_cnt_sclk;
wire end_cnt_sclk;
// 寄存要发送的数据
reg spi_sclk;
reg [7:0] rx_data;
reg [7:0] tx_data;
reg spi_cn_n;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
state_c <= IDLE;
end
else begin
state_c <= state_n;
end
end
always @(*)begin
case (state_c)
IDLE :begin
if(idle2wait)begin
state_n = WAIT;
end
else begin
state_n = state_c;
end
end
WAIT :begin
if(wait2data)begin
state_n = DATA;
end
else begin
state_n = state_c;
end
end
DATA :begin
if(data2done)begin
state_n = DONE;
end
else begin
state_n = state_c;
end
end
DONE :begin
if(done2idle)begin
state_n = IDLE;
end
else begin
state_n = state_c;
end
end
default: state_n = IDLE;
endcase
end
assign idle2wait = state_c == IDLE && (req);
assign wait2data = state_c == WAIT && (1'b1);
assign data2done = state_c == DATA && (end_cnt_bit);
assign done2idle = state_c == DONE && (1'b1);
// bit计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_bit <= 0;
end
else if(add_cnt_bit)begin
if(end_cnt_bit)begin
cnt_bit <= 0;
end
else begin
cnt_bit <= cnt_bit + 1;
end
end
else begin
cnt_bit <= cnt_bit;
end
end
assign add_cnt_bit = end_cnt_sclk;
assign end_cnt_bit = add_cnt_bit && cnt_bit == 8 - 1;
// sclk计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_sclk <= 0;
end
else if(add_cnt_sclk)begin
if(end_cnt_sclk)begin
cnt_sclk <= 0;
end
else begin
cnt_sclk <= cnt_sclk + 1;
end
end
else begin
cnt_sclk <= cnt_sclk;
end
end
assign add_cnt_sclk = (state_c == DATA);
assign end_cnt_sclk = add_cnt_sclk && cnt_sclk == SCLK - 1;
// 16分频串行时钟 CPHA=1,CPOL=1
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
if(CPHA == 0)begin
spi_sclk <= 1'b0;
end
else if(CPHA == 1)begin
spi_sclk <= 1'b1;
end
end
else if(add_cnt_sclk && cnt_sclk == SCLK_BEFORE - 1)begin
if(CPHA == 0)begin
spi_sclk <= 1'b1;
end
else if(CPHA == 1)begin
spi_sclk <= 1'b0;
end
end
else if(add_cnt_sclk && cnt_sclk == SCLK_AFTER - 1)begin
if(CPHA == 0)begin
spi_sclk <= 1'b0;
end
else if(CPHA == 1)begin
spi_sclk <= 1'b1;
end
end
end
// 发送的数据mosi 高位MSB先 CPHA=1,CPOL=1
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
tx_data <= 0;
end
else if(CPOL == 0)begin
if(add_cnt_sclk && cnt_sclk == SCLK_AFTER - 1)begin
tx_data <= din;
end
end
else if(CPOL == 1)begin
if(add_cnt_sclk && cnt_sclk == SCLK_BEFORE - 1)begin
tx_data <= din;
end
end
end
// 接收的数据miso 高位MSB先 CPHA=1,CPOL=1
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rx_data <= 0;
end
else if(CPOL == 0)begin
if(add_cnt_sclk && cnt_sclk == SCLK_BEFORE - 1)begin
rx_data[7-cnt_bit] <= miso;
end
end
else if(CPOL == 1)begin
if(add_cnt_sclk && cnt_sclk == SCLK_AFTER - 1)begin
rx_data[7-cnt_bit] <= miso;
end
end
end
assign mosi = tx_data[7-cnt_bit];
assign sclk = spi_sclk;
assign cs_n = ~req;
assign dout = rx_data;
assign done = (state_c == DONE);
endmodule
2.spi_read_ctrl.v
module spi_read_ctrl(
input clk,
input rst_n,
input [2:0] key_out,
input [7:0] din,
input done,
output reg req,
output [7:0] dout,
output reg [23:0] seg_data
);
localparam RDID_CMD = 8'h9F,// 读ID指令
RDDA_CMD = 8'h03,// 读数据指令
RDDA_ADD = 24'h0;// 读数据地址
localparam IDLE = 7'b000_0001,
RDIDCMD = 7'b000_0010,
RDID = 7'b000_0100,
RDDACMD = 7'b000_1000,
RDDAADD = 7'b001_0000,
RDDATA = 7'b010_0000,
DONE = 7'b100_0000;
reg [6:0] state_c;
reg [6:0] state_n;
wire idle2rdidcmd ;
wire idle2rddacmd ;
wire rdidcmd2rdid ;
wire rdid2done ;
wire rddacmd2rddaadd;
wire rddaadd2rddata ;
wire rddata2done ;
wire done2idle ;
// 字节计数器
reg [2:0] cnt_byte;
wire add_cnt_byte;
wire end_cnt_byte;
// 读id和读数据请求
reg rdid_req;
reg rdda_req;
reg [7:0] tx_data;
// 状态机
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
state_c <= IDLE;
end
else begin
state_c <= state_n;
end
end
always @(*)begin
case (state_c)
IDLE :begin
if(idle2rdidcmd)begin
state_n = RDIDCMD;
end
else if(idle2rddacmd)begin
state_n = RDDACMD;
end
else begin
state_n = state_c;
end
end
RDIDCMD :begin
if(rdidcmd2rdid)begin
state_n = RDID;
end
else begin
state_n = state_c;
end
end
RDID :begin
if(rdid2done)begin
state_n = DONE;
end
else begin
state_n = state_c;
end
end
RDDACMD :begin
if(rddacmd2rddaadd)begin
state_n = RDDAADD;
end
else begin
state_n = state_c;
end
end
RDDAADD :begin
if(rddaadd2rddata)begin
state_n = RDDATA;
end
else begin
state_n = state_c;
end
end
RDDATA :begin
if(rddata2done)begin
state_n = DONE;
end
else begin
state_n = state_c;
end
end
DONE :begin
if(done2idle)begin
state_n = IDLE;
end
else begin
state_n = state_c;
end
end
default: state_n = IDLE;
endcase
end
assign idle2rdidcmd = state_c == IDLE && (rdid_req);
assign idle2rddacmd = state_c == IDLE && (rdda_req);
assign rdidcmd2rdid = state_c == RDIDCMD && (end_cnt_byte);
assign rdid2done = state_c == RDID && (end_cnt_byte);
assign rddacmd2rddaadd = state_c == RDDACMD && (end_cnt_byte);
assign rddaadd2rddata = state_c == RDDAADD && (end_cnt_byte);
assign rddata2done = state_c == RDDATA && (end_cnt_byte);
assign done2idle = state_c == DONE && (1'b1);
// 字节计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_byte <= 0;
end
else if(add_cnt_byte)begin
if(end_cnt_byte)begin
cnt_byte <= 0;
end
else begin
cnt_byte <= cnt_byte + 1;
end
end
else begin
cnt_byte <= cnt_byte;
end
end
assign add_cnt_byte = (state_c != IDLE) && done;
assign end_cnt_byte = add_cnt_byte && cnt_byte == (((state_c == RDIDCMD) || (state_c == RDDACMD) || (state_c == RDDATA))?(1-1):(3-1));
// 读id和读数据请求
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rdid_req <= 0;
rdda_req <= 0;
req <= 0;
end
else if(key_out[0])begin
rdid_req <= 1'b1;
req <= 1'b1;
end
else if(key_out[1])begin
rdda_req <= 1'b1;
req <= 1'b1;
end
else if(state_c == DONE)begin
req <= 1'b0;
rdid_req <= 1'b0;
rdda_req <= 1'b0;
end
end
// 指令
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
tx_data <= 0;
end
else if(idle2rdidcmd)begin
tx_data <= RDID_CMD;
end
else if(idle2rddacmd)begin
tx_data <= RDDA_CMD;
end
else if(rddacmd2rddaadd)begin
tx_data <= RDDA_ADD;
end
end
// seg_data
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
seg_data <= 0;
end
else if(state_c == RDID && add_cnt_byte)begin
case(cnt_byte)
0 : seg_data[23:16] <= din;
1 : seg_data[15:8] <= din;
2 : seg_data[7:0] <= din;
default: seg_data <= seg_data;
endcase
end
else if(state_c == RDDATA && add_cnt_byte)begin
case(cnt_byte)
0 : seg_data[23:16] <= din;
default: seg_data <= seg_data;
endcase
end
else begin
seg_data <= seg_data;
end
end
// assign req = rdid_req || rdda_req;
assign dout = tx_data;
endmodule
3.spi_write_ctrl.v
module spi_write_ctrl(
input clk,
input rst_n,
input [2:0] key_out,
input [7:0] din,
input done,
output req,
output [7:0] dout
);
parameter CMD_TIME = 10,// 第一个指令到下一个指令200ns等待时间
PP_TIME = 250_000,// PP可编程时间5ms
SE_TIME = 150_000_000;// SE擦除时间3s
parameter WREN_CMD = 8'h06,
SE_CMD = 8'hD8,
SE_ADD = 24'h000000,
RDSR_CMD = 8'h05,
PP_CMD = 8'h02,
PP_ADD = 24'h000000,
DATA = 8'h78;
// 状态机
localparam IDLE =10'b00000_00001,
FIRWRENCMD =10'b00000_00010,
SECMD =10'b00000_00100,
SEADD =10'b00000_01000,
RDSRCMD =10'b00000_10000,
SECWRENCMD =10'b00001_00000,
PPCMD =10'b00010_00000,
PPADD =10'b00100_00000,
PPDATA =10'b01000_00000,
DONE =10'b10000_00000;
reg [9:0] state_c;
reg [9:0] state_n;
wire idle2firwrencmd ;
wire firwrencmd2secmd ;
wire secmd2seadd ;
wire seadd2rdsrcmd ;
wire rdsrcmd2secwrencmd ;
wire secwrencmd2ppcmd ;
wire ppcmd2ppadd ;
wire ppadd2ppdata ;
wire ppdata2done ;
wire done2idle ;
// 字节计数器
reg [1:0] cnt_byte;
wire add_cnt_byte;
wire end_cnt_byte;
// 100ms一个命令到下一个命令的等待时间
reg [3:0] cnt_200ns;
wire add_cnt_200ns;
wire end_cnt_200ns;
// se擦除等待时间
reg [27:0] cnt_3s;
wire add_cnt_3s;
wire end_cnt_3s;
// pp页编程等待时间
// reg cnt_5ms;
// wire add_cnt_5ms;
// wire end_cnt_5ms;
// 一个命令到下一个命令的等待标志
reg delay_flag;
// 寄存req
reg req_r;
// 寄存要发送的数据
reg [7:0] tx_data;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
state_c <= IDLE;
end
else begin
state_c <= state_n;
end
end
always @(*)begin
case (state_c)
IDLE :begin
if(idle2firwrencmd)begin
state_n = FIRWRENCMD;
end
else begin
state_n = state_c;
end
end
FIRWRENCMD :begin
if(firwrencmd2secmd)begin
state_n = SECMD;
end
else begin
state_n = state_c;
end
end
SECMD :begin
if(secmd2seadd)begin
state_n = SEADD;
end
else begin
state_n = state_c;
end
end
SEADD :begin
if(seadd2rdsrcmd)begin
state_n = RDSRCMD;
end
else begin
state_n = state_c;
end
end
RDSRCMD :begin
if(rdsrcmd2secwrencmd)begin
state_n = SECWRENCMD;
end
else begin
state_n = state_c;
end
end
SECWRENCMD :begin
if(secwrencmd2ppcmd)begin
state_n = PPCMD;
end
else begin
state_n = state_c;
end
end
PPCMD :begin
if(ppcmd2ppadd)begin
state_n = PPADD;
end
else begin
state_n = state_c;
end
end
PPADD :begin
if(ppadd2ppdata)begin
state_n = PPDATA;
end
else begin
state_n = state_c;
end
end
PPDATA :begin
if(ppdata2done)begin
state_n = DONE;
end
else begin
state_n = state_c;
end
end
DONE :begin
if(done2idle)begin
state_n = IDLE;
end
else begin
state_n = state_c;
end
end
default: state_n = IDLE;
endcase
end
assign idle2firwrencmd = state_c == IDLE && (key_out[2]);
assign firwrencmd2secmd = state_c == FIRWRENCMD && (end_cnt_200ns);
assign secmd2seadd = state_c == SECMD && (end_cnt_byte);
assign seadd2rdsrcmd = state_c == SEADD && (end_cnt_3s);
assign rdsrcmd2secwrencmd = state_c == RDSRCMD && (end_cnt_200ns);
assign secwrencmd2ppcmd = state_c == SECWRENCMD && (end_cnt_200ns);
assign ppcmd2ppadd = state_c == PPCMD && (end_cnt_byte);
assign ppadd2ppdata = state_c == PPADD && (end_cnt_byte);
assign ppdata2done = state_c == PPDATA && (end_cnt_byte);
assign done2idle = state_c == DONE && (1'b1);
// 字节计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_byte <= 0;
end
else if(add_cnt_byte)begin
if(end_cnt_byte)begin
cnt_byte <= 0;
end
else begin
cnt_byte <= cnt_byte + 1;
end
end
else begin
cnt_byte <= cnt_byte;
end
end
assign add_cnt_byte = ((state_c != IDLE) && done);
assign end_cnt_byte = add_cnt_byte && cnt_byte == (((state_c == FIRWRENCMD) || (state_c == SECMD) || (state_c == RDSRCMD) || (state_c == SECWRENCMD) || (state_c == PPCMD) || (state_c == PPDATA))?(1-1):(3-1));
// 100ms一个命令到下一个命令的等待时间计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_200ns <= 0;
end
else if(add_cnt_200ns)begin
if(end_cnt_200ns)begin
cnt_200ns <= 0;
end
else begin
cnt_200ns <= cnt_200ns + 1;
end
end
else begin
cnt_200ns <= cnt_200ns;
end
end
assign add_cnt_200ns = (((state_c == FIRWRENCMD) || (state_c == RDSRCMD) || (state_c == SECWRENCMD)) && delay_flag);
assign end_cnt_200ns = add_cnt_200ns && cnt_200ns == CMD_TIME - 1;
// SE擦除时间2s
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_3s <= 0;
end
else if(add_cnt_3s)begin
if(end_cnt_3s)begin
cnt_3s <= 0;
end
else begin
cnt_3s <= cnt_3s + 1;
end
end
else begin
cnt_3s <= cnt_3s;
end
end
assign add_cnt_3s = ((state_c == SEADD) && delay_flag);
assign end_cnt_3s = add_cnt_3s && cnt_3s == SE_TIME - 1;
// 一个命令到下一个命令的等待延长标志
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
delay_flag <= 0;
end
else if(end_cnt_byte)begin
delay_flag <= 1'b1;
end
else if(end_cnt_200ns || end_cnt_3s)begin
delay_flag <= 1'b0;
end
else begin
delay_flag <= delay_flag;
end
end
// req信号
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
req_r <= 1'b0;
end
else if(idle2firwrencmd)begin
req_r <= 1'b1;
end
else if((state_c == FIRWRENCMD) && end_cnt_byte)begin
req_r <= 1'b0;
end
else if(firwrencmd2secmd)begin
req_r <= 1'b1;
end
else if(secmd2seadd)begin
req_r <= 1'b1;
end
else if((state_c == SEADD) && end_cnt_byte)begin
req_r <= 1'b0;
end
else if(seadd2rdsrcmd)begin
req_r <= 1'b1;
end
else if((state_c == RDSRCMD) && end_cnt_byte)begin
req_r <= 1'b0;
end
else if(rdsrcmd2secwrencmd)begin
req_r <= 1'b1;
end
else if((state_c == SECWRENCMD) && end_cnt_byte)begin
req_r <= 1'b0;
end
else if(secwrencmd2ppcmd)begin
req_r <= 1'b1;
end
else if(ppcmd2ppadd)begin
req_r <= 1'b1;
end
else if(ppadd2ppdata)begin
req_r <= 1'b1;
end
else if(ppdata2done)begin
req_r <= 1'b0;
end
end
// dout传输的数据
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
tx_data <= 0;
end
else if(state_c == FIRWRENCMD)begin
tx_data <= WREN_CMD;
end
else if(state_c == SECMD)begin
tx_data <= SE_CMD;
end
else if(state_c == SEADD)begin
tx_data <= SE_ADD;
end
else if(state_c == RDSRCMD)begin
tx_data <= RDSR_CMD;
end
else if(state_c == SECWRENCMD)begin
tx_data <= WREN_CMD;
end
else if(state_c == PPCMD)begin
tx_data <= PP_CMD;
end
else if(state_c == PPADD)begin
tx_data <= PP_ADD;
end
else if(state_c == PPDATA)begin
tx_data <= DATA;
end
end
assign req = req_r;
assign dout = tx_data;
endmodule
4.spi_control.v
module spi_control(
input clk,
input rst_n,
input [2:0] key_out,
input [7:0] din,
input done,
output [7:0] dout,
output req,
output [23:0] seg_data
);
wire rd_req;
wire wr_req;
wire [7:0] rd_tx_data;
wire [7:0] wr_tx_data;
assign req = rd_req | wr_req;
assign dout = ({8{rd_req}} & rd_tx_data) | ({8{wr_req}} & wr_tx_data);
// 读控制模块
spi_read_ctrl u_spi_read_ctrl(
/* input */.clk (clk ),
/* input */.rst_n (rst_n ),
/* input [2:0] */.key_out (key_out ),
/* input [7:0] */.din (din ),
/* input */.done (done ),
/* output */.req (rd_req ),
/* output [7:0] */.dout (rd_tx_data),
/* output reg [23:0]*/.seg_data(seg_data)
);
// 写控制模块
spi_write_ctrl u_spi_write_ctrl(
/* input */.clk (clk ),
/* input */.rst_n (rst_n ),
/* input [2:0] */.key_out (key_out),
/* input [7:0] */.din (din ),
/* input */.done (done ),
/* output */.req (wr_req ),
/* output [7:0] */.dout (wr_tx_data)
);
endmodule
5.top.v
module top(
input clk,
input rst_n,
input [2:0] key_in,
output [7:0] seg_dig,
output [5:0] seg_sel,
input miso,// 主机采样从机发送
output mosi,// 主机发送从机
output sclk,// 串行时钟
output cs_n // 片选信号
);
wire [2:0] key_out;
wire req;
wire done;
wire [7:0] rx_data;
wire [7:0] tx_data;
wire [23:0] seg_data;
// 按键消抖模块
key_filter u_key_filter(
/* input */.clk (clk ),
/* input */.rst_n (rst_n ),
/* input [KEY_W-1:0] */.key_in (key_in ),
/* output reg [KEY_W-1:0] */.key_out (key_out)
);
// 数码管驱动
seg_driver u_seg_driver(
/* input */.clk (clk ),
/* input */.rst_n (rst_n ),
/* input [23:0] */.data (seg_data),
/* output reg [7:0] */.seg_dig (seg_dig),
/* output reg [5:0] */.seg_sel (seg_sel)
);
spi_control u_spi_control(
/* input */.clk (clk ),
/* input */.rst_n (rst_n ),
/* input [2:0] */.key_out (key_out ),
/* input [7:0] */.din (rx_data ),
/* input */.done (done ),
/* output [7:0] */.dout (tx_data ),
/* output */.req (req ),
/* output [23:0] */.seg_data (seg_data)
);
spi_interface u_spi_interface(
/* input */.clk (clk ),
/* input */.rst_n (rst_n),
/* // 接口与主机 */
/* input [7:0] */.din (tx_data),
/* input */.req (req ),
/* output [7:0] */.dout (rx_data ),
/* output */.done (done ),
/* // 接口与flash */
/* input */.miso (miso ),// 主机采样从机发送
/* output */.mosi (mosi ),// 主机发送从机
/* output */.sclk (sclk ),// 串行时钟
/* output */.cs_n (cs_n ) // 片选信号
);
endmodule
6.其他模块
按键消抖模块
数码管驱动模块