一.SPI协议简介
-
同步:SPI使用单独的数据线和单独的时钟线来控制着收发同步
-
全双工:主机从机之间通过两条线传输数据,可以由主机发送给从机,也可以由从机发送给主机。并可以同时传输
-
主机从机之间通过四根线交互.其中sclk为时钟信号,mosi为主机发送给从机的数据信号,miso为从机发送给主机的数据信号,cs_n为片选信号。(其中发送方式为MSB高位先发)
-
四种模式:
`其中CPOL为时钟极性:空闲状态为高电平时为1,空闲状态为低电平时为0; CPHA为时钟相位:表示第一个边沿为0,表示第二个边沿为1;模式 CPOL CPHA 描述 模式0 0 0 sclk上升沿采样,sclk下降沿发送 模式1 0 1 sclk上升沿发送,sclk下降沿采样 模式2 1 0 sclk上升沿发送,sclk下降沿采样 模式3 1 1 sclk上升沿采样,sclk下降沿发送 我们常用的是模式0和模式3
-
模式0:
CPOL = 0:空闲时为低电平,第一个跳变沿是上升沿,第二个跳变沿是下降沿。
CPHA = 0:在第一个跳变沿(上升沿)采样
-
模式1:
CPOL = 0:空闲时为低电平,第一个跳变沿是上升沿,第二个跳变沿是下降沿。
CPHA = 1:在第二个跳变沿(下降沿)采样
-
模式2:
CPOL = 1:空闲时为高电平,第一个跳变沿是下降沿,第二个跳变沿为上升沿。
CPHA = 0:在第一个跳变沿(下降沿)采样
-
模式3:
CPOL = 1:空闲时为高电平,第一个跳变沿是下降沿,第二个跳变沿为上升沿。
CPHA = 1:在第二个跳变沿(上升沿)采样
二.FLASH(M25P16)简介
- 存储结构:FLASH包括32个扇区(sector),每个扇区包含256个页(page),每个页包含256个字节(byte)。因此,每个字节的存储地址需要8bit的扇区地址+8bit的页地址+8bit的字节地址才能定位。
- 指令集
常用的指令有WREN(读使能),RDID(读ID),RDSR(读状态寄存器),READ(读数据),PP(页编程),SE(扇区擦除)
- 时间满足条件:页写,全擦除,扇区擦除指令执行完成需要一定的时间
指令与指令之间需要相隔一段时间
三.指令操作(手册分析)
3.1 WREN写使能
- 在执行PP,SE,BE,RESR指令前需要执行写使能操作。当片选信号拉低
后,就开始执行写使能指令,接着传输指令。指令发送完后,片选信号置为高电平。其时序如下
3.2 PP页编程
- 页编程操作需要3字节的地址字节确定编程起始地址,操作空间在1-256字节,也就是说,最多写满一页。其时序如下
3.3 SE扇区擦除
- 在页编程前,需要进行扇区擦除操作(将扇区内所有字节都变为FFh),但是在扇区擦除前需要先执行写使能操作,扇区擦除需要3个字节的地址字节,其时序如下
3.4 RDID读ID
-
RDID指令允许读取8位的制造商标识,然后读取两个字节的设备标识。设备标识由设备制造商指定,第一个字节表示内存类型(20h),第二个字节表示内存类型(20h),第三个字节表示设备的内存容量(15h)。
-
其时序如下
3.5 RDSR读状态寄存器
- 状态寄存器格式如下
WIP:表示内存是否处于写状态寄存器、页编程或擦除周期。当设置为1时,表示设备处于工作状态,当设置为0时,表示设备处于空闲状态。
WEL:表示内部Write Enable Latch的状态。当设置为1时,内部写使能被设置,当设置为0时,表示没有接收写状态寄存器,页编程或擦除指令。
BP2、BP1、BP0:块保护(BP2,BP1, BP0)位是非易失性的。它们定义了被软件保护的区域的大小编程和擦除指令。这些位是用写状态寄存器(WRSR)指令写的。
- 其时序如下
3.6 READ读数据
- 需要3字节的地址字节来确定开始读的位置,其时序如下
四.总体设计
4.1状态转移图
- 下图为spi_control模块的状态转移图
4.2模块框图
4.3 spi_interface模块
/**************************************功能介绍***********************************
Date :
Author : Alegg xy.
Version :
Description:
*********************************************************************************/
//---------<模块及端口声名>------------------------------------------------------
module spi_interface(
input clk ,
input rst_n ,
input [7:0] data_in ,
input data_in_vld ,
output master_ready,
output reg [7:0] data_out ,
output data_out_vld,
output done ,
input miso ,
output reg sclk ,
output reg mosi ,
output reg cs_n
);
//---------<参数定义>---------------------------------------------------------
parameter DIV = 4,//时钟分频倍数
MAX_100ns = 5;//100ns
//状态机参数定义
localparam IDLE = 5'b00001,//
DELAY_1 = 5'b00010,//
DATA = 5'b00100,//
DELAY_2 = 5'b01000,//
HOLD = 5'b10000;//
reg [4:0] cstate ;//现态
reg [4:0] nstate ;//次态
wire IDLE_DEALY_1;
wire DELAY_1_DATA;
wire DATA_DELAY_2;
wire DELAY_2_HOLD;
wire HOLD_IDLE ;
//fifo
wire fifo_empty;
wire fifo_full;
wire [7:0] fifo_q;
wire fifo_rdreq;
//时钟分频计数器
reg [1:0] cnt_div ;
wire add_cnt_div ;
wire end_cnt_div ;
//bit计数器
reg [2:0] cnt_bit ;
wire add_cnt_bit ;
wire end_cnt_bit ;
//延时计数器100ns
reg [2:0] cnt_delay ;
wire add_cnt_delay ;
wire end_cnt_delay ;
//---------<内部信号定义>-----------------------------------------------------
fifo fifo_inst (
.aclr ( ~rst_n ),
.clock ( clk ),
.data ( data_in ),
.rdreq ( fifo_rdreq ),
.wrreq ( data_in_vld ),
.empty ( fifo_empty ),
.full ( fifo_full ),
.q ( fifo_q ),
.usedw ( )
);
assign fifo_rdreq = !fifo_empty && (cnt_div == DIV - 2) && (cnt_bit == 7) ;
//第一段:时序逻辑描述状态转移
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cstate <= IDLE;
end
else begin
cstate <= nstate;
end
end
//第二段:组合逻辑描述状态转移规律和状态转移条件
always @(*) begin
case(cstate)
IDLE :begin
if (IDLE_DEALY_1) begin
nstate = DELAY_1;
end
else begin
nstate = cstate;
end
end
DELAY_1 :begin
if (DELAY_1_DATA) begin
nstate = DATA;
end
else begin
nstate = cstate;
end
end
DATA :begin
if (DATA_DELAY_2) begin
nstate = DELAY_2;
end
else begin
nstate = cstate;
end
end
DELAY_2 :begin
if (DELAY_2_HOLD) begin
nstate = HOLD;
end
else begin
nstate = cstate;
end
end
HOLD :begin
if (HOLD_IDLE) begin
nstate = IDLE;
end
else begin
nstate = cstate;
end
end
default : nstate = IDLE;
endcase
end
assign IDLE_DEALY_1 = (cstate == IDLE) && !fifo_empty;
assign DELAY_1_DATA = (cstate == DELAY_1) && 1;
assign DATA_DELAY_2 = (cstate == DATA) && fifo_empty;
assign DELAY_2_HOLD = (cstate == DELAY_2) && 1;
assign HOLD_IDLE = (cstate == HOLD) && end_cnt_delay;
//时钟分频
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_div <= 'd0;
end
else if(add_cnt_div)begin
if(end_cnt_div)begin
cnt_div <= 'd0;
end
else begin
cnt_div <= cnt_div + 1'd1;
end
end
end
assign add_cnt_div = cstate == DATA;
assign end_cnt_div = add_cnt_div && cnt_div == DIV - 1;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
sclk <= 1;
end
else if (cstate == DATA && (cnt_div < (DIV >> 1))) begin
sclk <= 0;
end
else begin
sclk <= 1;
end
end
//bit计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_bit <= 'd0;
end
else if(add_cnt_bit)begin
if(end_cnt_bit)begin
cnt_bit <= 'd0;
end
else begin
cnt_bit <= cnt_bit + 1'd1;
end
end
end
assign add_cnt_bit = end_cnt_div;
assign end_cnt_bit = add_cnt_bit && cnt_bit == 7;
//数据发送
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
mosi <= 1;
end
else if (cstate == DATA && (cnt_div == 0)) begin
mosi <= fifo_q[7-cnt_bit];//MSB高位先发
end
else begin
mosi <= mosi;
end
end
//cs_n
always @(*) begin
if (!rst_n) begin
cs_n = 1;
end
else if (cstate == IDLE || cstate == HOLD) begin
cs_n = 1;
end
else begin
cs_n = 0;
end
end
//延时计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_delay <= 'd0;
end
else if(add_cnt_delay)begin
if(end_cnt_delay)begin
cnt_delay <= 'd0;
end
else begin
cnt_delay <= cnt_delay + 1'd1;
end
end
end
assign add_cnt_delay = cstate == HOLD;
assign end_cnt_delay = add_cnt_delay && cnt_delay == MAX_100ns - 1;
//数据接收
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
data_out <= 0;
end
else if (cstate == DATA && (cnt_div == (DIV >> 1))) begin
data_out <= {data_out[6:0],miso};
end
end
assign data_out_vld = end_cnt_bit;
assign done = HOLD_IDLE;
assign master_ready = ~fifo_full;
endmodule
4.4 spi_control模块
/**************************************功能介绍***********************************
Date :
Author : Alegg xy.
Version :
Description:
*********************************************************************************/
//---------<模块及端口声名>------------------------------------------------------
module spi_control(
input clk ,
input rst_n ,
input rdid_req ,
input read_req ,
input wren_req ,
input ready ,
input [23:0] rd_addr ,//需要读出的数据地址
input [7:0] read_num ,//需要读出的数据数量
input [23:0] wr_addr ,//需要写入的数据地址
input [7:0] wr_num ,//需要写入的数据数量
input [7:0] data_in ,//需要写入的数据
input data_in_vld ,
input [7:0] data_out ,//返回的数据
input data_out_vld,
input miso ,
output sclk ,
output mosi ,
output cs_n
);
//---------<参数定义>---------------------------------------------------------
parameter MAX_TSE = 28'd150_000_000,//3s
MAX_TPP = 18'd150_000;//3ms
//状态机参数定义
localparam IDLE = 10'b0000000001,
RDID = 10'b0000000010,
READ = 10'b0000000100,
WREN = 10'b0000001000,
WAIT = 10'b0000010000,
SE = 10'b0000100000,
TSE = 10'b0001000000,
PP = 10'b0010000000,
TPP = 10'b0100000000,
HOLD = 10'b1000000000;
reg [9:0] cstate ;//现态
reg [9:0] nstate ;//次态
wire IDLE_RDID;
wire IDLE_READ;
wire IDLE_WREN;
wire RDID_HOLD;
wire READ_HOLD;
wire WREN_WAIT;
wire WAIT_SE ;
wire WAIT_PP ;
wire SE_TSE ;
wire PP_TPP ;
wire TSE_WREN ;
wire TPP_IDLE ;
wire HOLD_IDLE;
//与spi_interface交互
reg [7:0] master_data_in;
wire master_data_in_vld;
wire done;
wire [7:0] master_data_out;
wire master_data_out_vld;
wire master_ready;
reg [2:0] cnt_bit ;
wire add_cnt_bit ;
wire end_cnt_bit ;
reg [3:0] cnt_byte ;
wire add_cnt_byte ;
wire end_cnt_byte ;
reg [3:0] num;
reg [27:0] cnt_tse ;
wire add_cnt_tse ;
wire end_cnt_tse ;
reg [17:0] cnt_tpp ;
wire add_cnt_tpp ;
wire end_cnt_tpp ;
reg flag;//判断wait状态到se还是pp 0:se;1:pp
reg delay_flag;//延时开始使能
reg data_in_r;//数据寄存
reg rdid_flag;
reg read_flag;
//---------<内部信号定义>-----------------------------------------------------
//第一段:时序逻辑描述状态转移
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cstate <= IDLE;
end
else begin
cstate <= nstate;
end
end
//第二段:组合逻辑描述状态转移规律和状态转移条件
always @(*) begin
case(cstate)
IDLE :begin
if (IDLE_RDID) begin
nstate = RDID;
end
else if (IDLE_READ) begin
nstate = READ;
end
else if (IDLE_WREN) begin
nstate = WREN;
end
else begin
nstate = cstate;
end
end
RDID :begin
if (RDID_HOLD) begin
nstate = HOLD;
end
else begin
nstate = cstate;
end
end
READ :begin
if (READ_HOLD) begin
nstate = HOLD;
end
else begin
nstate = cstate;
end
end
WREN :begin
if (WREN_WAIT) begin
nstate = WAIT;
end
else begin
nstate = cstate;
end
end
WAIT :begin
if (WAIT_SE) begin
nstate = SE;
end
else if (WAIT_PP) begin
nstate = PP;
end
else begin
nstate = cstate;
end
end
SE :begin
if (SE_TSE) begin
nstate = TSE;
end
else begin
nstate = cstate;
end
end
TSE :begin
if (TSE_WREN) begin
nstate = WREN;
end
else begin
nstate = cstate;
end
end
PP :begin
if (PP_TPP) begin
nstate = TPP;
end
else begin
nstate = cstate;
end
end
TPP :begin
if (TPP_IDLE) begin
nstate = IDLE;
end
else begin
nstate = cstate;
end
end
HOLD :begin
if (HOLD_IDLE) begin
nstate = IDLE;
end
else begin
nstate = cstate;
end
end
default : nstate = IDLE;
endcase
end
assign IDLE_RDID = (cstate == IDLE) && rdid_req;
assign IDLE_READ = (cstate == IDLE) && read_req;
assign IDLE_WREN = (cstate == IDLE) && wren_req;
assign RDID_HOLD = (cstate == RDID) && end_cnt_byte;
assign READ_HOLD = (cstate == READ) && end_cnt_byte;
assign WREN_WAIT = (cstate == WREN) && end_cnt_byte;
assign WAIT_SE = (cstate == WAIT) && done && ~flag;
assign WAIT_PP = (cstate == WAIT) && done && flag;
assign SE_TSE = (cstate == SE) && end_cnt_byte;
assign PP_TPP = (cstate == PP) && end_cnt_byte;
assign TSE_WREN = (cstate == TSE) && end_cnt_tse;
assign TPP_IDLE = (cstate == TPP) && end_cnt_tpp;
assign HOLD_IDLE = (cstate == HOLD) && done;
//字节计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_bit <= 'd0;
end
else if(add_cnt_bit)begin
if(end_cnt_bit)begin
cnt_bit <= 'd0;
end
else begin
cnt_bit <= cnt_bit + 1'd1;
end
end
end
assign add_cnt_bit = (cstate == RDID || cstate == READ || cstate == WREN || cstate == SE || cstate == PP) && master_ready;
assign end_cnt_bit = add_cnt_bit && cnt_bit == 7;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_byte <= 'd0;
end
else if(add_cnt_byte)begin
if(end_cnt_byte)begin
cnt_byte <= 'd0;
end
else begin
cnt_byte <= cnt_byte + 1'd1;
end
end
end
assign add_cnt_byte = end_cnt_bit;
assign end_cnt_byte = add_cnt_byte && cnt_byte == num - 1;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
num <= 1;
end
else if (cstate == RDID) begin
num <= 4;
end
else if (cstate == READ) begin
num <= 4 + read_num;
end
else if (cstate == WREN) begin
num <= 1;
end
else if (cstate == SE) begin
num <= 4;
end
else if (cstate == PP) begin
num <= 5;
end
else begin
num <= 1;
end
end
//指令发送
always @(*) begin
case (cstate)
RDID : case (cnt_byte)
0 : master_data_in = 8'h9f;
1 : master_data_in = 8'hff;
2 : master_data_in = 8'hff;
3 : master_data_in = 8'hff;
default: master_data_in = 8'hff;
endcase
READ : case (cnt_byte)
0 : master_data_in = 8'h03;
1 : master_data_in = rd_addr[23:16];
2 : master_data_in = rd_addr[15:8];
3 : master_data_in = rd_addr[7:0];
default: master_data_in = 8'hff;
endcase
WREN : case (cnt_byte)
0 : master_data_in = 8'h06;
default: master_data_in = 8'hff;
endcase
SE : case (cnt_byte)
0 : master_data_in = 8'hd8;
1 : master_data_in = rd_addr[23:16];
2 : master_data_in = rd_addr[15:8];
3 : master_data_in = rd_addr[7:0];
default: master_data_in = 8'hff;
endcase
PP : case (cnt_byte)
0 : master_data_in = 8'h02;
1 : master_data_in = rd_addr[23:16];
2 : master_data_in = rd_addr[15:8];
3 : master_data_in = rd_addr[7:0];
4 : master_data_in = 8'hF1;
default: master_data_in = 8'hff;
endcase
endcase
end
assign master_data_in_vld = add_cnt_byte;
//数据寄存
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
data_in_r <= 0;
end
else if (data_in_vld) begin
data_in_r <= data_in;
end
else begin
data_in_r <= data_in_r;
end
end
//滤除无用数据
reg [3:0] cnt_out ;
wire add_cnt_out ;
wire end_cnt_out ;
reg [3:0] num_out;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_out <= 'd0;
end
else if (HOLD_IDLE || TPP_IDLE) begin
cnt_out <= 'd0;
end
else if(add_cnt_out)begin
if(end_cnt_out)begin
cnt_out <= 'd0;
end
else begin
cnt_out <= cnt_out + 1'd1;
end
end
end
assign add_cnt_out = master_data_out_vld;
assign end_cnt_out = add_cnt_out && cnt_out == num_out - 1;
always @(*) begin
if (!rst_n) begin
num_out = 0;
end
else if (cstate == RDID) begin
num_out = 1;
end
else if (cstate == READ) begin
num_out = 4;
end
end
//记录操作
reg [1:0] op_flag;
`define OP_RDID 1
`define OP_READ 2
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
op_flag <= 0;
end
else if (IDLE_RDID) begin
op_flag <= `OP_RDID;
end
else if (IDLE_READ) begin
op_flag <= `OP_READ;
end
else if (HOLD_IDLE) begin
op_flag <= 0;
end
end
//读ID使能
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
rdid_flag <= 0;
end
else if (end_cnt_out && op_flag == `OP_RDID) begin
rdid_flag <= 1;
end
else if (HOLD_IDLE) begin
rdid_flag <= 0;
end
end
//读read使能
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
read_flag <= 0;
end
else if (end_cnt_out && op_flag == `OP_READ) begin
read_flag <= 1;
end
else if (HOLD_IDLE) begin
read_flag <= 0;
end
end
//延时开始使能
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
delay_flag <= 0;
end
else if ((cstate == TSE || cstate == TPP) && done) begin
delay_flag <= 1;
end
else if (end_cnt_tpp || end_cnt_tse) begin
delay_flag <= 0;
end
end
//tSE延时
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_tse <= 'd0;
end
else if(add_cnt_tse)begin
if(end_cnt_tse)begin
cnt_tse <= 'd0;
end
else begin
cnt_tse <= cnt_tse + 1'd1;
end
end
end
assign add_cnt_tse = (cstate == TSE) && delay_flag;
assign end_cnt_tse = add_cnt_tse && cnt_tse == MAX_TSE - 1;
//tPP延时
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_tpp <= 'd0;
end
else if(add_cnt_tpp)begin
if(end_cnt_tpp)begin
cnt_tpp <= 'd0;
end
else begin
cnt_tpp <= cnt_tpp + 1'd1;
end
end
end
assign add_cnt_tpp = (cstate == TPP) && delay_flag;
assign end_cnt_tpp = add_cnt_tpp && cnt_tpp == MAX_TPP - 1;
//flag
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
flag <= 0;
end
else if (TSE_WREN) begin
flag <= 1;
end
else if (TPP_IDLE) begin
flag <= 0;
end
end
spi_interface u_spi_interface(
.clk (clk),
.rst_n (rst_n),
.data_in (master_data_in),
.data_in_vld (master_data_in_vld),
.master_ready(master_ready),
.data_out (master_data_out),
.data_out_vld(master_data_out_vld),
.done (done),
.miso (miso),
.sclk (sclk),
.mosi (mosi),
.cs_n (cs_n)
);
endmodule