一、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 :
beginif(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;
endelse 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;
endelse 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;
endelse 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)
beginif(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;
endelse 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)
beginif(!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.其他模块
按键消抖模块
数码管驱动模块

技术
下载桌面版
GitHub
Gitee
SourceForge
百度网盘(提取码:draw)
云服务器优惠
华为云优惠券
腾讯云优惠券
阿里云优惠券
Vultr优惠券
站点信息
问题反馈
邮箱:[email protected]
吐槽一下
QQ群:766591547
关注微信